diff --git a/Cargo.lock b/Cargo.lock
index 2ae1becd1..bdda6b012 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -39,9 +39,9 @@ dependencies = [
[[package]]
name = "aead-gcm-stream"
-version = "0.1.0"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a09ecb526d53de2842cc876ee5c9b51161ee60399edeca4cf74892a01b48177"
+checksum = "4947a169074c7e038fa43051d1c4e073f4488b0e4b0a30658f1e1a1b06449ce8"
dependencies = [
"aead",
"aes",
@@ -260,7 +260,7 @@ dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
- "synstructure",
+ "synstructure 0.12.6",
]
[[package]]
@@ -1622,6 +1622,7 @@ dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
+ "digest",
"fiat-crypto",
"rustc_version 0.4.0",
"subtle",
@@ -2314,11 +2315,12 @@ dependencies = [
[[package]]
name = "der"
-version = "0.7.8"
+version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
dependencies = [
"const-oid",
+ "der_derive",
"pem-rfc7468",
"zeroize",
]
@@ -2337,6 +2339,17 @@ dependencies = [
"rusticata-macros",
]
+[[package]]
+name = "der_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
[[package]]
name = "deranged"
version = "0.3.11"
@@ -2523,7 +2536,7 @@ version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
- "der 0.7.8",
+ "der 0.7.9",
"digest",
"elliptic-curve 0.13.8",
"rfc6979 0.4.0",
@@ -2531,6 +2544,32 @@ dependencies = [
"spki 0.7.3",
]
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8 0.10.2",
+ "signature 2.2.0",
+]
+
+[[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 2.2.0",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "either"
version = "1.10.0"
@@ -2564,6 +2603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct 0.2.0",
+ "base64ct",
"crypto-bigint 0.5.5",
"digest",
"ff 0.13.0",
@@ -2574,6 +2614,8 @@ dependencies = [
"pkcs8 0.10.2",
"rand_core",
"sec1 0.7.3",
+ "serde_json",
+ "serdect",
"subtle",
"zeroize",
]
@@ -5168,11 +5210,26 @@ version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
dependencies = [
- "der 0.7.8",
+ "der 0.7.9",
"pkcs8 0.10.2",
"spki 0.7.3",
]
+[[package]]
+name = "pkcs5"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
+dependencies = [
+ "aes",
+ "cbc",
+ "der 0.7.9",
+ "pbkdf2",
+ "scrypt",
+ "sha2",
+ "spki 0.7.3",
+]
+
[[package]]
name = "pkcs8"
version = "0.9.0"
@@ -5189,7 +5246,9 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
- "der 0.7.8",
+ "der 0.7.9",
+ "pkcs5",
+ "rand_core",
"spki 0.7.3",
]
@@ -6142,6 +6201,7 @@ dependencies = [
"aead-gcm-stream",
"aes",
"async-trait",
+ "base64 0.21.7",
"blake2",
"brotli 6.0.0",
"bytes",
@@ -6155,9 +6215,12 @@ dependencies = [
"deno_media_type",
"deno_net",
"deno_whoami",
+ "der 0.7.9",
"digest",
"dsa",
"ecb",
+ "ecdsa 0.16.9",
+ "ed25519-dalek",
"elliptic-curve 0.13.8",
"errno 0.2.8",
"faster-hex",
@@ -6186,6 +6249,7 @@ dependencies = [
"path-clean",
"pbkdf2",
"pin-project-lite",
+ "pkcs8 0.10.2",
"rand",
"regex",
"reqwest 0.12.4",
@@ -6202,6 +6266,7 @@ dependencies = [
"simd-json",
"sm3",
"spki 0.7.3",
+ "stable_deref_trait",
"thiserror",
"tokio",
"url",
@@ -6209,6 +6274,7 @@ dependencies = [
"windows-sys 0.48.0",
"x25519-dalek",
"x509-parser",
+ "yoke",
]
[[package]]
@@ -6328,9 +6394,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct 0.2.0",
- "der 0.7.8",
+ "der 0.7.9",
"generic-array",
"pkcs8 0.10.2",
+ "serdect",
"subtle",
"zeroize",
]
@@ -6450,6 +6517,16 @@ dependencies = [
"v8",
]
+[[package]]
+name = "serdect"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177"
+dependencies = [
+ "base16ct 0.2.0",
+ "serde",
+]
+
[[package]]
name = "serial_test"
version = "3.0.0"
@@ -6730,7 +6807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
dependencies = [
"base64ct",
- "der 0.7.8",
+ "der 0.7.9",
]
[[package]]
@@ -7292,6 +7369,17 @@ dependencies = [
"unicode-xid",
]
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+]
+
[[package]]
name = "system-configuration"
version = "0.5.1"
@@ -8566,6 +8654,30 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "synstructure 0.13.1",
+]
+
[[package]]
name = "zerocopy"
version = "0.7.32"
@@ -8586,6 +8698,27 @@ dependencies = [
"syn 2.0.48",
]
+[[package]]
+name = "zerofrom"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.48",
+ "synstructure 0.13.1",
+]
+
[[package]]
name = "zeroize"
version = "1.7.0"
diff --git a/Cargo.toml b/Cargo.toml
index 090d643ed..ef7ef004d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -148,10 +148,10 @@ brotli = "6.0.0"
cbc = { version = "=0.1.2", features = ["alloc"] }
ecb = "=0.1.2"
data-encoding = "2.3.3"
-elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem"] }
+elliptic-curve = { version = "0.13.4", features = ["alloc", "arithmetic", "ecdh", "std", "pem", "jwk"] }
p224 = { version = "0.13.0", features = ["ecdh"] }
-p256 = { version = "0.13.2", features = ["ecdh"] }
-p384 = { version = "0.13.0", features = ["ecdh"] }
+p256 = { version = "0.13.2", features = ["ecdh", "jwk"] }
+p384 = { version = "0.13.0", features = ["ecdh", "jwk"] }
sha1 = { version = "0.10.6", features = ["oid"] }
sha2 = { version = "0.10.8", features = ["oid"] }
lazy-regex = "3"
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index 5f78c8207..c8fb7bbf0 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -19,49 +19,55 @@ deno_net.workspace = true
deno_config = { workspace = true, default-features = false, features = ["package_json"] }
deno_whoami = "0.1.0"
-libc.workspace = true
-http.workspace = true
-libz-sys.workspace = true
-tokio.workspace = true
-async-trait.workspace = true
-once_cell.workspace = true
-nix.workspace = true
-num-bigint.workspace = true
-bytes.workspace = true
-faster-hex.workspace = true
-indexmap.workspace = true
-regex.workspace = true
-reqwest.workspace = true
-ring.workspace = true
-rsa.workspace = true
-url.workspace = true
aes.workspace = true
+async-trait.workspace = true
+base64.workspace = true
brotli.workspace = true
+bytes.workspace = true
cbc.workspace = true
-ecb.workspace = true
data-encoding.workspace = true
+ecb.workspace = true
elliptic-curve.workspace = true
+faster-hex.workspace = true
+h2.workspace = true
hkdf.workspace = true
http_v02.workspace = true
+http.workspace = true
+indexmap.workspace = true
lazy-regex.workspace = true
+libc.workspace = true
+libz-sys.workspace = true
+nix.workspace = true
+num-bigint.workspace = true
+once_cell.workspace = true
p224.workspace = true
p256.workspace = true
p384.workspace = true
+rand.workspace = true
+regex.workspace = true
+reqwest.workspace = true
+ring.workspace = true
+rsa.workspace = true
sha1.workspace = true
sha2.workspace = true
-rand.workspace = true
signature.workspace = true
spki.workspace = true
-winapi.workspace = true
-h2.workspace = true
thiserror.workspace = true
+tokio.workspace = true
+url.workspace = true
+winapi.workspace = true
-aead-gcm-stream = "0.1"
+aead-gcm-stream = "0.3"
+blake2 = "0.10.6"
const-oid = "0.9.5"
digest = { version = "0.10.5", features = ["core-api", "std"] }
dsa = "0.6.1"
+der = { version = "0.7.9", features = ["derive"] }
+ecdsa = "0.16.9"
+ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core", "signature"] }
errno = "0.2.8"
idna = "0.3.0"
+ipnetwork = "0.20.0"
k256 = "0.13.1"
md-5 = { version = "0.10.5", features = ["oid"] }
md4 = "0.10.2"
@@ -71,17 +77,18 @@ num-traits = "0.2.14"
path-clean = "=0.1.0"
pbkdf2 = "0.12.1"
pin-project-lite = "0.2.13"
+pkcs8 = { version = "0.10.2", features = ["std", "pkcs5", "encryption"] }
ripemd = { version = "0.1.3", features = ["oid"] }
scrypt = "0.11.0"
sec1 = "0.7"
serde = "1.0.149"
sha3 = { version = "0.10.8", features = ["oid"] }
-blake2 = "0.10.6"
-sm3 = "0.4.2"
simd-json = "0.13.4"
-x25519-dalek = "2.0.0"
+sm3 = "0.4.2"
+stable_deref_trait = "1.2.0"
+x25519-dalek = { version = "2.0.0", features = ["static_secrets"] }
x509-parser = "0.15.0"
-ipnetwork = "0.20.0"
+yoke = { version = "0.7.4", features = ["derive"] }
[target.'cfg(windows)'.dependencies]
windows-sys.workspace = true
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index becc3e815..6feedd590 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -216,63 +216,90 @@ deno_core::extension!(deno_node,
ops::buffer::op_is_ascii,
ops::buffer::op_is_utf8,
- ops::crypto::op_node_create_decipheriv,
+ ops::crypto::op_node_check_prime_async,
+ ops::crypto::op_node_check_prime_bytes_async,
+ ops::crypto::op_node_check_prime_bytes,
+ ops::crypto::op_node_check_prime,
ops::crypto::op_node_cipheriv_encrypt,
ops::crypto::op_node_cipheriv_final,
ops::crypto::op_node_cipheriv_set_aad,
- ops::crypto::op_node_decipheriv_set_aad,
+ ops::crypto::op_node_cipheriv_take,
ops::crypto::op_node_create_cipheriv,
+ ops::crypto::op_node_create_decipheriv,
ops::crypto::op_node_create_hash,
- ops::crypto::op_node_get_hashes,
ops::crypto::op_node_decipheriv_decrypt,
ops::crypto::op_node_decipheriv_final,
- ops::crypto::op_node_hash_update,
- ops::crypto::op_node_hash_update_str,
- ops::crypto::op_node_hash_digest,
- ops::crypto::op_node_hash_digest_hex,
+ ops::crypto::op_node_decipheriv_set_aad,
+ ops::crypto::op_node_decipheriv_take,
+ ops::crypto::op_node_dh_compute_secret,
+ ops::crypto::op_node_diffie_hellman,
+ ops::crypto::op_node_ecdh_compute_public_key,
+ ops::crypto::op_node_ecdh_compute_secret,
+ ops::crypto::op_node_ecdh_encode_pubkey,
+ ops::crypto::op_node_ecdh_generate_keys,
+ ops::crypto::op_node_fill_random_async,
+ ops::crypto::op_node_fill_random,
+ ops::crypto::op_node_gen_prime_async,
+ ops::crypto::op_node_gen_prime,
+ ops::crypto::op_node_get_hashes,
ops::crypto::op_node_hash_clone,
- ops::crypto::op_node_private_encrypt,
+ ops::crypto::op_node_hash_digest_hex,
+ ops::crypto::op_node_hash_digest,
+ ops::crypto::op_node_hash_update_str,
+ ops::crypto::op_node_hash_update,
+ ops::crypto::op_node_hkdf_async,
+ ops::crypto::op_node_hkdf,
+ ops::crypto::op_node_pbkdf2_async,
+ ops::crypto::op_node_pbkdf2,
ops::crypto::op_node_private_decrypt,
+ ops::crypto::op_node_private_encrypt,
ops::crypto::op_node_public_encrypt,
- ops::crypto::op_node_check_prime,
- ops::crypto::op_node_check_prime_async,
- ops::crypto::op_node_check_prime_bytes,
- ops::crypto::op_node_check_prime_bytes_async,
- ops::crypto::op_node_gen_prime,
- ops::crypto::op_node_gen_prime_async,
- ops::crypto::op_node_pbkdf2,
- 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_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,
ops::crypto::op_node_scrypt_async,
- ops::crypto::op_node_ecdh_generate_keys,
- 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::op_node_scrypt_sync,
+ ops::crypto::op_node_sign,
+ ops::crypto::op_node_sign_ed25519,
+ ops::crypto::op_node_verify,
+ ops::crypto::op_node_verify_ed25519,
+ ops::crypto::keys::op_node_create_private_key,
+ ops::crypto::keys::op_node_create_ed_raw,
+ ops::crypto::keys::op_node_create_rsa_jwk,
+ ops::crypto::keys::op_node_create_ec_jwk,
+ 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_public_key_jwk,
+ 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,
@@ -285,6 +312,7 @@ deno_core::extension!(deno_node,
ops::crypto::x509::op_node_x509_get_valid_to,
ops::crypto::x509::op_node_x509_get_serial_number,
ops::crypto::x509::op_node_x509_key_usage,
+ ops::crypto::x509::op_node_x509_public_key,
ops::fs::op_node_fs_exists_sync
,
ops::fs::op_node_cp_sync
,
ops::fs::op_node_cp
,
@@ -363,8 +391,6 @@ deno_core::extension!(deno_node,
ops::require::op_require_package_imports_resolve
,
ops::require::op_require_break_on_next_statement,
ops::util::op_node_guess_handle_type,
- 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/cipher.rs b/ext/node/ops/crypto/cipher.rs
index 084445d89..b620b89f4 100644
--- a/ext/node/ops/crypto/cipher.rs
+++ b/ext/node/ops/crypto/cipher.rs
@@ -4,10 +4,10 @@ use aes::cipher::block_padding::Pkcs7;
use aes::cipher::BlockDecryptMut;
use aes::cipher::BlockEncryptMut;
use aes::cipher::KeyIvInit;
-use deno_core::error::range_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::Resource;
+use digest::generic_array::GenericArray;
use digest::KeyInit;
use std::borrow::Cow;
@@ -64,11 +64,15 @@ impl CipherContext {
self.cipher.borrow_mut().encrypt(input, output);
}
- pub fn r#final(self, input: &[u8], output: &mut [u8]) -> Result {
+ pub fn take_tag(self) -> Tag {
+ Rc::try_unwrap(self.cipher).ok()?.into_inner().take_tag()
+ }
+
+ pub fn r#final(self, auto_pad: bool, input: &[u8], output: &mut [u8]) -> Result {
Rc::try_unwrap(self.cipher)
.map_err(|_| type_error("Cipher context is already in use"))?
.into_inner()
- .r#final(input, output)
+ .r#final(auto_pad, input, output)
}
}
@@ -87,11 +91,17 @@ impl DecipherContext {
self.decipher.borrow_mut().decrypt(input, output);
}
- pub fn r#final(self, input: &[u8], output: &mut [u8], auth_tag: &[u8]) -> Result<(), AnyError> {
+ pub fn r#final(
+ self,
+ auto_pad: bool,
+ input: &[u8],
+ output: &mut [u8],
+ auth_tag: &[u8],
+ ) -> Result<(), AnyError> {
Rc::try_unwrap(self.decipher)
.map_err(|_| type_error("Decipher context is already in use"))?
.into_inner()
- .r#final(input, output, auth_tag)
+ .r#final(auto_pad, input, output, auth_tag)
}
}
@@ -116,26 +126,24 @@ impl Cipher {
"aes-192-ecb" => Aes192Ecb(Box::new(ecb::Encryptor::new(key.into()))),
"aes-256-ecb" => Aes256Ecb(Box::new(ecb::Encryptor::new(key.into()))),
"aes-128-gcm" => {
- let mut cipher = aead_gcm_stream::AesGcm::::new(key.into());
- cipher.init(iv.try_into()?);
+ if iv.len() != 12 {
+ return Err(type_error("IV length must be 12 bytes"));
+ }
+
+ let cipher = aead_gcm_stream::AesGcm::::new(key.into(), iv);
Aes128Gcm(Box::new(cipher))
}
"aes-256-gcm" => {
- let mut cipher = aead_gcm_stream::AesGcm::::new(key.into());
- cipher.init(iv.try_into()?);
+ if iv.len() != 12 {
+ return Err(type_error("IV length must be 12 bytes"));
+ }
+
+ let cipher = aead_gcm_stream::AesGcm::::new(key.into(), iv);
Aes256Gcm(Box::new(cipher))
}
"aes256" | "aes-256-cbc" => {
- // PATCH(denoland/deno#25570): Mitigates denoland/deno#25279
- if key.len() != 32 {
- return Err(range_error("Invalid key length"));
- }
- if iv.len() != 16 {
- return Err(type_error("Invalid initialization vector"));
- }
-
Aes256Cbc(Box::new(cbc::Encryptor::new(key.into(), iv.into())))
}
_ => return Err(type_error(format!("Unknown cipher {algorithm_name}"))),
@@ -201,42 +209,86 @@ impl Cipher {
}
/// r#final encrypts the last block of the input data.
- fn r#final(self, input: &[u8], output: &mut [u8]) -> Result {
+ fn r#final(self, auto_pad: bool, input: &[u8], output: &mut [u8]) -> Result {
assert!(input.len() < 16);
use Cipher::*;
- match self {
- Aes128Cbc(encryptor) => {
+ match (self, auto_pad) {
+ (Aes128Cbc(encryptor), true) => {
let _ = (*encryptor)
.encrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot pad the input data"))?;
Ok(None)
}
- Aes128Ecb(encryptor) => {
+ (Aes128Cbc(mut encryptor), false) => {
+ encryptor.encrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(None)
+ }
+ (Aes128Ecb(encryptor), true) => {
let _ = (*encryptor)
.encrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot pad the input data"))?;
Ok(None)
}
- Aes192Ecb(encryptor) => {
+ (Aes128Ecb(mut encryptor), false) => {
+ encryptor.encrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(None)
+ }
+ (Aes192Ecb(encryptor), true) => {
let _ = (*encryptor)
.encrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot pad the input data"))?;
Ok(None)
}
- Aes256Ecb(encryptor) => {
+ (Aes192Ecb(mut encryptor), false) => {
+ encryptor.encrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(None)
+ }
+ (Aes256Ecb(encryptor), true) => {
let _ = (*encryptor)
.encrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot pad the input data"))?;
Ok(None)
}
- Aes128Gcm(cipher) => Ok(Some(cipher.finish().to_vec())),
- Aes256Gcm(cipher) => Ok(Some(cipher.finish().to_vec())),
- Aes256Cbc(encryptor) => {
+ (Aes256Ecb(mut encryptor), false) => {
+ encryptor.encrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(None)
+ }
+ (Aes128Gcm(cipher), _) => Ok(Some(cipher.finish().to_vec())),
+ (Aes256Gcm(cipher), _) => Ok(Some(cipher.finish().to_vec())),
+ (Aes256Cbc(encryptor), true) => {
let _ = (*encryptor)
.encrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot pad the input data"))?;
Ok(None)
}
+ (Aes256Cbc(mut encryptor), false) => {
+ encryptor.encrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(None)
+ }
+ }
+ }
+
+ fn take_tag(self) -> Tag {
+ use Cipher::*;
+ match self {
+ Aes128Gcm(cipher) => Some(cipher.finish().to_vec()),
+ Aes256Gcm(cipher) => Some(cipher.finish().to_vec()),
+ _ => None,
}
}
}
@@ -250,26 +302,24 @@ impl Decipher {
"aes-192-ecb" => Aes192Ecb(Box::new(ecb::Decryptor::new(key.into()))),
"aes-256-ecb" => Aes256Ecb(Box::new(ecb::Decryptor::new(key.into()))),
"aes-128-gcm" => {
- let mut decipher = aead_gcm_stream::AesGcm::::new(key.into());
- decipher.init(iv.try_into()?);
+ if iv.len() != 12 {
+ return Err(type_error("IV length must be 12 bytes"));
+ }
+
+ let decipher = aead_gcm_stream::AesGcm::::new(key.into(), iv);
Aes128Gcm(Box::new(decipher))
}
"aes-256-gcm" => {
- let mut decipher = aead_gcm_stream::AesGcm::::new(key.into());
- decipher.init(iv.try_into()?);
+ if iv.len() != 12 {
+ return Err(type_error("IV length must be 12 bytes"));
+ }
+
+ let decipher = aead_gcm_stream::AesGcm::::new(key.into(), iv);
Aes256Gcm(Box::new(decipher))
}
"aes256" | "aes-256-cbc" => {
- // PATCH(denoland/deno#25570): Mitigates denoland/deno#25279
- if key.len() != 32 {
- return Err(range_error("Invalid key length"));
- }
- if iv.len() != 16 {
- return Err(type_error("Invalid initialization vector"));
- }
-
Aes256Cbc(Box::new(cbc::Decryptor::new(key.into(), iv.into())))
}
_ => return Err(type_error(format!("Unknown cipher {algorithm_name}"))),
@@ -335,38 +385,72 @@ impl Decipher {
}
/// r#final decrypts the last block of the input data.
- fn r#final(self, input: &[u8], output: &mut [u8], auth_tag: &[u8]) -> Result<(), AnyError> {
+ fn r#final(
+ self,
+ auto_pad: bool,
+ input: &[u8],
+ output: &mut [u8],
+ auth_tag: &[u8],
+ ) -> Result<(), AnyError> {
use Decipher::*;
- match self {
- Aes128Cbc(decryptor) => {
+ match (self, auto_pad) {
+ (Aes128Cbc(decryptor), true) => {
assert!(input.len() == 16);
let _ = (*decryptor)
.decrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot unpad the input data"))?;
Ok(())
}
- Aes128Ecb(decryptor) => {
+ (Aes128Cbc(mut decryptor), false) => {
+ decryptor.decrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(())
+ }
+ (Aes128Ecb(decryptor), true) => {
assert!(input.len() == 16);
let _ = (*decryptor)
.decrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot unpad the input data"))?;
Ok(())
}
- Aes192Ecb(decryptor) => {
+ (Aes128Ecb(mut decryptor), false) => {
+ decryptor.decrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(())
+ }
+ (Aes192Ecb(decryptor), true) => {
assert!(input.len() == 16);
let _ = (*decryptor)
.decrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot unpad the input data"))?;
Ok(())
}
- Aes256Ecb(decryptor) => {
+ (Aes192Ecb(mut decryptor), false) => {
+ decryptor.decrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(())
+ }
+ (Aes256Ecb(decryptor), true) => {
assert!(input.len() == 16);
let _ = (*decryptor)
.decrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot unpad the input data"))?;
Ok(())
}
- Aes128Gcm(decipher) => {
+ (Aes256Ecb(mut decryptor), false) => {
+ decryptor.decrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(())
+ }
+ (Aes128Gcm(decipher), true) => {
let tag = decipher.finish();
if tag.as_slice() == auth_tag {
Ok(())
@@ -374,7 +458,10 @@ impl Decipher {
Err(type_error("Failed to authenticate data"))
}
}
- Aes256Gcm(decipher) => {
+ (Aes128Gcm(_), false) => Err(type_error(
+ "setAutoPadding(false) not supported for Aes256Gcm yet",
+ )),
+ (Aes256Gcm(decipher), true) => {
let tag = decipher.finish();
if tag.as_slice() == auth_tag {
Ok(())
@@ -382,13 +469,23 @@ impl Decipher {
Err(type_error("Failed to authenticate data"))
}
}
- Aes256Cbc(decryptor) => {
+ (Aes256Gcm(_), false) => Err(type_error(
+ "setAutoPadding(false) not supported for Aes256Gcm yet",
+ )),
+ (Aes256Cbc(decryptor), true) => {
assert!(input.len() == 16);
let _ = (*decryptor)
.decrypt_padded_b2b_mut::(input, output)
.map_err(|_| type_error("Cannot unpad the input data"))?;
Ok(())
}
+ (Aes256Cbc(mut decryptor), false) => {
+ decryptor.decrypt_block_b2b_mut(
+ GenericArray::from_slice(input),
+ GenericArray::from_mut_slice(output),
+ );
+ Ok(())
+ }
}
}
}
diff --git a/ext/node/ops/crypto/dh.rs b/ext/node/ops/crypto/dh.rs
index 78c4108d4..dd6fbbd1b 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 {
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(&self, generator: &BigUint, modulus: &BigUint) -> PublicKey {
diff --git a/ext/node/ops/crypto/digest.rs b/ext/node/ops/crypto/digest.rs
index 4c5adcb77..8a2da8ae1 100644
--- a/ext/node/ops/crypto/digest.rs
+++ b/ext/node/ops/crypto/digest.rs
@@ -61,7 +61,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)
}
};
}
@@ -78,75 +78,89 @@ 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) => {
- match $algorithm_name {
- "rsa-md5" | "md5" | "md5withrsaencryption" | "ssl3-md5" => {
- type $type = ::md5::Md5;
- $body
- }
- "rsa-ripemd160" | "ripemd" | "ripemd160" | "ripemd160withrsa" | "rmd160" => {
- type $type = ::ripemd::Ripemd160;
- $body
- }
- "rsa-sha1"
- | "rsa-sha1-2"
- | "sha1"
- | "sha1-2"
- | "sha1withrsaencryption"
- | "ssl3-sha1" => {
- type $type = ::sha1::Sha1;
- $body
- }
- "rsa-sha224" | "sha224" | "sha224withrsaencryption" => {
- type $type = ::sha2::Sha224;
- $body
- }
- "rsa-sha256" | "sha256" | "sha256withrsaencryption" => {
- type $type = ::sha2::Sha256;
- $body
- }
- "rsa-sha384" | "sha384" | "sha384withrsaencryption" => {
- type $type = ::sha2::Sha384;
- $body
- }
- "rsa-sha512" | "sha512" | "sha512withrsaencryption" => {
- type $type = ::sha2::Sha512;
- $body
- }
- "rsa-sha512/224" | "sha512-224" | "sha512-224withrsaencryption" => {
- type $type = ::sha2::Sha512_224;
- $body
- }
- "rsa-sha512/256" | "sha512-256" | "sha512-256withrsaencryption" => {
- type $type = ::sha2::Sha512_256;
- $body
- }
- "rsa-sha3-224" | "id-rsassa-pkcs1-v1_5-with-sha3-224" | "sha3-224" => {
- type $type = ::sha3::Sha3_224;
- $body
- }
- "rsa-sha3-256" | "id-rsassa-pkcs1-v1_5-with-sha3-256" | "sha3-256" => {
- type $type = ::sha3::Sha3_256;
- $body
- }
- "rsa-sha3-384" | "id-rsassa-pkcs1-v1_5-with-sha3-384" | "sha3-384" => {
- type $type = ::sha3::Sha3_384;
- $body
- }
- "rsa-sha3-512" | "id-rsassa-pkcs1-v1_5-with-sha3-512" | "sha3-512" => {
- type $type = ::sha3::Sha3_512;
- $body
- }
- _ => $other,
- }
- };
+ ($algorithm_name:expr, fn $(<$type:ident>)?($($hash_algorithm:ident: Option)?) $body:block, _ => $other:block) => {
+ match $algorithm_name {
+ "rsa-md5" | "md5" | "md5withrsaencryption" | "ssl3-md5" => {
+ $(let $hash_algorithm = None;)?
+ $(type $type = ::md5::Md5;)?
+ $body
+ }
+ "rsa-ripemd160" | "ripemd" | "ripemd160" | "ripemd160withrsa"
+ | "rmd160" => {
+ $(let $hash_algorithm = None;)?
+ $(type $type = ::ripemd::Ripemd160;)?
+ $body
+ }
+ "rsa-sha1"
+ | "rsa-sha1-2"
+ | "sha1"
+ | "sha1-2"
+ | "sha1withrsaencryption"
+ | "ssl3-sha1" => {
+ $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha1);)?
+ $(type $type = ::sha1::Sha1;)?
+ $body
+ }
+ "rsa-sha224" | "sha224" | "sha224withrsaencryption" => {
+ $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha224);)?
+ $(type $type = ::sha2::Sha224;)?
+ $body
+ }
+ "rsa-sha256" | "sha256" | "sha256withrsaencryption" => {
+ $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha256);)?
+ $(type $type = ::sha2::Sha256;)?
+ $body
+ }
+ "rsa-sha384" | "sha384" | "sha384withrsaencryption" => {
+ $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha384);)?
+ $(type $type = ::sha2::Sha384;)?
+ $body
+ }
+ "rsa-sha512" | "sha512" | "sha512withrsaencryption" => {
+ $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512);)?
+ $(type $type = ::sha2::Sha512;)?
+ $body
+ }
+ "rsa-sha512/224" | "sha512-224" | "sha512-224withrsaencryption" => {
+ $(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512_224);)?
+ $(type $type = ::sha2::Sha512_224;)?
+ $body
+ }
+ "rsa-sha512/256" | "sha512-256" | "sha512-256withrsaencryption" => {
+ $(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" => {
+ $(let $hash_algorithm = None;)?
+ $(type $type = ::sha3::Sha3_224;)?
+ $body
+ }
+ "rsa-sha3-256" | "id-rsassa-pkcs1-v1_5-with-sha3-256" | "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" => {
+ $(let $hash_algorithm = None;)?
+ $(type $type = ::sha3::Sha3_384;)?
+ $body
+ }
+ "rsa-sha3-512" | "id-rsassa-pkcs1-v1_5-with-sha3-512" | "sha3-512" => {
+ $(let $hash_algorithm = None;)?
+ $(type $type = ::sha3::Sha3_512;)?
+ $body
+ }
+ _ => $other,
+ }
+ };
}
pub(crate) use match_fixed_digest_with_oid;
diff --git a/ext/node/ops/crypto/keys.rs b/ext/node/ops/crypto/keys.rs
new file mode 100644
index 000000000..3d781d36d
--- /dev/null
+++ b/ext/node/ops/crypto/keys.rs
@@ -0,0 +1,1996 @@
+// 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 elliptic_curve::JwkEcKey;
+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::Tag;
+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 x509_parser::x509;
+
+use super::dh;
+use super::dh::DiffieHellmanGroup;
+use super::digest::match_fixed_digest_with_oid;
+use super::pkcs3;
+use super::pkcs3::DhParameter;
+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),
+ Dh(DhPrivateKey),
+}
+
+#[derive(Clone)]
+pub struct RsaPssPrivateKey {
+ pub key: RsaPrivateKey,
+ pub details: Option,
+}
+
+#[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 struct DhPrivateKey {
+ pub key: dh::PrivateKey,
+ pub params: DhParameter,
+}
+
+#[derive(Clone)]
+pub enum AsymmetricPublicKey {
+ Rsa(rsa::RsaPublicKey),
+ RsaPss(RsaPssPublicKey),
+ Dsa(dsa::VerifyingKey),
+ Ec(EcPublicKey),
+ X25519(x25519_dalek::PublicKey),
+ Ed25519(ed25519_dalek::VerifyingKey),
+ Dh(DhPublicKey),
+}
+
+#[derive(Clone)]
+pub struct RsaPssPublicKey {
+ pub key: rsa::RsaPublicKey,
+ pub details: Option,
+}
+
+#[derive(Clone)]
+pub enum EcPublicKey {
+ P224(p224::PublicKey),
+ P256(p256::PublicKey),
+ P384(p384::PublicKey),
+}
+
+#[derive(Clone)]
+pub struct DhPublicKey {
+ pub key: dh::PublicKey,
+ pub params: DhParameter,
+}
+
+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> {
+ 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 EcPublicKey {
+ pub fn to_jwk(&self) -> Result {
+ match self {
+ EcPublicKey::P224(_) => Err(type_error("Unsupported JWK EC curve: P224")),
+ EcPublicKey::P256(key) => Ok(key.to_jwk()),
+ EcPublicKey::P384(key) => Ok(key.to_jwk()),
+ }
+ }
+}
+
+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>,
+ pub mask_gen_algorithm: Option>,
+ pub salt_length: Option,
+}
+
+// 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> for RsaPssParameters<'a> {
+ type Error = rsa::pkcs8::der::Error;
+
+ fn try_from(
+ any: rsa::pkcs8::der::asn1::AnyRef<'a>,
+ ) -> rsa::pkcs8::der::Result {
+ any.sequence(|decoder| {
+ let hash_algorithm = decoder
+ .context_specific::(
+ HASH_ALGORITHM_TAG,
+ pkcs8::der::TagMode::Explicit,
+ )?
+ .map(TryInto::try_into)
+ .transpose()?;
+
+ let mask_gen_algorithm = decoder
+ .context_specific::(
+ MASK_GEN_ALGORITHM_TAG,
+ pkcs8::der::TagMode::Explicit,
+ )?
+ .map(TryInto::try_into)
+ .transpose()?;
+
+ let salt_length = decoder
+ .context_specific::(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 {
+ 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 signing_key = ed25519_dalek::SigningKey::try_from(pk_info)
+ .map_err(|_| type_error("invalid Ed25519 private key"))?;
+ AsymmetricPrivateKey::Ed25519(signing_key)
+ }
+ DH_KEY_AGREEMENT_OID => {
+ let params = pk_info
+ .algorithm
+ .parameters
+ .ok_or_else(|| type_error("missing dh parameters"))?;
+ let params = pkcs3::DhParameter::from_der(¶ms.to_der().unwrap())
+ .map_err(|_| type_error("malformed dh parameters"))?;
+ AsymmetricPrivateKey::Dh(DhPrivateKey {
+ key: dh::PrivateKey::from_bytes(pk_info.private_key),
+ params,
+ })
+ }
+ _ => return Err(type_error("unsupported private key oid")),
+ };
+
+ Ok(KeyObjectHandle::AsymmetricPrivate(private_key))
+ }
+
+ pub fn new_x509_public_key(
+ spki: &x509::SubjectPublicKeyInfo,
+ ) -> Result {
+ use x509_parser::der_parser::asn1_rs::oid;
+ use x509_parser::public_key::PublicKey;
+
+ let key = match spki.parsed()? {
+ PublicKey::RSA(key) => {
+ let public_key = RsaPublicKey::new(
+ rsa::BigUint::from_bytes_be(key.modulus),
+ rsa::BigUint::from_bytes_be(key.exponent),
+ )?;
+ AsymmetricPublicKey::Rsa(public_key)
+ }
+ PublicKey::EC(point) => {
+ let data = point.data();
+ if let Some(params) = &spki.algorithm.parameters {
+ let curve_oid = params.as_oid()?;
+ const ID_SECP224R1: &[u8] = &oid!(raw 1.3.132.0.33);
+ const ID_SECP256R1: &[u8] = &oid!(raw 1.2.840.10045.3.1.7);
+ const ID_SECP384R1: &[u8] = &oid!(raw 1.3.132.0.34);
+
+ match curve_oid.as_bytes() {
+ ID_SECP224R1 => {
+ let public_key = p224::PublicKey::from_sec1_bytes(data)?;
+ AsymmetricPublicKey::Ec(EcPublicKey::P224(public_key))
+ }
+ ID_SECP256R1 => {
+ let public_key = p256::PublicKey::from_sec1_bytes(data)?;
+ AsymmetricPublicKey::Ec(EcPublicKey::P256(public_key))
+ }
+ ID_SECP384R1 => {
+ let public_key = p384::PublicKey::from_sec1_bytes(data)?;
+ AsymmetricPublicKey::Ec(EcPublicKey::P384(public_key))
+ }
+ _ => return Err(type_error("unsupported ec named curve")),
+ }
+ } else {
+ return Err(type_error("missing ec parameters"));
+ }
+ }
+ PublicKey::DSA(_) => {
+ let verifying_key = dsa::VerifyingKey::from_public_key_der(spki.raw)
+ .map_err(|_| type_error("malformed DSS public key"))?;
+ AsymmetricPublicKey::Dsa(verifying_key)
+ }
+ _ => return Err(type_error("unsupported x509 public key type")),
+ };
+
+ Ok(KeyObjectHandle::AsymmetricPublic(key))
+ }
+
+ pub fn new_rsa_jwk(jwk: RsaJwkKey, is_public: bool) -> Result {
+ use base64::prelude::BASE64_URL_SAFE_NO_PAD;
+
+ let n = BASE64_URL_SAFE_NO_PAD.decode(jwk.n.as_bytes())?;
+ let e = BASE64_URL_SAFE_NO_PAD.decode(jwk.e.as_bytes())?;
+
+ if is_public {
+ let public_key = RsaPublicKey::new(
+ rsa::BigUint::from_bytes_be(&n),
+ rsa::BigUint::from_bytes_be(&e),
+ )?;
+
+ Ok(KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Rsa(
+ public_key,
+ )))
+ } else {
+ let d = BASE64_URL_SAFE_NO_PAD.decode(
+ jwk.d
+ .ok_or_else(|| type_error("missing RSA private component"))?
+ .as_bytes(),
+ )?;
+ let p = BASE64_URL_SAFE_NO_PAD.decode(
+ jwk.p
+ .ok_or_else(|| type_error("missing RSA private component"))?
+ .as_bytes(),
+ )?;
+ let q = BASE64_URL_SAFE_NO_PAD.decode(
+ jwk.q
+ .ok_or_else(|| type_error("missing RSA private component"))?
+ .as_bytes(),
+ )?;
+
+ let mut private_key = RsaPrivateKey::from_components(
+ rsa::BigUint::from_bytes_be(&n),
+ rsa::BigUint::from_bytes_be(&e),
+ rsa::BigUint::from_bytes_be(&d),
+ vec![
+ rsa::BigUint::from_bytes_be(&p),
+ rsa::BigUint::from_bytes_be(&q),
+ ],
+ )?;
+ private_key.precompute()?; // precompute CRT params
+
+ Ok(KeyObjectHandle::AsymmetricPrivate(
+ AsymmetricPrivateKey::Rsa(private_key),
+ ))
+ }
+ }
+
+ pub fn new_ec_jwk(jwk: &JwkEcKey, is_public: bool) -> Result {
+ // https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1
+ let handle = match jwk.crv() {
+ "P-256" if is_public => KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Ec(
+ EcPublicKey::P256(p256::PublicKey::from_jwk(jwk)?),
+ )),
+ "P-256" => KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Ec(
+ EcPrivateKey::P256(p256::SecretKey::from_jwk(jwk)?),
+ )),
+ "P-384" if is_public => KeyObjectHandle::AsymmetricPublic(AsymmetricPublicKey::Ec(
+ EcPublicKey::P384(p384::PublicKey::from_jwk(jwk)?),
+ )),
+ "P-384" => KeyObjectHandle::AsymmetricPrivate(AsymmetricPrivateKey::Ec(
+ EcPrivateKey::P384(p384::SecretKey::from_jwk(jwk)?),
+ )),
+ _ => {
+ return Err(type_error(format!("unsupported curve: {}", jwk.crv())));
+ }
+ };
+
+ Ok(handle)
+ }
+
+ pub fn new_ed_raw(
+ curve: &str,
+ data: &[u8],
+ is_public: bool,
+ ) -> Result {
+ match curve {
+ "Ed25519" => {
+ let data = data
+ .try_into()
+ .map_err(|_| type_error("invalid Ed25519 key"))?;
+ if !is_public {
+ Ok(KeyObjectHandle::AsymmetricPrivate(
+ AsymmetricPrivateKey::Ed25519(ed25519_dalek::SigningKey::from_bytes(data)),
+ ))
+ } else {
+ Ok(KeyObjectHandle::AsymmetricPublic(
+ AsymmetricPublicKey::Ed25519(ed25519_dalek::VerifyingKey::from_bytes(
+ data,
+ )?),
+ ))
+ }
+ }
+ "X25519" => {
+ let data: [u8; 32] = data
+ .try_into()
+ .map_err(|_| type_error("invalid x25519 key"))?;
+ if !is_public {
+ Ok(KeyObjectHandle::AsymmetricPrivate(
+ AsymmetricPrivateKey::X25519(x25519_dalek::StaticSecret::from(data)),
+ ))
+ } else {
+ Ok(KeyObjectHandle::AsymmetricPublic(
+ AsymmetricPublicKey::X25519(x25519_dalek::PublicKey::from(data)),
+ ))
+ }
+ }
+ _ => Err(type_error("unsupported curve")),
+ }
+ }
+
+ pub fn new_asymmetric_public_key_from_js(
+ key: &[u8],
+ format: &str,
+ typ: &str,
+ passphrase: Option<&[u8]>,
+ ) -> Result {
+ 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
+ | PrivateKeyInfo::PEM_LABEL
+ | sec1::EcPrivateKey::PEM_LABEL
+ | rsa::pkcs1::RsaPrivateKey::PEM_LABEL => {
+ let handle = KeyObjectHandle::new_asymmetric_private_key_from_js(
+ key, format, typ, passphrase,
+ )?;
+ match handle {
+ KeyObjectHandle::AsymmetricPrivate(private) => {
+ return Ok(KeyObjectHandle::AsymmetricPublic(
+ private.to_public_key(),
+ ))
+ }
+ KeyObjectHandle::AsymmetricPublic(_) | KeyObjectHandle::Secret(_) => {
+ unreachable!()
+ }
+ }
+ }
+ // 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 verifying_key = ed25519_dalek::VerifyingKey::try_from(spki)
+ .map_err(|_| type_error("invalid Ed25519 private key"))?;
+ AsymmetricPublicKey::Ed25519(verifying_key)
+ }
+ DH_KEY_AGREEMENT_OID => {
+ let params = spki
+ .algorithm
+ .parameters
+ .ok_or_else(|| type_error("missing dh parameters"))?;
+ let params = pkcs3::DhParameter::from_der(¶ms.to_der().unwrap())
+ .map_err(|_| type_error("malformed dh parameters"))?;
+ 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(DhPublicKey {
+ key: dh::PublicKey::from_bytes(subject_public_key),
+ params,
+ })
+ }
+ _ => return Err(type_error("unsupported public key oid")),
+ };
+
+ Ok(KeyObjectHandle::AsymmetricPublic(public_key))
+ }
+}
+
+fn parse_rsa_pss_params(
+ parameters: Option>,
+) -> Result