summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock65
-rw-r--r--ext/node/Cargo.toml6
-rw-r--r--ext/node/lib.rs58
-rw-r--r--ext/node/ops/crypto/dh.rs12
-rw-r--r--ext/node/ops/crypto/digest.rs45
-rw-r--r--ext/node/ops/crypto/keys.rs1727
-rw-r--r--ext/node/ops/crypto/mod.rs882
-rw-r--r--ext/node/ops/crypto/sign.rs396
-rw-r--r--ext/node/ops/vm_internal.rs1
-rw-r--r--ext/node/polyfills/_global.d.ts26
-rw-r--r--ext/node/polyfills/internal/crypto/_keys.ts10
-rw-r--r--ext/node/polyfills/internal/crypto/_randomFill.mjs33
-rw-r--r--ext/node/polyfills/internal/crypto/cipher.ts4
-rw-r--r--ext/node/polyfills/internal/crypto/diffiehellman.ts4
-rw-r--r--ext/node/polyfills/internal/crypto/hash.ts25
-rw-r--r--ext/node/polyfills/internal/crypto/hkdf.ts10
-rw-r--r--ext/node/polyfills/internal/crypto/keygen.ts117
-rw-r--r--ext/node/polyfills/internal/crypto/keys.ts558
-rw-r--r--ext/node/polyfills/internal/crypto/sig.ts63
-rw-r--r--ext/node/polyfills/internal/crypto/types.ts3
-rw-r--r--tests/unit_node/crypto/crypto_import_export.ts31
-rw-r--r--tests/unit_node/crypto/generate_keys.mjs147
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric.json60
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.derbin0 -> 1222 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.pem28
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.derbin0 -> 299 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.pem9
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.derbin0 -> 1215 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.pem28
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.derbin0 -> 292 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.pem9
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.derbin0 -> 1789 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.pem40
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.derbin0 -> 420 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.pem11
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.derbin0 -> 270 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.pem8
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.derbin0 -> 1218 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.pem28
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.derbin0 -> 294 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.pem9
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.derbin0 -> 398 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.pem11
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.derbin0 -> 1793 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.pem40
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.derbin0 -> 422 bytes
-rw-r--r--tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.pem11
-rw-r--r--tools/core_import_map.json2
-rw-r--r--tools/ops.d.ts4
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
new file mode 100644
index 000000000..6766804d4
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.pkcs8.der
Binary files differ
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
new file mode 100644
index 000000000..c2565f021
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_32_nohash.spki.der
Binary files differ
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
new file mode 100644
index 000000000..901b236be
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.pkcs8.der
Binary files differ
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
new file mode 100644
index 000000000..a3c109b49
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_2048_nosalt_nohash.spki.der
Binary files differ
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
new file mode 100644
index 000000000..b7387e998
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.pkcs8.der
Binary files differ
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
new file mode 100644
index 000000000..212c6d13e
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa-pss_3072_nosalt_nohash.spki.der
Binary files differ
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
new file mode 100644
index 000000000..e576acc9d
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs1.der
Binary files differ
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
new file mode 100644
index 000000000..47ca0f869
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.pkcs8.der
Binary files differ
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
new file mode 100644
index 000000000..5b319590d
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_2048.spki.der
Binary files differ
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
new file mode 100644
index 000000000..4633b98d0
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs1.der
Binary files differ
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
new file mode 100644
index 000000000..981d3ea1e
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.pkcs8.der
Binary files differ
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
new file mode 100644
index 000000000..d8b10a34a
--- /dev/null
+++ b/tests/unit_node/crypto/testdata/asymmetric/rsa_3072.spki.der
Binary files differ
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.