From abd25e59827416eebea0fc3730c9c00e8ced8d3a Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 9 Dec 2021 16:20:14 +0100 Subject: [PATCH] refactor(ext/crypto): clean up `importKey` logic (#13028) This commit de-duplicates and cleans up some logic in `importKey`. There are no functional changes in this commit (just moves code around). --- ext/crypto/00_crypto.js | 1049 ++++++++++++++++++--------------------- 1 file changed, 474 insertions(+), 575 deletions(-) diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 2efe550e01..bef5155e8d 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -845,608 +845,70 @@ switch (normalizedAlgorithm.name) { case "HMAC": { - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - let hash; - let data; - - // 4. https://w3c.github.io/webcrypto/#hmac-operations - switch (format) { - case "raw": { - 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. - hash = normalizedAlgorithm.hash; - // 6. - 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"); - } - - // 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 ( - ArrayPrototypeFind( - jwk.key_ops, - (u) => !ArrayPrototypeIncludes(recognisedUsages, u), - ) !== undefined - ) { - throw new DOMException( - "`key_ops` member of JsonWebKey is invalid", - "DataError", - ); - } - - if ( - !ArrayPrototypeEvery( - jwk.key_ops, - (u) => ArrayPrototypeIncludes(keyUsages, u), - ) - ) { - throw new DOMException( - "`key_ops` member of JsonWebKey is invalid", - "DataError", - ); - } - } - - // 9. - if (jwk.ext === false && extractable == true) { - throw new DOMException( - "`ext` member of JsonWebKey is invalid", - "DataError", - ); - } - - break; - } - 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", + return importKeyHMAC( + format, + normalizedAlgorithm, + keyData, extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, + keyUsages, ); - - return key; } - // TODO(@littledivy): RSA-PSS case "ECDSA": { - switch (format) { - case "raw": { - // 1. - if ( - !ArrayPrototypeIncludes( - supportedNamedCurves, - normalizedAlgorithm.namedCurve, - ) - ) { - throw new DOMException( - "Invalid namedCurve", - "DataError", - ); - } - - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["verify"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - const { data } = await core.opAsync("op_crypto_import_key", { - algorithm: "ECDSA", - namedCurve: normalizedAlgorithm.namedCurve, - }, keyData); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "raw", - data, - }); - - // 4-5. - const algorithm = { - name: "ECDSA", - namedCurve: normalizedAlgorithm.namedCurve, - }; - - // 6-8. - const key = constructKey( - "public", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } - } - case "RSASSA-PKCS1-v1_5": { - switch (format) { - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["sign"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - if (keyUsages.length == 0) { - throw new DOMException("Key usage is empty", "SyntaxError"); - } - - // 2-9. - const { modulusLength, publicExponent, data } = await core - .opAsync( - "op_crypto_import_key", - { - algorithm: "RSASSA-PKCS1-v1_5", - format: "pkcs8", - // Needed to perform step 7 without normalization. - hash: normalizedAlgorithm.hash.name, - }, - keyData, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - // PKCS#1 for RSA - type: "raw", - data, - }); - - const algorithm = { - name: "RSASSA-PKCS1-v1_5", - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } + return await importKeyECDSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ); } + case "RSASSA-PKCS1-v1_5": case "RSA-PSS": { - switch (format) { - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["sign"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - if (keyUsages.length == 0) { - throw new DOMException("Key usage is empty", "SyntaxError"); - } - - // 2-9. - const { modulusLength, publicExponent, data } = await core - .opAsync( - "op_crypto_import_key", - { - algorithm: "RSA-PSS", - format: "pkcs8", - // Needed to perform step 7 without normalization. - hash: normalizedAlgorithm.hash.name, - }, - keyData, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - // PKCS#1 for RSA - type: "raw", - data, - }); - - const algorithm = { - name: "RSA-PSS", - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } + return await importKeyRSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ["sign"], + ); } case "RSA-OAEP": { - switch (format) { - case "pkcs8": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["decrypt", "unwrapKey"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - if (keyUsages.length == 0) { - throw new DOMException("Key usage is empty", "SyntaxError"); - } - - // 2-9. - const { modulusLength, publicExponent, data } = await core - .opAsync( - "op_crypto_import_key", - { - algorithm: "RSA-OAEP", - format: "pkcs8", - // Needed to perform step 7 without normalization. - hash: normalizedAlgorithm.hash.name, - }, - keyData, - ); - - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - // PKCS#1 for RSA - type: "raw", - data, - }); - - const algorithm = { - name: "RSA-OAEP", - modulusLength, - publicExponent, - hash: normalizedAlgorithm.hash, - }; - - const key = constructKey( - "private", - extractable, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - return key; - } - default: - throw new DOMException("Not implemented", "NotSupportedError"); - } + return await importKeyRSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ["decrypt", "unwrapKey"], + ); } case "HKDF": { - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } - - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 2. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); - } - - // 3. - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "raw", - data: keyData, - }); - - // 4-8. - const algorithm = { - name: "HKDF", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - // 9. - return key; + return importKeyHKDF(format, keyData, extractable, keyUsages); } case "PBKDF2": { - // 1. - if (format !== "raw") { - throw new DOMException("Format not supported", "NotSupportedError"); - } - - // 2. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - // 3. - if (extractable !== false) { - throw new DOMException( - "Key must not be extractable", - "SyntaxError", - ); - } - - // 4. - const handle = {}; - WeakMapPrototypeSet(KEY_STORE, handle, { - type: "raw", - data: keyData, - }); - - // 5-9. - const algorithm = { - name: "PBKDF2", - }; - const key = constructKey( - "secret", - false, - usageIntersection(keyUsages, recognisedUsages), - algorithm, - handle, - ); - - // 10. - return key; - } - case "AES-CTR": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - return importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); - } - case "AES-CBC": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - return importKeyAES( - format, - normalizedAlgorithm, - keyData, - extractable, - keyUsages, - ); + return importKeyPBKDF2(format, keyData, extractable, keyUsages); } + case "AES-CTR": + case "AES-CBC": case "AES-GCM": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes([ - "encrypt", - "decrypt", - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - return importKeyAES( + return await importKeyAES( format, normalizedAlgorithm, keyData, extractable, keyUsages, + ["encrypt", "decrypt", "wrapKey", "unwrapKey"], ); } case "AES-KW": { - // 1. - if ( - ArrayPrototypeFind( - keyUsages, - (u) => - !ArrayPrototypeIncludes([ - "wrapKey", - "unwrapKey", - ], u), - ) !== undefined - ) { - throw new DOMException("Invalid key usages", "SyntaxError"); - } - - return importKeyAES( + return await importKeyAES( format, normalizedAlgorithm, keyData, extractable, keyUsages, + ["wrapKey", "unwrapKey"], ); } default: @@ -2659,7 +2121,18 @@ keyData, extractable, keyUsages, + supportedKeyUsages, ) { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + // 2. let data = keyData; switch (format) { @@ -2798,6 +2271,432 @@ // 8. return key; } + + function importKeyHMAC( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ) { + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["sign", "verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + let hash; + let data; + + // 4. https://w3c.github.io/webcrypto/#hmac-operations + switch (format) { + case "raw": { + 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. + hash = normalizedAlgorithm.hash; + // 6. + 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"); + } + + // 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 ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "`key_ops` member of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "`key_ops` member of JsonWebKey is invalid", + "DataError", + ); + } + } + + // 9. + if (jwk.ext === false && extractable == true) { + throw new DOMException( + "`ext` member of JsonWebKey is invalid", + "DataError", + ); + } + + break; + } + 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; + } + + async function importKeyECDSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + ) { + switch (format) { + case "raw": { + // 1. + if ( + !ArrayPrototypeIncludes( + supportedNamedCurves, + normalizedAlgorithm.namedCurve, + ) + ) { + throw new DOMException( + "Invalid namedCurve", + "DataError", + ); + } + + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["verify"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + const { data } = await core.opAsync("op_crypto_import_key", { + algorithm: "ECDSA", + namedCurve: normalizedAlgorithm.namedCurve, + }, keyData); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "raw", + data, + }); + + // 4-5. + const algorithm = { + name: "ECDSA", + namedCurve: normalizedAlgorithm.namedCurve, + }; + + // 6-8. + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } + } + + async function importKeyRSA( + format, + normalizedAlgorithm, + keyData, + extractable, + keyUsages, + supportedKeyUsages, + ) { + switch (format) { + case "pkcs8": { + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(supportedKeyUsages, u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + if (keyUsages.length == 0) { + throw new DOMException("Key usage is empty", "SyntaxError"); + } + + // 2-9. + const { modulusLength, publicExponent, data } = await core + .opAsync( + "op_crypto_import_key", + { + algorithm: normalizedAlgorithm.name, + format: "pkcs8", + // Needed to perform step 7 without normalization. + hash: normalizedAlgorithm.hash.name, + }, + keyData, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + // PKCS#1 for RSA + type: "raw", + data, + }); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + default: + throw new DOMException("Not implemented", "NotSupportedError"); + } + } + + function importKeyHKDF( + format, + keyData, + extractable, + keyUsages, + ) { + if (format !== "raw") { + throw new DOMException("Format not supported", "NotSupportedError"); + } + + // 1. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 2. + if (extractable !== false) { + throw new DOMException( + "Key must not be extractable", + "SyntaxError", + ); + } + + // 3. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "raw", + data: keyData, + }); + + // 4-8. + const algorithm = { + name: "HKDF", + }; + const key = constructKey( + "secret", + false, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 9. + return key; + } + + function importKeyPBKDF2( + format, + keyData, + extractable, + keyUsages, + ) { + // 1. + if (format !== "raw") { + throw new DOMException("Format not supported", "NotSupportedError"); + } + + // 2. + if ( + ArrayPrototypeFind( + keyUsages, + (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + + // 3. + if (extractable !== false) { + throw new DOMException( + "Key must not be extractable", + "SyntaxError", + ); + } + + // 4. + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, { + type: "raw", + data: keyData, + }); + + // 5-9. + const algorithm = { + name: "PBKDF2", + }; + const key = constructKey( + "secret", + false, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + // 10. + return key; + } + async function generateKeyAES(normalizedAlgorithm, extractable, usages) { // 2. if (!ArrayPrototypeIncludes([128, 192, 256], normalizedAlgorithm.length)) {