diff options
author | Luca Casonato <hello@lcas.dev> | 2021-12-14 17:02:14 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-14 17:02:14 +0100 |
commit | b220a58d1a678ffd46a42567909d0b0d59731d99 (patch) | |
tree | 423a9c086696e77b58a71317e005a713903fb7f5 /ext/crypto | |
parent | 5fe4d5c818d598abfdfc9ef1db0b00f56cf03c20 (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.js | 204 | ||||
-rw-r--r-- | ext/crypto/export_key.rs | 61 | ||||
-rw-r--r-- | ext/crypto/lib.deno_crypto.d.ts | 6 |
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>; |