diff options
Diffstat (limited to 'ext/node/ops')
-rw-r--r-- | ext/node/ops/crypto/dh.rs | 12 | ||||
-rw-r--r-- | ext/node/ops/crypto/digest.rs | 45 | ||||
-rw-r--r-- | ext/node/ops/crypto/keys.rs | 1727 | ||||
-rw-r--r-- | ext/node/ops/crypto/mod.rs | 882 | ||||
-rw-r--r-- | ext/node/ops/crypto/sign.rs | 396 | ||||
-rw-r--r-- | ext/node/ops/vm_internal.rs | 1 |
6 files changed, 2186 insertions, 877 deletions
diff --git a/ext/node/ops/crypto/dh.rs b/ext/node/ops/crypto/dh.rs index f60f84277..ff2bd030e 100644 --- a/ext/node/ops/crypto/dh.rs +++ b/ext/node/ops/crypto/dh.rs @@ -5,14 +5,21 @@ use num_bigint_dig::BigUint; use num_bigint_dig::RandBigInt; use num_traits::FromPrimitive; +#[derive(Clone)] pub struct PublicKey(BigUint); impl PublicKey { + pub fn from_bytes(bytes: &[u8]) -> Self { + let public_key = BigUint::from_bytes_be(bytes); + Self(public_key) + } + pub fn into_vec(self) -> Vec<u8> { self.0.to_bytes_be() } } +#[derive(Clone)] pub struct PrivateKey(BigUint); impl PrivateKey { @@ -22,6 +29,11 @@ impl PrivateKey { Self(exponent) } + pub fn from_bytes(bytes: &[u8]) -> Self { + let exponent = BigUint::from_bytes_be(bytes); + Self(exponent) + } + /// Diffie-Hellman modular exponentiation. /// s = g^x mod p pub fn compute_public_key( diff --git a/ext/node/ops/crypto/digest.rs b/ext/node/ops/crypto/digest.rs index 0a21a395a..1bb028155 100644 --- a/ext/node/ops/crypto/digest.rs +++ b/ext/node/ops/crypto/digest.rs @@ -67,7 +67,7 @@ macro_rules! match_fixed_digest { type $type = ::blake2::Blake2s256; $body } - _ => match_fixed_digest_with_eager_block_buffer!($algorithm_name, fn <$type>() $body, _ => $other) + _ => crate::ops::crypto::digest::match_fixed_digest_with_eager_block_buffer!($algorithm_name, fn <$type>() $body, _ => $other) } }; } @@ -84,22 +84,24 @@ macro_rules! match_fixed_digest_with_eager_block_buffer { type $type = crate::ops::crypto::md5_sha1::Md5Sha1; $body } - _ => match_fixed_digest_with_oid!($algorithm_name, fn <$type>() $body, _ => $other) + _ => crate::ops::crypto::digest::match_fixed_digest_with_oid!($algorithm_name, fn <$type>() $body, _ => $other) } }; } pub(crate) use match_fixed_digest_with_eager_block_buffer; macro_rules! match_fixed_digest_with_oid { - ($algorithm_name:expr, fn <$type:ident>() $body:block, _ => $other:block) => { + ($algorithm_name:expr, fn $(<$type:ident>)?($($hash_algorithm:ident: Option<RsaPssHashAlgorithm>)?) $body:block, _ => $other:block) => { match $algorithm_name { "rsa-md5" | "md5" | "md5withrsaencryption" | "ssl3-md5" => { - type $type = ::md5::Md5; + $(let $hash_algorithm = None;)? + $(type $type = ::md5::Md5;)? $body } "rsa-ripemd160" | "ripemd" | "ripemd160" | "ripemd160withrsa" | "rmd160" => { - type $type = ::ripemd::Ripemd160; + $(let $hash_algorithm = None;)? + $(type $type = ::ripemd::Ripemd160;)? $body } "rsa-sha1" @@ -108,47 +110,58 @@ macro_rules! match_fixed_digest_with_oid { | "sha1-2" | "sha1withrsaencryption" | "ssl3-sha1" => { - type $type = ::sha1::Sha1; + $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha1);)? + $(type $type = ::sha1::Sha1;)? $body } "rsa-sha224" | "sha224" | "sha224withrsaencryption" => { - type $type = ::sha2::Sha224; + $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha224);)? + $(type $type = ::sha2::Sha224;)? $body } "rsa-sha256" | "sha256" | "sha256withrsaencryption" => { - type $type = ::sha2::Sha256; + $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha256);)? + $(type $type = ::sha2::Sha256;)? $body } "rsa-sha384" | "sha384" | "sha384withrsaencryption" => { - type $type = ::sha2::Sha384; + $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha384);)? + $(type $type = ::sha2::Sha384;)? $body } "rsa-sha512" | "sha512" | "sha512withrsaencryption" => { - type $type = ::sha2::Sha512; + $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512);)? + $(type $type = ::sha2::Sha512;)? $body } "rsa-sha512/224" | "sha512-224" | "sha512-224withrsaencryption" => { - type $type = ::sha2::Sha512_224; + $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512_224);)? + $(type $type = ::sha2::Sha512_224;)? $body } "rsa-sha512/256" | "sha512-256" | "sha512-256withrsaencryption" => { - type $type = ::sha2::Sha512_256; + $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512_256);)? + $(type $type = ::sha2::Sha512_256;)? $body } "rsa-sha3-224" | "id-rsassa-pkcs1-v1_5-with-sha3-224" | "sha3-224" => { - type $type = ::sha3::Sha3_224; + $(let $hash_algorithm = None;)? + $(type $type = ::sha3::Sha3_224;)? $body } "rsa-sha3-256" | "id-rsassa-pkcs1-v1_5-with-sha3-256" | "sha3-256" => { - type $type = ::sha3::Sha3_256; + $(let $hash_algorithm = None;)? + $(type $type = ::sha3::Sha3_256;)? $body } "rsa-sha3-384" | "id-rsassa-pkcs1-v1_5-with-sha3-384" | "sha3-384" => { - type $type = ::sha3::Sha3_384; + $(let $hash_algorithm = None;)? + $(type $type = ::sha3::Sha3_384;)? $body } "rsa-sha3-512" | "id-rsassa-pkcs1-v1_5-with-sha3-512" | "sha3-512" => { - type $type = ::sha3::Sha3_512; + $(let $hash_algorithm = None;)? + $(type $type = ::sha3::Sha3_512;)? $body } _ => $other, diff --git a/ext/node/ops/crypto/keys.rs b/ext/node/ops/crypto/keys.rs new file mode 100644 index 000000000..86280c11a --- /dev/null +++ b/ext/node/ops/crypto/keys.rs @@ -0,0 +1,1727 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::borrow::Cow; +use std::cell::RefCell; + +use base64::Engine; +use deno_core::error::generic_error; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use deno_core::op2; +use deno_core::serde_v8::BigInt as V8BigInt; +use deno_core::unsync::spawn_blocking; +use deno_core::GarbageCollected; +use deno_core::ToJsBuffer; +use ed25519_dalek::pkcs8::BitStringRef; +use num_bigint::BigInt; +use num_traits::FromPrimitive as _; +use pkcs8::DecodePrivateKey as _; +use pkcs8::Document; +use pkcs8::EncodePrivateKey as _; +use pkcs8::EncryptedPrivateKeyInfo; +use pkcs8::PrivateKeyInfo; +use pkcs8::SecretDocument; +use rand::thread_rng; +use rand::RngCore as _; +use rsa::pkcs1::DecodeRsaPrivateKey as _; +use rsa::pkcs1::DecodeRsaPublicKey; +use rsa::pkcs1::EncodeRsaPrivateKey as _; +use rsa::pkcs1::EncodeRsaPublicKey; +use rsa::traits::PublicKeyParts; +use rsa::RsaPrivateKey; +use rsa::RsaPublicKey; +use sec1::der::Writer as _; +use sec1::pem::PemLabel as _; +use sec1::DecodeEcPrivateKey as _; +use sec1::LineEnding; +use spki::der::asn1; +use spki::der::asn1::OctetStringRef; +use spki::der::AnyRef; +use spki::der::Decode as _; +use spki::der::Encode as _; +use spki::der::PemWriter; +use spki::der::Reader as _; +use spki::DecodePublicKey as _; +use spki::EncodePublicKey as _; +use spki::SubjectPublicKeyInfoRef; + +use super::dh; +use super::digest::match_fixed_digest_with_oid; +use super::primes::Prime; + +#[derive(Clone)] +pub enum KeyObjectHandle { + AsymmetricPrivate(AsymmetricPrivateKey), + AsymmetricPublic(AsymmetricPublicKey), + Secret(Box<[u8]>), +} + +impl GarbageCollected for KeyObjectHandle {} + +#[derive(Clone)] +pub enum AsymmetricPrivateKey { + Rsa(RsaPrivateKey), + RsaPss(RsaPssPrivateKey), + Dsa(dsa::SigningKey), + Ec(EcPrivateKey), + X25519(x25519_dalek::StaticSecret), + Ed25519(ed25519_dalek::SigningKey), + #[allow(unused)] + Dh(dh::PrivateKey), +} + +#[derive(Clone)] +pub struct RsaPssPrivateKey { + pub key: RsaPrivateKey, + pub details: Option<RsaPssDetails>, +} + +#[derive(Clone, Copy)] +pub struct RsaPssDetails { + pub hash_algorithm: RsaPssHashAlgorithm, + pub mf1_hash_algorithm: RsaPssHashAlgorithm, + pub salt_length: u32, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum RsaPssHashAlgorithm { + Sha1, + Sha224, + Sha256, + Sha384, + Sha512, + Sha512_224, + Sha512_256, +} + +impl RsaPssHashAlgorithm { + pub fn as_str(&self) -> &'static str { + match self { + RsaPssHashAlgorithm::Sha1 => "sha1", + RsaPssHashAlgorithm::Sha224 => "sha224", + RsaPssHashAlgorithm::Sha256 => "sha256", + RsaPssHashAlgorithm::Sha384 => "sha384", + RsaPssHashAlgorithm::Sha512 => "sha512", + RsaPssHashAlgorithm::Sha512_224 => "sha512-224", + RsaPssHashAlgorithm::Sha512_256 => "sha512-256", + } + } + + pub fn salt_length(&self) -> u32 { + match self { + RsaPssHashAlgorithm::Sha1 => 20, + RsaPssHashAlgorithm::Sha224 | RsaPssHashAlgorithm::Sha512_224 => 28, + RsaPssHashAlgorithm::Sha256 | RsaPssHashAlgorithm::Sha512_256 => 32, + RsaPssHashAlgorithm::Sha384 => 48, + RsaPssHashAlgorithm::Sha512 => 64, + } + } +} + +#[derive(Clone)] +pub enum EcPrivateKey { + P224(p224::SecretKey), + P256(p256::SecretKey), + P384(p384::SecretKey), +} + +#[derive(Clone)] +pub enum AsymmetricPublicKey { + Rsa(rsa::RsaPublicKey), + RsaPss(RsaPssPublicKey), + Dsa(dsa::VerifyingKey), + Ec(EcPublicKey), + #[allow(unused)] + X25519(x25519_dalek::PublicKey), + Ed25519(ed25519_dalek::VerifyingKey), + #[allow(unused)] + Dh(dh::PublicKey), +} + +#[derive(Clone)] +pub struct RsaPssPublicKey { + pub key: rsa::RsaPublicKey, + pub details: Option<RsaPssDetails>, +} + +#[derive(Clone)] +pub enum EcPublicKey { + P224(p224::PublicKey), + P256(p256::PublicKey), + P384(p384::PublicKey), +} + +impl KeyObjectHandle { + /// Returns the private key if the handle is an asymmetric private key. + pub fn as_private_key(&self) -> Option<&AsymmetricPrivateKey> { + match self { + KeyObjectHandle::AsymmetricPrivate(key) => Some(key), + _ => None, + } + } + + /// Returns the public key if the handle is an asymmetric public key. If it is + /// a private key, it derives the public key from it and returns that. + pub fn as_public_key(&self) -> Option<Cow<'_, AsymmetricPublicKey>> { + match self { + KeyObjectHandle::AsymmetricPrivate(key) => { + Some(Cow::Owned(key.to_public_key())) + } + KeyObjectHandle::AsymmetricPublic(key) => Some(Cow::Borrowed(key)), + _ => None, + } + } + + /// Returns the secret key if the handle is a secret key. + pub fn as_secret_key(&self) -> Option<&[u8]> { + match self { + KeyObjectHandle::Secret(key) => Some(key), + _ => None, + } + } +} + +impl AsymmetricPrivateKey { + /// Derives the public key from the private key. + pub fn to_public_key(&self) -> AsymmetricPublicKey { + match self { + AsymmetricPrivateKey::Rsa(key) => { + AsymmetricPublicKey::Rsa(key.to_public_key()) + } + AsymmetricPrivateKey::RsaPss(key) => { + AsymmetricPublicKey::RsaPss(key.to_public_key()) + } + AsymmetricPrivateKey::Dsa(key) => { + AsymmetricPublicKey::Dsa(key.verifying_key().clone()) + } + AsymmetricPrivateKey::Ec(key) => { + AsymmetricPublicKey::Ec(key.to_public_key()) + } + AsymmetricPrivateKey::X25519(key) => { + AsymmetricPublicKey::X25519(x25519_dalek::PublicKey::from(key)) + } + AsymmetricPrivateKey::Ed25519(key) => { + AsymmetricPublicKey::Ed25519(key.verifying_key()) + } + AsymmetricPrivateKey::Dh(_) => { + panic!("cannot derive public key from DH private key") + } + } + } +} + +impl RsaPssPrivateKey { + /// Derives the public key from the private key. + pub fn to_public_key(&self) -> RsaPssPublicKey { + RsaPssPublicKey { + key: self.key.to_public_key(), + details: self.details, + } + } +} + +impl EcPrivateKey { + /// Derives the public key from the private key. + pub fn to_public_key(&self) -> EcPublicKey { + match self { + EcPrivateKey::P224(key) => EcPublicKey::P224(key.public_key()), + EcPrivateKey::P256(key) => EcPublicKey::P256(key.public_key()), + EcPrivateKey::P384(key) => EcPublicKey::P384(key.public_key()), + } + } +} + +// https://oidref.com/ +const ID_SHA1_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("1.3.14.3.2.26"); +const ID_SHA224_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.4"); +const ID_SHA256_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1"); +const ID_SHA384_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2"); +const ID_SHA512_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3"); +const ID_SHA512_224_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.5"); +const ID_SHA512_256_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.6"); + +const ID_MFG1: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.8"); +pub const ID_SECP224R1_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.33"); +pub const ID_SECP256R1_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); +pub const ID_SECP384R1_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.34"); + +pub const RSA_ENCRYPTION_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); +pub const RSASSA_PSS_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10"); +pub const DSA_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.10040.4.1"); +pub const EC_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); +pub const X25519_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.3.101.110"); +pub const ED25519_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.3.101.112"); +pub const DH_KEY_AGREEMENT_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.3.1"); + +// The parameters field associated with OID id-RSASSA-PSS +// Defined in RFC 3447, section A.2.3 +// +// RSASSA-PSS-params ::= SEQUENCE { +// hashAlgorithm [0] HashAlgorithm DEFAULT sha1, +// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, +// saltLength [2] INTEGER DEFAULT 20, +// trailerField [3] TrailerField DEFAULT trailerFieldBC +// } +pub struct RsaPssParameters<'a> { + pub hash_algorithm: Option<rsa::pkcs8::AlgorithmIdentifierRef<'a>>, + pub mask_gen_algorithm: Option<rsa::pkcs8::AlgorithmIdentifierRef<'a>>, + pub salt_length: Option<u32>, +} + +// Context-specific tag number for hashAlgorithm. +const HASH_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber = + rsa::pkcs8::der::TagNumber::new(0); + +// Context-specific tag number for maskGenAlgorithm. +const MASK_GEN_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber = + rsa::pkcs8::der::TagNumber::new(1); + +// Context-specific tag number for saltLength. +const SALT_LENGTH_TAG: rsa::pkcs8::der::TagNumber = + rsa::pkcs8::der::TagNumber::new(2); + +impl<'a> TryFrom<rsa::pkcs8::der::asn1::AnyRef<'a>> for RsaPssParameters<'a> { + type Error = rsa::pkcs8::der::Error; + + fn try_from( + any: rsa::pkcs8::der::asn1::AnyRef<'a>, + ) -> rsa::pkcs8::der::Result<RsaPssParameters> { + any.sequence(|decoder| { + let hash_algorithm = decoder + .context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>( + HASH_ALGORITHM_TAG, + pkcs8::der::TagMode::Explicit, + )? + .map(TryInto::try_into) + .transpose()?; + + let mask_gen_algorithm = decoder + .context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>( + MASK_GEN_ALGORITHM_TAG, + pkcs8::der::TagMode::Explicit, + )? + .map(TryInto::try_into) + .transpose()?; + + let salt_length = decoder + .context_specific::<u32>( + SALT_LENGTH_TAG, + pkcs8::der::TagMode::Explicit, + )? + .map(TryInto::try_into) + .transpose()?; + + Ok(Self { + hash_algorithm, + mask_gen_algorithm, + salt_length, + }) + }) + } +} + +impl KeyObjectHandle { + pub fn new_asymmetric_private_key_from_js( + key: &[u8], + format: &str, + typ: &str, + passphrase: Option<&[u8]>, + ) -> Result<KeyObjectHandle, AnyError> { + let document = match format { + "pem" => { + let pem = std::str::from_utf8(key).map_err(|err| { + type_error(format!( + "invalid PEM private key: not valid utf8 starting at byte {}", + err.valid_up_to() + )) + })?; + + if let Some(passphrase) = passphrase { + SecretDocument::from_pkcs8_encrypted_pem(pem, passphrase) + .map_err(|_| type_error("invalid encrypted PEM private key"))? + } else { + let (label, doc) = SecretDocument::from_pem(pem) + .map_err(|_| type_error("invalid PEM private key"))?; + + match label { + EncryptedPrivateKeyInfo::PEM_LABEL => { + return Err(type_error( + "encrypted private key requires a passphrase to decrypt", + )) + } + PrivateKeyInfo::PEM_LABEL => doc, + rsa::pkcs1::RsaPrivateKey::PEM_LABEL => { + SecretDocument::from_pkcs1_der(doc.as_bytes()) + .map_err(|_| type_error("invalid PKCS#1 private key"))? + } + sec1::EcPrivateKey::PEM_LABEL => { + SecretDocument::from_sec1_der(doc.as_bytes()) + .map_err(|_| type_error("invalid SEC1 private key"))? + } + _ => { + return Err(type_error(format!( + "unsupported PEM label: {}", + label + ))) + } + } + } + } + "der" => match typ { + "pkcs8" => { + if let Some(passphrase) = passphrase { + SecretDocument::from_pkcs8_encrypted_der(key, passphrase) + .map_err(|_| type_error("invalid encrypted PKCS#8 private key"))? + } else { + SecretDocument::from_pkcs8_der(key) + .map_err(|_| type_error("invalid PKCS#8 private key"))? + } + } + "pkcs1" => { + if passphrase.is_some() { + return Err(type_error( + "PKCS#1 private key does not support encryption with passphrase", + )); + } + SecretDocument::from_pkcs1_der(key) + .map_err(|_| type_error("invalid PKCS#1 private key"))? + } + "sec1" => { + if passphrase.is_some() { + return Err(type_error( + "SEC1 private key does not support encryption with passphrase", + )); + } + SecretDocument::from_sec1_der(key) + .map_err(|_| type_error("invalid SEC1 private key"))? + } + _ => return Err(type_error(format!("unsupported key type: {}", typ))), + }, + _ => { + return Err(type_error(format!("unsupported key format: {}", format))) + } + }; + + let pk_info = PrivateKeyInfo::try_from(document.as_bytes()) + .map_err(|_| type_error("invalid private key"))?; + + let alg = pk_info.algorithm.oid; + let private_key = match alg { + RSA_ENCRYPTION_OID => { + let private_key = + rsa::RsaPrivateKey::from_pkcs1_der(pk_info.private_key) + .map_err(|_| type_error("invalid PKCS#1 private key"))?; + AsymmetricPrivateKey::Rsa(private_key) + } + RSASSA_PSS_OID => { + let details = parse_rsa_pss_params(pk_info.algorithm.parameters)?; + let private_key = + rsa::RsaPrivateKey::from_pkcs1_der(pk_info.private_key) + .map_err(|_| type_error("invalid PKCS#1 private key"))?; + AsymmetricPrivateKey::RsaPss(RsaPssPrivateKey { + key: private_key, + details, + }) + } + DSA_OID => { + let private_key = dsa::SigningKey::try_from(pk_info) + .map_err(|_| type_error("invalid DSA private key"))?; + AsymmetricPrivateKey::Dsa(private_key) + } + EC_OID => { + let named_curve = pk_info.algorithm.parameters_oid().map_err(|_| { + type_error("malformed or missing named curve in ec parameters") + })?; + match named_curve { + ID_SECP224R1_OID => { + let secret_key = + p224::SecretKey::from_sec1_der(pk_info.private_key) + .map_err(|_| type_error("invalid SEC1 private key"))?; + AsymmetricPrivateKey::Ec(EcPrivateKey::P224(secret_key)) + } + ID_SECP256R1_OID => { + let secret_key = + p256::SecretKey::from_sec1_der(pk_info.private_key) + .map_err(|_| type_error("invalid SEC1 private key"))?; + AsymmetricPrivateKey::Ec(EcPrivateKey::P256(secret_key)) + } + ID_SECP384R1_OID => { + let secret_key = + p384::SecretKey::from_sec1_der(pk_info.private_key) + .map_err(|_| type_error("invalid SEC1 private key"))?; + AsymmetricPrivateKey::Ec(EcPrivateKey::P384(secret_key)) + } + _ => return Err(type_error("unsupported ec named curve")), + } + } + X25519_OID => { + let string_ref = OctetStringRef::from_der(pk_info.private_key) + .map_err(|_| type_error("invalid x25519 private key"))?; + if string_ref.as_bytes().len() != 32 { + return Err(type_error("x25519 private key is the wrong length")); + } + let mut bytes = [0; 32]; + bytes.copy_from_slice(string_ref.as_bytes()); + AsymmetricPrivateKey::X25519(x25519_dalek::StaticSecret::from(bytes)) + } + ED25519_OID => { + let string_ref = OctetStringRef::from_der(pk_info.private_key) + .map_err(|_| type_error("invalid Ed25519 private key"))?; + if string_ref.as_bytes().len() != 32 { + return Err(type_error("Ed25519 private key is the wrong length")); + } + let mut bytes = [0; 32]; + bytes.copy_from_slice(string_ref.as_bytes()); + AsymmetricPrivateKey::Ed25519(ed25519_dalek::SigningKey::from(bytes)) + } + DH_KEY_AGREEMENT_OID => AsymmetricPrivateKey::Dh( + dh::PrivateKey::from_bytes(pk_info.private_key), + ), + _ => return Err(type_error("unsupported private key oid")), + }; + + Ok(KeyObjectHandle::AsymmetricPrivate(private_key)) + } + + pub fn new_asymmetric_public_key_from_js( + key: &[u8], + format: &str, + typ: &str, + _passphrase: Option<&[u8]>, + ) -> Result<KeyObjectHandle, AnyError> { + let document = match format { + "pem" => { + let pem = std::str::from_utf8(key).map_err(|err| { + type_error(format!( + "invalid PEM public key: not valid utf8 starting at byte {}", + err.valid_up_to() + )) + })?; + + let (label, document) = Document::from_pem(pem) + .map_err(|_| type_error("invalid PEM public key"))?; + + match label { + SubjectPublicKeyInfoRef::PEM_LABEL => document, + rsa::pkcs1::RsaPublicKey::PEM_LABEL => { + Document::from_pkcs1_der(document.as_bytes()) + .map_err(|_| type_error("invalid PKCS#1 public key"))? + } + EncryptedPrivateKeyInfo::PEM_LABEL => { + // FIXME + return Err(type_error( + "deriving public key from encrypted private key", + )); + } + PrivateKeyInfo::PEM_LABEL => { + // FIXME + return Err(type_error("public key cannot be a private key")); + } + sec1::EcPrivateKey::PEM_LABEL => { + // FIXME + return Err(type_error("deriving public key from ec private key")); + } + rsa::pkcs1::RsaPrivateKey::PEM_LABEL => { + // FIXME + return Err(type_error("deriving public key from rsa private key")); + } + // TODO: handle x509 certificates as public keys + _ => { + return Err(type_error(format!("unsupported PEM label: {}", label))) + } + } + } + "der" => match typ { + "pkcs1" => Document::from_pkcs1_der(key) + .map_err(|_| type_error("invalid PKCS#1 public key"))?, + "spki" => Document::from_public_key_der(key) + .map_err(|_| type_error("invalid SPKI public key"))?, + _ => return Err(type_error(format!("unsupported key type: {}", typ))), + }, + _ => { + return Err(type_error(format!("unsupported key format: {}", format))) + } + }; + + let spki = SubjectPublicKeyInfoRef::try_from(document.as_bytes())?; + + let public_key = match spki.algorithm.oid { + RSA_ENCRYPTION_OID => { + let public_key = RsaPublicKey::from_pkcs1_der( + spki.subject_public_key.as_bytes().unwrap(), + )?; + AsymmetricPublicKey::Rsa(public_key) + } + RSASSA_PSS_OID => { + let details = parse_rsa_pss_params(spki.algorithm.parameters)?; + let public_key = RsaPublicKey::from_pkcs1_der( + spki.subject_public_key.as_bytes().unwrap(), + )?; + AsymmetricPublicKey::RsaPss(RsaPssPublicKey { + key: public_key, + details, + }) + } + DSA_OID => { + let verifying_key = dsa::VerifyingKey::try_from(spki) + .map_err(|_| type_error("malformed DSS public key"))?; + AsymmetricPublicKey::Dsa(verifying_key) + } + EC_OID => { + let named_curve = spki.algorithm.parameters_oid().map_err(|_| { + type_error("malformed or missing named curve in ec parameters") + })?; + let data = spki.subject_public_key.as_bytes().ok_or_else(|| { + type_error("malformed or missing public key in ec spki") + })?; + + match named_curve { + ID_SECP224R1_OID => { + let public_key = p224::PublicKey::from_sec1_bytes(data)?; + AsymmetricPublicKey::Ec(EcPublicKey::P224(public_key)) + } + ID_SECP256R1_OID => { + let public_key = p256::PublicKey::from_sec1_bytes(data)?; + AsymmetricPublicKey::Ec(EcPublicKey::P256(public_key)) + } + ID_SECP384R1_OID => { + let public_key = p384::PublicKey::from_sec1_bytes(data)?; + AsymmetricPublicKey::Ec(EcPublicKey::P384(public_key)) + } + _ => return Err(type_error("unsupported ec named curve")), + } + } + X25519_OID => { + let mut bytes = [0; 32]; + let data = spki.subject_public_key.as_bytes().ok_or_else(|| { + type_error("malformed or missing public key in x25519 spki") + })?; + if data.len() < 32 { + return Err(type_error("x25519 public key is too short")); + } + bytes.copy_from_slice(&data[0..32]); + AsymmetricPublicKey::X25519(x25519_dalek::PublicKey::from(bytes)) + } + ED25519_OID => { + let mut bytes = [0; 32]; + let data = spki.subject_public_key.as_bytes().ok_or_else(|| { + type_error("malformed or missing public key in ed25519 spki") + })?; + if data.len() < 32 { + return Err(type_error("ed25519 public key is too short")); + } + bytes.copy_from_slice(&data[0..32]); + let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&bytes) + .map_err(|_| type_error("ed25519 public key is malformed"))?; + AsymmetricPublicKey::Ed25519(verifying_key) + } + DH_KEY_AGREEMENT_OID => { + let Some(subject_public_key) = spki.subject_public_key.as_bytes() + else { + return Err(type_error("malformed or missing public key in dh spki")); + }; + AsymmetricPublicKey::Dh(dh::PublicKey::from_bytes(subject_public_key)) + } + _ => return Err(type_error("unsupported public key oid")), + }; + + Ok(KeyObjectHandle::AsymmetricPublic(public_key)) + } +} + +fn parse_rsa_pss_params( + parameters: Option<AnyRef<'_>>, +) -> Result<Option<RsaPssDetails>, deno_core::anyhow::Error> { + let details = if let Some(parameters) = parameters { + let params = RsaPssParameters::try_from(parameters) + .map_err(|_| type_error("malformed pss private key parameters"))?; + + let hash_algorithm = match params.hash_algorithm.map(|k| k.oid) { + Some(ID_SHA1_OID) => RsaPssHashAlgorithm::Sha1, + Some(ID_SHA224_OID) => RsaPssHashAlgorithm::Sha224, + Some(ID_SHA256_OID) => RsaPssHashAlgorithm::Sha256, + Some(ID_SHA384_OID) => RsaPssHashAlgorithm::Sha384, + Some(ID_SHA512_OID) => RsaPssHashAlgorithm::Sha512, + Some(ID_SHA512_224_OID) => RsaPssHashAlgorithm::Sha512_224, + Some(ID_SHA512_256_OID) => RsaPssHashAlgorithm::Sha512_256, + None => RsaPssHashAlgorithm::Sha1, + _ => return Err(type_error("unsupported pss hash algorithm")), + }; + + let mf1_hash_algorithm = match params.mask_gen_algorithm { + Some(alg) => { + if alg.oid != ID_MFG1 { + return Err(type_error("unsupported pss mask gen algorithm")); + } + let params = alg.parameters_oid().map_err(|_| { + type_error("malformed or missing pss mask gen algorithm parameters") + })?; + match params { + ID_SHA1_OID => RsaPssHashAlgorithm::Sha1, + ID_SHA224_OID => RsaPssHashAlgorithm::Sha224, + ID_SHA256_OID => RsaPssHashAlgorithm::Sha256, + ID_SHA384_OID => RsaPssHashAlgorithm::Sha384, + ID_SHA512_OID => RsaPssHashAlgorithm::Sha512, + ID_SHA512_224_OID => RsaPssHashAlgorithm::Sha512_224, + ID_SHA512_256_OID => RsaPssHashAlgorithm::Sha512_256, + _ => return Err(type_error("unsupported pss mask gen algorithm")), + } + } + None => hash_algorithm, + }; + + let salt_length = params + .salt_length + .unwrap_or_else(|| hash_algorithm.salt_length()); + + Some(RsaPssDetails { + hash_algorithm, + mf1_hash_algorithm, + salt_length, + }) + } else { + None + }; + Ok(details) +} + +impl AsymmetricPublicKey { + fn export_der(&self, typ: &str) -> Result<Box<[u8]>, AnyError> { + match typ { + "pkcs1" => match self { + AsymmetricPublicKey::Rsa(key) => { + let der = key + .to_pkcs1_der() + .map_err(|_| type_error("invalid RSA public key"))? + .into_vec() + .into_boxed_slice(); + Ok(der) + } + _ => Err(type_error( + "exporting non-RSA public key as PKCS#1 is not supported", + )), + }, + "spki" => { + let der = match self { + AsymmetricPublicKey::Rsa(key) => key + .to_public_key_der() + .map_err(|_| type_error("invalid RSA public key"))? + .into_vec() + .into_boxed_slice(), + AsymmetricPublicKey::RsaPss(_key) => { + return Err(generic_error( + "exporting RSA-PSS public key as SPKI is not supported yet", + )) + } + AsymmetricPublicKey::Dsa(key) => key + .to_public_key_der() + .map_err(|_| type_error("invalid DSA public key"))? + .into_vec() + .into_boxed_slice(), + AsymmetricPublicKey::Ec(key) => { + let (sec1, oid) = match key { + EcPublicKey::P224(key) => (key.to_sec1_bytes(), ID_SECP224R1_OID), + EcPublicKey::P256(key) => (key.to_sec1_bytes(), ID_SECP256R1_OID), + EcPublicKey::P384(key) => (key.to_sec1_bytes(), ID_SECP384R1_OID), + }; + + let spki = SubjectPublicKeyInfoRef { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: EC_OID, + parameters: Some(asn1::AnyRef::from(&oid)), + }, + subject_public_key: BitStringRef::from_bytes(&sec1) + .map_err(|_| type_error("invalid EC public key"))?, + }; + + spki + .to_der() + .map_err(|_| type_error("invalid EC public key"))? + .into_boxed_slice() + } + AsymmetricPublicKey::X25519(key) => { + let spki = SubjectPublicKeyInfoRef { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: X25519_OID, + parameters: None, + }, + subject_public_key: BitStringRef::from_bytes(key.as_bytes()) + .map_err(|_| type_error("invalid X25519 public key"))?, + }; + + spki + .to_der() + .map_err(|_| type_error("invalid X25519 public key"))? + .into_boxed_slice() + } + AsymmetricPublicKey::Ed25519(key) => { + let spki = SubjectPublicKeyInfoRef { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: ED25519_OID, + parameters: None, + }, + subject_public_key: BitStringRef::from_bytes(key.as_bytes()) + .map_err(|_| type_error("invalid Ed25519 public key"))?, + }; + + spki + .to_der() + .map_err(|_| type_error("invalid Ed25519 public key"))? + .into_boxed_slice() + } + AsymmetricPublicKey::Dh(key) => { + let public_key_bytes = key.clone().into_vec(); + let spki = + SubjectPublicKeyInfoRef { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: DH_KEY_AGREEMENT_OID, + parameters: None, + }, + subject_public_key: BitStringRef::from_bytes(&public_key_bytes) + .map_err(|_| type_error("invalid DH public key"))?, + }; + spki + .to_der() + .map_err(|_| type_error("invalid DH public key"))? + .into_boxed_slice() + } + }; + Ok(der) + } + _ => Err(type_error(format!("unsupported key type: {}", typ))), + } + } +} + +impl AsymmetricPrivateKey { + fn export_der( + &self, + typ: &str, + // cipher: Option<&str>, + // passphrase: Option<&str>, + ) -> Result<Box<[u8]>, AnyError> { + match typ { + "pkcs1" => match self { + AsymmetricPrivateKey::Rsa(key) => { + let der = key + .to_pkcs1_der() + .map_err(|_| type_error("invalid RSA private key"))? + .to_bytes() + .to_vec() + .into_boxed_slice(); + Ok(der) + } + _ => Err(type_error( + "exporting non-RSA private key as PKCS#1 is not supported", + )), + }, + "sec1" => match self { + AsymmetricPrivateKey::Ec(key) => { + let sec1 = match key { + EcPrivateKey::P224(key) => key.to_sec1_der(), + EcPrivateKey::P256(key) => key.to_sec1_der(), + EcPrivateKey::P384(key) => key.to_sec1_der(), + } + .map_err(|_| type_error("invalid EC private key"))?; + Ok(sec1.to_vec().into_boxed_slice()) + } + _ => Err(type_error( + "exporting non-EC private key as SEC1 is not supported", + )), + }, + "pkcs8" => { + let der = match self { + AsymmetricPrivateKey::Rsa(key) => { + let document = key + .to_pkcs8_der() + .map_err(|_| type_error("invalid RSA private key"))?; + document.to_bytes().to_vec().into_boxed_slice() + } + AsymmetricPrivateKey::RsaPss(_key) => { + return Err(generic_error( + "exporting RSA-PSS private key as PKCS#8 is not supported yet", + )) + } + AsymmetricPrivateKey::Dsa(key) => { + let document = key + .to_pkcs8_der() + .map_err(|_| type_error("invalid DSA private key"))?; + document.to_bytes().to_vec().into_boxed_slice() + } + AsymmetricPrivateKey::Ec(key) => { + let document = match key { + EcPrivateKey::P224(key) => key.to_pkcs8_der(), + EcPrivateKey::P256(key) => key.to_pkcs8_der(), + EcPrivateKey::P384(key) => key.to_pkcs8_der(), + } + .map_err(|_| type_error("invalid EC private key"))?; + document.to_bytes().to_vec().into_boxed_slice() + } + AsymmetricPrivateKey::X25519(key) => { + let private_key = OctetStringRef::new(key.as_bytes()) + .map_err(|_| type_error("invalid X25519 private key"))? + .to_der() + .map_err(|_| type_error("invalid X25519 private key"))?; + + let private_key = PrivateKeyInfo { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: X25519_OID, + parameters: None, + }, + private_key: &private_key, + public_key: None, + }; + + let der = private_key + .to_der() + .map_err(|_| type_error("invalid X25519 private key"))? + .into_boxed_slice(); + return Ok(der); + } + AsymmetricPrivateKey::Ed25519(key) => { + let private_key = OctetStringRef::new(key.as_bytes()) + .map_err(|_| type_error("invalid Ed25519 private key"))? + .to_der() + .map_err(|_| type_error("invalid Ed25519 private key"))?; + + let private_key = PrivateKeyInfo { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: ED25519_OID, + parameters: None, + }, + private_key: &private_key, + public_key: None, + }; + + private_key + .to_der() + .map_err(|_| type_error("invalid ED25519 private key"))? + .into_boxed_slice() + } + AsymmetricPrivateKey::Dh(key) => { + let private_key = key.clone().into_vec(); + let private_key = PrivateKeyInfo { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: DH_KEY_AGREEMENT_OID, + parameters: None, + }, + private_key: &private_key, + public_key: None, + }; + + private_key + .to_der() + .map_err(|_| type_error("invalid DH private key"))? + .into_boxed_slice() + } + }; + + Ok(der) + } + _ => Err(type_error(format!("unsupported key type: {}", typ))), + } + } +} + +#[op2] +#[cppgc] +pub fn op_node_create_private_key( + #[buffer] key: &[u8], + #[string] format: &str, + #[string] typ: &str, + #[buffer] passphrase: Option<&[u8]>, +) -> Result<KeyObjectHandle, AnyError> { + KeyObjectHandle::new_asymmetric_private_key_from_js( + key, format, typ, passphrase, + ) +} + +#[op2] +#[cppgc] +pub fn op_node_create_public_key( + #[buffer] key: &[u8], + #[string] format: &str, + #[string] typ: &str, + #[buffer] passphrase: Option<&[u8]>, +) -> Result<KeyObjectHandle, AnyError> { + KeyObjectHandle::new_asymmetric_public_key_from_js( + key, format, typ, passphrase, + ) +} + +#[op2] +#[cppgc] +pub fn op_node_create_secret_key( + #[buffer(copy)] key: Box<[u8]>, +) -> KeyObjectHandle { + KeyObjectHandle::Secret(key) +} + +#[op2] +#[string] +pub fn op_node_get_asymmetric_key_type( + #[cppgc] handle: &KeyObjectHandle, +) -> Result<&'static str, AnyError> { + match handle { + KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Rsa(_)) + | KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Rsa(_)) => { + Ok("rsa") + } + KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::RsaPss(_)) + | KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::RsaPss(_)) => { + Ok("rsa-pss") + } + KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Dsa(_)) + | KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Dsa(_)) => { + Ok("dsa") + } + KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Ec(_)) + | KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Ec(_)) => Ok("ec"), + KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::X25519(_)) + | KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::X25519(_)) => { + Ok("x25519") + } + KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Ed25519(_)) + | KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Ed25519(_)) => { + Ok("ed25519") + } + KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Dh(_)) + | KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Dh(_)) => Ok("dh"), + KeyObjectHandle::Secret(_) => { + Err(type_error("symmetric key is not an asymmetric key")) + } + } +} + +#[derive(serde::Serialize)] +#[serde(untagged)] +pub enum AsymmetricKeyDetails { + #[serde(rename_all = "camelCase")] + Rsa { + modulus_length: usize, + public_exponent: V8BigInt, + }, + #[serde(rename_all = "camelCase")] + RsaPss { + modulus_length: usize, + public_exponent: V8BigInt, + hash_algorithm: &'static str, + mgf1_hash_algorithm: &'static str, + salt_length: u32, + }, + #[serde(rename = "rsaPss")] + RsaPssBasic { + modulus_length: usize, + public_exponent: V8BigInt, + }, + #[serde(rename_all = "camelCase")] + Dsa { + modulus_length: usize, + divisor_length: usize, + }, + #[serde(rename_all = "camelCase")] + Ec { + named_curve: &'static str, + }, + X25519, + Ed25519, + Dh, +} + +#[op2] +#[serde] +pub fn op_node_get_asymmetric_key_details( + #[cppgc] handle: &KeyObjectHandle, +) -> Result<AsymmetricKeyDetails, AnyError> { + match handle { + KeyObjectHandle::AsymmetricPrivate(private_key) => match private_key { + AsymmetricPrivateKey::Rsa(key) => { + let modulus_length = key.n().bits(); + let public_exponent = + BigInt::from_bytes_be(num_bigint::Sign::Plus, &key.e().to_bytes_be()); + Ok(AsymmetricKeyDetails::Rsa { + modulus_length, + public_exponent: V8BigInt::from(public_exponent), + }) + } + AsymmetricPrivateKey::RsaPss(key) => { + let modulus_length = key.key.n().bits(); + let public_exponent = BigInt::from_bytes_be( + num_bigint::Sign::Plus, + &key.key.e().to_bytes_be(), + ); + let public_exponent = V8BigInt::from(public_exponent); + let details = match key.details { + Some(details) => AsymmetricKeyDetails::RsaPss { + modulus_length, + public_exponent, + hash_algorithm: details.hash_algorithm.as_str(), + mgf1_hash_algorithm: details.mf1_hash_algorithm.as_str(), + salt_length: details.salt_length, + }, + None => AsymmetricKeyDetails::RsaPssBasic { + modulus_length, + public_exponent, + }, + }; + Ok(details) + } + AsymmetricPrivateKey::Dsa(key) => { + let components = key.verifying_key().components(); + let modulus_length = components.p().bits(); + let divisor_length = components.q().bits(); + Ok(AsymmetricKeyDetails::Dsa { + modulus_length, + divisor_length, + }) + } + AsymmetricPrivateKey::Ec(key) => { + let named_curve = match key { + EcPrivateKey::P224(_) => "p224", + EcPrivateKey::P256(_) => "p256", + EcPrivateKey::P384(_) => "p384", + }; + Ok(AsymmetricKeyDetails::Ec { named_curve }) + } + AsymmetricPrivateKey::X25519(_) => Ok(AsymmetricKeyDetails::X25519), + AsymmetricPrivateKey::Ed25519(_) => Ok(AsymmetricKeyDetails::Ed25519), + AsymmetricPrivateKey::Dh(_) => Ok(AsymmetricKeyDetails::Dh), + }, + KeyObjectHandle::AsymmetricPublic(public_key) => match public_key { + AsymmetricPublicKey::Rsa(key) => { + let modulus_length = key.n().bits(); + let public_exponent = + BigInt::from_bytes_be(num_bigint::Sign::Plus, &key.e().to_bytes_be()); + Ok(AsymmetricKeyDetails::Rsa { + modulus_length, + public_exponent: V8BigInt::from(public_exponent), + }) + } + AsymmetricPublicKey::RsaPss(key) => { + let modulus_length = key.key.n().bits(); + let public_exponent = BigInt::from_bytes_be( + num_bigint::Sign::Plus, + &key.key.e().to_bytes_be(), + ); + let public_exponent = V8BigInt::from(public_exponent); + let details = match key.details { + Some(details) => AsymmetricKeyDetails::RsaPss { + modulus_length, + public_exponent, + hash_algorithm: details.hash_algorithm.as_str(), + mgf1_hash_algorithm: details.mf1_hash_algorithm.as_str(), + salt_length: details.salt_length, + }, + None => AsymmetricKeyDetails::RsaPssBasic { + modulus_length, + public_exponent, + }, + }; + Ok(details) + } + AsymmetricPublicKey::Dsa(key) => { + let components = key.components(); + let modulus_length = components.p().bits(); + let divisor_length = components.q().bits(); + Ok(AsymmetricKeyDetails::Dsa { + modulus_length, + divisor_length, + }) + } + AsymmetricPublicKey::Ec(key) => { + let named_curve = match key { + EcPublicKey::P224(_) => "p224", + EcPublicKey::P256(_) => "p256", + EcPublicKey::P384(_) => "p384", + }; + Ok(AsymmetricKeyDetails::Ec { named_curve }) + } + AsymmetricPublicKey::X25519(_) => Ok(AsymmetricKeyDetails::X25519), + AsymmetricPublicKey::Ed25519(_) => Ok(AsymmetricKeyDetails::Ed25519), + AsymmetricPublicKey::Dh(_) => Ok(AsymmetricKeyDetails::Dh), + }, + KeyObjectHandle::Secret(_) => { + Err(type_error("symmetric key is not an asymmetric key")) + } + } +} + +#[op2(fast)] +#[smi] +pub fn op_node_get_symmetric_key_size( + #[cppgc] handle: &KeyObjectHandle, +) -> Result<usize, AnyError> { + match handle { + KeyObjectHandle::AsymmetricPrivate(_) => { + Err(type_error("asymmetric key is not a symmetric key")) + } + KeyObjectHandle::AsymmetricPublic(_) => { + Err(type_error("asymmetric key is not a symmetric key")) + } + KeyObjectHandle::Secret(key) => Ok(key.len() * 8), + } +} + +#[op2] +#[cppgc] +pub fn op_node_generate_secret_key(#[smi] len: usize) -> KeyObjectHandle { + let mut key = vec![0u8; len]; + thread_rng().fill_bytes(&mut key); + KeyObjectHandle::Secret(key.into_boxed_slice()) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_secret_key_async( + #[smi] len: usize, +) -> KeyObjectHandle { + spawn_blocking(move || { + let mut key = vec![0u8; len]; + thread_rng().fill_bytes(&mut key); + KeyObjectHandle::Secret(key.into_boxed_slice()) + }) + .await + .unwrap() +} + +struct KeyObjectHandlePair { + private_key: RefCell<Option<KeyObjectHandle>>, + public_key: RefCell<Option<KeyObjectHandle>>, +} + +impl GarbageCollected for KeyObjectHandlePair {} + +impl KeyObjectHandlePair { + pub fn new( + private_key: AsymmetricPrivateKey, + public_key: AsymmetricPublicKey, + ) -> Self { + Self { + private_key: RefCell::new(Some(KeyObjectHandle::AsymmetricPrivate( + private_key, + ))), + public_key: RefCell::new(Some(KeyObjectHandle::AsymmetricPublic( + public_key, + ))), + } + } +} + +#[op2] +#[cppgc] +pub fn op_node_get_public_key_from_pair( + #[cppgc] pair: &KeyObjectHandlePair, +) -> Option<KeyObjectHandle> { + pair.public_key.borrow_mut().take() +} + +#[op2] +#[cppgc] +pub fn op_node_get_private_key_from_pair( + #[cppgc] pair: &KeyObjectHandlePair, +) -> Option<KeyObjectHandle> { + pair.private_key.borrow_mut().take() +} + +fn generate_rsa( + modulus_length: usize, + public_exponent: usize, +) -> KeyObjectHandlePair { + let private_key = RsaPrivateKey::new_with_exp( + &mut thread_rng(), + modulus_length, + &rsa::BigUint::from_usize(public_exponent).unwrap(), + ) + .unwrap(); + + let private_key = AsymmetricPrivateKey::Rsa(private_key); + let public_key = private_key.to_public_key(); + + KeyObjectHandlePair::new(private_key, public_key) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_rsa_key( + #[smi] modulus_length: usize, + #[smi] public_exponent: usize, +) -> KeyObjectHandlePair { + generate_rsa(modulus_length, public_exponent) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_rsa_key_async( + #[smi] modulus_length: usize, + #[smi] public_exponent: usize, +) -> KeyObjectHandlePair { + spawn_blocking(move || generate_rsa(modulus_length, public_exponent)) + .await + .unwrap() +} + +fn generate_rsa_pss( + modulus_length: usize, + public_exponent: usize, + hash_algorithm: Option<&str>, + mf1_hash_algorithm: Option<&str>, + salt_length: Option<u32>, +) -> Result<KeyObjectHandlePair, AnyError> { + let key = RsaPrivateKey::new_with_exp( + &mut thread_rng(), + modulus_length, + &rsa::BigUint::from_usize(public_exponent).unwrap(), + ) + .unwrap(); + + let details = if hash_algorithm.is_none() + && mf1_hash_algorithm.is_none() + && salt_length.is_none() + { + None + } else { + let hash_algorithm = hash_algorithm.unwrap_or("sha1"); + let mf1_hash_algorithm = mf1_hash_algorithm.unwrap_or(hash_algorithm); + let hash_algorithm = match_fixed_digest_with_oid!( + hash_algorithm, + fn (algorithm: Option<RsaPssHashAlgorithm>) { + algorithm.ok_or_else(|| type_error("digest not allowed for RSA-PSS keys: {}"))? + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA-PSS keys: {}", + hash_algorithm + ))) + } + ); + let mf1_hash_algorithm = match_fixed_digest_with_oid!( + mf1_hash_algorithm, + fn (algorithm: Option<RsaPssHashAlgorithm>) { + algorithm.ok_or_else(|| type_error("digest not allowed for RSA-PSS keys: {}"))? + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA-PSS keys: {}", + mf1_hash_algorithm + ))) + } + ); + let salt_length = + salt_length.unwrap_or_else(|| hash_algorithm.salt_length()); + + Some(RsaPssDetails { + hash_algorithm, + mf1_hash_algorithm, + salt_length, + }) + }; + + let private_key = + AsymmetricPrivateKey::RsaPss(RsaPssPrivateKey { key, details }); + let public_key = private_key.to_public_key(); + + Ok(KeyObjectHandlePair::new(private_key, public_key)) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_rsa_pss_key( + #[smi] modulus_length: usize, + #[smi] public_exponent: usize, + #[string] hash_algorithm: Option<String>, // todo: Option<&str> not supproted in ops yet + #[string] mf1_hash_algorithm: Option<String>, // todo: Option<&str> not supproted in ops yet + #[smi] salt_length: Option<u32>, +) -> Result<KeyObjectHandlePair, AnyError> { + generate_rsa_pss( + modulus_length, + public_exponent, + hash_algorithm.as_deref(), + mf1_hash_algorithm.as_deref(), + salt_length, + ) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_rsa_pss_key_async( + #[smi] modulus_length: usize, + #[smi] public_exponent: usize, + #[string] hash_algorithm: Option<String>, // todo: Option<&str> not supproted in ops yet + #[string] mf1_hash_algorithm: Option<String>, // todo: Option<&str> not supproted in ops yet + #[smi] salt_length: Option<u32>, +) -> Result<KeyObjectHandlePair, AnyError> { + spawn_blocking(move || { + generate_rsa_pss( + modulus_length, + public_exponent, + hash_algorithm.as_deref(), + mf1_hash_algorithm.as_deref(), + salt_length, + ) + }) + .await + .unwrap() +} + +fn dsa_generate( + modulus_length: usize, + divisor_length: usize, +) -> Result<KeyObjectHandlePair, AnyError> { + let mut rng = rand::thread_rng(); + use dsa::Components; + use dsa::KeySize; + use dsa::SigningKey; + + let key_size = match (modulus_length, divisor_length) { + #[allow(deprecated)] + (1024, 160) => KeySize::DSA_1024_160, + (2048, 224) => KeySize::DSA_2048_224, + (2048, 256) => KeySize::DSA_2048_256, + (3072, 256) => KeySize::DSA_3072_256, + _ => { + return Err(type_error( + "Invalid modulusLength+divisorLength combination", + )) + } + }; + let components = Components::generate(&mut rng, key_size); + let signing_key = SigningKey::generate(&mut rng, components); + let private_key = AsymmetricPrivateKey::Dsa(signing_key); + let public_key = private_key.to_public_key(); + + Ok(KeyObjectHandlePair::new(private_key, public_key)) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_dsa_key( + #[smi] modulus_length: usize, + #[smi] divisor_length: usize, +) -> Result<KeyObjectHandlePair, AnyError> { + dsa_generate(modulus_length, divisor_length) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_dsa_key_async( + #[smi] modulus_length: usize, + #[smi] divisor_length: usize, +) -> Result<KeyObjectHandlePair, AnyError> { + spawn_blocking(move || dsa_generate(modulus_length, divisor_length)) + .await + .unwrap() +} + +fn ec_generate(named_curve: &str) -> Result<KeyObjectHandlePair, AnyError> { + let mut rng = rand::thread_rng(); + // TODO(@littledivy): Support public key point encoding. + // Default is uncompressed. + let private_key = match named_curve { + "P-224" | "prime224v1" | "secp224r1" => { + let key = p224::SecretKey::random(&mut rng); + AsymmetricPrivateKey::Ec(EcPrivateKey::P224(key)) + } + "P-256" | "prime256v1" | "secp256r1" => { + let key = p256::SecretKey::random(&mut rng); + AsymmetricPrivateKey::Ec(EcPrivateKey::P256(key)) + } + "P-384" | "prime384v1" | "secp384r1" => { + let key = p384::SecretKey::random(&mut rng); + AsymmetricPrivateKey::Ec(EcPrivateKey::P384(key)) + } + _ => { + return Err(type_error(format!( + "unsupported named curve: {}", + named_curve + ))) + } + }; + let public_key = private_key.to_public_key(); + Ok(KeyObjectHandlePair::new(private_key, public_key)) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_ec_key( + #[string] named_curve: &str, +) -> Result<KeyObjectHandlePair, AnyError> { + ec_generate(named_curve) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_ec_key_async( + #[string] named_curve: String, +) -> Result<KeyObjectHandlePair, AnyError> { + spawn_blocking(move || ec_generate(&named_curve)) + .await + .unwrap() +} + +fn x25519_generate() -> KeyObjectHandlePair { + let keypair = x25519_dalek::StaticSecret::random_from_rng(thread_rng()); + let private_key = AsymmetricPrivateKey::X25519(keypair); + let public_key = private_key.to_public_key(); + KeyObjectHandlePair::new(private_key, public_key) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_x25519_key() -> KeyObjectHandlePair { + x25519_generate() +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_x25519_key_async() -> KeyObjectHandlePair { + spawn_blocking(x25519_generate).await.unwrap() +} + +fn ed25519_generate() -> KeyObjectHandlePair { + let keypair = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let private_key = AsymmetricPrivateKey::Ed25519(keypair); + let public_key = private_key.to_public_key(); + KeyObjectHandlePair::new(private_key, public_key) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_ed25519_key() -> KeyObjectHandlePair { + ed25519_generate() +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_ed25519_key_async() -> KeyObjectHandlePair { + spawn_blocking(ed25519_generate).await.unwrap() +} + +fn dh_group_generate( + group_name: &str, +) -> Result<KeyObjectHandlePair, AnyError> { + let dh = match group_name { + "modp5" => dh::DiffieHellman::group::<dh::Modp1536>(), + "modp14" => dh::DiffieHellman::group::<dh::Modp2048>(), + "modp15" => dh::DiffieHellman::group::<dh::Modp3072>(), + "modp16" => dh::DiffieHellman::group::<dh::Modp4096>(), + "modp17" => dh::DiffieHellman::group::<dh::Modp6144>(), + "modp18" => dh::DiffieHellman::group::<dh::Modp8192>(), + _ => return Err(type_error("Unsupported group name")), + }; + Ok(KeyObjectHandlePair::new( + AsymmetricPrivateKey::Dh(dh.private_key), + AsymmetricPublicKey::Dh(dh.public_key), + )) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_dh_group_key( + #[string] group_name: &str, +) -> Result<KeyObjectHandlePair, AnyError> { + dh_group_generate(group_name) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_dh_group_key_async( + #[string] group_name: String, +) -> Result<KeyObjectHandlePair, AnyError> { + spawn_blocking(move || dh_group_generate(&group_name)) + .await + .unwrap() +} + +fn dh_generate( + prime: Option<&[u8]>, + prime_len: usize, + generator: usize, +) -> Result<KeyObjectHandlePair, AnyError> { + let prime = prime + .map(|p| p.into()) + .unwrap_or_else(|| Prime::generate(prime_len)); + let dh = dh::DiffieHellman::new(prime, generator); + Ok(KeyObjectHandlePair::new( + AsymmetricPrivateKey::Dh(dh.private_key), + AsymmetricPublicKey::Dh(dh.public_key), + )) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_dh_key( + #[buffer] prime: Option<&[u8]>, + #[smi] prime_len: usize, + #[smi] generator: usize, +) -> Result<KeyObjectHandlePair, AnyError> { + dh_generate(prime, prime_len, generator) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_dh_key_async( + #[buffer(copy)] prime: Option<Box<[u8]>>, + #[smi] prime_len: usize, + #[smi] generator: usize, +) -> Result<KeyObjectHandlePair, AnyError> { + spawn_blocking(move || dh_generate(prime.as_deref(), prime_len, generator)) + .await + .unwrap() +} + +#[op2] +#[serde] +pub fn op_node_dh_keys_generate_and_export( + #[buffer] prime: Option<&[u8]>, + #[smi] prime_len: usize, + #[smi] generator: usize, +) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { + let prime = prime + .map(|p| p.into()) + .unwrap_or_else(|| Prime::generate(prime_len)); + let dh = dh::DiffieHellman::new(prime, generator); + let private_key = dh.private_key.into_vec().into_boxed_slice(); + let public_key = dh.public_key.into_vec().into_boxed_slice(); + Ok((private_key.into(), public_key.into())) +} + +#[op2] +#[buffer] +pub fn op_node_export_secret_key( + #[cppgc] handle: &KeyObjectHandle, +) -> Result<Box<[u8]>, AnyError> { + let key = handle + .as_secret_key() + .ok_or_else(|| type_error("key is not a secret key"))?; + Ok(key.to_vec().into_boxed_slice()) +} + +#[op2] +#[string] +pub fn op_node_export_secret_key_b64url( + #[cppgc] handle: &KeyObjectHandle, +) -> Result<String, AnyError> { + let key = handle + .as_secret_key() + .ok_or_else(|| type_error("key is not a secret key"))?; + Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(key)) +} + +#[op2] +#[string] +pub fn op_node_export_public_key_pem( + #[cppgc] handle: &KeyObjectHandle, + #[string] typ: &str, +) -> Result<String, AnyError> { + let public_key = handle + .as_public_key() + .ok_or_else(|| type_error("key is not an asymmetric public key"))?; + let data = public_key.export_der(typ)?; + + let label = match typ { + "pkcs1" => "RSA PUBLIC KEY", + "spki" => "PUBLIC KEY", + _ => unreachable!("export_der would have errored"), + }; + + let mut out = vec![0; 2048]; + let mut writer = PemWriter::new(label, LineEnding::LF, &mut out)?; + writer.write(&data)?; + let len = writer.finish()?; + out.truncate(len); + + Ok(String::from_utf8(out).expect("invalid pem is not possible")) +} + +#[op2] +#[buffer] +pub fn op_node_export_public_key_der( + #[cppgc] handle: &KeyObjectHandle, + #[string] typ: &str, +) -> Result<Box<[u8]>, AnyError> { + let public_key = handle + .as_public_key() + .ok_or_else(|| type_error("key is not an asymmetric public key"))?; + public_key.export_der(typ) +} + +#[op2] +#[string] +pub fn op_node_export_private_key_pem( + #[cppgc] handle: &KeyObjectHandle, + #[string] typ: &str, +) -> Result<String, AnyError> { + let private_key = handle + .as_private_key() + .ok_or_else(|| type_error("key is not an asymmetric private key"))?; + let data = private_key.export_der(typ)?; + + let label = match typ { + "pkcs1" => "RSA PRIVATE KEY", + "pkcs8" => "PRIVATE KEY", + "sec1" => "EC PRIVATE KEY", + _ => unreachable!("export_der would have errored"), + }; + + let mut out = vec![0; 2048]; + let mut writer = PemWriter::new(label, LineEnding::LF, &mut out)?; + writer.write(&data)?; + let len = writer.finish()?; + out.truncate(len); + + Ok(String::from_utf8(out).expect("invalid pem is not possible")) +} + +#[op2] +#[buffer] +pub fn op_node_export_private_key_der( + #[cppgc] handle: &KeyObjectHandle, + #[string] typ: &str, +) -> Result<Box<[u8]>, AnyError> { + let private_key = handle + .as_private_key() + .ok_or_else(|| type_error("key is not an asymmetric private key"))?; + private_key.export_der(typ) +} + +#[op2] +#[string] +pub fn op_node_key_type(#[cppgc] handle: &KeyObjectHandle) -> &'static str { + match handle { + KeyObjectHandle::AsymmetricPrivate(_) => "private", + KeyObjectHandle::AsymmetricPublic(_) => "public", + KeyObjectHandle::Secret(_) => "secret", + } +} + +#[op2] +#[cppgc] +pub fn op_node_derive_public_key_from_private_key( + #[cppgc] handle: &KeyObjectHandle, +) -> Result<KeyObjectHandle, AnyError> { + let Some(private_key) = handle.as_private_key() else { + return Err(type_error("expected private key")); + }; + + Ok(KeyObjectHandle::AsymmetricPublic( + private_key.to_public_key(), + )) +} diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index 1b545e024..07d901cbc 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -3,7 +3,6 @@ use deno_core::error::generic_error; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::op2; -use deno_core::serde_v8::BigInt as V8BigInt; use deno_core::unsync::spawn_blocking; use deno_core::JsBuffer; use deno_core::OpState; @@ -11,21 +10,12 @@ use deno_core::StringOrBuffer; use deno_core::ToJsBuffer; use elliptic_curve::sec1::ToEncodedPoint; use hkdf::Hkdf; +use keys::KeyObjectHandle; use num_bigint::BigInt; use num_bigint_dig::BigUint; -use num_traits::FromPrimitive; -use once_cell::sync::Lazy; use rand::distributions::Distribution; use rand::distributions::Uniform; -use rand::thread_rng; use rand::Rng; -use rsa::pkcs1::DecodeRsaPrivateKey; -use rsa::pkcs1::DecodeRsaPublicKey; -use rsa::pkcs8; -use rsa::pkcs8::der::asn1; -use rsa::pkcs8::der::Decode; -use rsa::pkcs8::der::Encode; -use rsa::pkcs8::der::Reader; use std::future::Future; use std::rc::Rc; @@ -34,24 +24,21 @@ use p256::NistP256; use p384::NistP384; use rsa::pkcs8::DecodePrivateKey; use rsa::pkcs8::DecodePublicKey; -use rsa::signature::hazmat::PrehashSigner; -use rsa::signature::hazmat::PrehashVerifier; -use rsa::signature::SignatureEncoding; use rsa::Oaep; use rsa::Pkcs1v15Encrypt; use rsa::RsaPrivateKey; use rsa::RsaPublicKey; -use spki::EncodePublicKey; mod cipher; mod dh; mod digest; +pub mod keys; mod md5_sha1; mod primes; +mod sign; pub mod x509; use self::digest::match_fixed_digest_with_eager_block_buffer; -use self::digest::match_fixed_digest_with_oid; #[op2(fast)] pub fn op_node_check_prime( @@ -339,156 +326,23 @@ pub fn op_node_decipheriv_final( } #[op2] -#[serde] +#[buffer] pub fn op_node_sign( + #[cppgc] handle: &KeyObjectHandle, #[buffer] digest: &[u8], #[string] digest_type: &str, - #[serde] key: StringOrBuffer, - #[string] _type: &str, - #[string] format: &str, -) -> Result<ToJsBuffer, AnyError> { - let (label, doc) = - pkcs8::SecretDocument::from_pem(std::str::from_utf8(&key).unwrap())?; - - let oid; - let pkey = match format { - "pem" => match label { - "PRIVATE KEY" => { - let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?; - oid = pk_info.algorithm.oid; - pk_info.private_key - } - "RSA PRIVATE KEY" => { - oid = RSA_ENCRYPTION_OID; - doc.as_bytes() - } - "EC PRIVATE KEY" => { - let ec_pk = sec1::EcPrivateKey::from_der(doc.as_bytes())?; - match ec_pk.parameters { - Some(sec1::EcParameters::NamedCurve(o)) => { - oid = o; - ec_pk.private_key - } - // https://datatracker.ietf.org/doc/html/rfc5915#section-3 - // - // Though the ASN.1 indicates that - // the parameters field is OPTIONAL, implementations that conform to - // this document MUST always include the parameters field. - _ => return Err(type_error("invalid ECPrivateKey params")), - } - } - _ => return Err(type_error("Invalid PEM label")), - }, - _ => return Err(type_error("Unsupported key format")), - }; - - match oid { - RSA_ENCRYPTION_OID => { - use rsa::pkcs1v15::SigningKey; - let key = RsaPrivateKey::from_pkcs1_der(pkey)?; - - // md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo - // (so has no prefix). - // See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285 - if digest_type == "md5-sha1" { - let signing_key = SigningKey::<md5_sha1::Md5Sha1>::new_unprefixed(key); - let signature = signing_key.sign_prehash(digest)?.to_vec(); - return Ok(signature.into()); - } - - let signature = match_fixed_digest_with_oid!( - digest_type, - fn <D>() { - let signing_key = SigningKey::<D>::new(key); - signing_key.sign_prehash(digest)?.to_vec() - }, - _ => { - return Err(type_error(format!( - "digest not allowed for RSA signature: {}", - digest_type - ))) - } - ); - Ok(signature.into()) - } - // signature structure encoding is DER by default for DSA and ECDSA. - // - // TODO(@littledivy): Validate public_key if present - ID_SECP256R1_OID => { - let key = p256::ecdsa::SigningKey::from_slice(pkey)?; - Ok( - key - .sign_prehash(digest) - .map(|sig: p256::ecdsa::Signature| sig.to_der().to_vec().into())?, - ) - } - ID_SECP384R1_OID => { - let key = p384::ecdsa::SigningKey::from_slice(pkey)?; - Ok( - key - .sign_prehash(digest) - .map(|sig: p384::ecdsa::Signature| sig.to_der().to_vec().into())?, - ) - } - _ => Err(type_error("Unsupported signing key")), - } +) -> Result<Box<[u8]>, AnyError> { + handle.sign_prehashed(digest_type, digest) } -#[op2] +#[op2(fast)] pub fn op_node_verify( + #[cppgc] handle: &KeyObjectHandle, #[buffer] digest: &[u8], #[string] digest_type: &str, - #[serde] key: StringOrBuffer, - #[string] key_type: &str, - #[string] key_format: &str, #[buffer] signature: &[u8], ) -> Result<bool, AnyError> { - match key_type { - "rsa" => { - use rsa::pkcs1v15::VerifyingKey; - let key = match key_format { - "pem" => RsaPublicKey::from_public_key_pem((&key).try_into()?) - .map_err(|_| type_error("Invalid RSA public key"))?, - // TODO(kt3k): Support der and jwk formats - _ => { - return Err(type_error(format!( - "Unsupported key format: {}", - key_format - ))) - } - }; - - // md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo - // (so has no prefix). - // See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285 - if digest_type == "md5-sha1" { - let verifying_key = - VerifyingKey::<md5_sha1::Md5Sha1>::new_unprefixed(key); - let verified = verifying_key - .verify_prehash(digest, &signature.try_into()?) - .is_ok(); - return Ok(verified); - } - - Ok(match_fixed_digest_with_oid!( - digest_type, - fn <D>() { - let verifying_key = VerifyingKey::<D>::new(key); - verifying_key.verify_prehash(digest, &signature.try_into()?).is_ok() - }, - _ => { - return Err(type_error(format!( - "digest not allowed for RSA signature: {}", - digest_type - ))) - } - )) - } - _ => Err(type_error(format!( - "Verifying with {} keys is not supported", - key_type - ))), - } + handle.verify_prehashed(digest_type, digest, signature) } fn pbkdf2_sync( @@ -542,13 +396,13 @@ pub async fn op_node_pbkdf2_async( } #[op2(fast)] -pub fn op_node_generate_secret(#[buffer] buf: &mut [u8]) { +pub fn op_node_fill_random(#[buffer] buf: &mut [u8]) { rand::thread_rng().fill(buf); } #[op2(async)] #[serde] -pub async fn op_node_generate_secret_async(#[smi] len: i32) -> ToJsBuffer { +pub async fn op_node_fill_random_async(#[smi] len: i32) -> ToJsBuffer { spawn_blocking(move || { let mut buf = vec![0u8; len as usize]; rand::thread_rng().fill(&mut buf[..]); @@ -560,11 +414,15 @@ pub async fn op_node_generate_secret_async(#[smi] len: i32) -> ToJsBuffer { fn hkdf_sync( digest_algorithm: &str, - ikm: &[u8], + handle: &KeyObjectHandle, salt: &[u8], info: &[u8], okm: &mut [u8], ) -> Result<(), AnyError> { + let Some(ikm) = handle.as_secret_key() else { + return Err(type_error("expected secret key")); + }; + match_fixed_digest_with_eager_block_buffer!( digest_algorithm, fn <D>() { @@ -581,328 +439,32 @@ fn hkdf_sync( #[op2(fast)] pub fn op_node_hkdf( #[string] digest_algorithm: &str, - #[buffer] ikm: &[u8], + #[cppgc] handle: &KeyObjectHandle, #[buffer] salt: &[u8], #[buffer] info: &[u8], #[buffer] okm: &mut [u8], ) -> Result<(), AnyError> { - hkdf_sync(digest_algorithm, ikm, salt, info, okm) + hkdf_sync(digest_algorithm, handle, salt, info, okm) } #[op2(async)] #[serde] pub async fn op_node_hkdf_async( #[string] digest_algorithm: String, - #[buffer] ikm: JsBuffer, + #[cppgc] handle: &KeyObjectHandle, #[buffer] salt: JsBuffer, #[buffer] info: JsBuffer, #[number] okm_len: usize, ) -> Result<ToJsBuffer, AnyError> { + let handle = handle.clone(); spawn_blocking(move || { let mut okm = vec![0u8; okm_len]; - hkdf_sync(&digest_algorithm, &ikm, &salt, &info, &mut okm)?; + hkdf_sync(&digest_algorithm, &handle, &salt, &info, &mut okm)?; Ok(okm.into()) }) .await? } -use rsa::pkcs1::EncodeRsaPrivateKey; -use rsa::pkcs1::EncodeRsaPublicKey; - -use self::primes::Prime; - -fn generate_rsa( - modulus_length: usize, - public_exponent: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - let mut rng = rand::thread_rng(); - let private_key = RsaPrivateKey::new_with_exp( - &mut rng, - modulus_length, - &rsa::BigUint::from_usize(public_exponent).unwrap(), - )?; - let public_key = private_key.to_public_key(); - let private_key_der = private_key.to_pkcs1_der()?.as_bytes().to_vec(); - let public_key_der = public_key.to_pkcs1_der()?.to_vec(); - - Ok((private_key_der.into(), public_key_der.into())) -} - -#[op2] -#[serde] -pub fn op_node_generate_rsa( - #[number] modulus_length: usize, - #[number] public_exponent: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - generate_rsa(modulus_length, public_exponent) -} - -#[op2(async)] -#[serde] -pub async fn op_node_generate_rsa_async( - #[number] modulus_length: usize, - #[number] public_exponent: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - spawn_blocking(move || generate_rsa(modulus_length, public_exponent)).await? -} - -#[op2] -#[string] -pub fn op_node_export_rsa_public_pem( - #[buffer] pkcs1_der: &[u8], -) -> Result<String, AnyError> { - let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?; - let export = public_key.to_public_key_pem(Default::default())?; - Ok(export) -} - -#[op2] -#[serde] -pub fn op_node_export_rsa_spki_der( - #[buffer] pkcs1_der: &[u8], -) -> Result<ToJsBuffer, AnyError> { - let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?; - let export = public_key.to_public_key_der()?.to_vec(); - Ok(export.into()) -} - -fn dsa_generate( - modulus_length: usize, - divisor_length: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - let mut rng = rand::thread_rng(); - use dsa::pkcs8::EncodePrivateKey; - use dsa::Components; - use dsa::KeySize; - use dsa::SigningKey; - - let key_size = match (modulus_length, divisor_length) { - #[allow(deprecated)] - (1024, 160) => KeySize::DSA_1024_160, - (2048, 224) => KeySize::DSA_2048_224, - (2048, 256) => KeySize::DSA_2048_256, - (3072, 256) => KeySize::DSA_3072_256, - _ => return Err(type_error("Invalid modulus_length or divisor_length")), - }; - let components = Components::generate(&mut rng, key_size); - let signing_key = SigningKey::generate(&mut rng, components); - let verifying_key = signing_key.verifying_key(); - - Ok(( - signing_key - .to_pkcs8_der() - .map_err(|_| type_error("Not valid pkcs8"))? - .as_bytes() - .to_vec() - .into(), - verifying_key - .to_public_key_der() - .map_err(|_| type_error("Not valid spki"))? - .to_vec() - .into(), - )) -} - -#[op2] -#[serde] -pub fn op_node_dsa_generate( - #[number] modulus_length: usize, - #[number] divisor_length: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - dsa_generate(modulus_length, divisor_length) -} - -#[op2(async)] -#[serde] -pub async fn op_node_dsa_generate_async( - #[number] modulus_length: usize, - #[number] divisor_length: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - spawn_blocking(move || dsa_generate(modulus_length, divisor_length)).await? -} - -fn ec_generate( - named_curve: &str, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - let mut rng = rand::thread_rng(); - // TODO(@littledivy): Support public key point encoding. - // Default is uncompressed. - match named_curve { - "P-256" | "prime256v1" | "secp256r1" => { - let key = p256::SecretKey::random(&mut rng); - let public_key = key.public_key(); - - Ok(( - key.to_bytes().to_vec().into(), - public_key.to_encoded_point(false).as_ref().to_vec().into(), - )) - } - "P-384" | "prime384v1" | "secp384r1" => { - let key = p384::SecretKey::random(&mut rng); - let public_key = key.public_key(); - - Ok(( - key.to_bytes().to_vec().into(), - public_key.to_encoded_point(false).as_ref().to_vec().into(), - )) - } - _ => Err(type_error("Unsupported named curve")), - } -} - -#[op2] -#[serde] -pub fn op_node_ec_generate( - #[string] named_curve: &str, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - ec_generate(named_curve) -} - -#[op2(async)] -#[serde] -pub async fn op_node_ec_generate_async( - #[string] named_curve: String, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - spawn_blocking(move || ec_generate(&named_curve)).await? -} - -fn ed25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - use ring::signature::Ed25519KeyPair; - use ring::signature::KeyPair; - - let mut rng = thread_rng(); - let mut seed = vec![0u8; 32]; - rng.fill(seed.as_mut_slice()); - - let pair = Ed25519KeyPair::from_seed_unchecked(&seed) - .map_err(|_| type_error("Failed to generate Ed25519 key"))?; - - let public_key = pair.public_key().as_ref().to_vec(); - Ok((seed.into(), public_key.into())) -} - -#[op2] -#[serde] -pub fn op_node_ed25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> -{ - ed25519_generate() -} - -#[op2(async)] -#[serde] -pub async fn op_node_ed25519_generate_async( -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - spawn_blocking(ed25519_generate).await? -} - -fn x25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - // u-coordinate of the base point. - const X25519_BASEPOINT_BYTES: [u8; 32] = [ - 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, - ]; - - let mut pkey = [0; 32]; - - let mut rng = thread_rng(); - rng.fill(pkey.as_mut_slice()); - - let pkey_copy = pkey.to_vec(); - // https://www.rfc-editor.org/rfc/rfc7748#section-6.1 - // pubkey = x25519(a, 9) which is constant-time Montgomery ladder. - // https://eprint.iacr.org/2014/140.pdf page 4 - // https://eprint.iacr.org/2017/212.pdf algorithm 8 - // pubkey is in LE order. - let pubkey = x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES); - - Ok((pkey_copy.into(), pubkey.to_vec().into())) -} - -#[op2] -#[serde] -pub fn op_node_x25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - x25519_generate() -} - -#[op2(async)] -#[serde] -pub async fn op_node_x25519_generate_async( -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - spawn_blocking(x25519_generate).await? -} - -fn dh_generate_group( - group_name: &str, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - let dh = match group_name { - "modp5" => dh::DiffieHellman::group::<dh::Modp1536>(), - "modp14" => dh::DiffieHellman::group::<dh::Modp2048>(), - "modp15" => dh::DiffieHellman::group::<dh::Modp3072>(), - "modp16" => dh::DiffieHellman::group::<dh::Modp4096>(), - "modp17" => dh::DiffieHellman::group::<dh::Modp6144>(), - "modp18" => dh::DiffieHellman::group::<dh::Modp8192>(), - _ => return Err(type_error("Unsupported group name")), - }; - - Ok(( - dh.private_key.into_vec().into(), - dh.public_key.into_vec().into(), - )) -} - -#[op2] -#[serde] -pub fn op_node_dh_generate_group( - #[string] group_name: &str, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - dh_generate_group(group_name) -} - -#[op2(async)] -#[serde] -pub async fn op_node_dh_generate_group_async( - #[string] group_name: String, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - spawn_blocking(move || dh_generate_group(&group_name)).await? -} - -fn dh_generate( - prime: Option<&[u8]>, - prime_len: usize, - generator: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - let prime = prime - .map(|p| p.into()) - .unwrap_or_else(|| Prime::generate(prime_len)); - let dh = dh::DiffieHellman::new(prime, generator); - - Ok(( - dh.private_key.into_vec().into(), - dh.public_key.into_vec().into(), - )) -} - -#[op2] -#[serde] -pub fn op_node_dh_generate( - #[serde] prime: Option<&[u8]>, - #[number] prime_len: usize, - #[number] generator: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - dh_generate(prime, prime_len, generator) -} - -// TODO(lev): This duplication should be avoided. -#[op2] -#[serde] -pub fn op_node_dh_generate2( - #[buffer] prime: JsBuffer, - #[number] prime_len: usize, - #[number] generator: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - dh_generate(Some(prime).as_deref(), prime_len, generator) -} - #[op2] #[serde] pub fn op_node_dh_compute_secret( @@ -918,17 +480,6 @@ pub fn op_node_dh_compute_secret( Ok(shared_secret.to_bytes_be().into()) } -#[op2(async)] -#[serde] -pub async fn op_node_dh_generate_async( - #[buffer] prime: Option<JsBuffer>, - #[number] prime_len: usize, - #[number] generator: usize, -) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> { - spawn_blocking(move || dh_generate(prime.as_deref(), prime_len, generator)) - .await? -} - #[op2(fast)] #[smi] pub fn op_node_random_int( @@ -1288,392 +839,3 @@ pub async fn op_node_gen_prime_async( ) -> Result<ToJsBuffer, AnyError> { Ok(spawn_blocking(move || gen_prime(size)).await?) } - -#[derive(serde::Serialize)] -#[serde(tag = "type")] -pub enum AsymmetricKeyDetails { - #[serde(rename = "rsa")] - #[serde(rename_all = "camelCase")] - Rsa { - modulus_length: usize, - public_exponent: V8BigInt, - }, - #[serde(rename = "rsa-pss")] - #[serde(rename_all = "camelCase")] - RsaPss { - modulus_length: usize, - public_exponent: V8BigInt, - hash_algorithm: String, - salt_length: u32, - }, - #[serde(rename = "ec")] - #[serde(rename_all = "camelCase")] - Ec { named_curve: String }, - #[serde(rename = "dh")] - Dh, -} - -// https://oidref.com/ -const ID_SHA1_OID: rsa::pkcs8::ObjectIdentifier = - rsa::pkcs8::ObjectIdentifier::new_unwrap("1.3.14.3.2.26"); -const ID_SHA256_OID: rsa::pkcs8::ObjectIdentifier = - rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1"); -const ID_SHA384_OID: rsa::pkcs8::ObjectIdentifier = - rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2"); -const ID_SHA512_OID: rsa::pkcs8::ObjectIdentifier = - rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3"); -const ID_MFG1: rsa::pkcs8::ObjectIdentifier = - rsa::pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.8"); -pub const ID_SECP256R1_OID: const_oid::ObjectIdentifier = - const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); -pub const ID_SECP384R1_OID: const_oid::ObjectIdentifier = - const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.34"); -pub const ID_SECP521R1_OID: const_oid::ObjectIdentifier = - const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.35"); - -// Default HashAlgorithm for RSASSA-PSS-params (sha1) -// -// sha1 HashAlgorithm ::= { -// algorithm id-sha1, -// parameters SHA1Parameters : NULL -// } -// -// SHA1Parameters ::= NULL -static SHA1_HASH_ALGORITHM: Lazy<rsa::pkcs8::AlgorithmIdentifierRef<'static>> = - Lazy::new(|| rsa::pkcs8::AlgorithmIdentifierRef { - // id-sha1 - oid: ID_SHA1_OID, - // NULL - parameters: Some(asn1::AnyRef::from(asn1::Null)), - }); - -// TODO(@littledivy): `pkcs8` should provide AlgorithmIdentifier to Any conversion. -static ENCODED_SHA1_HASH_ALGORITHM: Lazy<Vec<u8>> = - Lazy::new(|| SHA1_HASH_ALGORITHM.to_der().unwrap()); - -// Default MaskGenAlgrithm for RSASSA-PSS-params (mgf1SHA1) -// -// mgf1SHA1 MaskGenAlgorithm ::= { -// algorithm id-mgf1, -// parameters HashAlgorithm : sha1 -// } -static MGF1_SHA1_MASK_ALGORITHM: Lazy< - rsa::pkcs8::AlgorithmIdentifierRef<'static>, -> = Lazy::new(|| rsa::pkcs8::AlgorithmIdentifierRef { - // id-mgf1 - oid: ID_MFG1, - // sha1 - parameters: Some( - asn1::AnyRef::from_der(&ENCODED_SHA1_HASH_ALGORITHM).unwrap(), - ), -}); - -pub const RSA_ENCRYPTION_OID: const_oid::ObjectIdentifier = - const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); -pub const DH_KEY_AGREEMENT_OID: const_oid::ObjectIdentifier = - const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.3.1"); -pub const RSASSA_PSS_OID: const_oid::ObjectIdentifier = - const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10"); -pub const EC_OID: const_oid::ObjectIdentifier = - const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); - -// The parameters field associated with OID id-RSASSA-PSS -// Defined in RFC 3447, section A.2.3 -// -// RSASSA-PSS-params ::= SEQUENCE { -// hashAlgorithm [0] HashAlgorithm DEFAULT sha1, -// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, -// saltLength [2] INTEGER DEFAULT 20, -// trailerField [3] TrailerField DEFAULT trailerFieldBC -// } -pub struct PssPrivateKeyParameters<'a> { - pub hash_algorithm: rsa::pkcs8::AlgorithmIdentifierRef<'a>, - #[allow(dead_code)] - pub mask_gen_algorithm: rsa::pkcs8::AlgorithmIdentifierRef<'a>, - pub salt_length: u32, -} - -// Context-specific tag number for hashAlgorithm. -const HASH_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber = - rsa::pkcs8::der::TagNumber::new(0); - -// Context-specific tag number for maskGenAlgorithm. -const MASK_GEN_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber = - rsa::pkcs8::der::TagNumber::new(1); - -// Context-specific tag number for saltLength. -const SALT_LENGTH_TAG: rsa::pkcs8::der::TagNumber = - rsa::pkcs8::der::TagNumber::new(2); - -impl<'a> TryFrom<rsa::pkcs8::der::asn1::AnyRef<'a>> - for PssPrivateKeyParameters<'a> -{ - type Error = rsa::pkcs8::der::Error; - - fn try_from( - any: rsa::pkcs8::der::asn1::AnyRef<'a>, - ) -> rsa::pkcs8::der::Result<PssPrivateKeyParameters> { - any.sequence(|decoder| { - let hash_algorithm = decoder - .context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>( - HASH_ALGORITHM_TAG, - pkcs8::der::TagMode::Explicit, - )? - .map(TryInto::try_into) - .transpose()? - .unwrap_or(*SHA1_HASH_ALGORITHM); - - let mask_gen_algorithm = decoder - .context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>( - MASK_GEN_ALGORITHM_TAG, - pkcs8::der::TagMode::Explicit, - )? - .map(TryInto::try_into) - .transpose()? - .unwrap_or(*MGF1_SHA1_MASK_ALGORITHM); - - let salt_length = decoder - .context_specific::<u32>( - SALT_LENGTH_TAG, - pkcs8::der::TagMode::Explicit, - )? - .map(TryInto::try_into) - .transpose()? - .unwrap_or(20); - - Ok(Self { - hash_algorithm, - mask_gen_algorithm, - salt_length, - }) - }) - } -} - -fn parse_private_key( - key: &[u8], - format: &str, - type_: &str, -) -> Result<pkcs8::SecretDocument, AnyError> { - match format { - "pem" => { - let pem = std::str::from_utf8(key).map_err(|err| { - type_error(format!( - "Invalid PEM private key: not valid utf8 starting at byte {}", - err.valid_up_to() - )) - })?; - let (_, doc) = pkcs8::SecretDocument::from_pem(pem)?; - Ok(doc) - } - "der" => { - match type_ { - "pkcs8" => pkcs8::SecretDocument::from_pkcs8_der(key) - .map_err(|_| type_error("Invalid PKCS8 private key")), - "pkcs1" => pkcs8::SecretDocument::from_pkcs1_der(key) - .map_err(|_| type_error("Invalid PKCS1 private key")), - // TODO(@littledivy): sec1 type - _ => Err(type_error(format!("Unsupported key type: {}", type_))), - } - } - _ => Err(type_error(format!("Unsupported key format: {}", format))), - } -} - -#[op2] -#[serde] -pub fn op_node_create_private_key( - #[buffer] key: &[u8], - #[string] format: &str, - #[string] type_: &str, -) -> Result<AsymmetricKeyDetails, AnyError> { - use rsa::pkcs1::der::Decode; - - let doc = parse_private_key(key, format, type_)?; - let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?; - - let alg = pk_info.algorithm.oid; - - match alg { - RSA_ENCRYPTION_OID => { - let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?; - let modulus_length = private_key.modulus.as_bytes().len() * 8; - - Ok(AsymmetricKeyDetails::Rsa { - modulus_length, - public_exponent: BigInt::from_bytes_be( - num_bigint::Sign::Plus, - private_key.public_exponent.as_bytes(), - ) - .into(), - }) - } - DH_KEY_AGREEMENT_OID => Ok(AsymmetricKeyDetails::Dh), - RSASSA_PSS_OID => { - let params = PssPrivateKeyParameters::try_from( - pk_info - .algorithm - .parameters - .ok_or_else(|| type_error("Malformed parameters".to_string()))?, - ) - .map_err(|_| type_error("Malformed parameters".to_string()))?; - - let hash_alg = params.hash_algorithm; - let hash_algorithm = match hash_alg.oid { - ID_SHA1_OID => "sha1", - ID_SHA256_OID => "sha256", - ID_SHA384_OID => "sha384", - ID_SHA512_OID => "sha512", - _ => return Err(type_error("Unsupported hash algorithm")), - }; - - let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?; - let modulus_length = private_key.modulus.as_bytes().len() * 8; - Ok(AsymmetricKeyDetails::RsaPss { - modulus_length, - public_exponent: BigInt::from_bytes_be( - num_bigint::Sign::Plus, - private_key.public_exponent.as_bytes(), - ) - .into(), - hash_algorithm: hash_algorithm.to_string(), - salt_length: params.salt_length, - }) - } - EC_OID => { - let named_curve = pk_info - .algorithm - .parameters_oid() - .map_err(|_| type_error("malformed parameters"))?; - let named_curve = match named_curve { - ID_SECP256R1_OID => "p256", - ID_SECP384R1_OID => "p384", - ID_SECP521R1_OID => "p521", - _ => return Err(type_error("Unsupported named curve")), - }; - - Ok(AsymmetricKeyDetails::Ec { - named_curve: named_curve.to_string(), - }) - } - _ => Err(type_error("Unsupported algorithm")), - } -} - -fn parse_public_key( - key: &[u8], - format: &str, - type_: &str, -) -> Result<pkcs8::Document, AnyError> { - match format { - "pem" => { - let pem = std::str::from_utf8(key).map_err(|err| { - type_error(format!( - "Invalid PEM private key: not valid utf8 starting at byte {}", - err.valid_up_to() - )) - })?; - let (label, doc) = pkcs8::Document::from_pem(pem)?; - if label != "PUBLIC KEY" { - return Err(type_error("Invalid PEM label")); - } - Ok(doc) - } - "der" => match type_ { - "pkcs1" => pkcs8::Document::from_pkcs1_der(key) - .map_err(|_| type_error("Invalid PKCS1 public key")), - _ => Err(type_error(format!("Unsupported key type: {}", type_))), - }, - _ => Err(type_error(format!("Unsupported key format: {}", format))), - } -} - -#[op2] -#[serde] -pub fn op_node_create_public_key( - #[buffer] key: &[u8], - #[string] format: &str, - #[string] type_: &str, -) -> Result<AsymmetricKeyDetails, AnyError> { - let mut doc = None; - - let pk_info = if type_ != "spki" { - doc.replace(parse_public_key(key, format, type_)?); - spki::SubjectPublicKeyInfoRef::try_from(doc.as_ref().unwrap().as_bytes())? - } else { - spki::SubjectPublicKeyInfoRef::try_from(key)? - }; - - let alg = pk_info.algorithm.oid; - - match alg { - RSA_ENCRYPTION_OID => { - let public_key = rsa::pkcs1::RsaPublicKey::from_der( - pk_info.subject_public_key.raw_bytes(), - )?; - let modulus_length = public_key.modulus.as_bytes().len() * 8; - - Ok(AsymmetricKeyDetails::Rsa { - modulus_length, - public_exponent: BigInt::from_bytes_be( - num_bigint::Sign::Plus, - public_key.public_exponent.as_bytes(), - ) - .into(), - }) - } - RSASSA_PSS_OID => { - let params = PssPrivateKeyParameters::try_from( - pk_info - .algorithm - .parameters - .ok_or_else(|| type_error("Malformed parameters".to_string()))?, - ) - .map_err(|_| type_error("Malformed parameters".to_string()))?; - - let hash_alg = params.hash_algorithm; - let hash_algorithm = match hash_alg.oid { - ID_SHA1_OID => "sha1", - ID_SHA256_OID => "sha256", - ID_SHA384_OID => "sha384", - ID_SHA512_OID => "sha512", - _ => return Err(type_error("Unsupported hash algorithm")), - }; - - let public_key = rsa::pkcs1::RsaPublicKey::from_der( - pk_info.subject_public_key.raw_bytes(), - )?; - let modulus_length = public_key.modulus.as_bytes().len() * 8; - Ok(AsymmetricKeyDetails::RsaPss { - modulus_length, - public_exponent: BigInt::from_bytes_be( - num_bigint::Sign::Plus, - public_key.public_exponent.as_bytes(), - ) - .into(), - hash_algorithm: hash_algorithm.to_string(), - salt_length: params.salt_length, - }) - } - EC_OID => { - let named_curve = pk_info - .algorithm - .parameters_oid() - .map_err(|_| type_error("malformed parameters"))?; - let named_curve = match named_curve { - ID_SECP256R1_OID => "p256", - ID_SECP384R1_OID => "p384", - ID_SECP521R1_OID => "p521", - _ => return Err(type_error("Unsupported named curve")), - }; - - Ok(AsymmetricKeyDetails::Ec { - named_curve: named_curve.to_string(), - }) - } - DH_KEY_AGREEMENT_OID => Ok(AsymmetricKeyDetails::Dh), - _ => Err(type_error("Unsupported algorithm")), - } -} diff --git a/ext/node/ops/crypto/sign.rs b/ext/node/ops/crypto/sign.rs new file mode 100644 index 000000000..9aea3aab7 --- /dev/null +++ b/ext/node/ops/crypto/sign.rs @@ -0,0 +1,396 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use deno_core::error::generic_error; +use deno_core::error::type_error; +use deno_core::error::AnyError; +use digest::Digest; +use digest::FixedOutput; +use digest::FixedOutputReset; +use digest::OutputSizeUser; +use digest::Reset; +use digest::Update; +use rand::rngs::OsRng; +use rsa::signature::hazmat::PrehashSigner as _; +use rsa::signature::hazmat::PrehashVerifier as _; +use rsa::traits::SignatureScheme as _; +use spki::der::Decode; + +use crate::ops::crypto::digest::match_fixed_digest; +use crate::ops::crypto::digest::match_fixed_digest_with_oid; + +use super::keys::AsymmetricPrivateKey; +use super::keys::AsymmetricPublicKey; +use super::keys::EcPrivateKey; +use super::keys::EcPublicKey; +use super::keys::KeyObjectHandle; +use super::keys::RsaPssHashAlgorithm; + +impl KeyObjectHandle { + pub fn sign_prehashed( + &self, + digest_type: &str, + digest: &[u8], + ) -> Result<Box<[u8]>, AnyError> { + let private_key = self + .as_private_key() + .ok_or_else(|| type_error("key is not a private key"))?; + + match private_key { + AsymmetricPrivateKey::Rsa(key) => { + let signer = if digest_type == "md5-sha1" { + rsa::pkcs1v15::Pkcs1v15Sign::new_unprefixed() + } else { + match_fixed_digest_with_oid!( + digest_type, + fn <D>() { + rsa::pkcs1v15::Pkcs1v15Sign::new::<D>() + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA signature: {}", + digest_type + ))) + } + ) + }; + + let signature = signer + .sign(Some(&mut OsRng), key, digest) + .map_err(|_| generic_error("failed to sign digest with RSA"))?; + Ok(signature.into()) + } + AsymmetricPrivateKey::RsaPss(key) => { + let mut hash_algorithm = None; + let mut salt_length = None; + match &key.details { + Some(details) => { + if details.hash_algorithm != details.mf1_hash_algorithm { + return Err(type_error( + "rsa-pss with different mf1 hash algorithm and hash algorithm is not supported", + )); + } + hash_algorithm = Some(details.hash_algorithm); + salt_length = Some(details.salt_length as usize); + } + None => {} + }; + let pss = match_fixed_digest_with_oid!( + digest_type, + fn <D>(algorithm: Option<RsaPssHashAlgorithm>) { + if let Some(hash_algorithm) = hash_algorithm.take() { + if Some(hash_algorithm) != algorithm { + return Err(type_error(format!( + "private key does not allow {} to be used, expected {}", + digest_type, hash_algorithm.as_str() + ))); + } + } + if let Some(salt_length) = salt_length { + rsa::pss::Pss::new_with_salt::<D>(salt_length) + } else { + rsa::pss::Pss::new::<D>() + } + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA-PSS signature: {}", + digest_type + ))) + } + ); + let signature = pss + .sign(Some(&mut OsRng), &key.key, digest) + .map_err(|_| generic_error("failed to sign digest with RSA-PSS"))?; + Ok(signature.into()) + } + AsymmetricPrivateKey::Dsa(key) => { + let res = match_fixed_digest!( + digest_type, + fn <D>() { + key.sign_prehashed_rfc6979::<D>(digest) + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA signature: {}", + digest_type + ))) + } + ); + + let signature = + res.map_err(|_| generic_error("failed to sign digest with DSA"))?; + Ok(signature.into()) + } + AsymmetricPrivateKey::Ec(key) => match key { + EcPrivateKey::P224(key) => { + let signing_key = p224::ecdsa::SigningKey::from(key); + let signature: p224::ecdsa::Signature = signing_key + .sign_prehash(digest) + .map_err(|_| type_error("failed to sign digest"))?; + Ok(signature.to_der().to_bytes()) + } + EcPrivateKey::P256(key) => { + let signing_key = p256::ecdsa::SigningKey::from(key); + let signature: p256::ecdsa::Signature = signing_key + .sign_prehash(digest) + .map_err(|_| type_error("failed to sign digest"))?; + Ok(signature.to_der().to_bytes()) + } + EcPrivateKey::P384(key) => { + let signing_key = p384::ecdsa::SigningKey::from(key); + let signature: p384::ecdsa::Signature = signing_key + .sign_prehash(digest) + .map_err(|_| type_error("failed to sign digest"))?; + Ok(signature.to_der().to_bytes()) + } + }, + AsymmetricPrivateKey::X25519(_) => { + Err(type_error("x25519 key cannot be used for signing")) + } + AsymmetricPrivateKey::Ed25519(key) => { + if !matches!( + digest_type, + "rsa-sha512" | "sha512" | "sha512withrsaencryption" + ) { + return Err(type_error(format!( + "digest not allowed for Ed25519 signature: {}", + digest_type + ))); + } + + let mut precomputed_digest = PrecomputedDigest([0; 64]); + if digest.len() != precomputed_digest.0.len() { + return Err(type_error("Invalid sha512 digest")); + } + precomputed_digest.0.copy_from_slice(digest); + + let signature = key + .sign_prehashed(precomputed_digest, None) + .map_err(|_| generic_error("failed to sign digest with Ed25519"))?; + + Ok(signature.to_bytes().into()) + } + AsymmetricPrivateKey::Dh(_) => { + Err(type_error("DH key cannot be used for signing")) + } + } + } + + pub fn verify_prehashed( + &self, + digest_type: &str, + digest: &[u8], + signature: &[u8], + ) -> Result<bool, AnyError> { + let public_key = self + .as_public_key() + .ok_or_else(|| type_error("key is not a public or private key"))?; + + match &*public_key { + AsymmetricPublicKey::Rsa(key) => { + let signer = if digest_type == "md5-sha1" { + rsa::pkcs1v15::Pkcs1v15Sign::new_unprefixed() + } else { + match_fixed_digest_with_oid!( + digest_type, + fn <D>() { + rsa::pkcs1v15::Pkcs1v15Sign::new::<D>() + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA signature: {}", + digest_type + ))) + } + ) + }; + + Ok(signer.verify(key, digest, signature).is_ok()) + } + AsymmetricPublicKey::RsaPss(key) => { + let mut hash_algorithm = None; + let mut salt_length = None; + match &key.details { + Some(details) => { + if details.hash_algorithm != details.mf1_hash_algorithm { + return Err(type_error( + "rsa-pss with different mf1 hash algorithm and hash algorithm is not supported", + )); + } + hash_algorithm = Some(details.hash_algorithm); + salt_length = Some(details.salt_length as usize); + } + None => {} + }; + let pss = match_fixed_digest_with_oid!( + digest_type, + fn <D>(algorithm: Option<RsaPssHashAlgorithm>) { + if let Some(hash_algorithm) = hash_algorithm.take() { + if Some(hash_algorithm) != algorithm { + return Err(type_error(format!( + "private key does not allow {} to be used, expected {}", + digest_type, hash_algorithm.as_str() + ))); + } + } + if let Some(salt_length) = salt_length { + rsa::pss::Pss::new_with_salt::<D>(salt_length) + } else { + rsa::pss::Pss::new::<D>() + } + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA-PSS signature: {}", + digest_type + ))) + } + ); + Ok(pss.verify(&key.key, digest, signature).is_ok()) + } + AsymmetricPublicKey::Dsa(key) => { + let signature = dsa::Signature::from_der(signature) + .map_err(|_| type_error("Invalid DSA signature"))?; + Ok(key.verify_prehash(digest, &signature).is_ok()) + } + AsymmetricPublicKey::Ec(key) => match key { + EcPublicKey::P224(key) => { + let verifying_key = p224::ecdsa::VerifyingKey::from(key); + let signature = p224::ecdsa::Signature::from_der(signature) + .map_err(|_| type_error("Invalid ECDSA signature"))?; + Ok(verifying_key.verify_prehash(digest, &signature).is_ok()) + } + EcPublicKey::P256(key) => { + let verifying_key = p256::ecdsa::VerifyingKey::from(key); + let signature = p256::ecdsa::Signature::from_der(signature) + .map_err(|_| type_error("Invalid ECDSA signature"))?; + Ok(verifying_key.verify_prehash(digest, &signature).is_ok()) + } + EcPublicKey::P384(key) => { + let verifying_key = p384::ecdsa::VerifyingKey::from(key); + let signature = p384::ecdsa::Signature::from_der(signature) + .map_err(|_| type_error("Invalid ECDSA signature"))?; + Ok(verifying_key.verify_prehash(digest, &signature).is_ok()) + } + }, + AsymmetricPublicKey::X25519(_) => { + Err(type_error("x25519 key cannot be used for verification")) + } + AsymmetricPublicKey::Ed25519(key) => { + if !matches!( + digest_type, + "rsa-sha512" | "sha512" | "sha512withrsaencryption" + ) { + return Err(type_error(format!( + "digest not allowed for Ed25519 signature: {}", + digest_type + ))); + } + + let mut signature_fixed = [0u8; 64]; + if signature.len() != signature_fixed.len() { + return Err(type_error("Invalid Ed25519 signature")); + } + signature_fixed.copy_from_slice(signature); + + let signature = ed25519_dalek::Signature::from_bytes(&signature_fixed); + + let mut precomputed_digest = PrecomputedDigest([0; 64]); + precomputed_digest.0.copy_from_slice(digest); + + Ok( + key + .verify_prehashed_strict(precomputed_digest, None, &signature) + .is_ok(), + ) + } + AsymmetricPublicKey::Dh(_) => { + Err(type_error("DH key cannot be used for verification")) + } + } + } +} + +struct PrecomputedDigest([u8; 64]); + +impl OutputSizeUser for PrecomputedDigest { + type OutputSize = <sha2::Sha512 as OutputSizeUser>::OutputSize; +} + +impl Digest for PrecomputedDigest { + fn new() -> Self { + unreachable!() + } + + fn new_with_prefix(_data: impl AsRef<[u8]>) -> Self { + unreachable!() + } + + fn update(&mut self, _data: impl AsRef<[u8]>) { + unreachable!() + } + + fn chain_update(self, _data: impl AsRef<[u8]>) -> Self { + unreachable!() + } + + fn finalize(self) -> digest::Output<Self> { + self.0.into() + } + + fn finalize_into(self, _out: &mut digest::Output<Self>) { + unreachable!() + } + + fn finalize_reset(&mut self) -> digest::Output<Self> + where + Self: digest::FixedOutputReset, + { + unreachable!() + } + + fn finalize_into_reset(&mut self, _out: &mut digest::Output<Self>) + where + Self: digest::FixedOutputReset, + { + unreachable!() + } + + fn reset(&mut self) + where + Self: digest::Reset, + { + unreachable!() + } + + fn output_size() -> usize { + unreachable!() + } + + fn digest(_data: impl AsRef<[u8]>) -> digest::Output<Self> { + unreachable!() + } +} + +impl Reset for PrecomputedDigest { + fn reset(&mut self) { + unreachable!() + } +} + +impl FixedOutputReset for PrecomputedDigest { + fn finalize_into_reset(&mut self, _out: &mut digest::Output<Self>) { + unreachable!() + } +} + +impl FixedOutput for PrecomputedDigest { + fn finalize_into(self, _out: &mut digest::Output<Self>) { + unreachable!() + } +} + +impl Update for PrecomputedDigest { + fn update(&mut self, _data: &[u8]) { + unreachable!() + } +} diff --git a/ext/node/ops/vm_internal.rs b/ext/node/ops/vm_internal.rs index f61308228..815f570ea 100644 --- a/ext/node/ops/vm_internal.rs +++ b/ext/node/ops/vm_internal.rs @@ -93,7 +93,6 @@ impl ContextifyContext { sandbox_obj: v8::Local<v8::Object>, ) { let tmp = init_global_template(scope, ContextInitMode::UseSnapshot); - let context = create_v8_context(scope, tmp, ContextInitMode::UseSnapshot); Self::from_context(scope, context, sandbox_obj); } |