From 5ee211017912bc0837cbce3b845e6ab4a7887885 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 29 Aug 2021 17:53:51 +0530 Subject: [PATCH] feat(ext/crypto): support JWK export for HMAC (#11864) --- cli/tests/unit/webcrypto_test.ts | 11 +++++-- ext/crypto/00_crypto.js | 49 ++++++++++++++++++++++++++++++-- ext/crypto/lib.deno_crypto.d.ts | 8 ++++-- 3 files changed, 61 insertions(+), 7 deletions(-) diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index a37e32eba1..475efde6c0 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -254,6 +254,8 @@ const jwk: JsonWebKey = { // unpadded base64 for rawKey. k: "AQIDBAUGBwgJCgsMDQ4PEA", alg: "HS256", + ext: true, + "key_ops": ["sign"], }; unitTest(async function subtleCryptoHmacImportExport() { @@ -297,7 +299,10 @@ unitTest(async function subtleCryptoHmacImportExport() { new Uint8Array(actual2), expected, ); - // TODO(@littledivy): Add a test for exporting JWK key when supported. - const exportedKey = await crypto.subtle.exportKey("raw", key1); - assertEquals(new Uint8Array(exportedKey), rawKey); + + const exportedKey1 = await crypto.subtle.exportKey("raw", key1); + assertEquals(new Uint8Array(exportedKey1), rawKey); + + const exportedKey2 = await crypto.subtle.exportKey("jwk", key2); + assertEquals(exportedKey2, jwk); }); diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 375170ad9c..ab6347d41b 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -12,7 +12,7 @@ const core = window.Deno.core; const webidl = window.__bootstrap.webidl; const { DOMException } = window.__bootstrap.domException; - const { atob } = window.__bootstrap.base64; + const { atob, btoa } = window.__bootstrap.base64; const { ArrayPrototypeFind, @@ -122,6 +122,13 @@ return keyBytes; } + function unpaddedBase64(bytes) { + const binaryString = core.decode(bytes); + const base64String = btoa(binaryString); + + return StringPrototypeReplace(base64String, /=/g, ""); + } + // See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm // 18.4.4 function normalizeAlgorithm(algorithm, op) { @@ -970,10 +977,48 @@ // 4-5. return bits.buffer; } - // TODO(@littledivy): jwk + case "jwk": { + // 1-3. + const jwk = { + kty: "oct", + k: unpaddedBase64(innerKey.data), + }; + // 4. + const algorithm = key[_algorithm]; + // 5. + const hash = algorithm.hash; + // 6. + switch (hash.name) { + case "SHA-1": + jwk.alg = "HS1"; + break; + case "SHA-256": + jwk.alg = "HS256"; + break; + case "SHA-384": + jwk.alg = "HS384"; + break; + case "SHA-512": + jwk.alg = "HS512"; + break; + default: + throw new DOMException( + "Hash algorithm not supported", + "NotSupportedError", + ); + } + // 7. + jwk.key_ops = key.usages; + // 8. + jwk.ext = key[_extractable]; + // 9. + return jwk; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } + // TODO(@littledivy): Redundant break but deno_lint complains without it + break; } // TODO(@littledivy): RSASSA-PKCS1-v1_5 // TODO(@littledivy): RSA-PSS diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index 2e8d2f8b2b..5169e5c3bd 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -25,7 +25,7 @@ type KeyUsage = | "unwrapKey" | "verify" | "wrapKey"; - +type KeyFormat = "jwk" | "pkcs8" | "raw" | "spki"; type NamedCurve = string; interface RsaOtherPrimesInfo { @@ -164,7 +164,11 @@ interface SubtleCrypto { extractable: boolean, keyUsages: KeyUsage[], ): Promise; - exportKey(format: "raw", key: CryptoKey): Promise; + exportKey(format: "jwk", key: CryptoKey): Promise; + exportKey( + format: Exclude, + key: CryptoKey, + ): Promise; sign( algorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams, key: CryptoKey,