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, 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) +} + +use base64::prelude::BASE64_URL_SAFE_NO_PAD; + +fn bytes_to_b64(bytes: &[u8]) -> String { + BASE64_URL_SAFE_NO_PAD.encode(bytes) +} + +impl AsymmetricPublicKey { + fn export_jwk(&self) -> Result { + match self { + AsymmetricPublicKey::Ec(key) => { + let jwk = key.to_jwk()?; + Ok(deno_core::serde_json::json!(jwk)) + } + AsymmetricPublicKey::X25519(key) => { + let bytes = key.as_bytes(); + let jwk = deno_core::serde_json::json!({ + "kty": "OKP", + "crv": "X25519", + "x": bytes_to_b64(bytes), + }); + Ok(jwk) + } + AsymmetricPublicKey::Ed25519(key) => { + let bytes = key.to_bytes(); + let jwk = deno_core::serde_json::json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": bytes_to_b64(&bytes), + }); + Ok(jwk) + } + AsymmetricPublicKey::Rsa(key) => { + let n = key.n(); + let e = key.e(); + + let jwk = deno_core::serde_json::json!({ + "kty": "RSA", + "n": bytes_to_b64(&n.to_bytes_be()), + "e": bytes_to_b64(&e.to_bytes_be()), + }); + Ok(jwk) + } + AsymmetricPublicKey::RsaPss(key) => { + let n = key.key.n(); + let e = key.key.e(); + + let jwk = deno_core::serde_json::json!({ + "kty": "RSA", + "n": bytes_to_b64(&n.to_bytes_be()), + "e": bytes_to_b64(&e.to_bytes_be()), + }); + Ok(jwk) + } + _ => Err(type_error("jwk export not implemented for this key type")), + } + } + + fn export_der(&self, typ: &str) -> Result, 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.key.clone().into_vec(); + let params = key.params.to_der().unwrap(); + let spki = SubjectPublicKeyInfoRef { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: DH_KEY_AGREEMENT_OID, + parameters: Some(AnyRef::new(Tag::Sequence, ¶ms).unwrap()), + }, + 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, 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.key.clone().into_vec(); + let params = key.params.to_der().unwrap(); + let private_key = PrivateKeyInfo { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: DH_KEY_AGREEMENT_OID, + parameters: Some(AnyRef::new(Tag::Sequence, ¶ms).unwrap()), + }, + 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::new_asymmetric_private_key_from_js(key, format, typ, passphrase) +} + +#[op2] +#[cppgc] +pub fn op_node_create_ed_raw( + #[string] curve: &str, + #[buffer] key: &[u8], + is_public: bool, +) -> Result { + KeyObjectHandle::new_ed_raw(curve, key, is_public) +} + +#[derive(serde::Deserialize)] +pub struct RsaJwkKey { + n: String, + e: String, + d: Option, + p: Option, + q: Option, +} + +#[op2] +#[cppgc] +pub fn op_node_create_rsa_jwk( + #[serde] jwk: RsaJwkKey, + is_public: bool, +) -> Result { + KeyObjectHandle::new_rsa_jwk(jwk, is_public) +} + +#[op2] +#[cppgc] +pub fn op_node_create_ec_jwk( + #[serde] jwk: elliptic_curve::JwkEcKey, + is_public: bool, +) -> Result { + KeyObjectHandle::new_ec_jwk(&jwk, is_public) +} + +#[op2] +#[cppgc] +pub fn op_node_create_public_key( + #[buffer] key: &[u8], + #[string] format: &str, + #[string] typ: &str, + #[buffer] passphrase: Option<&[u8]>, +) -> Result { + 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 { + 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 { + 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>, + public_key: RefCell>, +} + +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 { + pair.public_key.borrow_mut().take() +} + +#[op2] +#[cppgc] +pub fn op_node_get_private_key_from_pair( + #[cppgc] pair: &KeyObjectHandlePair, +) -> Option { + 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, +) -> Result { + 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) { + 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) { + 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, // todo: Option<&str> not supproted in ops yet + #[string] mf1_hash_algorithm: Option, // todo: Option<&str> not supproted in ops yet + #[smi] salt_length: Option, +) -> Result { + 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, // todo: Option<&str> not supproted in ops yet + #[string] mf1_hash_algorithm: Option, // todo: Option<&str> not supproted in ops yet + #[smi] salt_length: Option, +) -> Result { + 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 { + 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 { + 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 { + spawn_blocking(move || dsa_generate(modulus_length, divisor_length)) + .await + .unwrap() +} + +fn ec_generate(named_curve: &str) -> Result { + 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 { + ec_generate(named_curve) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_ec_key_async( + #[string] named_curve: String, +) -> Result { + 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 u32_slice_to_u8_slice(slice: &[u32]) -> &[u8] { + // SAFETY: just reinterpreting the slice as u8 + unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, std::mem::size_of_val(slice)) } +} + +fn dh_group_generate(group_name: &str) -> Result { + let (dh, prime, generator) = match group_name { + "modp5" => ( + dh::DiffieHellman::group::(), + dh::Modp1536::MODULUS, + dh::Modp1536::GENERATOR, + ), + "modp14" => ( + dh::DiffieHellman::group::(), + dh::Modp2048::MODULUS, + dh::Modp2048::GENERATOR, + ), + "modp15" => ( + dh::DiffieHellman::group::(), + dh::Modp3072::MODULUS, + dh::Modp3072::GENERATOR, + ), + "modp16" => ( + dh::DiffieHellman::group::(), + dh::Modp4096::MODULUS, + dh::Modp4096::GENERATOR, + ), + "modp17" => ( + dh::DiffieHellman::group::(), + dh::Modp6144::MODULUS, + dh::Modp6144::GENERATOR, + ), + "modp18" => ( + dh::DiffieHellman::group::(), + dh::Modp8192::MODULUS, + dh::Modp8192::GENERATOR, + ), + _ => return Err(type_error("Unsupported group name")), + }; + let params = DhParameter { + prime: asn1::Int::new(u32_slice_to_u8_slice(prime)).unwrap(), + base: asn1::Int::new(generator.to_be_bytes().as_slice()).unwrap(), + private_value_length: None, + }; + Ok(KeyObjectHandlePair::new( + AsymmetricPrivateKey::Dh(DhPrivateKey { + key: dh.private_key, + params: params.clone(), + }), + AsymmetricPublicKey::Dh(DhPublicKey { + key: dh.public_key, + params, + }), + )) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_dh_group_key( + #[string] group_name: &str, +) -> Result { + dh_group_generate(group_name) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_dh_group_key_async( + #[string] group_name: String, +) -> Result { + spawn_blocking(move || dh_group_generate(&group_name)) + .await + .unwrap() +} + +fn dh_generate( + prime: Option<&[u8]>, + prime_len: usize, + generator: usize, +) -> Result { + let prime = prime + .map(|p| p.into()) + .unwrap_or_else(|| Prime::generate(prime_len)); + let dh = dh::DiffieHellman::new(prime.clone(), generator); + let params = DhParameter { + prime: asn1::Int::new(&prime.0.to_bytes_be()).unwrap(), + base: asn1::Int::new(generator.to_be_bytes().as_slice()).unwrap(), + private_value_length: None, + }; + Ok(KeyObjectHandlePair::new( + AsymmetricPrivateKey::Dh(DhPrivateKey { + key: dh.private_key, + params: params.clone(), + }), + AsymmetricPublicKey::Dh(DhPublicKey { + key: dh.public_key, + params, + }), + )) +} + +#[op2] +#[cppgc] +pub fn op_node_generate_dh_key( + #[buffer] prime: Option<&[u8]>, + #[smi] prime_len: usize, + #[smi] generator: usize, +) -> Result { + dh_generate(prime, prime_len, generator) +} + +#[op2(async)] +#[cppgc] +pub async fn op_node_generate_dh_key_async( + #[buffer(copy)] prime: Option>, + #[smi] prime_len: usize, + #[smi] generator: usize, +) -> Result { + 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, 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 { + 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] +#[serde] +pub fn op_node_export_public_key_jwk( + #[cppgc] handle: &KeyObjectHandle, +) -> Result { + let public_key = handle + .as_public_key() + .ok_or_else(|| type_error("key is not an asymmetric public key"))?; + + public_key.export_jwk() +} + +#[op2] +#[string] +pub fn op_node_export_public_key_pem( + #[cppgc] handle: &KeyObjectHandle, + #[string] typ: &str, +) -> Result { + 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, 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 { + 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, 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 { + 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 ad31a8650..d4821a2ed 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,17 @@ use deno_core::StringOrBuffer; use deno_core::ToJsBuffer; use elliptic_curve::sec1::ToEncodedPoint; use hkdf::Hkdf; +use keys::AsymmetricPrivateKey; +use keys::AsymmetricPublicKey; +use keys::EcPrivateKey; +use keys::EcPublicKey; +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 ring::signature::Ed25519KeyPair; use std::future::Future; use std::rc::Rc; @@ -34,24 +29,22 @@ 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 pkcs3; 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(#[bigint] num: i64, #[number] checks: usize) -> bool { @@ -193,7 +186,6 @@ pub fn op_node_public_encrypt( } } -// PATCH(denoland/deno#25570): Mitigates denoland/deno#25279 #[op2(fast)] #[smi] pub fn op_node_create_cipheriv( @@ -201,9 +193,13 @@ pub fn op_node_create_cipheriv( #[string] algorithm: &str, #[buffer] key: &[u8], #[buffer] iv: &[u8], -) -> Result { - let context = cipher::CipherContext::new(algorithm, key, iv)?; - Ok(state.resource_table.add(context)) +) -> u32 { + state + .resource_table + .add(match cipher::CipherContext::new(algorithm, key, iv) { + Ok(context) => context, + Err(_) => return 0, + }) } #[op2(fast)] @@ -240,16 +236,28 @@ pub fn op_node_cipheriv_encrypt( pub fn op_node_cipheriv_final( state: &mut OpState, #[smi] rid: u32, + auto_pad: bool, #[buffer] input: &[u8], - #[buffer] output: &mut [u8], + #[anybuffer] output: &mut [u8], ) -> Result>, AnyError> { let context = state.resource_table.take::(rid)?; let context = Rc::try_unwrap(context).map_err(|_| type_error("Cipher context is already in use"))?; - context.r#final(input, output) + context.r#final(auto_pad, input, output) +} + +#[op2] +#[buffer] +pub fn op_node_cipheriv_take( + state: &mut OpState, + #[smi] rid: u32, +) -> Result>, AnyError> { + let context = state.resource_table.take::(rid)?; + let context = + Rc::try_unwrap(context).map_err(|_| type_error("Cipher context is already in use"))?; + Ok(context.take_tag()) } -// PATCH(denoland/deno#25570): Mitigates denoland/deno#25279 #[op2(fast)] #[smi] pub fn op_node_create_decipheriv( @@ -257,9 +265,13 @@ pub fn op_node_create_decipheriv( #[string] algorithm: &str, #[buffer] key: &[u8], #[buffer] iv: &[u8], -) -> Result { - let context = cipher::DecipherContext::new(algorithm, key, iv)?; - Ok(state.resource_table.add(context)) +) -> u32 { + state + .resource_table + .add(match cipher::DecipherContext::new(algorithm, key, iv) { + Ok(context) => context, + Err(_) => return 0, + }) } #[op2(fast)] @@ -292,164 +304,55 @@ pub fn op_node_decipheriv_decrypt( } #[op2(fast)] +pub fn op_node_decipheriv_take(state: &mut OpState, #[smi] rid: u32) -> Result<(), AnyError> { + let context = state.resource_table.take::(rid)?; + Rc::try_unwrap(context).map_err(|_| type_error("Cipher context is already in use"))?; + Ok(()) +} + +#[op2] pub fn op_node_decipheriv_final( state: &mut OpState, #[smi] rid: u32, + auto_pad: bool, #[buffer] input: &[u8], - #[buffer] output: &mut [u8], + #[anybuffer] output: &mut [u8], #[buffer] auth_tag: &[u8], ) -> Result<(), AnyError> { let context = state.resource_table.take::(rid)?; let context = Rc::try_unwrap(context).map_err(|_| type_error("Cipher context is already in use"))?; - context.r#final(input, output, auth_tag) + context.r#final(auto_pad, input, output, auth_tag) } #[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 { - 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::::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 () { - let signing_key = SigningKey::::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")), - } + #[smi] pss_salt_length: Option, + #[smi] dsa_signature_encoding: u32, +) -> Result, AnyError> { + handle.sign_prehashed(digest_type, digest, pss_salt_length, dsa_signature_encoding) } #[op2] 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], + #[smi] pss_salt_length: Option, + #[smi] dsa_signature_encoding: u32, ) -> Result { - 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::::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 () { - let verifying_key = VerifyingKey::::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, + pss_salt_length, + dsa_signature_encoding, + ) } fn pbkdf2_sync( @@ -503,13 +406,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[..]); @@ -521,11 +424,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 () { @@ -542,317 +449,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 { + 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 { - 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 { - 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::(), - "modp14" => dh::DiffieHellman::group::(), - "modp15" => dh::DiffieHellman::group::(), - "modp16" => dh::DiffieHellman::group::(), - "modp17" => dh::DiffieHellman::group::(), - "modp18" => dh::DiffieHellman::group::(), - _ => 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( @@ -868,16 +490,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, - #[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(#[smi] min: i32, #[smi] max: i32) -> Result { @@ -1218,372 +830,118 @@ pub async fn op_node_gen_prime_async(#[number] size: usize) -> Result Result, AnyError> { + let private = private + .as_private_key() + .ok_or_else(|| type_error("Expected private key"))?; + let public = public + .as_public_key() + .ok_or_else(|| type_error("Expected public key"))?; + + let res = match (private, &*public) { + ( + AsymmetricPrivateKey::Ec(EcPrivateKey::P224(private)), + AsymmetricPublicKey::Ec(EcPublicKey::P224(public)), + ) => p224::ecdh::diffie_hellman(private.to_nonzero_scalar(), public.as_affine()) + .raw_secret_bytes() + .to_vec() + .into_boxed_slice(), + ( + AsymmetricPrivateKey::Ec(EcPrivateKey::P256(private)), + AsymmetricPublicKey::Ec(EcPublicKey::P256(public)), + ) => p256::ecdh::diffie_hellman(private.to_nonzero_scalar(), public.as_affine()) + .raw_secret_bytes() + .to_vec() + .into_boxed_slice(), + ( + AsymmetricPrivateKey::Ec(EcPrivateKey::P384(private)), + AsymmetricPublicKey::Ec(EcPublicKey::P384(public)), + ) => p384::ecdh::diffie_hellman(private.to_nonzero_scalar(), public.as_affine()) + .raw_secret_bytes() + .to_vec() + .into_boxed_slice(), + (AsymmetricPrivateKey::X25519(private), AsymmetricPublicKey::X25519(public)) => private + .diffie_hellman(public) + .to_bytes() + .into_iter() + .collect(), + (AsymmetricPrivateKey::Dh(private), AsymmetricPublicKey::Dh(public)) => { + if private.params.prime != public.params.prime + || private.params.base != public.params.base + { + return Err(type_error("DH parameters mismatch")); + } -// 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> = - 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> = - 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> = - 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, -} + // OSIP - Octet-String-to-Integer primitive + let public_key = public.key.clone().into_vec(); + let pubkey = BigUint::from_bytes_be(&public_key); -// 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 PssPrivateKeyParameters<'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()? - .unwrap_or(*SHA1_HASH_ALGORITHM); - - let mask_gen_algorithm = decoder - .context_specific::( - 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::(SALT_LENGTH_TAG, pkcs8::der::TagMode::Explicit)? - .map(TryInto::try_into) - .transpose()? - .unwrap_or(20); - - Ok(Self { - hash_algorithm, - mask_gen_algorithm, - salt_length, - }) - }) - } -} + // Exponentiation (z = y^x mod p) + let prime = BigUint::from_bytes_be(private.params.prime.as_bytes()); + let private_key = private.key.clone().into_vec(); + let private_key = BigUint::from_bytes_be(&private_key); + let shared_secret = pubkey.modpow(&private_key, &prime); -fn parse_private_key( - key: &[u8], - format: &str, - type_: &str, -) -> Result { - 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) + shared_secret.to_bytes_be().into() } - "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_))), - } + _ => { + return Err(type_error( + "Unsupported key type for diffie hellman, or key type mismatch", + )) } - _ => 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 { - 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")), - } + Ok(res) } -fn parse_public_key(key: &[u8], format: &str, type_: &str) -> Result { - 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(fast)] +pub fn op_node_sign_ed25519( + #[cppgc] key: &KeyObjectHandle, + #[buffer] data: &[u8], + #[buffer] signature: &mut [u8], +) -> Result<(), AnyError> { + let private = key + .as_private_key() + .ok_or_else(|| type_error("Expected private key"))?; + + let ed25519 = match private { + AsymmetricPrivateKey::Ed25519(private) => private, + _ => return Err(type_error("Expected Ed25519 private key")), + }; + + let pair = Ed25519KeyPair::from_seed_unchecked(ed25519.as_bytes().as_slice()) + .map_err(|_| type_error("Invalid Ed25519 private key"))?; + signature.copy_from_slice(pair.sign(data).as_ref()); + + Ok(()) } -#[op2] -#[serde] -pub fn op_node_create_public_key( - #[buffer] key: &[u8], - #[string] format: &str, - #[string] type_: &str, -) -> Result { - let mut doc = None; +#[op2(fast)] +pub fn op_node_verify_ed25519( + #[cppgc] key: &KeyObjectHandle, + #[buffer] data: &[u8], + #[buffer] signature: &[u8], +) -> Result { + let public = key + .as_public_key() + .ok_or_else(|| type_error("Expected public key"))?; - 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 ed25519 = match &*public { + AsymmetricPublicKey::Ed25519(public) => public, + _ => return Err(type_error("Expected Ed25519 public 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")), - } + let verified = ring::signature::UnparsedPublicKey::new( + &ring::signature::ED25519, + ed25519.as_bytes().as_slice(), + ) + .verify(data, signature) + .is_ok(); + + Ok(verified) } diff --git a/ext/node/ops/crypto/pkcs3.rs b/ext/node/ops/crypto/pkcs3.rs new file mode 100644 index 000000000..227159142 --- /dev/null +++ b/ext/node/ops/crypto/pkcs3.rs @@ -0,0 +1,20 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// +// PKCS #3: Diffie-Hellman Key Agreement Standard + +use der::Sequence; +use spki::der; +use spki::der::asn1; + +// The parameters fields associated with OID dhKeyAgreement +// +// DHParameter ::= SEQUENCE { +// prime INTEGER, -- p +// base INTEGER, -- g +// privateValueLength INTEGER OPTIONAL } +#[derive(Clone, Sequence)] +pub struct DhParameter { + pub prime: asn1::Int, + pub base: asn1::Int, + pub private_value_length: Option, +} diff --git a/ext/node/ops/crypto/primes.rs b/ext/node/ops/crypto/primes.rs index a01e4c817..efc4c4cb8 100644 --- a/ext/node/ops/crypto/primes.rs +++ b/ext/node/ops/crypto/primes.rs @@ -8,6 +8,7 @@ use num_traits::Zero; use rand::Rng; use std::ops::Deref; +#[derive(Clone)] pub struct Prime(pub num_bigint_dig::BigUint); impl Prime { diff --git a/ext/node/ops/crypto/sign.rs b/ext/node/ops/crypto/sign.rs new file mode 100644 index 000000000..86d228b4d --- /dev/null +++ b/ext/node/ops/crypto/sign.rs @@ -0,0 +1,308 @@ +// 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 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; +use core::ops::Add; +use ecdsa::der::MaxOverhead; +use ecdsa::der::MaxSize; +use elliptic_curve::generic_array::ArrayLength; +use elliptic_curve::FieldBytesSize; + +fn dsa_signature( + encoding: u32, + signature: ecdsa::Signature, +) -> Result, AnyError> +where + MaxSize: ArrayLength, + as Add>::Output: Add + ArrayLength, +{ + match encoding { + // DER + 0 => Ok(signature.to_der().to_bytes().to_vec().into_boxed_slice()), + // IEEE P1363 + 1 => Ok(signature.to_bytes().to_vec().into_boxed_slice()), + _ => Err(type_error("invalid DSA signature encoding")), + } +} + +impl KeyObjectHandle { + pub fn sign_prehashed( + &self, + digest_type: &str, + digest: &[u8], + pss_salt_length: Option, + dsa_signature_encoding: u32, + ) -> Result, 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 () { + rsa::pkcs1v15::Pkcs1v15Sign::new::() + }, + _ => { + 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 => {} + }; + if let Some(s) = pss_salt_length { + salt_length = Some(s as usize); + } + let pss = match_fixed_digest_with_oid!( + digest_type, + fn (algorithm: Option) { + 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::(salt_length) + } else { + rsa::pss::Pss::new::() + } + }, + _ => { + 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 () { + key.sign_prehashed_rfc6979::(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"))?; + + dsa_signature(dsa_signature_encoding, signature) + } + 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"))?; + + dsa_signature(dsa_signature_encoding, signature) + } + 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"))?; + + dsa_signature(dsa_signature_encoding, signature) + } + }, + AsymmetricPrivateKey::X25519(_) => { + Err(type_error("x25519 key cannot be used for signing")) + } + AsymmetricPrivateKey::Ed25519(_) => Err(type_error( + "Ed25519 key cannot be used for prehashed signing", + )), + AsymmetricPrivateKey::Dh(_) => Err(type_error("DH key cannot be used for signing")), + } + } + + pub fn verify_prehashed( + &self, + digest_type: &str, + digest: &[u8], + signature: &[u8], + pss_salt_length: Option, + dsa_signature_encoding: u32, + ) -> Result { + 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 () { + rsa::pkcs1v15::Pkcs1v15Sign::new::() + }, + _ => { + 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 => {} + }; + if let Some(s) = pss_salt_length { + salt_length = Some(s as usize); + } + let pss = match_fixed_digest_with_oid!( + digest_type, + fn (algorithm: Option) { + 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::(salt_length) + } else { + rsa::pss::Pss::new::() + } + }, + _ => { + 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 = if dsa_signature_encoding == 0 { + p224::ecdsa::Signature::from_der(signature) + } else { + p224::ecdsa::Signature::from_bytes(signature.into()) + }; + let Ok(signature) = signature else { + return Ok(false); + }; + Ok(verifying_key.verify_prehash(digest, &signature).is_ok()) + } + EcPublicKey::P256(key) => { + let verifying_key = p256::ecdsa::VerifyingKey::from(key); + let signature = if dsa_signature_encoding == 0 { + p256::ecdsa::Signature::from_der(signature) + } else { + p256::ecdsa::Signature::from_bytes(signature.into()) + }; + let Ok(signature) = signature else { + return Ok(false); + }; + Ok(verifying_key.verify_prehash(digest, &signature).is_ok()) + } + EcPublicKey::P384(key) => { + let verifying_key = p384::ecdsa::VerifyingKey::from(key); + let signature = if dsa_signature_encoding == 0 { + p384::ecdsa::Signature::from_der(signature) + } else { + p384::ecdsa::Signature::from_bytes(signature.into()) + }; + let Ok(signature) = signature else { + return Ok(false); + }; + Ok(verifying_key.verify_prehash(digest, &signature).is_ok()) + } + }, + AsymmetricPublicKey::X25519(_) => { + Err(type_error("x25519 key cannot be used for verification")) + } + AsymmetricPublicKey::Ed25519(_) => Err(type_error( + "Ed25519 key cannot be used for prehashed verification", + )), + AsymmetricPublicKey::Dh(_) => Err(type_error("DH key cannot be used for verification")), + } + } +} diff --git a/ext/node/ops/crypto/x509.rs b/ext/node/ops/crypto/x509.rs index bc47de35f..d2d0b1bb5 100644 --- a/ext/node/ops/crypto/x509.rs +++ b/ext/node/ops/crypto/x509.rs @@ -2,7 +2,6 @@ use deno_core::error::AnyError; use deno_core::op2; -use deno_core::v8; use x509_parser::der_parser::asn1_rs::Any; use x509_parser::der_parser::asn1_rs::Tag; @@ -11,19 +10,33 @@ use x509_parser::extensions; use x509_parser::pem; use x509_parser::prelude::*; +use super::KeyObjectHandle; + +use std::ops::Deref; +use yoke::Yoke; +use yoke::Yokeable; + use digest::Digest; +enum CertificateSources { + Der(Box<[u8]>), + Pem(pem::Pem), +} + +#[derive(Yokeable)] +struct CertificateView<'a> { + cert: X509Certificate<'a>, +} + pub(crate) struct Certificate { - _buf: Vec, - pem: Option, - cert: X509Certificate<'static>, + inner: Yoke, Box>, } impl deno_core::GarbageCollected for Certificate {} impl Certificate { fn fingerprint(&self) -> Option { - self.pem.as_ref().map(|pem| { + if let CertificateSources::Pem(pem) = self.inner.backing_cart().as_ref() { let mut hasher = D::new(); hasher.update(&pem.contents); let bytes = hasher.finalize(); @@ -33,13 +46,15 @@ impl Certificate { hex.push_str(&format!("{:02X}:", byte)); } hex.pop(); - hex - }) + Some(hex) + } else { + None + } } } -impl std::ops::Deref for Certificate { - type Target = X509Certificate<'static>; +impl<'a> Deref for CertificateView<'a> { + type Target = X509Certificate<'a>; fn deref(&self) -> &Self::Target { &self.cert @@ -47,34 +62,32 @@ impl std::ops::Deref for Certificate { } #[op2] -pub fn op_node_x509_parse<'s>( - scope: &'s mut v8::HandleScope, - #[buffer] buf: &[u8], -) -> Result, AnyError> { - let pem = match pem::parse_x509_pem(buf) { - Ok((_, pem)) => Some(pem), - Err(_) => None, +#[cppgc] +pub fn op_node_x509_parse(#[buffer] buf: &[u8]) -> Result { + let source = match pem::parse_x509_pem(buf) { + Ok((_, pem)) => CertificateSources::Pem(pem), + Err(_) => CertificateSources::Der(buf.to_vec().into_boxed_slice()), }; - let cert = pem - .as_ref() - .map(|pem| pem.parse_x509()) - .unwrap_or_else(|| X509Certificate::from_der(buf).map(|(_, cert)| cert))?; - - let cert = Certificate { - _buf: buf.to_vec(), - // SAFETY: Extending the lifetime of the certificate. Backing buffer is - // owned by the resource. - cert: unsafe { std::mem::transmute::, X509Certificate<'_>>(cert) }, - pem, - }; + let inner = Yoke::, Box>::try_attach_to_cart( + Box::new(source), + |source| { + let cert = match source { + CertificateSources::Pem(pem) => pem.parse_x509()?, + CertificateSources::Der(buf) => { + X509Certificate::from_der(buf).map(|(_, cert)| cert)? + } + }; + Ok::<_, AnyError>(CertificateView { cert }) + }, + )?; - let obj = deno_core::cppgc::make_cppgc_object(scope, cert); - Ok(obj) + Ok(Certificate { inner }) } #[op2(fast)] pub fn op_node_x509_ca(#[cppgc] cert: &Certificate) -> Result { + let cert = cert.inner.get().deref(); Ok(cert.is_ca()) } @@ -83,6 +96,7 @@ pub fn op_node_x509_check_email( #[cppgc] cert: &Certificate, #[string] email: &str, ) -> Result { + let cert = cert.inner.get().deref(); let subject = cert.subject(); if subject .iter_email() @@ -138,15 +152,26 @@ pub fn op_node_x509_fingerprint512( #[op2] #[string] pub fn op_node_x509_get_issuer(#[cppgc] cert: &Certificate) -> Result { + let cert = cert.inner.get().deref(); Ok(x509name_to_string(cert.issuer(), oid_registry())?) } #[op2] #[string] pub fn op_node_x509_get_subject(#[cppgc] cert: &Certificate) -> Result { + let cert = cert.inner.get().deref(); Ok(x509name_to_string(cert.subject(), oid_registry())?) } +#[op2] +#[cppgc] +pub fn op_node_x509_public_key(#[cppgc] cert: &Certificate) -> Result { + let cert = cert.inner.get().deref(); + let public_key = &cert.tbs_certificate.subject_pki; + + KeyObjectHandle::new_x509_public_key(public_key) +} + // Attempt to convert attribute to string. If type is not a string, return value is the hex // encoding of the attribute value fn attribute_value_to_string(attr: &Any, _attr_type: &Oid) -> Result { @@ -204,18 +229,21 @@ fn x509name_to_string( #[op2] #[string] pub fn op_node_x509_get_valid_from(#[cppgc] cert: &Certificate) -> Result { + let cert = cert.inner.get().deref(); Ok(cert.validity().not_before.to_string()) } #[op2] #[string] pub fn op_node_x509_get_valid_to(#[cppgc] cert: &Certificate) -> Result { + let cert = cert.inner.get().deref(); Ok(cert.validity().not_after.to_string()) } #[op2] #[string] pub fn op_node_x509_get_serial_number(#[cppgc] cert: &Certificate) -> Result { + let cert = cert.inner.get().deref(); let mut s = cert.serial.to_str_radix(16); s.make_ascii_uppercase(); Ok(s) @@ -223,6 +251,7 @@ pub fn op_node_x509_get_serial_number(#[cppgc] cert: &Certificate) -> Result Result { + let cert = cert.inner.get().deref(); let key_usage = cert .extensions() .iter() 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)[kKeyType] !== undefined ); } -export function isCryptoKey(obj: unknown): boolean { +export function isCryptoKey( + obj: unknown, +): obj is CryptoKey { return ( obj != null && (obj as Record)[kKeyObject] !== undefined ); diff --git a/ext/node/polyfills/internal/crypto/_randomFill.mjs b/ext/node/polyfills/internal/crypto/_randomFill.mjs index 5de756536..8ef864562 100644 --- a/ext/node/polyfills/internal/crypto/_randomFill.mjs +++ b/ext/node/polyfills/internal/crypto/_randomFill.mjs @@ -1,16 +1,11 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // 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 { 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 { 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) { @@ -86,8 +73,10 @@ export function randomFillSync(buf, offset = 0, size) { return buf; } - const bytes = new Uint8Array(buf.buffer ? buf.buffer : buf, offset, size); - op_node_generate_secret(bytes); + const bytes = isAnyArrayBuffer(buf) + ? new Uint8Array(buf, offset, size) + : new Uint8Array(buf.buffer, buf.byteOffset + offset, size); + 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 2589004f6..95e11a124 100644 --- a/ext/node/polyfills/internal/crypto/cipher.ts +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -12,11 +12,13 @@ import { op_node_cipheriv_encrypt, op_node_cipheriv_final, op_node_cipheriv_set_aad, + op_node_cipheriv_take, op_node_create_cipheriv, op_node_create_decipheriv, op_node_decipheriv_decrypt, op_node_decipheriv_final, op_node_decipheriv_set_aad, + op_node_decipheriv_take, op_node_private_decrypt, op_node_private_encrypt, op_node_public_encrypt, @@ -41,7 +43,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) || @@ -161,6 +165,8 @@ export class Cipheriv extends Transform implements Cipher { #authTag?: Buffer; + #autoPadding = true; + constructor( cipher: string, key: CipherKey, @@ -189,8 +195,17 @@ export class Cipheriv extends Transform implements Cipher { final(encoding: string = getDefaultEncoding()): Buffer | string { const buf = new Buffer(16); + if (this.#cache.cache.byteLength == 0) { + const maybeTag = op_node_cipheriv_take(this.#context); + if (maybeTag) this.#authTag = Buffer.from(maybeTag); + return encoding === "buffer" ? Buffer.from([]) : ""; + } + if (!this.#autoPadding && this.#cache.cache.byteLength != 16) { + throw new Error("Invalid final block size"); + } const maybeTag = op_node_cipheriv_final( this.#context, + this.#autoPadding, this.#cache.cache, buf, ); @@ -215,8 +230,8 @@ export class Cipheriv extends Transform implements Cipher { return this; } - setAutoPadding(_autoPadding?: boolean): this { - notImplemented("crypto.Cipheriv.prototype.setAutoPadding"); + setAutoPadding(autoPadding?: boolean): this { + this.#autoPadding = !!autoPadding; return this; } @@ -298,6 +313,8 @@ export class Decipheriv extends Transform implements Cipher { /** DecipherContext resource id */ #context: number; + #autoPadding = true; + /** ciphertext data cache */ #cache: BlockModeCache; @@ -332,18 +349,23 @@ export class Decipheriv extends Transform implements Cipher { } final(encoding: string = getDefaultEncoding()): Buffer | string { + if (!this.#needsBlockCache || this.#cache.cache.byteLength === 0) { + op_node_decipheriv_take(this.#context); + return encoding === "buffer" ? Buffer.from([]) : ""; + } + if (this.#cache.cache.byteLength != 16) { + throw new Error("Invalid final block size"); + } + let buf = new Buffer(16); op_node_decipheriv_final( this.#context, + this.#autoPadding, this.#cache.cache, buf, this.#authTag || NO_TAG, ); - if (!this.#needsBlockCache) { - return encoding === "buffer" ? Buffer.from([]) : ""; - } - buf = buf.subarray(0, 16 - buf.at(-1)); // Padded in Pkcs7 mode return encoding === "buffer" ? buf : buf.toString(encoding); } @@ -363,8 +385,9 @@ export class Decipheriv extends Transform implements Cipher { return this; } - setAutoPadding(_autoPadding?: boolean): this { - notImplemented("crypto.Decipheriv.prototype.setAutoPadding"); + setAutoPadding(autoPadding?: boolean): this { + this.#autoPadding = Boolean(autoPadding); + return this; } update( diff --git a/ext/node/polyfills/internal/crypto/diffiehellman.ts b/ext/node/polyfills/internal/crypto/diffiehellman.ts index 6058433ba..a439306a9 100644 --- a/ext/node/polyfills/internal/crypto/diffiehellman.ts +++ b/ext/node/polyfills/internal/crypto/diffiehellman.ts @@ -6,7 +6,8 @@ import { op_node_dh_compute_secret, - op_node_dh_generate2, + op_node_dh_keys_generate_and_export, + op_node_diffie_hellman, op_node_ecdh_compute_public_key, op_node_ecdh_compute_secret, op_node_ecdh_encode_pubkey, @@ -40,7 +41,12 @@ import type { BinaryToTextEncoding, ECDHKeyFormat, } from "ext:deno_node/internal/crypto/types.ts"; -import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts"; +import { + getKeyObjectHandle, + kConsumePrivate, + kConsumePublic, + KeyObject, +} from "ext:deno_node/internal/crypto/keys.ts"; import type { BufferEncoding } from "ext:deno_node/_global.d.ts"; const DH_GENERATOR = 2; @@ -198,7 +204,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, @@ -1305,11 +1311,14 @@ export class ECDH { } } -export function diffieHellman(_options: { +export function diffieHellman(options: { privateKey: KeyObject; publicKey: KeyObject; }): Buffer { - notImplemented("crypto.diffieHellman"); + const privateKey = getKeyObjectHandle(options.privateKey, kConsumePrivate); + const publicKey = getKeyObjectHandle(options.publicKey, kConsumePublic); + const bytes = op_node_diffie_hellman(privateKey, publicKey); + return Buffer.from(bytes); } export default { 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..a40c76c0d 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 { @@ -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); + + let privateKey = new PrivateKeyObject(privateKeyHandle); + let 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 { - 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 ca22e12c6..c91c23cc3 100644 --- a/ext/node/polyfills/internal/crypto/keys.ts +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -12,18 +12,31 @@ const { } = primordials; import { + op_node_create_ec_jwk, + op_node_create_ed_raw, 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_rsa_jwk, + 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_jwk, + 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_JWK, ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, @@ -41,23 +54,22 @@ 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, + validateString, } 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 +156,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 +194,6 @@ export class KeyObject { get symmetricKeySize(): number | undefined { notImplemented("crypto.KeyObject.prototype.symmetricKeySize"); - return undefined; } @@ -192,7 +201,6 @@ export class KeyObject { if (!isCryptoKey(key)) { throw new ERR_INVALID_ARG_TYPE("key", "CryptoKey", key); } - notImplemented("crypto.KeyObject.prototype.from"); } @@ -212,12 +220,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 +238,452 @@ export interface JsonWebKeyInput { format: "jwk"; } -export function prepareAsymmetricKey(key) { - if (isStringOrBuffer(key)) { - return { format: "pem", data: getArrayBufferOrView(key, "key") }; - } else if (isKeyObject(key)) { +export 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]; +} + +function getKeyObjectHandleFromJwk(key, ctx) { + validateObject(key, "key"); + validateOneOf( + key.kty, + "key.kty", + ["RSA", "EC", "OKP"], + ); + const isPublic = ctx === kConsumePublic || ctx === kCreatePublic; + + if (key.kty === "OKP") { + validateString(key.crv, "key.crv"); + validateOneOf( + key.crv, + "key.crv", + ["Ed25519", "Ed448", "X25519", "X448"], + ); + validateString(key.x, "key.x"); + + if (!isPublic) { + validateString(key.d, "key.d"); + } + + let keyData; + if (isPublic) { + keyData = Buffer.from(key.x, "base64"); + } else { + keyData = Buffer.from(key.d, "base64"); + } + + switch (key.crv) { + case "Ed25519": + case "X25519": + if (keyData.byteLength !== 32) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + case "Ed448": + if (keyData.byteLength !== 57) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + case "X448": + if (keyData.byteLength !== 56) { + throw new ERR_CRYPTO_INVALID_JWK(); + } + break; + } + + return op_node_create_ed_raw(key.crv, keyData, isPublic); + } + + if (key.kty === "EC") { + validateString(key.crv, "key.crv"); + validateString(key.x, "key.x"); + validateString(key.y, "key.y"); + + if (!isPublic) { + validateString(key.d, "key.d"); + } + + return op_node_create_ec_jwk(key, isPublic); + } + + // RSA + validateString(key.n, "key.n"); + validateString(key.e, "key.e"); + + const jwk = { + kty: key.kty, + n: key.n, + e: key.e, + }; + + if (!isPublic) { + validateString(key.d, "key.d"); + validateString(key.p, "key.p"); + validateString(key.q, "key.q"); + validateString(key.dp, "key.dp"); + validateString(key.dq, "key.dq"); + validateString(key.qi, "key.qi"); + jwk.d = key.d; + jwk.p = key.p; + jwk.q = key.q; + jwk.dp = key.dp; + jwk.dq = key.dq; + jwk.qi = key.qi; + } + + return op_node_create_rsa_jwk(jwk, isPublic); +} + +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 assymetric 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") { + return { + // @ts-ignore __proto__ is magic + __proto__: null, + handle: getKeyObjectHandleFromJwk(data, ctx), + format, + }; + } + // 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); - throw new TypeError("Invalid key type"); + 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); + } + } + + 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 +705,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 +716,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 +735,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 +756,103 @@ 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) { + 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); - } - - 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`, - ); - } + constructor(handle: KeyObjectHandle) { + super("public", handle); } -} -export function setOwnedKey(key: Uint8Array): unknown { - const handle = {}; - KEY_STORE.set(handle, key); - return handle; -} + export(options: JwkKeyExportOptions | KeyExportOptions) { + if (options && options.format === "jwk") { + return op_node_export_public_key_jwk(this[kHandle]); + } + + 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..bcbcb469b 100644 --- a/ext/node/polyfills/internal/crypto/sig.ts +++ b/ext/node/polyfills/internal/crypto/sig.ts @@ -4,9 +4,16 @@ // 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_get_asymmetric_key_type, + op_node_sign, + op_node_sign_ed25519, + op_node_verify, + op_node_verify_ed25519, +} from "ext:core/ops"; -import { notImplemented } from "ext:deno_node/_utils.ts"; import { validateFunction, validateString, @@ -22,12 +29,14 @@ import type { PublicKeyInput, } from "ext:deno_node/internal/crypto/types.ts"; import { + kConsumePrivate, + kConsumePublic, KeyObject, prepareAsymmetricKey, + PrivateKeyObject, + PublicKeyObject, } 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"; @@ -49,6 +58,35 @@ export interface VerifyKeyObjectInput extends SigningOptions { key: KeyObject; } +function getSaltLength(options) { + return getIntOption("saltLength", options); +} + +function getDSASignatureEncoding(options) { + if (typeof options === "object") { + const { dsaEncoding = "der" } = options; + if (dsaEncoding === "der") { + return 0; + } else if (dsaEncoding === "ieee-p1363") { + return 1; + } + throw new ERR_INVALID_ARG_VALUE("options.dsaEncoding", dsaEncoding); + } + + return 0; +} + +function getIntOption(name, options) { + const value = options[name]; + if (value !== undefined) { + if (value === value >> 0) { + return value; + } + throw new ERR_INVALID_ARG_VALUE(`options.${name}`, value); + } + return undefined; +} + export type KeyLike = string | Buffer | KeyObject; export class SignImpl extends Writable { @@ -72,16 +110,35 @@ 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); + + // Options specific to RSA-PSS + const pssSaltLength = getSaltLength(privateKey); + + // Options specific to (EC)DSA + const dsaSigEnc = getDSASignatureEncoding(privateKey); + + 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, + pssSaltLength, + dsaSigEnc, )); return encoding ? ret.toString(encoding) : ret; } @@ -127,33 +184,37 @@ 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); + + // Options specific to RSA-PSS + const pssSaltLength = getSaltLength(publicKey); + + // Options specific to (EC)DSA + const dsaSigEnc = getDSASignatureEncoding(publicKey); + + 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), + pssSaltLength, + dsaSigEnc, ); } } @@ -182,7 +243,34 @@ export function signOneShot( throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); } - const result = Sign(algorithm!).update(data).sign(key); + const res = prepareAsymmetricKey(key, 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, + ); + } + + let result: Buffer; + if (op_node_get_asymmetric_key_type(handle) === "ed25519") { + if (algorithm != null && algorithm !== "sha512") { + throw new TypeError("Only 'sha512' is supported for Ed25519 keys"); + } + result = new Buffer(64); + op_node_sign_ed25519(handle, data, result); + } else if (algorithm == null) { + throw new TypeError( + "Algorithm must be specified when using non-Ed25519 keys", + ); + } else { + result = Sign(algorithm!).update(data) + .sign(new PrivateKeyObject(handle)); + } if (callback) { setTimeout(() => callback(null, result)); @@ -210,7 +298,33 @@ export function verifyOneShot( throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); } - const result = Verify(algorithm!).update(data).verify(key, signature); + const res = prepareAsymmetricKey(key, kConsumePublic); + let handle; + if ("handle" in res) { + handle = res.handle; + } else { + handle = op_node_create_public_key( + res.data, + res.format, + res.type ?? "", + res.passphrase, + ); + } + + let result: boolean; + if (op_node_get_asymmetric_key_type(handle) === "ed25519") { + if (algorithm != null && algorithm !== "sha512") { + throw new TypeError("Only 'sha512' is supported for Ed25519 keys"); + } + result = op_node_verify_ed25519(handle, data, signature); + } else if (algorithm == null) { + throw new TypeError( + "Algorithm must be specified when using non-Ed25519 keys", + ); + } else { + result = Verify(algorithm!).update(data) + .verify(new PublicKeyObject(handle), signature); + } if (callback) { setTimeout(() => callback(null, result)); 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/ext/node/polyfills/internal/crypto/x509.ts b/ext/node/polyfills/internal/crypto/x509.ts index 50a7ab48d..699e45a51 100644 --- a/ext/node/polyfills/internal/crypto/x509.ts +++ b/ext/node/polyfills/internal/crypto/x509.ts @@ -17,9 +17,13 @@ import { op_node_x509_get_valid_to, op_node_x509_key_usage, op_node_x509_parse, + op_node_x509_public_key, } from "ext:core/ops"; -import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts"; +import { + KeyObject, + PublicKeyObject, +} from "ext:deno_node/internal/crypto/keys.ts"; import { Buffer } from "node:buffer"; import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts"; @@ -144,10 +148,9 @@ export class X509Certificate { return result; } - get publicKey(): KeyObject { - notImplemented("crypto.X509Certificate.prototype.publicKey"); - - return {} as KeyObject; + get publicKey(): PublicKeyObject { + const handle = op_node_x509_public_key(this.#handle); + return new PublicKeyObject(handle); } get raw(): Buffer { diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index 6529e9894..a3fba8863 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -905,6 +905,12 @@ export class ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE extends NodeTypeError { } } +export class ERR_CRYPTO_INVALID_JWK extends NodeError { + constructor() { + super("ERR_CRYPTO_INVALID_JWK", "Invalid JWK"); + } +} + export class ERR_CRYPTO_INVALID_STATE extends NodeError { constructor(x: string) { super("ERR_CRYPTO_INVALID_STATE", `Invalid state for operation ${x}`);