diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 4e2a90f3bb..8180613878 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -852,6 +852,7 @@ * @param {CryptoKey} key * @returns {Promise} */ + // deno-lint-ignore require-await async exportKey(format, key) { webidl.assertBranded(this, SubtleCrypto); const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'"; @@ -878,7 +879,7 @@ case "RSASSA-PKCS1-v1_5": case "RSA-PSS": case "RSA-OAEP": { - return await exportKeyRSA(format, key, innerKey); + return exportKeyRSA(format, key, innerKey); } case "AES-CTR": case "AES-CBC": @@ -2532,7 +2533,7 @@ } } - async function exportKeyRSA(format, key, innerKey) { + function exportKeyRSA(format, key, innerKey) { switch (format) { case "pkcs8": { // 1. @@ -2544,12 +2545,10 @@ } // 2. - const data = await core.opAsync("op_crypto_export_key", { - key: innerKey, - format: "pkcs8", + const data = core.opSync("op_crypto_export_key", { algorithm: key[_algorithm].name, - hash: key[_algorithm].hash.name, - }); + format: "pkcs8", + }, innerKey); // 3. return data.buffer; @@ -2564,12 +2563,10 @@ } // 2. - const data = await core.opAsync("op_crypto_export_key", { - key: innerKey, - format: "spki", + const data = core.opSync("op_crypto_export_key", { algorithm: key[_algorithm].name, - hash: key[_algorithm].hash.name, - }); + format: "spki", + }, innerKey); // 3. return data.buffer; diff --git a/ext/crypto/export_key.rs b/ext/crypto/export_key.rs new file mode 100644 index 0000000000..22a4b55ca2 --- /dev/null +++ b/ext/crypto/export_key.rs @@ -0,0 +1,113 @@ +use deno_core::error::AnyError; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use serde::Deserialize; +use serde::Serialize; +use spki::der::asn1; +use spki::der::Encodable; + +use crate::shared::*; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExportKeyOptions { + format: ExportKeyFormat, + #[serde(flatten)] + algorithm: ExportKeyAlgorithm, +} + +#[derive(Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ExportKeyFormat { + Pkcs8, + Spki, + Jwk, +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", tag = "algorithm")] +pub enum ExportKeyAlgorithm { + #[serde(rename = "RSASSA-PKCS1-v1_5")] + RsassaPkcs1v15 {}, + #[serde(rename = "RSA-PSS")] + RsaPss {}, + #[serde(rename = "RSA-OAEP")] + RsaOaep {}, +} + +#[derive(Serialize)] +#[serde(untagged)] +pub enum ExportKeyResult { + Pkcs8(ZeroCopyBuf), + Spki(ZeroCopyBuf), +} + +pub fn op_crypto_export_key( + _state: &mut OpState, + opts: ExportKeyOptions, + key_data: RawKeyData, +) -> Result { + match opts.algorithm { + ExportKeyAlgorithm::RsassaPkcs1v15 {} + | ExportKeyAlgorithm::RsaPss {} + | ExportKeyAlgorithm::RsaOaep {} => export_key_rsa(opts.format, key_data), + } +} + +fn export_key_rsa( + format: ExportKeyFormat, + key_data: RawKeyData, +) -> Result { + match format { + ExportKeyFormat::Spki => { + let subject_public_key = &key_data.as_rsa_public_key()?; + + // the SPKI structure + let key_info = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifier { + // rsaEncryption(1) + oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"), + // parameters field should not be ommited (None). + // It MUST have ASN.1 type NULL. + parameters: Some(asn1::Any::from(asn1::Null)), + }, + subject_public_key, + }; + + // Infallible because we know the public key is valid. + let spki_der = key_info.to_vec().unwrap(); + Ok(ExportKeyResult::Spki(spki_der.into())) + } + ExportKeyFormat::Pkcs8 => { + let private_key = key_data.as_rsa_private_key()?; + + // the PKCS#8 v1 structure + // PrivateKeyInfo ::= SEQUENCE { + // version Version, + // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, + // privateKey PrivateKey, + // attributes [0] IMPLICIT Attributes OPTIONAL } + + // version is 0 when publickey is None + + let pk_info = rsa::pkcs8::PrivateKeyInfo { + attributes: None, + public_key: None, + algorithm: rsa::pkcs8::AlgorithmIdentifier { + // rsaEncryption(1) + oid: rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"), + // parameters field should not be ommited (None). + // It MUST have ASN.1 type NULL as per defined in RFC 3279 Section 2.3.1 + parameters: Some(asn1::Any::from(asn1::Null)), + }, + private_key, + }; + + // Infallible because we know the private key is valid. + let pkcs8_der = pk_info.to_vec().unwrap(); + + Ok(ExportKeyResult::Pkcs8(pkcs8_der.into())) + } + _ => Err(unsupported_format()), + } +} diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 379b101a29..971b32bbba 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -10,6 +10,7 @@ use deno_core::op_sync; use deno_core::Extension; use deno_core::OpState; use deno_core::ZeroCopyBuf; +use export_key::op_crypto_export_key; use serde::Deserialize; use std::cell::RefCell; @@ -56,6 +57,7 @@ use std::path::PathBuf; pub use rand; // Re-export rand +mod export_key; mod import_key; mod key; mod shared; @@ -93,7 +95,7 @@ pub fn init(maybe_seed: Option) -> Extension { ("op_crypto_verify_key", op_async(op_crypto_verify_key)), ("op_crypto_derive_bits", op_async(op_crypto_derive_bits)), ("op_crypto_import_key", op_sync(op_crypto_import_key)), - ("op_crypto_export_key", op_async(op_crypto_export_key)), + ("op_crypto_export_key", op_sync(op_crypto_export_key)), ("op_crypto_encrypt_key", op_async(op_crypto_encrypt_key)), ("op_crypto_decrypt_key", op_async(op_crypto_decrypt_key)), ("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)), @@ -569,205 +571,6 @@ pub async fn op_crypto_verify_key( Ok(verification) } -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ExportKeyArg { - key: KeyData, - algorithm: Algorithm, - format: KeyFormat, - // RSA-PSS - hash: Option, -} - -pub async fn op_crypto_export_key( - _state: Rc>, - args: ExportKeyArg, - _: (), -) -> Result { - let algorithm = args.algorithm; - match algorithm { - Algorithm::RsassaPkcs1v15 => { - match args.format { - KeyFormat::Pkcs8 => { - // private_key is a PKCS#1 DER-encoded private key - - let private_key = &args.key.data; - - // the PKCS#8 v1 structure - // PrivateKeyInfo ::= SEQUENCE { - // version Version, - // privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, - // privateKey PrivateKey, - // attributes [0] IMPLICIT Attributes OPTIONAL } - - // version is 0 when publickey is None - - let pk_info = rsa::pkcs8::PrivateKeyInfo { - attributes: None, - public_key: None, - algorithm: rsa::pkcs8::AlgorithmIdentifier { - // rsaEncryption(1) - oid: rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"), - // parameters field should not be ommited (None). - // It MUST have ASN.1 type NULL as per defined in RFC 3279 Section 2.3.1 - parameters: Some(asn1::Any::from(asn1::Null)), - }, - private_key, - }; - - Ok(pk_info.to_der().as_ref().to_vec().into()) - } - KeyFormat::Spki => { - // public_key is a PKCS#1 DER-encoded public key - - let subject_public_key = &args.key.data; - - // the SPKI structure - let key_info = spki::SubjectPublicKeyInfo { - algorithm: spki::AlgorithmIdentifier { - // rsaEncryption(1) - oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"), - // parameters field should not be ommited (None). - // It MUST have ASN.1 type NULL. - parameters: Some(asn1::Any::from(asn1::Null)), - }, - subject_public_key, - }; - - // Infallible based on spec because of the way we import and generate keys. - let spki_der = key_info.to_vec().unwrap(); - Ok(spki_der.into()) - } - // TODO(@littledivy): jwk - _ => unreachable!(), - } - } - Algorithm::RsaPss => { - match args.format { - KeyFormat::Pkcs8 => { - // Intentionally unused but required. Not encoded into PKCS#8 (see below). - let _hash = args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))?; - - // private_key is a PKCS#1 DER-encoded private key - let private_key = &args.key.data; - - // version is 0 when publickey is None - - let pk_info = rsa::pkcs8::PrivateKeyInfo { - attributes: None, - public_key: None, - algorithm: rsa::pkcs8::AlgorithmIdentifier { - // Spec wants the OID to be id-RSASSA-PSS (1.2.840.113549.1.1.10) but ring and RSA do not support it. - // Instead, we use rsaEncryption (1.2.840.113549.1.1.1) as specified in RFC 3447. - // Node, Chromium and Firefox also use rsaEncryption (1.2.840.113549.1.1.1) and do not support id-RSASSA-PSS. - - // parameters are set to NULL opposed to what spec wants (see above) - oid: rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"), - // parameters field should not be ommited (None). - // It MUST have ASN.1 type NULL as per defined in RFC 3279 Section 2.3.1 - parameters: Some(asn1::Any::from(asn1::Null)), - }, - private_key, - }; - - Ok(pk_info.to_der().as_ref().to_vec().into()) - } - KeyFormat::Spki => { - // Intentionally unused but required. Not encoded into SPKI (see below). - let _hash = args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))?; - - // public_key is a PKCS#1 DER-encoded public key - let subject_public_key = &args.key.data; - - // the SPKI structure - let key_info = spki::SubjectPublicKeyInfo { - algorithm: spki::AlgorithmIdentifier { - // rsaEncryption(1) - oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"), - // parameters field should not be ommited (None). - // It MUST have ASN.1 type NULL. - parameters: Some(asn1::Any::from(asn1::Null)), - }, - subject_public_key, - }; - - // Infallible based on spec because of the way we import and generate keys. - let spki_der = key_info.to_vec().unwrap(); - Ok(spki_der.into()) - } - // TODO(@littledivy): jwk - _ => unreachable!(), - } - } - Algorithm::RsaOaep => { - match args.format { - KeyFormat::Pkcs8 => { - // Intentionally unused but required. Not encoded into PKCS#8 (see below). - let _hash = args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))?; - - // private_key is a PKCS#1 DER-encoded private key - let private_key = &args.key.data; - - // version is 0 when publickey is None - - let pk_info = rsa::pkcs8::PrivateKeyInfo { - attributes: None, - public_key: None, - algorithm: rsa::pkcs8::AlgorithmIdentifier { - // Spec wants the OID to be id-RSAES-OAEP (1.2.840.113549.1.1.10) but ring and RSA crate do not support it. - // Instead, we use rsaEncryption (1.2.840.113549.1.1.1) as specified in RFC 3447. - // Chromium and Firefox also use rsaEncryption (1.2.840.113549.1.1.1) and do not support id-RSAES-OAEP. - - // parameters are set to NULL opposed to what spec wants (see above) - oid: rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"), - // parameters field should not be ommited (None). - // It MUST have ASN.1 type NULL as per defined in RFC 3279 Section 2.3.1 - parameters: Some(asn1::Any::from(asn1::Null)), - }, - private_key, - }; - - Ok(pk_info.to_der().as_ref().to_vec().into()) - } - KeyFormat::Spki => { - // Intentionally unused but required. Not encoded into SPKI (see below). - let _hash = args - .hash - .ok_or_else(|| type_error("Missing argument hash".to_string()))?; - - // public_key is a PKCS#1 DER-encoded public key - let subject_public_key = &args.key.data; - - // the SPKI structure - let key_info = spki::SubjectPublicKeyInfo { - algorithm: spki::AlgorithmIdentifier { - // rsaEncryption(1) - oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"), - // parameters field should not be ommited (None). - // It MUST have ASN.1 type NULL. - parameters: Some(asn1::Any::from(asn1::Null)), - }, - subject_public_key, - }; - - // Infallible based on spec because of the way we import and generate keys. - let spki_der = key_info.to_vec().unwrap(); - Ok(spki_der.into()) - } - // TODO(@littledivy): jwk - _ => unreachable!(), - } - } - _ => Err(type_error("Unsupported algorithm".to_string())), - } -} - #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct DeriveKeyArg { diff --git a/ext/crypto/shared.rs b/ext/crypto/shared.rs index 696b1c087c..1af0169efd 100644 --- a/ext/crypto/shared.rs +++ b/ext/crypto/shared.rs @@ -1,8 +1,12 @@ use std::borrow::Cow; use deno_core::error::custom_error; +use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::ZeroCopyBuf; +use rsa::pkcs1::FromRsaPrivateKey; +use rsa::pkcs1::ToRsaPublicKey; +use rsa::RsaPrivateKey; use serde::Deserialize; use serde::Serialize; @@ -61,6 +65,33 @@ pub enum RawKeyData { Public(ZeroCopyBuf), } +impl RawKeyData { + pub fn as_rsa_public_key(&self) -> Result, AnyError> { + match self { + RawKeyData::Public(data) => Ok(Cow::Borrowed(data)), + RawKeyData::Private(data) => { + let private_key = RsaPrivateKey::from_pkcs1_der(data) + .map_err(|_| type_error("expected valid private key"))?; + + let public_key_doc = private_key + .to_public_key() + .to_pkcs1_der() + .map_err(|_| type_error("expected valid public key"))?; + + Ok(Cow::Owned(public_key_doc.as_der().into())) + } + _ => Err(type_error("expected public key")), + } + } + + pub fn as_rsa_private_key(&self) -> Result<&[u8], AnyError> { + match self { + RawKeyData::Private(data) => Ok(data), + _ => Err(type_error("expected private key")), + } + } +} + pub fn data_error(msg: impl Into>) -> AnyError { custom_error("DOMExceptionDataError", msg) }