diff options
author | Luca Casonato <hello@lcas.dev> | 2024-08-07 08:43:58 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-07 08:43:58 +0200 |
commit | 4fa8869f2487749a9f190cb3047f4f3e6d571f27 (patch) | |
tree | 640c13e45e0bf1c63340c15f64b08b614ddcf120 | |
parent | 9a83efa04b6e733ca0fdbf9e780c4b77f0d9f4be (diff) |
feat(ext/node): rewrite crypto keys (#24463)
This completely rewrites how we handle key material in ext/node. Changes
in this
PR:
- **Signing**
- RSA
- RSA-PSS 🆕
- DSA 🆕
- EC
- ED25519 🆕
- **Verifying**
- RSA
- RSA-PSS 🆕
- DSA 🆕
- EC 🆕
- ED25519 🆕
- **Private key import**
- Passphrase encrypted private keys 🆕
- RSA
- PEM
- DER (PKCS#1) 🆕
- DER (PKCS#8) 🆕
- RSA-PSS
- PEM
- DER (PKCS#1) 🆕
- DER (PKCS#8) 🆕
- DSA 🆕
- EC
- PEM
- DER (SEC1) 🆕
- DER (PKCS#8) 🆕
- X25519 🆕
- ED25519 🆕
- DH
- **Public key import**
- RSA
- PEM
- DER (PKCS#1) 🆕
- DER (PKCS#8) 🆕
- RSA-PSS 🆕
- DSA 🆕
- EC 🆕
- X25519 🆕
- ED25519 🆕
- DH 🆕
- **Private key export**
- RSA 🆕
- DSA 🆕
- EC 🆕
- X25519 🆕
- ED25519 🆕
- DH 🆕
- **Public key export**
- RSA
- DSA 🆕
- EC 🆕
- X25519 🆕
- ED25519 🆕
- DH 🆕
- **Key pair generation**
- Overhauled, but supported APIs unchanged
This PR adds a lot of new individual functionality. But most importantly
because
of the new key material representation, it is now trivial to add new
algorithms
(as shown by this PR).
Now, when adding a new algorithm, it is also widely supported - for
example
previously we supported ED25519 key pair generation, but we could not
import,
export, sign or verify with ED25519. We can now do all of those things.
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. |