summaryrefslogtreecommitdiff
path: root/ext/crypto/00_crypto.js
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2021-12-14 12:25:07 +0100
committerGitHub <noreply@github.com>2021-12-14 12:25:07 +0100
commit5fe4d5c818d598abfdfc9ef1db0b00f56cf03c20 (patch)
treebdc58af8544cd1c059e3fe86bbf230ebde0212a6 /ext/crypto/00_crypto.js
parentc9d32e0581f9095e67433db688bdbedeb6b2a8c2 (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.js267
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");
}