diff options
author | Luca Casonato <hello@lcas.dev> | 2021-12-14 12:25:07 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-14 12:25:07 +0100 |
commit | 5fe4d5c818d598abfdfc9ef1db0b00f56cf03c20 (patch) | |
tree | bdc58af8544cd1c059e3fe86bbf230ebde0212a6 /ext/crypto/00_crypto.js | |
parent | c9d32e0581f9095e67433db688bdbedeb6b2a8c2 (diff) |
feat(ext/crypto): support importing RSA JWKs (#13071)
This commit adds support for importing RSA JWKs in the Web Crypto API.
Co-authored-by: Sean Michael Wykes <sean.wykes@nascent.com.br>
Diffstat (limited to 'ext/crypto/00_crypto.js')
-rw-r--r-- | ext/crypto/00_crypto.js | 267 |
1 files changed, 259 insertions, 8 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 258363586..99efde2d4 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -21,6 +21,7 @@ ArrayBuffer, ArrayBufferIsView, BigInt64Array, + StringPrototypeToLowerCase, StringPrototypeToUpperCase, StringPrototypeReplace, StringFromCharCode, @@ -2254,16 +2255,19 @@ const SUPPORTED_RSA_KEY_USAGES = { "RSASSA-PKCS1-v1_5": { - spki: ["verify"], - pkcs8: ["sign"], + public: ["verify"], + private: ["sign"], + jwtUse: "sig", }, "RSA-PSS": { - spki: ["verify"], - pkcs8: ["sign"], + public: ["verify"], + private: ["sign"], + jwtUse: "sig", }, "RSA-OAEP": { - spki: ["encrypt", "wrapKey"], - pkcs8: ["decrypt", "unwrapKey"], + public: ["encrypt", "wrapKey"], + private: ["decrypt", "unwrapKey"], + jwtUse: "enc", }, }; @@ -2282,7 +2286,7 @@ keyUsages, (u) => !ArrayPrototypeIncludes( - SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].pkcs8, + SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].private, u, ), ) !== undefined @@ -2328,7 +2332,7 @@ keyUsages, (u) => !ArrayPrototypeIncludes( - SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].spki, + SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].public, u, ), ) !== undefined @@ -2367,6 +2371,253 @@ return key; } + case "jwk": { + // 1. + const jwk = keyData; + + // 2. + if (jwk.d !== undefined) { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].private, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } else { + if ( + ArrayPrototypeFind( + keyUsages, + (u) => + !ArrayPrototypeIncludes( + SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].public, + u, + ), + ) !== undefined + ) { + throw new DOMException("Invalid key usages", "SyntaxError"); + } + } + + // 3. + if (StringPrototypeToUpperCase(jwk.kty) !== "RSA") { + throw new DOMException( + "'kty' property of JsonWebKey must be 'RSA'", + "DataError", + ); + } + + // 4. + if ( + keyUsages.length > 0 && jwk.use !== undefined && + StringPrototypeToLowerCase(jwk.use) !== + SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].jwtUse + ) { + throw new DOMException( + `'use' property of JsonWebKey must be '${ + SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].jwtUse + }'`, + "DataError", + ); + } + + // 5. + if (jwk.key_ops !== undefined) { + if ( + ArrayPrototypeFind( + jwk.key_ops, + (u) => !ArrayPrototypeIncludes(recognisedUsages, u), + ) !== undefined + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + + if ( + !ArrayPrototypeEvery( + jwk.key_ops, + (u) => ArrayPrototypeIncludes(keyUsages, u), + ) + ) { + throw new DOMException( + "'key_ops' property of JsonWebKey is invalid", + "DataError", + ); + } + } + + if (jwk.ext === false && extractable === true) { + throw new DOMException( + "'ext' property of JsonWebKey must not be false if extractable is true", + "DataError", + ); + } + + // 7. + let hash; + + // 8. + switch (jwk.alg) { + case undefined: + hash = undefined; + break; + case "RS1": + hash = "SHA-1"; + break; + case "RS256": + hash = "SHA-256"; + break; + case "RS384": + hash = "SHA-384"; + break; + case "RS512": + hash = "SHA-512"; + break; + default: + throw new DOMException( + `'alg' property of JsonWebKey must be one of 'RS1', 'RS256', 'RS384', 'RS512'`, + "DataError", + ); + } + + // 9. + if (hash !== undefined) { + // 9.1. + const normalizedHash = normalizeAlgorithm(hash, "digest"); + + // 9.2. + if (normalizedHash.name !== normalizedAlgorithm.hash.name) { + throw new DOMException( + `'alg' property of JsonWebKey must be '${normalizedAlgorithm.name}'`, + "DataError", + ); + } + } + + // 10. + if (jwk.d !== undefined) { + // Private key + const optimizationsPresent = jwk.p !== undefined || + jwk.q !== undefined || jwk.dp !== undefined || + jwk.dq !== undefined || jwk.qi !== undefined; + if (optimizationsPresent) { + if (jwk.q === undefined) { + throw new DOMException( + "'q' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.dp === undefined) { + throw new DOMException( + "'dp' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.dq === undefined) { + throw new DOMException( + "'dq' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.qi === undefined) { + throw new DOMException( + "'qi' property of JsonWebKey is required for private keys", + "DataError", + ); + } + if (jwk.oth !== undefined) { + throw new DOMException( + "'oth' property of JsonWebKey is not supported", + "NotSupportedError", + ); + } + } else { + throw new DOMException( + "only optimized private keys are supported", + "NotSupportedError", + ); + } + + const { modulusLength, publicExponent, rawData } = core.opSync( + "op_crypto_import_key", + { + algorithm: normalizedAlgorithm.name, + hash: normalizedAlgorithm.hash.name, + }, + { jwkPrivateRsa: jwk }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "private", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } else { + // Validate that this is a valid public key. + if (jwk.n === undefined) { + throw new DOMException( + "'n' property of JsonWebKey is required for public keys", + "DataError", + ); + } + if (jwk.e === undefined) { + throw new DOMException( + "'e' property of JsonWebKey is required for public keys", + "DataError", + ); + } + + const { modulusLength, publicExponent, rawData } = core.opSync( + "op_crypto_import_key", + { + algorithm: normalizedAlgorithm.name, + hash: normalizedAlgorithm.hash.name, + }, + { jwkPublicRsa: jwk }, + ); + + const handle = {}; + WeakMapPrototypeSet(KEY_STORE, handle, rawData); + + const algorithm = { + name: normalizedAlgorithm.name, + modulusLength, + publicExponent, + hash: normalizedAlgorithm.hash, + }; + + const key = constructKey( + "public", + extractable, + usageIntersection(keyUsages, recognisedUsages), + algorithm, + handle, + ); + + return key; + } + } default: throw new DOMException("Not implemented", "NotSupportedError"); } |