diff options
Diffstat (limited to 'ext/crypto')
-rw-r--r-- | ext/crypto/00_crypto.js | 240 | ||||
-rw-r--r-- | ext/crypto/01_webidl.js | 118 | ||||
-rw-r--r-- | ext/crypto/lib.deno_crypto.d.ts | 35 |
3 files changed, 348 insertions, 45 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 5c80ac0ca..375170ad9 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -12,13 +12,18 @@ const core = window.Deno.core; const webidl = window.__bootstrap.webidl; const { DOMException } = window.__bootstrap.domException; + const { atob } = window.__bootstrap.base64; const { ArrayPrototypeFind, - ArrayBufferIsView, + ArrayPrototypeEvery, ArrayPrototypeIncludes, + ArrayBuffer, + ArrayBufferIsView, BigInt64Array, StringPrototypeToUpperCase, + StringPrototypeReplace, + StringPrototypeCharCodeAt, Symbol, SymbolFor, SymbolToStringTag, @@ -100,6 +105,23 @@ }, }; + // Decodes the unpadded base64 to the octet sequence containing key value `k` defined in RFC7518 Section 6.4 + function decodeSymmetricKey(key) { + // Decode from base64url without `=` padding. + const base64 = StringPrototypeReplace( + StringPrototypeReplace(key, /\-/g, "+"), + /\_/g, + "/", + ); + const decodedKey = atob(base64); + const keyLength = decodedKey.length; + const keyBytes = new Uint8Array(keyLength); + for (let i = 0; i < keyLength; i++) { + keyBytes[i] = StringPrototypeCharCodeAt(decodedKey, i); + } + return keyBytes; + } + // See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm // 18.4.4 function normalizeAlgorithm(algorithm, op) { @@ -631,7 +653,7 @@ prefix, context: "Argument 1", }); - keyData = webidl.converters.BufferSource(keyData, { + keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, { prefix, context: "Argument 2", }); @@ -649,22 +671,32 @@ }); // 2. - if (ArrayBufferIsView(keyData)) { - keyData = new Uint8Array( - keyData.buffer, - keyData.byteOffset, - keyData.byteLength, - ); + if (format !== "jwk") { + if (ArrayBufferIsView(keyData) || keyData instanceof ArrayBuffer) { + if (ArrayBufferIsView(keyData)) { + keyData = new Uint8Array( + keyData.buffer, + keyData.byteOffset, + keyData.byteLength, + ); + } else { + keyData = new Uint8Array(keyData); + } + keyData = TypedArrayPrototypeSlice(keyData); + } else { + throw new TypeError("keyData is a JsonWebKey"); + } } else { - keyData = new Uint8Array(keyData); + if (ArrayBufferIsView(keyData) || keyData instanceof ArrayBuffer) { + throw new TypeError("keyData is not a JsonWebKey"); + } } - keyData = TypedArrayPrototypeSlice(keyData); const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey"); switch (normalizedAlgorithm.name) { - // https://w3c.github.io/webcrypto/#hmac-operations case "HMAC": { + // 2. if ( ArrayPrototypeFind( keyUsages, @@ -674,59 +706,177 @@ throw new DOMException("Invalid key usages", "SyntaxError"); } + // 3. + let hash; + let data; + + // 4. https://w3c.github.io/webcrypto/#hmac-operations switch (format) { case "raw": { - const hash = normalizedAlgorithm.hash; + data = keyData; + hash = normalizedAlgorithm.hash; + break; + } + case "jwk": { + // TODO(@littledivy): Why does the spec validate JWK twice? + const jwk = keyData; + // 2. + if (jwk.kty !== "oct") { + throw new DOMException( + "`kty` member of JsonWebKey must be `oct`", + "DataError", + ); + } + + // Section 6.4.1 of RFC7518 + if (!jwk.k) { + throw new DOMException( + "`k` member of JsonWebKey must be present", + "DataError", + ); + } + + // 4. + data = decodeSymmetricKey(jwk.k); // 5. - let length = keyData.byteLength * 8; + hash = normalizedAlgorithm.hash; // 6. - if (length === 0) { - throw new DOMException("Key length is zero", "DataError"); + switch (hash.name) { + case "SHA-1": { + if (jwk.alg !== undefined && jwk.alg !== "HS1") { + throw new DOMException( + "`alg` member of JsonWebKey must be `HS1`", + "DataError", + ); + } + break; + } + case "SHA-256": { + if (jwk.alg !== undefined && jwk.alg !== "HS256") { + throw new DOMException( + "`alg` member of JsonWebKey must be `HS256`", + "DataError", + ); + } + break; + } + case "SHA-384": { + if (jwk.alg !== undefined && jwk.alg !== "HS384") { + throw new DOMException( + "`alg` member of JsonWebKey must be `HS384`", + "DataError", + ); + } + break; + } + case "SHA-512": { + if (jwk.alg !== undefined && jwk.alg !== "HS512") { + throw new DOMException( + "`alg` member of JsonWebKey must be `HS512`", + "DataError", + ); + } + break; + } + default: + throw new TypeError("unreachable"); } - if (normalizeAlgorithm.length) { - // 7. + + // 7. + if (keyUsages.length > 0 && jwk.use && jwk.use !== "sign") { + throw new DOMException( + "`use` member of JsonWebKey must be `sign`", + "DataError", + ); + } + + // 8. + // Section 4.3 of RFC7517 + if (jwk.key_ops) { if ( - normalizedAlgorithm.length > length || - normalizedAlgorithm.length <= (length - 8) + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined ) { throw new DOMException( - "Key length is invalid", + "`key_ops` member of JsonWebKey is invalid", "DataError", ); } - length = normalizeAlgorithm.length; - } - if (keyUsages.length == 0) { - throw new DOMException("Key usage is empty", "SyntaxError"); + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "`key_ops` member of JsonWebKey is invalid", + "DataError", + ); + } } - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "raw", - data: keyData, - }); - - const algorithm = { - name: "HMAC", - length, - hash, - }; - - const key = constructKey( - "secret", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); + // 9. + if (jwk.ext === false && extractable == true) { + throw new DOMException( + "`ext` member of JsonWebKey is invalid", + "DataError", + ); + } - return key; + break; } - // TODO(@littledivy): jwk default: throw new DOMException("Not implemented", "NotSupportedError"); } + + // 5. + let length = data.byteLength * 8; + // 6. + if (length === 0) { + throw new DOMException("Key length is zero", "DataError"); + } + // 7. + if (normalizedAlgorithm.length !== undefined) { + if ( + normalizedAlgorithm.length > length || + normalizedAlgorithm.length <= (length - 8) + ) { + throw new DOMException( + "Key length is invalid", + "DataError", + ); + } + length = normalizedAlgorithm.length; + } + + if (keyUsages.length == 0) { + throw new DOMException("Key usage is empty", "SyntaxError"); + } + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "raw", + data, + }); + + const algorithm = { + name: "HMAC", + length, + hash, + }; + + const key = constructKey( + "secret", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; } // TODO(@littledivy): RSASSA-PKCS1-v1_5 // TODO(@littledivy): RSA-PSS diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js index 43bc5e822..e781e4334 100644 --- a/ext/crypto/01_webidl.js +++ b/ext/crypto/01_webidl.js @@ -9,6 +9,7 @@ ((window) => { const webidl = window.__bootstrap.webidl; const { CryptoKey } = window.__bootstrap.crypto; + const { ArrayBufferIsView, ArrayBuffer } = window.__bootstrap.primordials; webidl.converters.AlgorithmIdentifier = (V, opts) => { // Union for (object or DOMString) @@ -18,6 +19,14 @@ return webidl.converters.DOMString(V, opts); }; + webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => { + // Union for (BufferSource or JsonWebKey) + if (ArrayBufferIsView(V) || V instanceof ArrayBuffer) { + return webidl.converters.BufferSource(V, opts); + } + return webidl.converters.JsonWebKey(V, opts); + }; + webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [ "public", "private", @@ -178,6 +187,115 @@ webidl.converters.HmacImportParams = webidl .createDictionaryConverter("HmacImportParams", dictHmacImportParams); + const dictRsaOtherPrimesInfo = [ + { + key: "r", + converter: webidl.converters["DOMString"], + }, + { + key: "d", + converter: webidl.converters["DOMString"], + }, + { + key: "t", + converter: webidl.converters["DOMString"], + }, + ]; + + webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter( + "RsaOtherPrimesInfo", + dictRsaOtherPrimesInfo, + ); + webidl.converters["sequence<RsaOtherPrimesInfo>"] = webidl + .createSequenceConverter( + webidl.converters.RsaOtherPrimesInfo, + ); + + const dictJsonWebKey = [ + // Sections 4.2 and 4.3 of RFC7517. + // https://datatracker.ietf.org/doc/html/rfc7517#section-4 + { + key: "kty", + converter: webidl.converters["DOMString"], + }, + { + key: "use", + converter: webidl.converters["DOMString"], + }, + { + key: "key_ops", + converter: webidl.converters["sequence<DOMString>"], + }, + { + key: "alg", + converter: webidl.converters["DOMString"], + }, + // JSON Web Key Parameters Registration + { + key: "ext", + converter: webidl.converters["boolean"], + }, + // Section 6 of RFC7518 JSON Web Algorithms + // https://datatracker.ietf.org/doc/html/rfc7518#section-6 + { + key: "crv", + converter: webidl.converters["DOMString"], + }, + { + key: "x", + converter: webidl.converters["DOMString"], + }, + { + key: "y", + converter: webidl.converters["DOMString"], + }, + { + key: "d", + converter: webidl.converters["DOMString"], + }, + { + key: "n", + converter: webidl.converters["DOMString"], + }, + { + key: "e", + converter: webidl.converters["DOMString"], + }, + { + key: "p", + converter: webidl.converters["DOMString"], + }, + { + key: "q", + converter: webidl.converters["DOMString"], + }, + { + key: "dp", + converter: webidl.converters["DOMString"], + }, + { + key: "dq", + converter: webidl.converters["DOMString"], + }, + { + key: "qi", + converter: webidl.converters["DOMString"], + }, + { + key: "oth", + converter: webidl.converters["sequence<RsaOtherPrimesInfo>"], + }, + { + key: "k", + converter: webidl.converters["DOMString"], + }, + ]; + + webidl.converters.JsonWebKey = webidl.createDictionaryConverter( + "JsonWebKey", + dictJsonWebKey, + ); + const dictPbkdf2Params = [ ...dictAlgorithm, { diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts index 6b1901611..2e8d2f8b2 100644 --- a/ext/crypto/lib.deno_crypto.d.ts +++ b/ext/crypto/lib.deno_crypto.d.ts @@ -28,6 +28,34 @@ type KeyUsage = type NamedCurve = string; +interface RsaOtherPrimesInfo { + d?: string; + r?: string; + t?: string; +} + +interface JsonWebKey { + alg?: string; + crv?: string; + d?: string; + dp?: string; + dq?: string; + e?: string; + ext?: boolean; + k?: string; + // deno-lint-ignore camelcase + key_ops?: string[]; + kty?: string; + n?: string; + oth?: RsaOtherPrimesInfo[]; + p?: string; + q?: string; + qi?: string; + use?: string; + x?: string; + y?: string; +} + interface HmacKeyGenParams extends Algorithm { hash: HashAlgorithmIdentifier; length?: number; @@ -123,6 +151,13 @@ interface SubtleCrypto { keyUsages: KeyUsage[], ): Promise<CryptoKeyPair | CryptoKey>; importKey( + format: "jwk", + keyData: JsonWebKey, + algorithm: AlgorithmIdentifier | HmacImportParams, + extractable: boolean, + keyUsages: KeyUsage[], + ): Promise<CryptoKey>; + importKey( format: "raw", keyData: BufferSource, algorithm: AlgorithmIdentifier | HmacImportParams, |