summaryrefslogtreecommitdiff
path: root/ext/crypto
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2021-12-14 17:02:14 +0100
committerGitHub <noreply@github.com>2021-12-14 17:02:14 +0100
commitb220a58d1a678ffd46a42567909d0b0d59731d99 (patch)
tree423a9c086696e77b58a71317e005a713903fb7f5 /ext/crypto
parent5fe4d5c818d598abfdfc9ef1db0b00f56cf03c20 (diff)
feat(ext/crypto): support exporting RSA JWKs (#13081)
This commit adds support for exporting RSA JWKs in the Web Crypto API. It also does some minor fixes for RSA JWK imports. Co-authored-by: Sean Michael Wykes <sean.wykes@nascent.com.br>
Diffstat (limited to 'ext/crypto')
-rw-r--r--ext/crypto/00_crypto.js204
-rw-r--r--ext/crypto/export_key.rs61
-rw-r--r--ext/crypto/lib.deno_crypto.d.ts6
3 files changed, 234 insertions, 37 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js
index 99efde2d4..0e09197a8 100644
--- a/ext/crypto/00_crypto.js
+++ b/ext/crypto/00_crypto.js
@@ -15,31 +15,32 @@
const { btoa } = window.__bootstrap.base64;
const {
- ArrayPrototypeFind,
- ArrayPrototypeEvery,
- ArrayPrototypeIncludes,
ArrayBuffer,
ArrayBufferIsView,
+ ArrayPrototypeEvery,
+ ArrayPrototypeFind,
+ ArrayPrototypeIncludes,
BigInt64Array,
+ Int16Array,
+ Int32Array,
+ Int8Array,
+ ObjectAssign,
+ StringFromCharCode,
+ StringPrototypeReplace,
StringPrototypeToLowerCase,
StringPrototypeToUpperCase,
- StringPrototypeReplace,
- StringFromCharCode,
Symbol,
SymbolFor,
SyntaxError,
- WeakMap,
- WeakMapPrototypeGet,
- WeakMapPrototypeSet,
- Int8Array,
- Uint8Array,
TypedArrayPrototypeSlice,
- Int16Array,
+ TypeError,
Uint16Array,
- Int32Array,
Uint32Array,
+ Uint8Array,
Uint8ClampedArray,
- TypeError,
+ WeakMap,
+ WeakMapPrototypeGet,
+ WeakMapPrototypeSet,
} = window.__bootstrap.primordials;
// P-521 is not yet supported.
@@ -2464,27 +2465,75 @@
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",
- );
+ if (normalizedAlgorithm.name === "RSASSA-PKCS1-v1_5") {
+ 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",
+ );
+ }
+ } else if (normalizedAlgorithm.name === "RSA-PSS") {
+ switch (jwk.alg) {
+ case undefined:
+ hash = undefined;
+ break;
+ case "PS1":
+ hash = "SHA-1";
+ break;
+ case "PS256":
+ hash = "SHA-256";
+ break;
+ case "PS384":
+ hash = "SHA-384";
+ break;
+ case "PS512":
+ hash = "SHA-512";
+ break;
+ default:
+ throw new DOMException(
+ `'alg' property of JsonWebKey must be one of 'PS1', 'PS256', 'PS384', 'PS512'`,
+ "DataError",
+ );
+ }
+ } else {
+ switch (jwk.alg) {
+ case undefined:
+ hash = undefined;
+ break;
+ case "RSA-OAEP":
+ hash = "SHA-1";
+ break;
+ case "RSA-OAEP-256":
+ hash = "SHA-256";
+ break;
+ case "RSA-OAEP-384":
+ hash = "SHA-384";
+ break;
+ case "RSA-OAEP-512":
+ hash = "SHA-512";
+ break;
+ default:
+ throw new DOMException(
+ `'alg' property of JsonWebKey must be one of 'RSA-OAEP', 'RSA-OAEP-256', 'RSA-OAEP-384', or 'RSA-OAEP-512'`,
+ "DataError",
+ );
+ }
}
// 9.
@@ -2822,6 +2871,93 @@
// 3.
return data.buffer;
}
+ case "jwk": {
+ // 1-2.
+ const jwk = {
+ kty: "RSA",
+ };
+
+ // 3.
+ const hash = key[_algorithm].hash.name;
+
+ // 4.
+ if (key[_algorithm].name === "RSASSA-PKCS1-v1_5") {
+ switch (hash) {
+ case "SHA-1":
+ jwk.alg = "RS1";
+ break;
+ case "SHA-256":
+ jwk.alg = "RS256";
+ break;
+ case "SHA-384":
+ jwk.alg = "RS384";
+ break;
+ case "SHA-512":
+ jwk.alg = "RS512";
+ break;
+ default:
+ throw new DOMException(
+ "Hash algorithm not supported",
+ "NotSupportedError",
+ );
+ }
+ } else if (key[_algorithm].name === "RSA-PSS") {
+ switch (hash) {
+ case "SHA-1":
+ jwk.alg = "PS1";
+ break;
+ case "SHA-256":
+ jwk.alg = "PS256";
+ break;
+ case "SHA-384":
+ jwk.alg = "PS384";
+ break;
+ case "SHA-512":
+ jwk.alg = "PS512";
+ break;
+ default:
+ throw new DOMException(
+ "Hash algorithm not supported",
+ "NotSupportedError",
+ );
+ }
+ } else {
+ switch (hash) {
+ case "SHA-1":
+ jwk.alg = "RSA-OAEP";
+ break;
+ case "SHA-256":
+ jwk.alg = "RSA-OAEP-256";
+ break;
+ case "SHA-384":
+ jwk.alg = "RSA-OAEP-384";
+ break;
+ case "SHA-512":
+ jwk.alg = "RSA-OAEP-512";
+ break;
+ default:
+ throw new DOMException(
+ "Hash algorithm not supported",
+ "NotSupportedError",
+ );
+ }
+ }
+
+ // 5-6.
+ const data = core.opSync("op_crypto_export_key", {
+ format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
+ algorithm: key[_algorithm].name,
+ }, innerKey);
+ ObjectAssign(jwk, data);
+
+ // 7.
+ jwk.key_ops = key.usages;
+
+ // 8.
+ jwk.ext = key[_extractable];
+
+ return jwk;
+ }
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
diff --git a/ext/crypto/export_key.rs b/ext/crypto/export_key.rs
index 22a4b55ca..2e74d61f2 100644
--- a/ext/crypto/export_key.rs
+++ b/ext/crypto/export_key.rs
@@ -1,9 +1,12 @@
+use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
+use rsa::pkcs1::UIntBytes;
use serde::Deserialize;
use serde::Serialize;
use spki::der::asn1;
+use spki::der::Decodable;
use spki::der::Encodable;
use crate::shared::*;
@@ -21,7 +24,8 @@ pub struct ExportKeyOptions {
pub enum ExportKeyFormat {
Pkcs8,
Spki,
- Jwk,
+ JwkPublic,
+ JwkPrivate,
}
#[derive(Deserialize)]
@@ -40,6 +44,20 @@ pub enum ExportKeyAlgorithm {
pub enum ExportKeyResult {
Pkcs8(ZeroCopyBuf),
Spki(ZeroCopyBuf),
+ JwkPublicRsa {
+ n: String,
+ e: String,
+ },
+ JwkPrivateRsa {
+ n: String,
+ e: String,
+ d: String,
+ p: String,
+ q: String,
+ dp: String,
+ dq: String,
+ qi: String,
+ },
}
pub fn op_crypto_export_key(
@@ -54,6 +72,10 @@ pub fn op_crypto_export_key(
}
}
+fn uint_to_b64(bytes: UIntBytes) -> String {
+ base64::encode_config(bytes.as_bytes(), base64::URL_SAFE_NO_PAD)
+}
+
fn export_key_rsa(
format: ExportKeyFormat,
key_data: RawKeyData,
@@ -108,6 +130,41 @@ fn export_key_rsa(
Ok(ExportKeyResult::Pkcs8(pkcs8_der.into()))
}
- _ => Err(unsupported_format()),
+ ExportKeyFormat::JwkPublic => {
+ let public_key = key_data.as_rsa_public_key()?;
+ let public_key = rsa::pkcs1::RsaPublicKey::from_der(&public_key)
+ .map_err(|_| {
+ custom_error(
+ "DOMExceptionOperationError",
+ "failed to decode public key",
+ )
+ })?;
+
+ Ok(ExportKeyResult::JwkPublicRsa {
+ n: uint_to_b64(public_key.modulus),
+ e: uint_to_b64(public_key.public_exponent),
+ })
+ }
+ ExportKeyFormat::JwkPrivate => {
+ let private_key = key_data.as_rsa_private_key()?;
+ let private_key = rsa::pkcs1::RsaPrivateKey::from_der(private_key)
+ .map_err(|_| {
+ custom_error(
+ "DOMExceptionOperationError",
+ "failed to decode private key",
+ )
+ })?;
+
+ Ok(ExportKeyResult::JwkPrivateRsa {
+ n: uint_to_b64(private_key.modulus),
+ e: uint_to_b64(private_key.public_exponent),
+ d: uint_to_b64(private_key.private_exponent),
+ p: uint_to_b64(private_key.prime1),
+ q: uint_to_b64(private_key.prime2),
+ dp: uint_to_b64(private_key.exponent1),
+ dq: uint_to_b64(private_key.exponent2),
+ qi: uint_to_b64(private_key.coefficient),
+ })
+ }
}
}
diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts
index 0338448af..7681a56ab 100644
--- a/ext/crypto/lib.deno_crypto.d.ts
+++ b/ext/crypto/lib.deno_crypto.d.ts
@@ -101,6 +101,10 @@ interface HmacImportParams extends Algorithm {
length?: number;
}
+interface RsaHashedImportParams extends Algorithm {
+ hash: HashAlgorithmIdentifier;
+}
+
interface EcKeyAlgorithm extends KeyAlgorithm {
namedCurve: NamedCurve;
}
@@ -191,7 +195,7 @@ interface SubtleCrypto {
importKey(
format: "jwk",
keyData: JsonWebKey,
- algorithm: AlgorithmIdentifier | HmacImportParams,
+ algorithm: AlgorithmIdentifier | HmacImportParams | RsaHashedImportParams,
extractable: boolean,
keyUsages: KeyUsage[],
): Promise<CryptoKey>;