From d31378726e78490e88b8a9ec3001b86ea009d978 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 5 Dec 2021 09:25:11 +0530 Subject: [PATCH] feat(ext/crypto): implement unwrapKey (#12539) --- cli/tests/unit/webcrypto_test.ts | 54 +++++++++++ ext/crypto/00_crypto.js | 159 +++++++++++++++++++++++++++++++ ext/crypto/lib.deno_crypto.d.ts | 16 ++++ tools/wpt/expectation.json | 6 -- 4 files changed, 229 insertions(+), 6 deletions(-) diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index 5087c70765..0660d9a7e8 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -691,6 +691,60 @@ Deno.test(async function testAesKeyGen() { assertEquals(algorithm.length, 256); }); +Deno.test(async function testUnwrapKey() { + const subtle = crypto.subtle; + + const AES_KEY: AesKeyAlgorithm & AesCbcParams = { + name: "AES-CBC", + length: 128, + iv: new Uint8Array(16), + }; + + const RSA_KEY: RsaHashedKeyGenParams & RsaOaepParams = { + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-1", + }; + + const aesKey = await subtle.generateKey(AES_KEY, true, [ + "encrypt", + "decrypt", + ]); + + const rsaKeyPair = await subtle.generateKey( + { + name: "RSA-OAEP", + hash: "SHA-1", + publicExponent: new Uint8Array([1, 0, 1]), + modulusLength: 2048, + }, + false, + ["wrapKey", "encrypt", "unwrapKey", "decrypt"], + ); + + const enc = await subtle.wrapKey( + "raw", + aesKey, + rsaKeyPair.publicKey, + RSA_KEY, + ); + const unwrappedKey = await subtle.unwrapKey( + "raw", + enc, + rsaKeyPair.privateKey, + RSA_KEY, + AES_KEY, + false, + ["encrypt", "decrypt"], + ); + + assert(unwrappedKey instanceof CryptoKey); + assertEquals(unwrappedKey.type, "secret"); + assertEquals(unwrappedKey.extractable, false); + assertEquals(unwrappedKey.usages, ["encrypt", "decrypt"]); +}); + Deno.test(async function testDecryptWithInvalidIntializationVector() { const data = new Uint8Array([42, 42, 42, 42]); const key = await crypto.subtle.generateKey( diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 311dcfbf19..2efe550e01 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -140,6 +140,10 @@ // TODO(@littledivy): Enable this once implemented. // "AES-KW": "AesKeyWrapParams", }, + "unwrapKey": { + // TODO(@littledivy): Enable this once implemented. + // "AES-KW": "AesKeyWrapParams", + }, }; const aesJwkAlg = { @@ -2070,6 +2074,161 @@ ); } } + /** + * @param {string} format + * @param {BufferSource} wrappedKey + * @param {CryptoKey} unwrappingKey + * @param {AlgorithmIdentifier} unwrapAlgorithm + * @param {AlgorithmIdentifier} unwrappedKeyAlgorithm + * @param {boolean} extractable + * @param {KeyUsage[]} keyUsages + * @returns {Promise} + */ + async unwrapKey( + format, + wrappedKey, + unwrappingKey, + unwrapAlgorithm, + unwrappedKeyAlgorithm, + extractable, + keyUsages, + ) { + webidl.assertBranded(this, SubtleCrypto); + const prefix = "Failed to execute 'unwrapKey' on 'SubtleCrypto'"; + webidl.requiredArguments(arguments.length, 7, { prefix }); + format = webidl.converters.KeyFormat(format, { + prefix, + context: "Argument 1", + }); + wrappedKey = webidl.converters.BufferSource(wrappedKey, { + prefix, + context: "Argument 2", + }); + unwrappingKey = webidl.converters.CryptoKey(unwrappingKey, { + prefix, + context: "Argument 3", + }); + unwrapAlgorithm = webidl.converters.AlgorithmIdentifier(unwrapAlgorithm, { + prefix, + context: "Argument 4", + }); + unwrappedKeyAlgorithm = webidl.converters.AlgorithmIdentifier( + unwrappedKeyAlgorithm, + { + prefix, + context: "Argument 5", + }, + ); + extractable = webidl.converters.boolean(extractable, { + prefix, + context: "Argument 6", + }); + keyUsages = webidl.converters["sequence"](keyUsages, { + prefix, + context: "Argument 7", + }); + + // 2. + if (ArrayBufferIsView(wrappedKey)) { + wrappedKey = new Uint8Array( + wrappedKey.buffer, + wrappedKey.byteOffset, + wrappedKey.byteLength, + ); + } else { + wrappedKey = new Uint8Array(wrappedKey); + } + wrappedKey = TypedArrayPrototypeSlice(wrappedKey); + + let normalizedAlgorithm; + + try { + // 3. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "unwrapKey"); + } catch (_) { + // 4. + normalizedAlgorithm = normalizeAlgorithm(unwrapAlgorithm, "decrypt"); + } + + // 6. + const normalizedKeyAlgorithm = normalizeAlgorithm( + unwrappedKeyAlgorithm, + "importKey", + ); + + // 11. + if (normalizedAlgorithm.name !== unwrappingKey[_algorithm].name) { + throw new DOMException( + "Unwrapping algorithm doesn't match key algorithm.", + "InvalidAccessError", + ); + } + + // 12. + if (!ArrayPrototypeIncludes(unwrappingKey[_usages], "unwrapKey")) { + throw new DOMException( + "Key does not support the 'unwrapKey' operation.", + "InvalidAccessError", + ); + } + + // 13. + let key; + if ( + supportedAlgorithms["unwrapKey"][normalizedAlgorithm.name] !== undefined + ) { + // TODO(@littledivy): Implement this for AES-KW. + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); + } else if ( + supportedAlgorithms["decrypt"][normalizedAlgorithm.name] !== undefined + ) { + key = await this.decrypt( + normalizedAlgorithm, + unwrappingKey, + wrappedKey, + ); + } else { + throw new DOMException( + "Algorithm not supported", + "NotSupportedError", + ); + } + + // 14. + const bytes = key; + if (format == "jwk") { + // TODO(@littledivy): Implement JWK. + throw new DOMException( + "Not implemented", + "NotSupportedError", + ); + } + + // 15. + const result = await this.importKey( + format, + bytes, + normalizedKeyAlgorithm, + extractable, + keyUsages, + ); + // 16. + if ( + (result[_type] == "secret" || result[_type] == "private") && + keyUsages.length == 0 + ) { + throw new SyntaxError("Invalid key type."); + } + // 17. + result[_extractable] = extractable; + // 18. + result[_usages] = usageIntersection(keyUsages, recognisedUsages); + // 19. + return result; + } /** * @param {string} algorithm diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index 682296d3c1..013f08bf84 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -257,6 +257,22 @@ interface SubtleCrypto { wrappingKey: CryptoKey, wrapAlgorithm: AlgorithmIdentifier | RsaOaepParams, ): Promise; + unwrapKey( + format: KeyFormat, + wrappedKey: BufferSource, + unwrappingKey: CryptoKey, + unwrapAlgorithm: + | AlgorithmIdentifier + | RsaOaepParams + | AesCbcParams, + unwrappedKeyAlgorithm: + | AlgorithmIdentifier + | RsaHashedImportParams + | HmacImportParams + | AesKeyAlgorithm, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise; } declare interface Crypto { diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index fed082ef80..b202dacaa7 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -4151,15 +4151,9 @@ "historical.any.html": false, "historical.any.worker.html": false, "idlharness.https.any.html": [ - "SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)", - "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" ], "idlharness.https.any.worker.html": [ - "SubtleCrypto interface: operation unwrapKey(KeyFormat, BufferSource, CryptoKey, AlgorithmIdentifier, AlgorithmIdentifier, boolean, sequence)", - "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", "WorkerGlobalScope interface: attribute crypto" ], "import_export": {