From 5065c7bcd9973056b9b0d9df71d139da83596acc Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Fri, 1 Oct 2021 15:09:49 +0530 Subject: [PATCH] feat(ext/crypto): implement wrapKey (#12125) --- cli/tests/unit/webcrypto_test.ts | 37 +++++++++++ ext/crypto/00_crypto.js | 102 +++++++++++++++++++++++++++++++ ext/crypto/lib.deno_crypto.d.ts | 6 ++ tools/wpt/expectation.json | 3 - 4 files changed, 145 insertions(+), 3 deletions(-) diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index 56a23bfb5f..493cf95172 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -499,3 +499,40 @@ unitTest(async function testHkdfDeriveBits() { ); assertEquals(result.byteLength, 128 / 8); }); + +unitTest(async function testWrapKey() { + // Test wrapKey + const key = await crypto.subtle.generateKey( + { + name: "RSA-OAEP", + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-256", + }, + true, + ["wrapKey", "unwrapKey"], + ); + + const hmacKey = await crypto.subtle.generateKey( + { + name: "HMAC", + hash: "SHA-256", + length: 128, + }, + true, + ["sign"], + ); + + const wrappedKey = await crypto.subtle.wrapKey( + "raw", + hmacKey, + key.publicKey, + { + name: "RSA-OAEP", + label: new Uint8Array(8), + }, + ); + + assert(wrappedKey instanceof ArrayBuffer); + assertEquals(wrappedKey.byteLength, 512); +}); diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index a503d316ac..fdd0f612d8 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -120,6 +120,10 @@ "decrypt": { "RSA-OAEP": "RsaOaepParams", }, + "wrapKey": { + // TODO(@littledivy): Enable this once implemented. + // "AES-KW": "AesKeyWrapParams", + }, }; // Decodes the unpadded base64 to the octet sequence containing key value `k` defined in RFC7518 Section 6.4 @@ -1519,6 +1523,104 @@ throw new TypeError("unreachable"); } + /** + * @param {string} algorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async wrapKey(format, key, wrappingKey, wrapAlgorithm) { + webidl.assertBranded(this, SubtleCrypto); + const prefix = "Failed to execute 'wrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 4, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + key = webidl.converters.CryptoKey(key, { + prefix, + context: "Argument 2", + }); + wrappingKey = webidl.converters.CryptoKey(wrappingKey, { + prefix, + context: "Argument 3", + }); + wrapAlgorithm = webidl.converters.AlgorithmIdentifier(wrapAlgorithm, { + prefix, + context: "Argument 4", + }); + + let normalizedAlgorithm; + + try { + // 2. + normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "wrapKey"); + } catch (_) { + // 3. + normalizedAlgorithm = normalizeAlgorithm(wrapAlgorithm, "encrypt"); + } + + // 8. + if (normalizedAlgorithm.name !== wrappingKey[_algorithm].name) { + throw new DOMException( + "Wrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } + + // 9. + if (!ArrayPrototypeIncludes(wrappingKey[_usages], "wrapKey")) { + throw new DOMException( + "Key does not support the 'wrapKey' operation.", + "InvalidAccessError", + ); + } + + // 10. NotSupportedError will be thrown in step 12. + // 11. + if (key[_extractable] === false) { + throw new DOMException( + "Key is not extractable", + "InvalidAccessError", + ); + } + + // 12. + const exportedKey = await this.exportKey(format, key); + + let bytes; + // 13. + if (format !== "jwk") { + bytes = exportedKey; + } else { + // TODO(@littledivy): Implement JWK. + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); + } + + // 14-15. + if ( + supportedAlgorithms["wrapKey"][normalizedAlgorithm.name] !== undefined + ) { + // TODO(@littledivy): Implement this for AES-KW. + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); + } else if ( + supportedAlgorithms["encrypt"][normalizedAlgorithm.name] !== undefined + ) { + return this.encrypt(normalizedAlgorithm, wrappingKey, bytes); + } else { + throw new DOMException( + "Algorithm not supported", + "NotSupportedError", + ); + } + } + /** * @param {string} algorithm * @param {boolean} extractable diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index 55b94c24de..3dd66d59a3 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -215,6 +215,12 @@ interface SubtleCrypto { baseKey: CryptoKey, length: number, ): Promise; + wrapKey( + format: KeyFormat, + key: CryptoKey, + wrappingKey: CryptoKey, + wrapAlgorithm: AlgorithmIdentifier | RsaOaepParams, + ): Promise; } declare interface Crypto { diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 5235c1828f..72c276d7b5 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -12149,12 +12149,9 @@ "historical.any.html": false, "idlharness.https.any.html": [ "SubtleCrypto interface: operation deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)", - "SubtleCrypto interface: operation wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)", "SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)", "SubtleCrypto interface: crypto.subtle must inherit property \"deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence)\" with the proper type", "SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError", - "SubtleCrypto interface: crypto.subtle must inherit property \"wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier)\" with the proper type", - "SubtleCrypto interface: calling wrapKey(KeyFormat, CryptoKey, CryptoKey, AlgorithmIdentifier) on crypto.subtle with too few arguments must throw TypeError", "SubtleCrypto interface: crypto.subtle must inherit property \"unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)\" with the proper type", "SubtleCrypto interface: calling unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence) on crypto.subtle with too few arguments must throw TypeError", "Window interface: attribute crypto"