Skip to content

Commit

Permalink
feat(ext/crypto): support importing RSA JWKs (#13071)
Browse files Browse the repository at this point in the history
This commit adds support for importing RSA JWKs in the Web Crypto API.

Co-authored-by: Sean Michael Wykes <sean.wykes@nascent.com.br>
  • Loading branch information
lucacasonato and cryptographix authored Dec 14, 2021
1 parent c9d32e0 commit 5fe4d5c
Show file tree
Hide file tree
Showing 3 changed files with 371 additions and 254 deletions.
267 changes: 259 additions & 8 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
ArrayBuffer,
ArrayBufferIsView,
BigInt64Array,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
StringPrototypeReplace,
StringFromCharCode,
Expand Down Expand Up @@ -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",
},
};

Expand All @@ -2282,7 +2286,7 @@
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].pkcs8,
SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].private,
u,
),
) !== undefined
Expand Down Expand Up @@ -2328,7 +2332,7 @@
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].spki,
SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].public,
u,
),
) !== undefined
Expand Down Expand Up @@ -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");
}
Expand Down
Loading

0 comments on commit 5fe4d5c

Please sign in to comment.