diff options
49 files changed, 3360 insertions, 1161 deletions
diff --git a/Cargo.lock b/Cargo.lock index 3b2172060..e3f51056c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,6 +1010,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", + "digest", "fiat-crypto", "rustc_version 0.4.0", "subtle", @@ -1748,6 +1749,7 @@ dependencies = [ "aead-gcm-stream", "aes", "async-trait", + "base64 0.21.7", "blake2", "brotli", "bytes", @@ -1766,6 +1768,7 @@ dependencies = [ "digest", "dsa", "ecb", + "ed25519-dalek", "elliptic-curve", "errno 0.2.8", "faster-hex", @@ -1798,6 +1801,7 @@ dependencies = [ "path-clean", "pbkdf2", "pin-project-lite", + "pkcs8", "rand", "regex", "ring", @@ -1820,6 +1824,7 @@ dependencies = [ "windows-sys 0.52.0", "x25519-dalek", "x509-parser", + "yoke", ] [[package]] @@ -2539,6 +2544,32 @@ dependencies = [ ] [[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "signature", + "subtle", + "zeroize", +] + +[[package]] name = "editpe" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -5013,12 +5044,29 @@ dependencies = [ ] [[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", + "pkcs5", + "rand_core", "spki", ] @@ -8376,6 +8424,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "zerofrom", +] + +[[package]] name = "zerocopy" version = "0.7.32" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -8397,6 +8456,12 @@ dependencies = [ ] [[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" + +[[package]] name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index 5f00455a7..70d8f70cf 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -20,6 +20,7 @@ sync_fs = ["deno_package_json/sync", "node_resolver/sync"] aead-gcm-stream = "0.1" aes.workspace = true async-trait.workspace = true +base64.workspace = true blake2 = "0.10.6" brotli.workspace = true bytes.workspace = true @@ -38,6 +39,7 @@ deno_whoami = "0.1.0" digest = { version = "0.10.5", features = ["core-api", "std"] } dsa = "0.6.1" ecb.workspace = true +ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core", "signature"] } elliptic-curve.workspace = true errno = "0.2.8" faster-hex.workspace = true @@ -70,6 +72,7 @@ p384.workspace = true path-clean = "=0.1.0" pbkdf2 = "0.12.1" pin-project-lite = "0.2.13" +pkcs8 = { version = "0.10.2", features = ["std", "pkcs5", "encryption"] } rand.workspace = true regex.workspace = true ring.workspace = true @@ -89,8 +92,9 @@ thiserror.workspace = true tokio.workspace = true url.workspace = true winapi.workspace = true -x25519-dalek = "2.0.0" +x25519-dalek = { version = "2.0.0", features = ["static_secrets"] } x509-parser = "0.15.0" +yoke = "0.7.4" [target.'cfg(windows)'.dependencies] windows-sys.workspace = true diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 00070fae9..3de6ddce6 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -247,25 +247,10 @@ deno_core::extension!(deno_node, ops::crypto::op_node_pbkdf2_async, ops::crypto::op_node_hkdf, ops::crypto::op_node_hkdf_async, - ops::crypto::op_node_generate_secret, - ops::crypto::op_node_generate_secret_async, + ops::crypto::op_node_fill_random, + ops::crypto::op_node_fill_random_async, ops::crypto::op_node_sign, - ops::crypto::op_node_generate_rsa, - ops::crypto::op_node_generate_rsa_async, - ops::crypto::op_node_dsa_generate, - ops::crypto::op_node_dsa_generate_async, - ops::crypto::op_node_ec_generate, - ops::crypto::op_node_ec_generate_async, - ops::crypto::op_node_ed25519_generate, - ops::crypto::op_node_ed25519_generate_async, - ops::crypto::op_node_x25519_generate, - ops::crypto::op_node_x25519_generate_async, - ops::crypto::op_node_dh_generate_group, - ops::crypto::op_node_dh_generate_group_async, - ops::crypto::op_node_dh_generate, - ops::crypto::op_node_dh_generate2, ops::crypto::op_node_dh_compute_secret, - ops::crypto::op_node_dh_generate_async, ops::crypto::op_node_verify, ops::crypto::op_node_random_int, ops::crypto::op_node_scrypt_sync, @@ -274,8 +259,41 @@ deno_core::extension!(deno_node, ops::crypto::op_node_ecdh_compute_secret, ops::crypto::op_node_ecdh_compute_public_key, ops::crypto::op_node_ecdh_encode_pubkey, - ops::crypto::op_node_export_rsa_public_pem, - ops::crypto::op_node_export_rsa_spki_der, + ops::crypto::keys::op_node_create_private_key, + ops::crypto::keys::op_node_create_public_key, + ops::crypto::keys::op_node_create_secret_key, + ops::crypto::keys::op_node_derive_public_key_from_private_key, + ops::crypto::keys::op_node_dh_keys_generate_and_export, + ops::crypto::keys::op_node_export_private_key_der, + ops::crypto::keys::op_node_export_private_key_pem, + ops::crypto::keys::op_node_export_public_key_der, + ops::crypto::keys::op_node_export_public_key_pem, + ops::crypto::keys::op_node_export_secret_key_b64url, + ops::crypto::keys::op_node_export_secret_key, + ops::crypto::keys::op_node_generate_dh_group_key_async, + ops::crypto::keys::op_node_generate_dh_group_key, + ops::crypto::keys::op_node_generate_dh_key_async, + ops::crypto::keys::op_node_generate_dh_key, + ops::crypto::keys::op_node_generate_dsa_key_async, + ops::crypto::keys::op_node_generate_dsa_key, + ops::crypto::keys::op_node_generate_ec_key_async, + ops::crypto::keys::op_node_generate_ec_key, + ops::crypto::keys::op_node_generate_ed25519_key_async, + ops::crypto::keys::op_node_generate_ed25519_key, + ops::crypto::keys::op_node_generate_rsa_key_async, + ops::crypto::keys::op_node_generate_rsa_key, + ops::crypto::keys::op_node_generate_rsa_pss_key, + ops::crypto::keys::op_node_generate_rsa_pss_key_async, + ops::crypto::keys::op_node_generate_secret_key_async, + ops::crypto::keys::op_node_generate_secret_key, + ops::crypto::keys::op_node_generate_x25519_key_async, + ops::crypto::keys::op_node_generate_x25519_key, + ops::crypto::keys::op_node_get_asymmetric_key_details, + ops::crypto::keys::op_node_get_asymmetric_key_type, + ops::crypto::keys::op_node_get_private_key_from_pair, + ops::crypto::keys::op_node_get_public_key_from_pair, + ops::crypto::keys::op_node_get_symmetric_key_size, + ops::crypto::keys::op_node_key_type, ops::crypto::x509::op_node_x509_parse, ops::crypto::x509::op_node_x509_ca, ops::crypto::x509::op_node_x509_check_email, @@ -378,8 +396,6 @@ deno_core::extension!(deno_node, ops::require::op_require_break_on_next_statement, ops::util::op_node_guess_handle_type, ops::worker_threads::op_worker_threads_filename<P>, - ops::crypto::op_node_create_private_key, - ops::crypto::op_node_create_public_key, ops::ipc::op_node_child_ipc_pipe, ops::ipc::op_node_ipc_write, ops::ipc::op_node_ipc_read, 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); } diff --git a/ext/node/polyfills/_global.d.ts b/ext/node/polyfills/_global.d.ts index e9d645f9c..8587acbbb 100644 --- a/ext/node/polyfills/_global.d.ts +++ b/ext/node/polyfills/_global.d.ts @@ -2,20 +2,18 @@ import { EventEmitter } from "ext:deno_node/_events.d.ts"; import { Buffer } from "node:buffer"; -/** One of: - * | "ascii" - * | "utf8" - * | "utf-8" - * | "utf16le" - * | "ucs2" - * | "ucs-2" - * | "base64" - * | "base64url" - * | "latin1" - * | "binary" - * | "hex"; - */ -export type BufferEncoding = string; +export type BufferEncoding = + | "ascii" + | "utf8" + | "utf-8" + | "utf16le" + | "ucs2" + | "ucs-2" + | "base64" + | "base64url" + | "latin1" + | "binary" + | "hex"; export interface Buffered { chunk: Buffer; diff --git a/ext/node/polyfills/internal/crypto/_keys.ts b/ext/node/polyfills/internal/crypto/_keys.ts index 9da91f022..e79986245 100644 --- a/ext/node/polyfills/internal/crypto/_keys.ts +++ b/ext/node/polyfills/internal/crypto/_keys.ts @@ -1,19 +1,25 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// This file is here because to break a circular dependency between streams and +// crypto. + // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials import { kKeyObject } from "ext:deno_node/internal/crypto/constants.ts"; +import type { KeyObject } from "ext:deno_node/internal/crypto/keys.ts"; export const kKeyType = Symbol("kKeyType"); -export function isKeyObject(obj: unknown): boolean { +export function isKeyObject(obj: unknown): obj is KeyObject { return ( obj != null && (obj as Record<symbol, unknown>)[kKeyType] !== undefined ); } -export function isCryptoKey(obj: unknown): boolean { +export function isCryptoKey( + obj: unknown, +): obj is CryptoKey { return ( obj != null && (obj as Record<symbol, unknown>)[kKeyObject] !== undefined ); diff --git a/ext/node/polyfills/internal/crypto/_randomFill.mjs b/ext/node/polyfills/internal/crypto/_randomFill.mjs index e53918b39..808ab4565 100644 --- a/ext/node/polyfills/internal/crypto/_randomFill.mjs +++ b/ext/node/polyfills/internal/crypto/_randomFill.mjs @@ -3,14 +3,9 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials -import { - op_node_generate_secret, - op_node_generate_secret_async, -} from "ext:core/ops"; - -import { - MAX_SIZE as kMaxUint32, -} from "ext:deno_node/internal/crypto/_randomBytes.ts"; +import { op_node_fill_random, op_node_fill_random_async } from "ext:core/ops"; + +import { MAX_SIZE as kMaxUint32 } from "ext:deno_node/internal/crypto/_randomBytes.ts"; import { Buffer } from "node:buffer"; import { isAnyArrayBuffer, isArrayBufferView } from "node:util/types"; import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; @@ -37,12 +32,7 @@ function assertSize(size, offset, length) { } } -export default function randomFill( - buf, - offset, - size, - cb, -) { +export default function randomFill(buf, offset, size, cb) { if (typeof offset === "function") { cb = offset; offset = 0; @@ -55,14 +45,11 @@ export default function randomFill( assertOffset(offset, buf.length); assertSize(size, offset, buf.length); - op_node_generate_secret_async(Math.floor(size)) - .then( - (randomData) => { - const randomBuf = Buffer.from(randomData.buffer); - randomBuf.copy(buf, offset, 0, size); - cb(null, buf); - }, - ); + op_node_fill_random_async(Math.floor(size)).then((randomData) => { + const randomBuf = Buffer.from(randomData.buffer); + randomBuf.copy(buf, offset, 0, size); + cb(null, buf); + }); } export function randomFillSync(buf, offset = 0, size) { @@ -89,7 +76,7 @@ export function randomFillSync(buf, offset = 0, size) { const bytes = isAnyArrayBuffer(buf) ? new Uint8Array(buf, offset, size) : new Uint8Array(buf.buffer, buf.byteOffset + offset, size); - op_node_generate_secret(bytes); + op_node_fill_random(bytes); return buf; } diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts index f8a46896d..d83d4fa8f 100644 --- a/ext/node/polyfills/internal/crypto/cipher.ts +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -41,7 +41,9 @@ import { isArrayBufferView, } from "ext:deno_node/internal/util/types.ts"; -export function isStringOrBuffer(val) { +export function isStringOrBuffer( + val: unknown, +): val is string | Buffer | ArrayBuffer | ArrayBufferView { return typeof val === "string" || isArrayBufferView(val) || isAnyArrayBuffer(val) || diff --git a/ext/node/polyfills/internal/crypto/diffiehellman.ts b/ext/node/polyfills/internal/crypto/diffiehellman.ts index 6058433ba..16a1f2498 100644 --- a/ext/node/polyfills/internal/crypto/diffiehellman.ts +++ b/ext/node/polyfills/internal/crypto/diffiehellman.ts @@ -6,7 +6,7 @@ import { op_node_dh_compute_secret, - op_node_dh_generate2, + op_node_dh_keys_generate_and_export, op_node_ecdh_compute_public_key, op_node_ecdh_compute_secret, op_node_ecdh_encode_pubkey, @@ -198,7 +198,7 @@ export class DiffieHellman { generateKeys(encoding: BinaryToTextEncoding): string; generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string { const generator = this.#checkGenerator(); - const [privateKey, publicKey] = op_node_dh_generate2( + const [privateKey, publicKey] = op_node_dh_keys_generate_and_export( this.#prime, this.#primeLength ?? 0, generator, diff --git a/ext/node/polyfills/internal/crypto/hash.ts b/ext/node/polyfills/internal/crypto/hash.ts index 2e040be25..c42ca3989 100644 --- a/ext/node/polyfills/internal/crypto/hash.ts +++ b/ext/node/polyfills/internal/crypto/hash.ts @@ -6,6 +6,7 @@ import { op_node_create_hash, + op_node_export_secret_key, op_node_get_hashes, op_node_hash_clone, op_node_hash_digest, @@ -32,7 +33,6 @@ import type { Encoding, } from "ext:deno_node/internal/crypto/types.ts"; import { - getKeyMaterial, KeyObject, prepareSecretKey, } from "ext:deno_node/internal/crypto/keys.ts"; @@ -46,7 +46,10 @@ import { getDefaultEncoding, toBuf, } from "ext:deno_node/internal/crypto/util.ts"; -import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts"; +import { + isAnyArrayBuffer, + isArrayBufferView, +} from "ext:deno_node/internal/util/types.ts"; const { ReflectApply, ObjectSetPrototypeOf } = primordials; @@ -217,22 +220,28 @@ class HmacImpl extends Transform { validateString(hmac, "hmac"); - const u8Key = key instanceof KeyObject - ? getKeyMaterial(key) - : prepareSecretKey(key, options?.encoding) as Buffer; + key = prepareSecretKey(key, options?.encoding); + let keyData; + if (isArrayBufferView(key)) { + keyData = key; + } else if (isAnyArrayBuffer(key)) { + keyData = new Uint8Array(key); + } else { + keyData = op_node_export_secret_key(key); + } const alg = hmac.toLowerCase(); this.#algorithm = alg; const blockSize = (alg === "sha512" || alg === "sha384") ? 128 : 64; - const keySize = u8Key.length; + const keySize = keyData.length; let bufKey: Buffer; if (keySize > blockSize) { const hash = new Hash(alg, options); - bufKey = hash.update(u8Key).digest() as Buffer; + bufKey = hash.update(keyData).digest() as Buffer; } else { - bufKey = Buffer.concat([u8Key, this.#ZEROES], blockSize); + bufKey = Buffer.concat([keyData, this.#ZEROES], blockSize); } this.#ipad = Buffer.allocUnsafe(blockSize); diff --git a/ext/node/polyfills/internal/crypto/hkdf.ts b/ext/node/polyfills/internal/crypto/hkdf.ts index cca40a3c6..cb1dbee46 100644 --- a/ext/node/polyfills/internal/crypto/hkdf.ts +++ b/ext/node/polyfills/internal/crypto/hkdf.ts @@ -18,13 +18,12 @@ import { hideStackFrames, } from "ext:deno_node/internal/errors.ts"; import { + kHandle, toBuf, validateByteSource, } from "ext:deno_node/internal/crypto/util.ts"; import { createSecretKey, - getKeyMaterial, - isKeyObject, KeyObject, } from "ext:deno_node/internal/crypto/keys.ts"; import type { BinaryLike } from "ext:deno_node/internal/crypto/types.ts"; @@ -33,10 +32,11 @@ import { isAnyArrayBuffer, isArrayBufferView, } from "ext:deno_node/internal/util/types.ts"; +import { isKeyObject } from "ext:deno_node/internal/crypto/_keys.ts"; const validateParameters = hideStackFrames((hash, key, salt, info, length) => { validateString(hash, "digest"); - key = getKeyMaterial(prepareKey(key)); + key = prepareKey(key); validateByteSource(salt, "salt"); validateByteSource(info, "info"); @@ -111,7 +111,7 @@ export function hkdf( hash = hash.toLowerCase(); - op_node_hkdf_async(hash, key, salt, info, length) + op_node_hkdf_async(hash, key[kHandle], salt, info, length) .then((okm) => callback(null, okm.buffer)) .catch((err) => callback(new ERR_CRYPTO_INVALID_DIGEST(err), undefined)); } @@ -135,7 +135,7 @@ export function hkdfSync( const okm = new Uint8Array(length); try { - op_node_hkdf(hash, key, salt, info, okm); + op_node_hkdf(hash, key[kHandle], salt, info, okm); } catch (e) { throw new ERR_CRYPTO_INVALID_DIGEST(e); } diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index dd5d5ad7e..4e2543cd9 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -10,7 +10,6 @@ import { PrivateKeyObject, PublicKeyObject, SecretKeyObject, - setOwnedKey, } from "ext:deno_node/internal/crypto/keys.ts"; import { notImplemented } from "ext:deno_node/_utils.ts"; import { @@ -32,22 +31,26 @@ import { Buffer } from "node:buffer"; import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; import { - op_node_dh_generate, - op_node_dh_generate_async, - op_node_dh_generate_group, - op_node_dh_generate_group_async, - op_node_dsa_generate, - op_node_dsa_generate_async, - op_node_ec_generate, - op_node_ec_generate_async, - op_node_ed25519_generate, - op_node_ed25519_generate_async, - op_node_generate_rsa, - op_node_generate_rsa_async, - op_node_generate_secret, - op_node_generate_secret_async, - op_node_x25519_generate, - op_node_x25519_generate_async, + op_node_generate_dh_group_key, + op_node_generate_dh_group_key_async, + op_node_generate_dh_key, + op_node_generate_dh_key_async, + op_node_generate_dsa_key, + op_node_generate_dsa_key_async, + op_node_generate_ec_key, + op_node_generate_ec_key_async, + op_node_generate_ed25519_key, + op_node_generate_ed25519_key_async, + op_node_generate_rsa_key, + op_node_generate_rsa_key_async, + op_node_generate_rsa_pss_key, + op_node_generate_rsa_pss_key_async, + op_node_generate_secret_key, + op_node_generate_secret_key_async, + op_node_generate_x25519_key, + op_node_generate_x25519_key_async, + op_node_get_private_key_from_pair, + op_node_get_public_key_from_pair, } from "ext:core/ops"; function validateGenerateKey( @@ -82,10 +85,11 @@ export function generateKeySync( validateGenerateKey(type, options); const { length } = options; - const key = new Uint8Array(Math.floor(length / 8)); - op_node_generate_secret(key); + const len = Math.floor(length / 8); - return new SecretKeyObject(setOwnedKey(key)); + const handle = op_node_generate_secret_key(len); + + return new SecretKeyObject(handle); } export function generateKey( @@ -99,11 +103,11 @@ export function generateKey( validateFunction(callback, "callback"); const { length } = options; - op_node_generate_secret_async(Math.floor(length / 8)).then( - (key) => { - callback(null, new SecretKeyObject(setOwnedKey(key))); - }, - ); + const len = Math.floor(length / 8); + + op_node_generate_secret_key_async(len).then((handle) => { + callback(null, new SecretKeyObject(handle)); + }); } export interface BasePrivateKeyEncodingOptions<T extends KeyFormat> { @@ -565,9 +569,12 @@ export function generateKeyPair( privateKey: any, ) => void, ) { - createJob(kAsync, type, options).then(([privateKey, publicKey]) => { - privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type }); - publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type }); + createJob(kAsync, type, options).then((pair) => { + const privateKeyHandle = op_node_get_private_key_from_pair(pair); + const publicKeyHandle = op_node_get_public_key_from_pair(pair); + + const privateKey = new PrivateKeyObject(privateKeyHandle); + const publicKey = new PublicKeyObject(publicKeyHandle); if (typeof options === "object" && options !== null) { const { publicKeyEncoding, privateKeyEncoding } = options as any; @@ -766,10 +773,13 @@ export function generateKeyPairSync( ): | KeyPairKeyObjectResult | KeyPairSyncResult<string | Buffer, string | Buffer> { - let [privateKey, publicKey] = createJob(kSync, type, options); + const pair = createJob(kSync, type, options); + + const privateKeyHandle = op_node_get_private_key_from_pair(pair); + const publicKeyHandle = op_node_get_public_key_from_pair(pair); - privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type }); - publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type }); + let privateKey = new PrivateKeyObject(privateKeyHandle); + let publicKey = new PublicKeyObject(publicKeyHandle); if (typeof options === "object" && options !== null) { const { publicKeyEncoding, privateKeyEncoding } = options as any; @@ -812,12 +822,12 @@ function createJob(mode, type, options) { if (type === "rsa") { if (mode === kSync) { - return op_node_generate_rsa( + return op_node_generate_rsa_key( modulusLength, publicExponent, ); } else { - return op_node_generate_rsa_async( + return op_node_generate_rsa_key_async( modulusLength, publicExponent, ); @@ -867,14 +877,20 @@ function createJob(mode, type, options) { } if (mode === kSync) { - return op_node_generate_rsa( + return op_node_generate_rsa_pss_key( modulusLength, publicExponent, + hashAlgorithm, + mgf1HashAlgorithm ?? mgf1Hash, + saltLength, ); } else { - return op_node_generate_rsa_async( + return op_node_generate_rsa_pss_key_async( modulusLength, publicExponent, + hashAlgorithm, + mgf1HashAlgorithm ?? mgf1Hash, + saltLength, ); } } @@ -891,12 +907,13 @@ function createJob(mode, type, options) { } if (mode === kSync) { - return op_node_dsa_generate(modulusLength, divisorLength); + return op_node_generate_dsa_key(modulusLength, divisorLength); + } else { + return op_node_generate_dsa_key_async( + modulusLength, + divisorLength, + ); } - return op_node_dsa_generate_async( - modulusLength, - divisorLength, - ); } case "ec": { validateObject(options, "options"); @@ -913,22 +930,22 @@ function createJob(mode, type, options) { } if (mode === kSync) { - return op_node_ec_generate(namedCurve); + return op_node_generate_ec_key(namedCurve); } else { - return op_node_ec_generate_async(namedCurve); + return op_node_generate_ec_key_async(namedCurve); } } case "ed25519": { if (mode === kSync) { - return op_node_ed25519_generate(); + return op_node_generate_ed25519_key(); } - return op_node_ed25519_generate_async(); + return op_node_generate_ed25519_key_async(); } case "x25519": { if (mode === kSync) { - return op_node_x25519_generate(); + return op_node_generate_x25519_key(); } - return op_node_x25519_generate_async(); + return op_node_generate_x25519_key_async(); } case "ed448": case "x448": { @@ -952,9 +969,9 @@ function createJob(mode, type, options) { validateString(group, "options.group"); if (mode === kSync) { - return op_node_dh_generate_group(group); + return op_node_generate_dh_group_key(group); } else { - return op_node_dh_generate_group_async(group); + return op_node_generate_dh_group_key_async(group); } } @@ -979,9 +996,9 @@ function createJob(mode, type, options) { const g = generator == null ? 2 : generator; if (mode === kSync) { - return op_node_dh_generate(prime, primeLength ?? 0, g); + return op_node_generate_dh_key(prime, primeLength ?? 0, g); } else { - return op_node_dh_generate_async( + return op_node_generate_dh_key_async( prime, primeLength ?? 0, g, diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts index 26cd86b44..31d674e67 100644 --- a/ext/node/polyfills/internal/crypto/keys.ts +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -14,16 +14,24 @@ const { import { op_node_create_private_key, op_node_create_public_key, - op_node_export_rsa_public_pem, - op_node_export_rsa_spki_der, + op_node_create_secret_key, + op_node_derive_public_key_from_private_key, + op_node_export_private_key_der, + op_node_export_private_key_pem, + op_node_export_public_key_der, + op_node_export_public_key_pem, + op_node_export_secret_key, + op_node_export_secret_key_b64url, + op_node_get_asymmetric_key_details, + op_node_get_asymmetric_key_type, + op_node_get_symmetric_key_size, + op_node_key_type, } from "ext:core/ops"; -import { - kHandle, - kKeyObject, -} from "ext:deno_node/internal/crypto/constants.ts"; +import { kHandle } from "ext:deno_node/internal/crypto/constants.ts"; import { isStringOrBuffer } from "ext:deno_node/internal/crypto/cipher.ts"; import { + ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -41,23 +49,21 @@ import { } from "ext:deno_node/internal/util/types.ts"; import { hideStackFrames } from "ext:deno_node/internal/errors.ts"; import { - isCryptoKey as isCryptoKey_, - isKeyObject as isKeyObject_, + isCryptoKey, + isKeyObject, kKeyType, } from "ext:deno_node/internal/crypto/_keys.ts"; import { validateObject, validateOneOf, } from "ext:deno_node/internal/validators.mjs"; -import { - forgivingBase64UrlEncode as encodeToBase64Url, -} from "ext:deno_web/00_infra.js"; +import { BufferEncoding } from "ext:deno_node/_global.d.ts"; export const getArrayBufferOrView = hideStackFrames( ( - buffer, - name, - encoding, + buffer: ArrayBufferView | ArrayBuffer | string | Buffer, + name: string, + encoding?: BufferEncoding | "buffer", ): | ArrayBuffer | SharedArrayBuffer @@ -144,32 +150,30 @@ export interface JwkKeyExportOptions { format: "jwk"; } -export function isKeyObject(obj: unknown): obj is KeyObject { - return isKeyObject_(obj); +export enum KeyHandleContext { + kConsumePublic = 0, + kConsumePrivate = 1, + kCreatePublic = 2, + kCreatePrivate = 3, } -export function isCryptoKey( - obj: unknown, -): obj is { type: string; [kKeyObject]: KeyObject } { - return isCryptoKey_(obj); -} +export const kConsumePublic = KeyHandleContext.kConsumePublic; +export const kConsumePrivate = KeyHandleContext.kConsumePrivate; +export const kCreatePublic = KeyHandleContext.kCreatePublic; +export const kCreatePrivate = KeyHandleContext.kCreatePrivate; -function copyBuffer(input: string | Buffer | ArrayBufferView) { - if (typeof input === "string") return Buffer.from(input); - return ( - (ArrayBuffer.isView(input) - ? new Uint8Array(input.buffer, input.byteOffset, input.byteLength) - : new Uint8Array(input)).slice() - ); +function isJwk(obj: unknown): obj is { kty: unknown } { + // @ts-ignore this is fine + return typeof obj === "object" && obj != null && obj.kty !== undefined; } -const KEY_STORE = new WeakMap(); +export type KeyObjectHandle = { ___keyObjectHandle: true }; export class KeyObject { [kKeyType]: KeyObjectType; - [kHandle]: unknown; + [kHandle]: KeyObjectHandle; - constructor(type: KeyObjectType, handle: unknown) { + constructor(type: KeyObjectType, handle: KeyObjectHandle) { if (type !== "secret" && type !== "public" && type !== "private") { throw new ERR_INVALID_ARG_VALUE("type", type); } @@ -184,7 +188,6 @@ export class KeyObject { get symmetricKeySize(): number | undefined { notImplemented("crypto.KeyObject.prototype.symmetricKeySize"); - return undefined; } @@ -192,7 +195,6 @@ export class KeyObject { if (!isCryptoKey(key)) { throw new ERR_INVALID_ARG_TYPE("key", "CryptoKey", key); } - notImplemented("crypto.KeyObject.prototype.from"); } @@ -212,12 +214,13 @@ export class KeyObject { export(options?: KeyExportOptions<"der">): Buffer; export(options?: JwkKeyExportOptions): JsonWebKey; export(_options?: unknown): string | Buffer | JsonWebKey { - notImplemented("crypto.KeyObject.prototype.asymmetricKeyType"); + notImplemented("crypto.KeyObject.prototype.export"); } } ObjectDefineProperties(KeyObject.prototype, { [SymbolToStringTag]: { + // @ts-expect-error __proto__ is magic __proto__: null, configurable: true, value: "KeyObject", @@ -229,48 +232,356 @@ export interface JsonWebKeyInput { format: "jwk"; } -export function prepareAsymmetricKey(key) { - if (isStringOrBuffer(key)) { - return { format: "pem", data: getArrayBufferOrView(key, "key") }; - } else if (isKeyObject(key)) { +function getKeyObjectHandle(key: KeyObject, ctx: KeyHandleContext) { + if (ctx === kCreatePrivate) { + throw new ERR_INVALID_ARG_TYPE( + "key", + ["string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"], + key, + ); + } + + if (key.type !== "private") { + if (ctx === kConsumePrivate || ctx === kCreatePublic) { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "private"); + } + if (key.type !== "public") { + throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE( + key.type, + "private or public", + ); + } + } + + return key[kHandle]; +} + +export function prepareAsymmetricKey( + key: + | string + | ArrayBuffer + | Buffer + | ArrayBufferView + | KeyObject + | CryptoKey + | PrivateKeyInput + | PublicKeyInput + | JsonWebKeyInput, + ctx: KeyHandleContext, +): + | { handle: KeyObjectHandle; format?: "jwk" } + | { + data: ArrayBuffer | ArrayBufferView; + format: KeyFormat; + type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined; + passphrase: Buffer | ArrayBuffer | ArrayBufferView | undefined; + } { + if (isKeyObject(key)) { + // Best case: A key object, as simple as that. + return { + // @ts-ignore __proto__ is magic + __proto__: null, + handle: getKeyObjectHandle(key, ctx), + }; + } else if (isCryptoKey(key)) { + notImplemented("using CryptoKey as input"); + } else if (isStringOrBuffer(key)) { + // Expect PEM by default, mostly for backward compatibility. return { - // Assumes that asymmetric keys are stored as PEM. + // @ts-ignore __proto__ is magic + __proto__: null, format: "pem", - data: getKeyMaterial(key), + data: getArrayBufferOrView(key, "key"), }; - } else if (typeof key == "object") { - const { key: data, encoding, format, type } = key; + } else if (typeof key === "object") { + const { key: data, format } = key; + // The 'key' property can be a KeyObject as well to allow specifying + // additional options such as padding along with the key. + if (isKeyObject(data)) { + return { + // @ts-ignore __proto__ is magic + __proto__: null, + handle: getKeyObjectHandle(data, ctx), + }; + } else if (isCryptoKey(data)) { + notImplemented("using CryptoKey as input"); + } else if (isJwk(data) && format === "jwk") { + notImplemented("using JWK as input"); + } + // Either PEM or DER using PKCS#1 or SPKI. if (!isStringOrBuffer(data)) { - throw new TypeError("Invalid key type"); + throw new ERR_INVALID_ARG_TYPE( + "key.key", + getKeyTypes(ctx !== kCreatePrivate), + data, + ); } + const isPublic = (ctx === kConsumePrivate || ctx === kCreatePrivate) + ? false + : undefined; return { - data: getArrayBufferOrView(data, "key", encoding), - format: format ?? "pem", - encoding, - type, + data: getArrayBufferOrView( + data, + "key", + (key as PrivateKeyInput | PublicKeyInput).encoding, + ), + ...parseKeyEncoding(key, undefined, isPublic), }; } + throw new ERR_INVALID_ARG_TYPE( + "key", + getKeyTypes(ctx !== kCreatePrivate), + key, + ); +} + +function parseKeyEncoding( + enc: { + cipher?: string; + passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView; + encoding?: BufferEncoding | "buffer"; + format?: string; + type?: string; + }, + keyType: string | undefined, + isPublic: boolean | undefined, + objName?: string, +): { + format: KeyFormat; + type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined; + passphrase: Buffer | ArrayBuffer | ArrayBufferView | undefined; + cipher: string | undefined; +} { + if (enc === null || typeof enc !== "object") { + throw new ERR_INVALID_ARG_TYPE("options", "object", enc); + } + + const isInput = keyType === undefined; + + const { + format, + type, + } = parseKeyFormatAndType(enc, keyType, isPublic, objName); + + let cipher, passphrase, encoding; + if (isPublic !== true) { + ({ cipher, passphrase, encoding } = enc); + + if (!isInput) { + if (cipher != null) { + if (typeof cipher !== "string") { + throw new ERR_INVALID_ARG_VALUE(option("cipher", objName), cipher); + } + if ( + format === "der" && + (type === "pkcs1" || type === "sec1") + ) { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + type, + "does not support encryption", + ); + } + } else if (passphrase !== undefined) { + throw new ERR_INVALID_ARG_VALUE(option("cipher", objName), cipher); + } + } - throw new TypeError("Invalid key type"); + if ( + (isInput && passphrase !== undefined && + !isStringOrBuffer(passphrase)) || + (!isInput && cipher != null && !isStringOrBuffer(passphrase)) + ) { + throw new ERR_INVALID_ARG_VALUE( + option("passphrase", objName), + passphrase, + ); + } + } + + if (passphrase !== undefined) { + passphrase = getArrayBufferOrView(passphrase, "key.passphrase", encoding); + } + + return { + // @ts-ignore __proto__ is magic + __proto__: null, + format, + type, + cipher, + passphrase, + }; +} + +function option(name: string, objName?: string) { + return objName === undefined + ? `options.${name}` + : `options.${objName}.${name}`; +} + +function parseKeyFormatAndType( + enc: { format?: string; type?: string }, + keyType: string | undefined, + isPublic: boolean | undefined, + objName?: string, +): { + format: KeyFormat; + type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined; +} { + const { format: formatStr, type: typeStr } = enc; + + const isInput = keyType === undefined; + const format = parseKeyFormat( + formatStr, + isInput ? "pem" : undefined, + option("format", objName), + ); + + const type = parseKeyType( + typeStr, + !isInput || format === "der", + keyType, + isPublic, + option("type", objName), + ); + + return { + // @ts-ignore __proto__ is magic + __proto__: null, + format, + type, + }; +} + +function parseKeyFormat( + formatStr: string | undefined, + defaultFormat: KeyFormat | undefined, + optionName: string, +): KeyFormat { + if (formatStr === undefined && defaultFormat !== undefined) { + return defaultFormat; + } else if (formatStr === "pem") { + return "pem"; + } else if (formatStr === "der") { + return "der"; + } + throw new ERR_INVALID_ARG_VALUE(optionName, formatStr); +} + +function parseKeyType( + typeStr: string | undefined, + required: boolean, + keyType: string | undefined, + isPublic: boolean | undefined, + optionName: string, +): "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined { + if (typeStr === undefined && !required) { + return undefined; + } else if (typeStr === "pkcs1") { + if (keyType !== undefined && keyType !== "rsa") { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + typeStr, + "can only be used for RSA keys", + ); + } + return "pkcs1"; + } else if (typeStr === "spki" && isPublic !== false) { + return "spki"; + } else if (typeStr === "pkcs8" && isPublic !== true) { + return "pkcs8"; + } else if (typeStr === "sec1" && isPublic !== true) { + if (keyType !== undefined && keyType !== "ec") { + throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS( + typeStr, + "can only be used for EC keys", + ); + } + return "sec1"; + } + throw new ERR_INVALID_ARG_VALUE(optionName, typeStr); +} + +// Parses the public key encoding based on an object. keyType must be undefined +// when this is used to parse an input encoding and must be a valid key type if +// used to parse an output encoding. +function parsePublicKeyEncoding( + enc: { + cipher?: string; + passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView; + encoding?: BufferEncoding | "buffer"; + format?: string; + type?: string; + }, + keyType: string | undefined, + objName?: string, +) { + return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName); +} + +// Parses the private key encoding based on an object. keyType must be undefined +// when this is used to parse an input encoding and must be a valid key type if +// used to parse an output encoding. +function parsePrivateKeyEncoding( + enc: { + cipher?: string; + passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView; + encoding?: BufferEncoding | "buffer"; + format?: string; + type?: string; + }, + keyType: string | undefined, + objName?: string, +) { + return parseKeyEncoding(enc, keyType, false, objName); } export function createPrivateKey( key: PrivateKeyInput | string | Buffer | JsonWebKeyInput, ): PrivateKeyObject { - const { data, format, type } = prepareAsymmetricKey(key); - const details = op_node_create_private_key(data, format, type); - const handle = setOwnedKey(copyBuffer(data)); - return new PrivateKeyObject(handle, details); + const res = prepareAsymmetricKey(key, kCreatePrivate); + if ("handle" in res) { + const type = op_node_key_type(res.handle); + if (type === "private") { + return new PrivateKeyObject(res.handle); + } else { + throw new TypeError(`Can not create private key from ${type} key`); + } + } else { + const handle = op_node_create_private_key( + res.data, + res.format, + res.type ?? "", + res.passphrase, + ); + return new PrivateKeyObject(handle); + } } export function createPublicKey( key: PublicKeyInput | string | Buffer | JsonWebKeyInput, ): PublicKeyObject { - const { data, format, type } = prepareAsymmetricKey(key); - const details = op_node_create_public_key(data, format, type); - const handle = setOwnedKey(copyBuffer(data)); - return new PublicKeyObject(handle, details); + const res = prepareAsymmetricKey( + key, + kCreatePublic, + ); + if ("handle" in res) { + const type = op_node_key_type(res.handle); + if (type === "private") { + const handle = op_node_derive_public_key_from_private_key(res.handle); + return new PublicKeyObject(handle); + } else if (type === "public") { + return new PublicKeyObject(res.handle); + } else { + throw new TypeError(`Can not create private key from ${type} key`); + } + } else { + const handle = op_node_create_public_key( + res.data, + res.format, + res.type ?? "", + ); + return new PublicKeyObject(handle); + } } function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) { @@ -292,10 +603,10 @@ function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) { } export function prepareSecretKey( - key: string | ArrayBufferView | ArrayBuffer | KeyObject, + key: string | ArrayBufferView | ArrayBuffer | KeyObject | CryptoKey, encoding: string | undefined, bufferOnly = false, -) { +): Buffer | ArrayBuffer | ArrayBufferView | KeyObjectHandle { if (!bufferOnly) { if (isKeyObject(key)) { if (key.type !== "secret") { @@ -303,10 +614,7 @@ export function prepareSecretKey( } return key[kHandle]; } else if (isCryptoKey(key)) { - if (key.type !== "secret") { - throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret"); - } - return key[kKeyObject][kHandle]; + notImplemented("using CryptoKey as input"); } } if ( @@ -325,21 +633,20 @@ export function prepareSecretKey( } export class SecretKeyObject extends KeyObject { - constructor(handle: unknown) { + constructor(handle: KeyObjectHandle) { super("secret", handle); } get symmetricKeySize() { - return KEY_STORE.get(this[kHandle]).byteLength; + return op_node_get_symmetric_key_size(this[kHandle]); } get asymmetricKeyType() { return undefined; } - export(): Buffer; - export(options?: JwkKeyExportOptions): JsonWebKey { - const key = KEY_STORE.get(this[kHandle]); + export(options?: { format?: "buffer" | "jwk" }): Buffer | JsonWebKey { + let format: "buffer" | "jwk" = "buffer"; if (options !== undefined) { validateObject(options, "options"); validateOneOf( @@ -347,111 +654,102 @@ export class SecretKeyObject extends KeyObject { "options.format", [undefined, "buffer", "jwk"], ); - if (options.format === "jwk") { + format = options.format ?? "buffer"; + } + switch (format) { + case "buffer": + return Buffer.from(op_node_export_secret_key(this[kHandle])); + case "jwk": return { kty: "oct", - k: encodeToBase64Url(key), + k: op_node_export_secret_key_b64url(this[kHandle]), }; - } } - return key.slice(); } } -const kAsymmetricKeyType = Symbol("kAsymmetricKeyType"); -const kAsymmetricKeyDetails = Symbol("kAsymmetricKeyDetails"); - class AsymmetricKeyObject extends KeyObject { - constructor(type: KeyObjectType, handle: unknown, details: unknown) { + constructor(type: KeyObjectType, handle: KeyObjectHandle) { super(type, handle); - this[kAsymmetricKeyType] = details.type; - this[kAsymmetricKeyDetails] = { ...details }; } get asymmetricKeyType() { - return this[kAsymmetricKeyType]; + return op_node_get_asymmetric_key_type(this[kHandle]); } get asymmetricKeyDetails() { - return this[kAsymmetricKeyDetails]; + return op_node_get_asymmetric_key_details(this[kHandle]); } } export class PrivateKeyObject extends AsymmetricKeyObject { - constructor(handle: unknown, details: unknown) { - super("private", handle, details); + constructor(handle: KeyObjectHandle) { + super("private", handle); } - export(_options: unknown) { - notImplemented("crypto.PrivateKeyObject.prototype.export"); + export(options: JwkKeyExportOptions | KeyExportOptions<KeyFormat>) { + if (options && options.format === "jwk") { + notImplemented("jwk private key export not implemented"); + } + const { + format, + type, + } = parsePrivateKeyEncoding(options, this.asymmetricKeyType); + + if (format === "pem") { + return op_node_export_private_key_pem(this[kHandle], type); + } else { + return Buffer.from(op_node_export_private_key_der(this[kHandle], type)); + } } } export class PublicKeyObject extends AsymmetricKeyObject { - constructor(handle: unknown, details: unknown) { - super("public", handle, details); + constructor(handle: KeyObjectHandle) { + super("public", handle); } - export(options: unknown) { - const key = KEY_STORE.get(this[kHandle]); - switch (this.asymmetricKeyType) { - case "rsa": - case "rsa-pss": { - switch (options.format) { - case "pem": - return op_node_export_rsa_public_pem(key); - case "der": { - if (options.type == "pkcs1") { - return key; - } else { - return op_node_export_rsa_spki_der(key); - } - } - default: - throw new TypeError(`exporting ${options.type} is not implemented`); - } - } - default: - throw new TypeError( - `exporting ${this.asymmetricKeyType} is not implemented`, - ); + export(options: JwkKeyExportOptions | KeyExportOptions<KeyFormat>) { + if (options && options.format === "jwk") { + notImplemented("jwk public key export not implemented"); } - } -} - -export function setOwnedKey(key: Uint8Array): unknown { - const handle = {}; - KEY_STORE.set(handle, key); - return handle; -} + const { + format, + type, + } = parsePublicKeyEncoding(options, this.asymmetricKeyType); -export function getKeyMaterial(key: KeyObject): Uint8Array { - return KEY_STORE.get(key[kHandle]); + if (format === "pem") { + return op_node_export_public_key_pem(this[kHandle], type); + } else { + return Buffer.from(op_node_export_public_key_der(this[kHandle], type)); + } + } } -export function createSecretKey(key: ArrayBufferView): KeyObject; -export function createSecretKey( - key: string, - encoding: string, -): KeyObject; export function createSecretKey( - key: string | ArrayBufferView, + key: string | ArrayBufferView | ArrayBuffer | KeyObject | CryptoKey, encoding?: string, ): KeyObject { - key = prepareSecretKey(key, encoding, true); - const handle = setOwnedKey(copyBuffer(key)); - return new SecretKeyObject(handle); + const preparedKey = prepareSecretKey(key, encoding, true); + if (isArrayBufferView(preparedKey) || isAnyArrayBuffer(preparedKey)) { + const handle = op_node_create_secret_key(preparedKey); + return new SecretKeyObject(handle); + } else { + const type = op_node_key_type(preparedKey); + if (type === "secret") { + return new SecretKeyObject(preparedKey); + } else { + throw new TypeError(`can not create secret key from ${type} key`); + } + } } export default { createPrivateKey, createPublicKey, createSecretKey, - isKeyObject, - isCryptoKey, KeyObject, prepareSecretKey, - setOwnedKey, SecretKeyObject, PrivateKeyObject, PublicKeyObject, diff --git a/ext/node/polyfills/internal/crypto/sig.ts b/ext/node/polyfills/internal/crypto/sig.ts index 473670d2a..c711c7193 100644 --- a/ext/node/polyfills/internal/crypto/sig.ts +++ b/ext/node/polyfills/internal/crypto/sig.ts @@ -4,9 +4,13 @@ // TODO(petamoriken): enable prefer-primordials for node polyfills // deno-lint-ignore-file prefer-primordials -import { op_node_sign, op_node_verify } from "ext:core/ops"; +import { + op_node_create_private_key, + op_node_create_public_key, + op_node_sign, + op_node_verify, +} from "ext:core/ops"; -import { notImplemented } from "ext:deno_node/_utils.ts"; import { validateFunction, validateString, @@ -22,12 +26,12 @@ import type { PublicKeyInput, } from "ext:deno_node/internal/crypto/types.ts"; import { + kConsumePrivate, + kConsumePublic, KeyObject, prepareAsymmetricKey, } from "ext:deno_node/internal/crypto/keys.ts"; -import { createHash, Hash } from "ext:deno_node/internal/crypto/hash.ts"; -import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; -import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts"; +import { createHash } from "ext:deno_node/internal/crypto/hash.ts"; import { ERR_CRYPTO_SIGN_KEY_REQUIRED } from "ext:deno_node/internal/errors.ts"; export type DSAEncoding = "der" | "ieee-p1363"; @@ -72,16 +76,26 @@ export class SignImpl extends Writable { } sign( - privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput, + // deno-lint-ignore no-explicit-any + privateKey: any, encoding?: BinaryToTextEncoding, ): Buffer | string { - const { data, format, type } = prepareAsymmetricKey(privateKey); + const res = prepareAsymmetricKey(privateKey, kConsumePrivate); + let handle; + if ("handle" in res) { + handle = res.handle; + } else { + handle = op_node_create_private_key( + res.data, + res.format, + res.type ?? "", + res.passphrase, + ); + } const ret = Buffer.from(op_node_sign( + handle, this.hash.digest(), this.#digestType, - data!, - type, - format, )); return encoding ? ret.toString(encoding) : ret; } @@ -127,32 +141,27 @@ export class VerifyImpl extends Writable { } verify( - publicKey: BinaryLike | VerifyKeyObjectInput | VerifyPublicKeyInput, + // deno-lint-ignore no-explicit-any + publicKey: any, signature: BinaryLike, encoding?: BinaryToTextEncoding, ): boolean { - let keyData: BinaryLike; - let keyType: KeyType; - let keyFormat: KeyFormat; - if (typeof publicKey === "string" || isArrayBufferView(publicKey)) { - // if the key is BinaryLike, interpret it as a PEM encoded RSA key - // deno-lint-ignore no-explicit-any - keyData = publicKey as any; - keyType = "rsa"; - keyFormat = "pem"; + const res = prepareAsymmetricKey(publicKey, kConsumePublic); + let handle; + if ("handle" in res) { + handle = res.handle; } else { - // TODO(kt3k): Add support for the case when publicKey is a KeyObject, - // CryptoKey, etc - notImplemented( - "crypto.Verify.prototype.verify with non BinaryLike input", + handle = op_node_create_public_key( + res.data, + res.format, + res.type ?? "", + res.passphrase, ); } return op_node_verify( + handle, this.hash.digest(), this.#digestType, - keyData!, - keyType, - keyFormat, Buffer.from(signature, encoding), ); } diff --git a/ext/node/polyfills/internal/crypto/types.ts b/ext/node/polyfills/internal/crypto/types.ts index 45c0ea286..17b15127e 100644 --- a/ext/node/polyfills/internal/crypto/types.ts +++ b/ext/node/polyfills/internal/crypto/types.ts @@ -1,6 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. +import { BufferEncoding } from "ext:deno_node/_global.d.ts"; import { Buffer } from "../../buffer.ts"; export type HASH_DATA = string | ArrayBufferView | Buffer | ArrayBuffer; @@ -34,6 +35,7 @@ export type KeyType = export interface PrivateKeyInput { key: string | Buffer; + encoding: BufferEncoding | "buffer"; format?: KeyFormat | undefined; type?: "pkcs1" | "pkcs8" | "sec1" | undefined; passphrase?: string | Buffer | undefined; @@ -41,6 +43,7 @@ export interface PrivateKeyInput { export interface PublicKeyInput { key: string | Buffer; + encoding: BufferEncoding | "buffer"; format?: KeyFormat | undefined; type?: "pkcs1" | "spki" | undefined; } diff --git a/tests/unit_node/crypto/crypto_import_export.ts b/tests/unit_node/crypto/crypto_import_export.ts new file mode 100644 index 000000000..fc41cbacc --- /dev/null +++ b/tests/unit_node/crypto/crypto_import_export.ts @@ -0,0 +1,31 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +import crypto, { KeyFormat } from "node:crypto"; +import path from "node:path"; +import { Buffer } from "node:buffer"; +import { assert } from "@std/assert/mod.ts"; +import asymmetric from "./testdata/asymmetric.json" with { type: "json" }; + +Deno.test("crypto.createPrivateKey", async (t) => { + for (const key of asymmetric) { + await testCreatePrivateKey(t, key.name, "pem", "pkcs8"); + await testCreatePrivateKey(t, key.name, "der", "pkcs8"); + } +}); + +function testCreatePrivateKey( + t: Deno.TestContext, + name: string, + format: KeyFormat, + type: "pkcs8" | "pkcs1" | "sec1", +) { + if (name.includes("dh")) return; + return t.step(`crypto.createPrivateKey ${name} ${format} ${type}`, () => { + const file = path.join( + "./tests/unit_node/crypto/testdata/asymmetric", + `${name}.${type}.${format}`, + ); + const key = Buffer.from(Deno.readFileSync(file)); + const privateKey = crypto.createPrivateKey({ key, format, type }); + assert(privateKey); + }); +} diff --git a/tests/unit_node/crypto/generate_keys.mjs b/tests/unit_node/crypto/generate_keys.mjs new file mode 100644 index 000000000..8646fbcd1 --- /dev/null +++ b/tests/unit_node/crypto/generate_keys.mjs @@ -0,0 +1,147 @@ +import { writeFileSync } from "node:fs"; +import { join } from "node:path"; +import crypto from "node:crypto"; +import console from "node:console"; + +const keyTypes = [ + { + type: "rsa", + modulusLength: 2048, + }, + { + type: "rsa", + modulusLength: 3072, + }, + { + type: "rsa-pss", + modulusLength: 2048, + }, + { + type: "rsa-pss", + modulusLength: 3072, + }, + { + type: "rsa-pss", + modulusLength: 2048, + saltLength: 32, + }, + { + type: "rsa-pss", + modulusLength: 2048, + hashAlgorithm: "sha512", + }, + { + type: "dsa", + modulusLength: 2048, + }, + { + type: "dsa", + modulusLength: 3072, + }, + { + type: "ec", + namedCurve: "P-224", + }, + { + type: "ec", + namedCurve: "P-256", + }, + { + type: "ec", + namedCurve: "P-384", + }, + { + type: "x25519", + }, + { + type: "ed25519", + }, + { + type: "dh", + group: "modp14", + }, +]; + +const data = "Hello, World!"; + +const entries = []; + +for (const keyType of keyTypes) { + console.log(keyType); + const { privateKey, publicKey } = crypto.generateKeyPairSync(keyType.type, { + modulusLength: keyType.modulusLength, + namedCurve: keyType.namedCurve, + group: keyType.group, + saltLength: keyType.saltLength, + hashAlgorithm: keyType.hashAlgorithm, + }); + + let name = keyType.type; + if (keyType.type === "rsa-pss") { + name += `_${keyType.modulusLength}_${keyType.saltLength ?? "nosalt"}_${ + keyType.hashAlgorithm ?? "nohash" + }`; + } else if (keyType.type === "rsa" || keyType.type === "dsa") { + name += `_${keyType.modulusLength}`; + } else if (keyType.type === "ec") { + name += `_${keyType.namedCurve}`; + } else if (keyType.type === "dh") { + name += `_${keyType.group}`; + } + + exportAndWrite(name, privateKey, "pem", "pkcs8"); + exportAndWrite(name, privateKey, "der", "pkcs8"); + exportAndWrite(name, publicKey, "pem", "spki"); + exportAndWrite(name, publicKey, "der", "spki"); + + if (keyType.type === "rsa") { + exportAndWrite(name, privateKey, "pem", "pkcs1"); + exportAndWrite(name, privateKey, "der", "pkcs1"); + exportAndWrite(name, publicKey, "pem", "pkcs1"); + exportAndWrite(name, publicKey, "der", "pkcs1"); + } + if (keyType.type === "ec") { + exportAndWrite(name, privateKey, "pem", "sec1"); + exportAndWrite(name, privateKey, "der", "sec1"); + } + + let signed; + if (keyType.type === "ed25519") { + signed = crypto + .sign(null, Buffer.from(data), privateKey) + .toString("base64"); + } else if (keyType.type !== "x25519" && keyType.type !== "dh") { + console.log("signing", keyType.type); + signed = crypto + .createSign("sha512") + .update(data) + .sign(privateKey, "base64"); + } + + entries.push({ + name, + keyType: keyType.type, + signed, + }); +} + +writeFileSync( + join("tests", "unit_node", "crypto", "testdata", "asymmetric.json"), + JSON.stringify(entries, null, 2), +); + +function exportAndWrite(name, key, format, type) { + const pem = key.export({ + format, + type, + }); + const filename = join( + "tests", + "unit_node", + "crypto", + "testdata", + "asymmetric", + `${name}.${type}.${format}`, + ); + writeFileSync(filename, pem); +} diff --git a/tests/unit_node/crypto/testdata/asymmetric.json b/tests/unit_node/crypto/testdata/asymmetric.json new file mode 100644 index 000000000..82d2e7b63 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric.json @@ -0,0 +1,60 @@ +[ + { + "name": "rsa_2048", + "type": "rsa", + "signed": "FqWl/E1LhnLrSi6coOr0V8I1Skce3VHkxgboZ4DxnBZRZdabVm/nhevBop+cbrdzbHaBRzhrFbi/k2NDAUd4o1D2//g5+bVCoJlCp/t38GJ3oW8eKvAU4cBE8VCrNDCpHi62aEyOIHbFNcKvSvX10Pgvz1jxkU1S+sc4S/mFfc5mWjYZ96JC0dFSjpS5OfdweCzb6uixE+dzTsYtMDaLt5yKddOMMM3ElBITygK2DOlZHS8PqTvR+D2YhJzetB3gdcHGVek1AaDBRbsIzG2PSGT0BekJGDlUJYMD0FeCX456gX22qT+0e/6r9iI8AiPkrcb3dJHKnn0n8SFmflN6bA==" + }, + { + "name": "rsa_3072", + "type": "rsa", + "signed": "BreP5Jwjnpfei2wDgETkwMiIjh7zi39Gne/3w1kQH/L2yrlUAA+f0urhBkt8JjMAkisQBvsqqJ6ECFUnc4hvxfMKlPZifGu+DeLMWB0POsCkLSrNvxUYD97mm9WQ3wpVV0hfCQO0q3GPv3OYfTLMB23ugJeWuyTcm/Kbw7QWtrPKBRNysKiguLdAnvyYaRrt/vxQv7kwaNtS0GM3OkgNd/8IPEI2kuUCrRkeAnXH9zCtazOWaqVLoVBJtDuTvbvdy5N5j/im1Z2ZGqviI3rz4+N9iAaS6Xxf8Zo83yBdXftqcz9LtJuqZpORKyxAIb5ShcNLtQUewWH8nesRRY0oU/dx53fctB0vDNpijFsEizKD7uNOT+ZvAwb9DUkxNI2Ex1GPM7kBUzhY9WQ1cAtbzn5djHxJlpLxheiG9aTI/GN9jxMqSwhV0hY+QBd8yvfNi0Ei3IwlHZ4ri58nbt8ZAIBSDAGSN7qpqdVlwG7duqpvUIaX2/wMjXKJQ/Im46XK" + }, + { + "name": "rsa-pss_2048", + "type": "rsa-pss", + "signed": "wqguH2sfPcAk2PEca0p9wPBGfil+S3BW61Supa3g2AnJC8seDQL7C2MMnCvyzVD7QOoCLHQ5yMc4snaMoDAZUyC/jCm7kMNgsDNGsVP8Rvs8Pm+mHWEHrtpye8gTJiOQf1vb6Dar4xIk05qi028F8D4lLE/GQkfNUZjxyMCDLTTtFacngDinm39od75tqVtCeJijz8KEslmVnjLNazhxpvXK+3dbudNcnnu50sRxj1F1ogATmDqg01KkmeE47tffvyJXwNACdw5cZPbXOkBqvmY9a2h4xa4hZGBFH06zAxR5baPWlmtv28ZUbl1GoOk6m4Uu6jUyauI+PbeuXays2A==" + }, + { + "name": "rsa-pss_3072", + "type": "rsa-pss", + "signed": "GJgbQ6cu7cJnGrjgtY0waTlYG7azhVj1PySwcxp2uubyoch2fQHkRcVYhtppfFdz0SNHH4Nflp5gxdQkTfopyO7fDNTULdGT4/w4fdXOqnB2vq1H88CBQtyVxoW1VwEiu96rkkYX1DOEIyyCI7wCWOt33iTQMUHvYIy/3jOR4pdvqDekoI691e5Uodo16sXLiTDbzmTvplR4uhtlc89KkHGy1NQk4yGOuBS3fC8ksGSr89J1SLwRluNuyn8t4N0n0a/NbBu/DolnltvJzIRA7OhXyCdK1HLg5BM3QNR/LPjxPqXbTYGJ04iC5qErGT9cX9Rz+IqhqtSCBUwNCT93QpPZ7s7v1nj7Xq74HBqenknxhJdwnpxF3RKnIx5DvioeysmNX6Le8LighBR5mJEwOyz2Ls0/afSUazYmV9cA5L2uJDsBYWHjM2OnK6LdV9nTMgXa2h4suoIzo3YOqGxo9NKNPWyJ1q9Uda2dL4CazH1Rhz4NXRZarrv+YQ1958pq" + }, + { + "name": "dsa_2048", + "type": "dsa", + "signed": "MD0CHAeYKrUmejhDlvR4hvmHoHru9n5dWg9FeOkAZsUCHQCW5dn/MAFYQJXDCdVaST1tptQYQlM84qJCoaYT" + }, + { + "name": "dsa_3072", + "type": "dsa", + "signed": "MD0CHDGw+2G7O5UfjS0IODyGnyV0F18Hx1C3byJ7zHoCHQCOvumjVcvMKui3MNngS7zauiJgsdbDBcB7Un8M" + }, + { + "name": "ec_P-224", + "type": "ec", + "signed": "MD0CHQC9Yl4Typq3ql5E9NJTSjW/i2ZK928awxjgk8QwAhwSc5UELwCWK0xllhW5CExLdCI4znemLQ8Qp0+v" + }, + { + "name": "ec_P-256", + "type": "ec", + "signed": "MEUCIQDPzGSDwlIZ3Y45Xvpd28OvNGnOlqzT9Dbl5o5tXjq2qgIgJb7MpvTMrcE6ad/VZwaAXppRNN58+1N2LBOHG3+rH3I=" + }, + { + "name": "ec_P-384", + "type": "ec", + "signed": "MGYCMQDaV74tdPl146lFK1UYFQsDHMNIZGnVvU+LWrNXlsl+D+yBgg6PVpN19G5r6JT4hasCMQDsHnbwCywXFog1judfAyvdkIVJr1YgrlpshxOf0hylyD2PuegWBBU54dwQUblVRZs=" + }, + { + "name": "x25519", + "type": "x25519" + }, + { + "name": "ed25519", + "type": "ed25519", + "signed": "ytAlJrcGJDwT5xW7NbK5Rjv66H0Zow5nUKTBPekzBR8pbXKBbgOHmI+n2cjQsvGalD5L71tJWrWjMbpnlgFPCQ==" + }, + { + "name": "dh_modp14", + "type": "dh" + } +] diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.der b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.der Binary files differnew file mode 100644 index 000000000..6766804d4 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.pem new file mode 100644 index 000000000..580446a9b --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEwgIBADASBgkqhkiG9w0BAQowBaIDAgEgBIIEpzCCBKMCAQACggEBAKowWMm2 +m7dV5HbzQvMNNtiMahbqcS6P4cfE6bG6TSoiSYhK5q5a+A1hkezzNSOAEd/1zkfc +fU/vgHGlQ21sQZxsgCisFbupXOpNVKcobU0oF9GyyVOniqPXfNLFGOjNiLq6+mRU +poB3gAf9z7my3QUPrO7es6FdEzNXrkHFaIYypBRiwOBVcvsPZXhnxKm8NudnRIin +7xlCLMRsk1422Znl6ovIiC3y+7PcYZgpHTcepzNX7jNKONF13LBJqmTEeJb8PkIR +2/xjn1whThO0mZ4iq9aBwLc/ouqLzo5pN6Ap5fmEoaIoZdc1A6MBB6thMDaLOJdT +HDiGNzMmPMuaYVkCAwEAAQKCAQAED4yBmDgoF3yj3nGYenyc9BSKPxYlgzGDBBBD +HHoy6sr4NxJNpdyRXcSlUHD4hUzxe/9yQHQ9SnS7l9k11/PX612I97P6jIolVvER +xOGlXUZca2ovzLmRzhM7qINSRBvMTQHeHKHbtgFuhchPF3gvPFsj2KY7iWZRghJy +aqcjPL5nt5LQ/DgU5MGNmoPT8rm2YogJgZ1IScbFqcncp577ibRTs7NO/kOT8CvY +l2GiqQlrlh87ogu0d5KMgWyJ38fYYPVZhXUBWdgXzMgNx2hswmBdefSPYaQoFf8l +2AuNrHDPqJYsRdCMVLigpha94uuR3YW5HQtdEQzYJfcBdF8xAoGBAOxPZv+mrXSv +SpbPWH9+nUak5EpmvLIRh+2KuNozVdJA+IhJ3lCixj2v8+rHRWOYNSLKgXOTi2WP +4ioMCvIVnEk4nTIgfOq9CRBm0JiOw9G6TUhx/30PxQbGB6f0Q1TA/0QMmr9TwdYt +oWFk3tDhkyrpHvkn+nZJ6aSjYkZZciPxAoGBALhejGadDVLx8kEQifAyI7JayoP1 +kbTTZUmBC7wTENvwEWkEif1YXxFeYzWi3quSUbG4mn3sBrieQPOFH69up/JPk0M9 +Zj9ZUYn2Qbdv5tSj6o1bBLZBKb0D/YJA6PDewAJ2VWJbG0/R1qGhLjxwvmGSm9H4 +so7rrMH5BK5sAVvpAoGAfI+KGj3Qdo4jggT/gAzMeD1YfINU+YPWI3cY4yNmHHLU +znopblWuqzuBFgM95zaG47TcsYBXXQyPyVwZtOuBOvNVoOORFObZzUR0tcWjIHzU +WdiFNHXIhD6EMJrHlvg4VbVTKIDMzsm0pDLYZEBTI65H/kt9cTaaqobYuX5SdPEC +gYEAl9LEK3wJDNTQeWP1MycW3jiFrET8x7uNHQp3b1kD+RmoPKLQPyAWqWbgq7qD +QyYqv/8Uub0zi7RGKELn5L9q7c85pZVaLbCPxNxVIYm0vEZ/UAgzySHADTbL/AcX +y8Kiu2RWy7fatdBGvrLMMFlnbVIdnrr9z1Oj39gAUuH9/IECgYBjRbnZgI9YhD5C +g3fpuGeHNeS3JLI+4pXReA5+GhiNn7avwd9VbHRfEzrxOlEBC6hBWaU4oOpJTub/ +qK76euERcNP7UX4e97/C0A/bxXsVavDJU0pTUdSck9UmPWRZZSIy6cnu+rlSq5mr +9/x7CuqrrtNHWhRAcGAWuzJZWa77iA== +-----END PRIVATE KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.der b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.der Binary files differnew file mode 100644 index 000000000..c2565f021 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.pem new file mode 100644 index 000000000..3e965d08c --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBJzASBgkqhkiG9w0BAQowBaIDAgEgA4IBDwAwggEKAoIBAQCqMFjJtpu3VeR2 +80LzDTbYjGoW6nEuj+HHxOmxuk0qIkmISuauWvgNYZHs8zUjgBHf9c5H3H1P74Bx +pUNtbEGcbIAorBW7qVzqTVSnKG1NKBfRsslTp4qj13zSxRjozYi6uvpkVKaAd4AH +/c+5st0FD6zu3rOhXRMzV65BxWiGMqQUYsDgVXL7D2V4Z8SpvDbnZ0SIp+8ZQizE +bJNeNtmZ5eqLyIgt8vuz3GGYKR03HqczV+4zSjjRddywSapkxHiW/D5CEdv8Y59c +IU4TtJmeIqvWgcC3P6Lqi86OaTegKeX5hKGiKGXXNQOjAQerYTA2iziXUxw4hjcz +JjzLmmFZAgMBAAE= +-----END PUBLIC KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.der b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.der Binary files differnew file mode 100644 index 000000000..901b236be --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.pem new file mode 100644 index 000000000..5b4a28e14 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEuwIBADALBgkqhkiG9w0BAQoEggSnMIIEowIBAAKCAQEAvM6bE/DSk4I8cNMX +P6ERLuHxLBiM99GHQ3HtVcRjGIzLtLvH3XN5BE32o0uROd11f+bPTNWdNmy9Z1zz +AiM93y+vpf5m/9loi+102XiPc/RBeUDo+dOoSOt8S0P+A1pQUpehsxj917DLseAS +72HpFVEaSDn0cG1jjcWv/gWR4rhluiKHXSD711f/rYLABJWsCxpQHjSkNthxxLyF +A2X9oyuReX3Cw1t7RIgkCoExFx8WfasunSbjXWE5nmqoCo8ED8ae3hBmyWTP1d/p +zWLN6792UwweuTPIPo7nNcrJnn2Pleu5M/Wt0g0ma+FhgED7p5O6mMKZW+Tbv6bx +keaamwIDAQABAoIBAAiCkgppQ+NinJhfQ1vs6mD/8j1jW+WGxRrKoQC23CO6oysG +WSo1U0ciCMGCeM1U0a3E75DppxO/IfqkWRf1W7ARQNC66GgCNh6hC6/BYLjAdk4q +veM7GGUQcLlC5IAuVEl/A28pECP4mGSm//XGFFnq+dY3bMwtVXdTDj+cEV/GRGe8 +ORYiEr0k9Mo3aHg/dAEYSnOchakEbCZ3BErmZaXmjm/Qcpd638eaxNWzdiYbULXd +fwQCRt4P7fOnMqQpT6gb76cL9X/99D0p4AUY5wH4Y4otMQMVhoH2gEYBnKTJ2s6g +gGzaBQGGsEubDhPHYqW3i/VVUCdNOesrRJm09iECgYEA+MBG/anB+9/lYLe/SKdu +x48iGKausXNvF/I9aSeH5Mn3jT0wWOl5glqeAhBmC0NDX7lHYkDd8PFJ2F7ErN29 +JIFqVPllrUz9c0ZvRNx6MT/OaV4xAY2zMQcrF0FZnHz6IlIopUXJajN+OFAJGZQg +f6+E0ddIoaFx2ER4yYZQXSECgYEAwk8i7wk5E6hcscN5lMwxr2RdetUBWmw4WjwA +gEEUxy3B5NGPhY2EEVLtyk5qxKCxO7zgH03OJF3+upehbKF67xBDHWFXol6SCtHN +dUvzMctyY9NVfgY4lun/elxjIT97FIc9ksBelDHQoYJbW24W4IpH+YxPJPHZvCc3 +qPK4pDsCgYB9QBu+yAZj89XEgGDxjVTraLfLX8pgkXYjwZaIZx425jcex+ubKVxE +dapP8b/f9etrvJgj8fOOyX/cUcOII1KEmiFXTgiTXUvVCmcmbjmcqLsfNQ1J8faD +Pk/FMuOTNx6fv2y141DKh8kLQ8mBNqOyh0dCfbsVn3v6YGfNWTMH4QKBgDHRnnBR +Ggw7N4DwUGThMb/5aKpMoKsxYgVoquw3Q3+J8NOdE0I3tWvHqmYbUu6VELvzQjdk +eH0EiIIIzH+Qq3dN4RdQVOqxeppBjn8LeRAETJDhp2LHb4zp8/HIbDYjE4iA4D2X +CujOGOLADHJVuJHbgnauDcC4LY47M98iVErvAoGBAO1gVbqqyJSijMTC+wwGApPR +HIScfFhxuJCyxIxNMviuxO5vPIWZGcAYYBkiL08IVLuRyPlyurFUf69aU0jGjpqS +ncjwoSmMCyUvEppsC33rcgCJkP5eFSe/UgJLibCVv44BTsrcJaRtl+m1EwFODJnQ +WlHPWp95bdo70yfOFeme +-----END PRIVATE KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.der b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.der Binary files differnew file mode 100644 index 000000000..a3c109b49 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.pem new file mode 100644 index 000000000..300d3bd76 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBALzOmxPw0pOCPHDTFz+hES7h +8SwYjPfRh0Nx7VXEYxiMy7S7x91zeQRN9qNLkTnddX/mz0zVnTZsvWdc8wIjPd8v +r6X+Zv/ZaIvtdNl4j3P0QXlA6PnTqEjrfEtD/gNaUFKXobMY/dewy7HgEu9h6RVR +Gkg59HBtY43Fr/4FkeK4Zboih10g+9dX/62CwASVrAsaUB40pDbYccS8hQNl/aMr +kXl9wsNbe0SIJAqBMRcfFn2rLp0m411hOZ5qqAqPBA/Gnt4QZslkz9Xf6c1izeu/ +dlMMHrkzyD6O5zXKyZ59j5XruTP1rdINJmvhYYBA+6eTupjCmVvk27+m8ZHmmpsC +AwEAAQ== +-----END PUBLIC KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.der b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.der Binary files differnew file mode 100644 index 000000000..b7387e998 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.pem new file mode 100644 index 000000000..5f01f19d4 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG+QIBADALBgkqhkiG9w0BAQoEggblMIIG4QIBAAKCAYEAj/0hMizVaj713Pll +PV2uHmvT6jwNM7LqUg/txJDDE2I+MOyUaoIntaDlxl4gdiDqVfB3E5nH8VIYy1ir +Dc0GXoMr+s3GZ4vJcPrsckyCBHOGnKc71DQ2xwZnn8DdF6JI92GSFGnSmA4bZSXd +7KF6NduGnQ8USucDDUENG1aMz+Ol7btE5BXUngOEVWPc4qExvamelAc1f9J2VLQs +8UWVO/JGyCZ3YlI3L/fAnNEItffmy/dPfNacNJPM8egQImg7RHIC/9EZBeNi039x +5XNhnTcLW7dXr2dyOUyhk7GmOdZ4dYtolRsFpI8C6IWHmTrZAWo5FIAzfkgTeMVf +YkYg6h1j8ftvlGv7bTmDfxDT5i5iDNG7M3OfYpszsSp+7Dhu2Uro43Si5/YisCyH +YiRZ7C+8bnYQiCoXkHUh6nx6lpuNsTwxTJFQqAzQ0gIn7nHqMd2pczYPphhus2oH +L6hrb3A31Sbi3Od3vn57I+Sye2qpURa0nY9Aztp9LRWtuIgFAgMBAAECggGAE530 +RZp7mf3Jb+lvfFiUxhLBPKahUtOTnooKhg8F8NBf8jjtMISaheGNpITC7e0ml/5K +rtoy9iMN+zhRl7AA5+P9nC4oU4elMnYRPDJFvXcC1jlnio3xj5SFDiwPwy5KPB/o +L81KEorLezCZR2mplYha0u+Qv3KxRP9BmqGkThdxDg5HQL7jqIKIpzyThbHZ7cHb +98Vyqb9B/WDNxbJJ9nCX6aFp/vR+DdQ2gvbstaP4Zv4C75pqjjQ7xhJsjk9+XVBt +vGSOTDP4XoLUnmgXkLMvv+RnfCY1MNeM9HjTCeVGG96xxzPnoJyGKUfLIZGmZBfa ++TbxJ+3lF+mLeUNksjNEEGruSKtxbnx0VR81I2ir/RKRGXXm5MXAPIOUEPvfnQ2z +6JA+fBx4FGH9bzfG3oDEzRYcXd42ppoy/+dmoFHMCaF6CPWPIfHW3kKR3JaSmfri +ezyY2moSArpz+tUvki8/2qsqhgJEYdgyOIqh+D5NDfdLI6jVLwqrufK9HuHhAoHB +AMCuLqYrXsqVwEh0mRYo8EGT2rX0j6H/hu4ZrSkGQjAaN7YwDC0pIB5JZ02mi1uD +ctqrSj2hT1yhqxBDXLXLxz0auNqwLFOn/IFpuumwU4BG2EhKizWzJMSq26SwOwTZ +zJ9avm5N33cIDhZ3X2WTU99iNcXjMCWCQVW7O+aekHFq3hf5wyoLOYzifNR0R5/0 +ysWf14D0xjFq88hprH2nq9ILReV3pBWnF7qh2ma8GCJ5lnLnTXh8b2jM1YrmXNw9 +oQKBwQC/Tp6IKIy61yXwCfYnwfI0xfZ2uJIesHSK3TumctYJVt7hcEIYhRJm6H8p +TghD+RnK2MrtJpoiWdyVTZEGlOpguVkQXre4egVGHZaxap13sX7WPP7Go1/eYYKC +vPs6I30p1lWACPccdx4EHMtX3ywackyU3VoU9CjdoI6kpQ0YujvpWktfYiQNGHxJ +CJRJdXRySM1n077Du/CbCa5M2Grv/qMbRY6NMqUCeeczBEahYvoLjg0rBgyhV8Ua +tp1GB+UCgb9w5Z5vvvnLufLwgWExmssroXaFJscCJLbqzCCp6QDfLn12QrDfxTkM +hfYBiZQeCudBORxHAD2ACToyTUYFP2F+bCnj+VX/rm5FZa4fPzGt99TChusKi+z/ +Tx1jYd8y+Grs9D6gYwCMviuC/m7nFWwPd3wKdxO+5pNhqFNwzXfU/MklQzMXb3xJ +EGwUEA3nq4ckRIQBC2sViZVN2J05AwWqDPDngzKhfUZqFGvjxlJd4OBGc0DxV/fv +cBWvTUsbwQKBwD4ARPDr83A6elkpYVXRTaY84nvnpbO8jIjU2pg/b47nUKTPtRwt +RKGFiHDikVs2SF2tlVb2w82OQidBVFdFvBBNAciEahantT3cKRKm8xHvvKPCcO// +0vpHr/yfMuzMw6vjgyJTxYK8OEYs7tozwh9wG9HDS4au+u+ZMaG6vFvfP7uJQBRR +wk1cOiuRVD3aizrezXNw9MqLBpe4s3zERyQw6rvT9zdbyo7a40ttf1aetVs5Vpsz +ArTntUHLHX2s4QKBwDB5UFQdhVAIFs1HabVhFQMpxOnAjng0X8t9aGIduG/xfscj +P20BA6dU/WzCgRGTJkXUbzR68dvyEGmZdLpzoZbKoj+QLrvbdaPlIRuUtRiy4IYE +5uQAElpItb2C4ES7ELjcZldS0nOq0W/ZPXIOnaclQQMZ+OZTl8oZucbKiVC0vJVh +PVoEUFbEwURny911oDNPVzvW0T+FuViJCXoRwy+YwjQfRGa89c2cEixP+pAElHHr +W8yoba6AEekotJ+M+A== +-----END PRIVATE KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.der b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.der Binary files differnew file mode 100644 index 000000000..212c6d13e --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.pem new file mode 100644 index 000000000..3904b6f1b --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.pem @@ -0,0 +1,11 @@ +-----BEGIN PUBLIC KEY----- +MIIBoDALBgkqhkiG9w0BAQoDggGPADCCAYoCggGBAI/9ITIs1Wo+9dz5ZT1drh5r +0+o8DTOy6lIP7cSQwxNiPjDslGqCJ7Wg5cZeIHYg6lXwdxOZx/FSGMtYqw3NBl6D +K/rNxmeLyXD67HJMggRzhpynO9Q0NscGZ5/A3ReiSPdhkhRp0pgOG2Ul3eyhejXb +hp0PFErnAw1BDRtWjM/jpe27ROQV1J4DhFVj3OKhMb2pnpQHNX/SdlS0LPFFlTvy +Rsgmd2JSNy/3wJzRCLX35sv3T3zWnDSTzPHoECJoO0RyAv/RGQXjYtN/ceVzYZ03 +C1u3V69ncjlMoZOxpjnWeHWLaJUbBaSPAuiFh5k62QFqORSAM35IE3jFX2JGIOod +Y/H7b5Rr+205g38Q0+YuYgzRuzNzn2KbM7Eqfuw4btlK6ON0ouf2IrAsh2IkWewv +vG52EIgqF5B1Iep8epabjbE8MUyRUKgM0NICJ+5x6jHdqXM2D6YYbrNqBy+oa29w +N9Um4tznd75+eyPksntqqVEWtJ2PQM7afS0VrbiIBQIDAQAB +-----END PUBLIC KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.der b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.der Binary files differnew file mode 100644 index 000000000..e576acc9d --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.pem new file mode 100644 index 000000000..355b9642b --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.pem @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAmenZwtjzGH5KudheRB7fkoQhKqnD8NL0PxH9KX9tZY61+U9hQXr0 +NtwQ/yuRAP162NgVS9WrnSDXCMWVQyf3OXzGDSmQBNUbTYvs6VLJJxzeiZ+bCyfL +EOR49SUtJmzgwgdRItXFOY4Vjuen1tLt1SKLErHhJ9c1vnaQPpBlL0hpYoot3KoZ +GF/Ji/v0MIa9sPRPIYTY3IVm1KaPDrW7Vnlv8vm2b1bfd1WEEUZwx7nnQDoOHe65 +q8JOiz1vSd7sxt6sl5ffPfPpgW8Uwv+nzWdGfULmoXnT8aEorv69Mw8k+YBonxSN +xVvH/ScUsrPXb2WbFL4Aqvu2jaTMC3/ZhwIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.der b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.der Binary files differnew file mode 100644 index 000000000..47ca0f869 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.pem new file mode 100644 index 000000000..551bc2391 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZ6dnC2PMYfkq5 +2F5EHt+ShCEqqcPw0vQ/Ef0pf21ljrX5T2FBevQ23BD/K5EA/XrY2BVL1audINcI +xZVDJ/c5fMYNKZAE1RtNi+zpUsknHN6Jn5sLJ8sQ5Hj1JS0mbODCB1Ei1cU5jhWO +56fW0u3VIosSseEn1zW+dpA+kGUvSGliii3cqhkYX8mL+/Qwhr2w9E8hhNjchWbU +po8OtbtWeW/y+bZvVt93VYQRRnDHuedAOg4d7rmrwk6LPW9J3uzG3qyXl9898+mB +bxTC/6fNZ0Z9QuahedPxoSiu/r0zDyT5gGifFI3FW8f9JxSys9dvZZsUvgCq+7aN +pMwLf9mHAgMBAAECggEABL0SfPFUYENfH8qkC4fbZtwV8B83tTd7DC5zGch4QNsW +Ciqh+jPQk5g1FvR4UeX78Fol8BndEm9BssQjT/585r9LLHn3mtZ7l5vm7tk88GA7 +3IcJhtxL4BjD12Ea7nOffDz6bmg52rqsbXDML4zhHdTWIEmFGGA27nnuMsCCUcrM +kPebv9u/voQkRGdwyYH3+z8rO+IzQI/jGL0FkNi2czPHH3i5kZlCEAnqWlA2rfwz +dzCxycfRgTJ6HL6rQ4ru8pjlDqVsdlcbAaB8oqhiWZ1pXKo1IMhSal8C6sZ0eXQz +lJkdTcmuky9fgSsk91ggn+WeTYqaXAOVBNXtUyk8sQKBgQDXj6uLwP8novKr35CO +t2lBeqAfQHGxsTWa5Bj+1N1Jz+li6YMmvGqUWWUWaJLkV5B+P7Q7JjnBHlIAeICV +SEwjYJQSQy1+fLzdl5LxcPg4W2mz8RHMofpTDgO542lPrPNFRVwULN3nBuu0fI/y +YF7oNEl1BWgBIatjVU+9R2UjVwKBgQC2yYwitwdscfObl+CLDEkbYKyPNUDjPnPV +wbhlhex6UPqcoVd4p7Dk+zP3VGiQqc3zYBd4FrPBOCq5bxGNvSLen0q8SiDTgcsR +dDUlpjjkG+JFfEXIjo7x1gXkaXxgQFrZ7kpUTmkaOA5ekfqgcF1JdnumLEAf3M5s +kV5p3YXNUQKBgQCmfYbnqCbqvEZmTYRfVnXrZwTpXmLx9YcLnQVZPZu0+OqvxN/R +OVGwRuN2zUo3JxKpEBbqYHnXGM4JIwldQ7vazytOd6hZu4o8NGgAJ1rwXFpl6tnu +jWTEZVynZGfgbBpw9ENMKeMyHvxKKLMdZyWmf0wFICnWReUUEb5G2S/afQKBgFj8 +O6W21v1baE4qHR10SK70XG2HbmRyxe+dVIjQLvTJMYhJH41UjdCb3ouc4x7yG5pN +AH/tBWueTWZjBPesySn6AGcz61EskdCYczs19eJPFNPhERP3Gu3u1IWDORKeodwQ +nsz2M0KZYZ12kb3DlhaqgL3AMyOP2kqOZplBR99RAoGBAMF0Q12eFamvQuIajv2O +oK9LNcRyFhAwBxl+whzRaXanLNLDgPaofNBLEVi0pltcreELP79YvYeMvwB8bv8k +6062d6Icxv8LbiLMg52ce1tCH542Vcl+IUWBYtxdj3juSG0tFHoREEVum+hIiZOa +SkHtWuAaYJ+eYANAuTU2tddK +-----END PRIVATE KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.der b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.der Binary files differnew file mode 100644 index 000000000..5b319590d --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.pem new file mode 100644 index 000000000..fd26fdd93 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmenZwtjzGH5KudheRB7f +koQhKqnD8NL0PxH9KX9tZY61+U9hQXr0NtwQ/yuRAP162NgVS9WrnSDXCMWVQyf3 +OXzGDSmQBNUbTYvs6VLJJxzeiZ+bCyfLEOR49SUtJmzgwgdRItXFOY4Vjuen1tLt +1SKLErHhJ9c1vnaQPpBlL0hpYoot3KoZGF/Ji/v0MIa9sPRPIYTY3IVm1KaPDrW7 +Vnlv8vm2b1bfd1WEEUZwx7nnQDoOHe65q8JOiz1vSd7sxt6sl5ffPfPpgW8Uwv+n +zWdGfULmoXnT8aEorv69Mw8k+YBonxSNxVvH/ScUsrPXb2WbFL4Aqvu2jaTMC3/Z +hwIDAQAB +-----END PUBLIC KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.der b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.der Binary files differnew file mode 100644 index 000000000..4633b98d0 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.pem new file mode 100644 index 000000000..1521eb10e --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.pem @@ -0,0 +1,11 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBigKCAYEAoHs4aVryR35C5J4lN+QwS5wsxLJHfV+bMC0j339jPAMja0B203bB +HnGDbb23Natodh6HwjhD7qhWe5gGWIGR2QfskYOgswznjfo5zhdgQCwUsZ9sSYL5 +aBvD/8XHoaEPAX03e78RKRPmIdIVExQw/cMewtpPtnxa6rS7sCd5AFRQ1/yN3Orj +mY3+nDmaw2XmvL9t13Icacixk5DqmGZ51CXDkv546gQug2frRospyKQzn1tBf1Be +aeG95XcLBYih7lj39MQ317ocF4e0tRzHGzzWVEhbx8ewpmPzt7yqQUpoNExRzrh6 +9KMVwS08YQRXVrnmiOgg9tYN7S3wNcrIGzkpEITqFk5JNcwxaDggmnt4D7rH5C6W +1Q1R09IMehZL4zNuWUjzaHS7Tazuoh0635VXk26MXNnlWdLJHMq+UvX/pJMQ4smD +P15A3DUPYgFN8fCAbabXDaxb9gO4QaPMqFGbXBGWVcb4T72+k4zLxkeSzxaJ++J5 +DVXp2ij6kS/1AgMBAAE= +-----END RSA PUBLIC KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.der b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.der Binary files differnew file mode 100644 index 000000000..981d3ea1e --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.pem new file mode 100644 index 000000000..bc00997d1 --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.pem @@ -0,0 +1,40 @@ +-----BEGIN PRIVATE KEY----- +MIIG/QIBADANBgkqhkiG9w0BAQEFAASCBucwggbjAgEAAoIBgQCgezhpWvJHfkLk +niU35DBLnCzEskd9X5swLSPff2M8AyNrQHbTdsEecYNtvbc1q2h2HofCOEPuqFZ7 +mAZYgZHZB+yRg6CzDOeN+jnOF2BALBSxn2xJgvloG8P/xcehoQ8BfTd7vxEpE+Yh +0hUTFDD9wx7C2k+2fFrqtLuwJ3kAVFDX/I3c6uOZjf6cOZrDZea8v23XchxpyLGT +kOqYZnnUJcOS/njqBC6DZ+tGiynIpDOfW0F/UF5p4b3ldwsFiKHuWPf0xDfXuhwX +h7S1HMcbPNZUSFvHx7CmY/O3vKpBSmg0TFHOuHr0oxXBLTxhBFdWueaI6CD21g3t +LfA1ysgbOSkQhOoWTkk1zDFoOCCae3gPusfkLpbVDVHT0gx6FkvjM25ZSPNodLtN +rO6iHTrflVeTboxc2eVZ0skcyr5S9f+kkxDiyYM/XkDcNQ9iAU3x8IBtptcNrFv2 +A7hBo8yoUZtcEZZVxvhPvb6TjMvGR5LPFon74nkNVenaKPqRL/UCAwEAAQKCAYAh +XyZOsFEgAVdPO7yxF/RcEMS1fX5EycgXd1eVnyOnY96ua6gaFsCXgFLsk+5AjJ2r +LHOeNX2Y6CwdxPIS3xTRbSaqZtUYzrbrLQzuuKk1BwWhny2RRtlu5xE1w67dA0U/ +0cZjJwseP+tT/qAPyNvcNUJVEjlP0SHF22IJQlgU87OhCn2EolrEgITg/1CZz/uw +pd0lV28lp0yYC2NhYDP25Ah8rYq+3TI/9LD2CTPd65lpirw+yZtsiLsot73Uv4Ha +axC3m6pRX9h/wuzMK/6P9bp/ixRAqhiJ1InbpNWBwp+yVWwXkXjlCZ5NUJpf+Nzf +YwBTwnn4xHW13VzT+U6lObaBgnjs3K0szr+q+Z3toaZYxTx7ozHRqH3bwUbyCorI +VqPZuZJIUfgfhWwOnEBhPc3uaoCd0xNCQplZFIFzZcekXwM8ANsaDpbmxwYbA7XG +VaXzZdp/lOGOeocc/qWdlb6RTIFwwgi4LpgTNjg69LuVuWVpGPEVryH3HRfQJfEC +gcEA2kiiUaXF5cxZoj3/tdX1WhN7KSnAL9dOLDOmiUs/6Vbs0ryDXRDXD26KmpL7 +AAC1+a6aY0r331HmfAE6J/w9KshNH3a44GFSLvkljK2sUViXjaZdAHRdJv8ujn6s +v6KQl+pwx5ajmMWC4RlExGHf6jtSEn5n+DkNVQmWVBTCDKDe7gXpY7WtDaoMot35 +0mTWSJ04E+xojGoB6ez/+kgFU7XVFd9I/9cDur7+4Qjap6Y84pfPWg7MQL58VSgG +Pj5FAoHBALw1zpxh1zKO+Hwu6hz+Max+PUqQOZvi9/52iunNArLAwoht6nYTz79h +5HLrzgWZfv4U1zVdHEHo0hfgXNI5eKgV6NtC2K/jCWEh4qd9AaAEkFsqhHeQGhPm +JS6kDek+mSaZtWzQimSObax3fqRyiFsjAgA7h0rFw2He3A110ll6oCiQ1YU/GtbO +iTUl3von3KHMgzJiEQs3A0BXSjSui7pDdM4Sp7P5GpfVx5hcoIHfyeZyYCzqMusQ +t3IwoEvd8QKBwGKP2Xs0dx9EHlT72bKpYZfCpCH6ECWJ+mpLCC+GIt2hul2NcWNc +bz8wkrUpGNzvdTvAc0XSXAoiWQg1JaVYZ+Yhe3Fxkhj+2LUNGr9izCQO9J+pNuU1 +pbouDz+YQzhklxWBblsw0b3xsR8i4cIqz4hcMLrZCOk0GakEIzTkCprZKNAhKzky +l2lMF1iTEnLFxVwUYXXdkXeVhjeyJANG8eDSgdzWbYfX9n8kLsI91T1N+r/1/FKV +y/SQDmQFJoyEYQKBwETRO9GsaL5AzpTBprKhM1KJ+ik4YQghzmHJwHNKldD8cGTo +I9G23sBwr9JhbDxZ6rhGsIX/nKRw19kJHYd0oix7jmAVqhtt3XEZrcFmEOEMqifb +fQImu5JJFZFfRQLi86bMjyzRd7ja5zknnDPO+RKx0zp4ibiKZS11CmsViKtVDhin +FJua/lpKvJqlVUmMp7y1hcc3WloKbHLrN+PZapfZsGzlH1LMI7Ae59NLExlJ1Y4g +hhAGNkYDH8BuDT6QUQKBwQDSCVMup/MISIWtIQ/V3hhndpa10k2Bkrnu1quybJbZ +fGEm9fL/j/FoiMM0CWpEa0jDpUN/vPtiLXBg4k7Q9qhKpephLTR3xFwwfqsNkUx2 +O7BTIb3vu8iZ97HvRaCtcCSx7/Uz21GZQK4QUP0BtfSgZ/gjp8TvxKaoev5pIkLN +g5DJeEumyJvAkJkhKnR5uM7frFI+uXLH4fa3xkc9uKbVZHhr2PcV0aa4OjRA/rOY +GoAA1ZpHWKqWYvM6EJsH/qQ= +-----END PRIVATE KEY----- diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.der b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.der Binary files differnew file mode 100644 index 000000000..d8b10a34a --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.der diff --git a/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.pem b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.pem new file mode 100644 index 000000000..e3adbc86e --- /dev/null +++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.pem @@ -0,0 +1,11 @@ +-----BEGIN PUBLIC KEY----- +MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAoHs4aVryR35C5J4lN+Qw +S5wsxLJHfV+bMC0j339jPAMja0B203bBHnGDbb23Natodh6HwjhD7qhWe5gGWIGR +2QfskYOgswznjfo5zhdgQCwUsZ9sSYL5aBvD/8XHoaEPAX03e78RKRPmIdIVExQw +/cMewtpPtnxa6rS7sCd5AFRQ1/yN3OrjmY3+nDmaw2XmvL9t13Icacixk5DqmGZ5 +1CXDkv546gQug2frRospyKQzn1tBf1BeaeG95XcLBYih7lj39MQ317ocF4e0tRzH +GzzWVEhbx8ewpmPzt7yqQUpoNExRzrh69KMVwS08YQRXVrnmiOgg9tYN7S3wNcrI +GzkpEITqFk5JNcwxaDggmnt4D7rH5C6W1Q1R09IMehZL4zNuWUjzaHS7Tazuoh06 +35VXk26MXNnlWdLJHMq+UvX/pJMQ4smDP15A3DUPYgFN8fCAbabXDaxb9gO4QaPM +qFGbXBGWVcb4T72+k4zLxkeSzxaJ++J5DVXp2ij6kS/1AgMBAAE= +-----END PUBLIC KEY----- diff --git a/tools/core_import_map.json b/tools/core_import_map.json index bdf9e066b..d31821fb7 100644 --- a/tools/core_import_map.json +++ b/tools/core_import_map.json @@ -1,5 +1,7 @@ { "imports": { + "ext:core/mod.js": "../../deno_core/core/core.d.ts", + "ext:core/ops": "./ops.d.ts", "ext:deno_broadcast_channel/01_broadcast_channel.js": "../ext/broadcast_channel/01_broadcast_channel.js", "ext:deno_cache/01_cache.js": "../ext/cache/01_cache.js", "ext:deno_canvas/01_image.js": "../ext/canvas/01_image.js", diff --git a/tools/ops.d.ts b/tools/ops.d.ts new file mode 100644 index 000000000..8acb1e588 --- /dev/null +++ b/tools/ops.d.ts @@ -0,0 +1,4 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +// This file is intentionally empty - that puts this file into script mode, +// which then allows all symbols to be imported from the file. |