1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(ext/node): rewrite crypto keys (#24463)

This completely rewrites how we handle key material in ext/node. Changes
in this
PR:

- **Signing**
  - RSA
  - RSA-PSS 🆕
  - DSA 🆕
  - EC
  - ED25519 🆕
- **Verifying**
  - RSA
  - RSA-PSS 🆕
  - DSA 🆕
  - EC 🆕
  - ED25519 🆕
- **Private key import**
  - Passphrase encrypted private keys 🆕
  - RSA
    - PEM
    - DER (PKCS#1) 🆕
    - DER (PKCS#8) 🆕
  - RSA-PSS
    - PEM
    - DER (PKCS#1) 🆕
    - DER (PKCS#8) 🆕
  - DSA 🆕
  - EC
    - PEM
    - DER (SEC1) 🆕
    - DER (PKCS#8) 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH
- **Public key import**
  - RSA
    - PEM
    - DER (PKCS#1) 🆕
    - DER (PKCS#8) 🆕
  - RSA-PSS 🆕
  - DSA 🆕
  - EC 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH 🆕
- **Private key export**
  - RSA 🆕
  - DSA 🆕
  - EC 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH 🆕
- **Public key export**
  - RSA
  - DSA 🆕
  - EC 🆕
  - X25519 🆕
  - ED25519 🆕
  - DH 🆕
- **Key pair generation**
  - Overhauled, but supported APIs unchanged

This PR adds a lot of new individual functionality. But most importantly
because
of the new key material representation, it is now trivial to add new
algorithms
(as shown by this PR).

Now, when adding a new algorithm, it is also widely supported - for
example
previously we supported ED25519 key pair generation, but we could not
import,
export, sign or verify with ED25519. We can now do all of those things.
This commit is contained in:
Luca Casonato 2024-08-07 08:43:58 +02:00 committed by GitHub
parent 9a83efa04b
commit 4fa8869f24
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 3371 additions and 1172 deletions

65
Cargo.lock generated
View file

@ -1010,6 +1010,7 @@ dependencies = [
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
"fiat-crypto",
"rustc_version 0.4.0",
"subtle",
@ -1748,6 +1749,7 @@ dependencies = [
"aead-gcm-stream",
"aes",
"async-trait",
"base64 0.21.7",
"blake2",
"brotli",
"bytes",
@ -1766,6 +1768,7 @@ dependencies = [
"digest",
"dsa",
"ecb",
"ed25519-dalek",
"elliptic-curve",
"errno 0.2.8",
"faster-hex",
@ -1798,6 +1801,7 @@ dependencies = [
"path-clean",
"pbkdf2",
"pin-project-lite",
"pkcs8",
"rand",
"regex",
"ring",
@ -1820,6 +1824,7 @@ dependencies = [
"windows-sys 0.52.0",
"x25519-dalek",
"x509-parser",
"yoke",
]
[[package]]
@ -2538,6 +2543,32 @@ dependencies = [
"spki",
]
[[package]]
name = "ed25519"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
"signature",
]
[[package]]
name = "ed25519-dalek"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
dependencies = [
"curve25519-dalek",
"ed25519",
"rand_core",
"serde",
"sha2",
"signature",
"subtle",
"zeroize",
]
[[package]]
name = "editpe"
version = "0.1.0"
@ -5012,6 +5043,21 @@ dependencies = [
"spki",
]
[[package]]
name = "pkcs5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
dependencies = [
"aes",
"cbc",
"der",
"pbkdf2",
"scrypt",
"sha2",
"spki",
]
[[package]]
name = "pkcs8"
version = "0.10.2"
@ -5019,6 +5065,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
dependencies = [
"der",
"pkcs5",
"rand_core",
"spki",
]
@ -8375,6 +8423,17 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"zerofrom",
]
[[package]]
name = "zerocopy"
version = "0.7.32"
@ -8396,6 +8455,12 @@ dependencies = [
"syn 2.0.72",
]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
[[package]]
name = "zeroize"
version = "1.7.0"

View file

@ -20,6 +20,7 @@ sync_fs = ["deno_package_json/sync", "node_resolver/sync"]
aead-gcm-stream = "0.1"
aes.workspace = true
async-trait.workspace = true
base64.workspace = true
blake2 = "0.10.6"
brotli.workspace = true
bytes.workspace = true
@ -38,6 +39,7 @@ deno_whoami = "0.1.0"
digest = { version = "0.10.5", features = ["core-api", "std"] }
dsa = "0.6.1"
ecb.workspace = true
ed25519-dalek = { version = "2.1.1", features = ["digest", "pkcs8", "rand_core", "signature"] }
elliptic-curve.workspace = true
errno = "0.2.8"
faster-hex.workspace = true
@ -70,6 +72,7 @@ p384.workspace = true
path-clean = "=0.1.0"
pbkdf2 = "0.12.1"
pin-project-lite = "0.2.13"
pkcs8 = { version = "0.10.2", features = ["std", "pkcs5", "encryption"] }
rand.workspace = true
regex.workspace = true
ring.workspace = true
@ -89,8 +92,9 @@ thiserror.workspace = true
tokio.workspace = true
url.workspace = true
winapi.workspace = true
x25519-dalek = "2.0.0"
x25519-dalek = { version = "2.0.0", features = ["static_secrets"] }
x509-parser = "0.15.0"
yoke = "0.7.4"
[target.'cfg(windows)'.dependencies]
windows-sys.workspace = true

View file

@ -247,25 +247,10 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_pbkdf2_async,
ops::crypto::op_node_hkdf,
ops::crypto::op_node_hkdf_async,
ops::crypto::op_node_generate_secret,
ops::crypto::op_node_generate_secret_async,
ops::crypto::op_node_fill_random,
ops::crypto::op_node_fill_random_async,
ops::crypto::op_node_sign,
ops::crypto::op_node_generate_rsa,
ops::crypto::op_node_generate_rsa_async,
ops::crypto::op_node_dsa_generate,
ops::crypto::op_node_dsa_generate_async,
ops::crypto::op_node_ec_generate,
ops::crypto::op_node_ec_generate_async,
ops::crypto::op_node_ed25519_generate,
ops::crypto::op_node_ed25519_generate_async,
ops::crypto::op_node_x25519_generate,
ops::crypto::op_node_x25519_generate_async,
ops::crypto::op_node_dh_generate_group,
ops::crypto::op_node_dh_generate_group_async,
ops::crypto::op_node_dh_generate,
ops::crypto::op_node_dh_generate2,
ops::crypto::op_node_dh_compute_secret,
ops::crypto::op_node_dh_generate_async,
ops::crypto::op_node_verify,
ops::crypto::op_node_random_int,
ops::crypto::op_node_scrypt_sync,
@ -274,8 +259,41 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_ecdh_compute_secret,
ops::crypto::op_node_ecdh_compute_public_key,
ops::crypto::op_node_ecdh_encode_pubkey,
ops::crypto::op_node_export_rsa_public_pem,
ops::crypto::op_node_export_rsa_spki_der,
ops::crypto::keys::op_node_create_private_key,
ops::crypto::keys::op_node_create_public_key,
ops::crypto::keys::op_node_create_secret_key,
ops::crypto::keys::op_node_derive_public_key_from_private_key,
ops::crypto::keys::op_node_dh_keys_generate_and_export,
ops::crypto::keys::op_node_export_private_key_der,
ops::crypto::keys::op_node_export_private_key_pem,
ops::crypto::keys::op_node_export_public_key_der,
ops::crypto::keys::op_node_export_public_key_pem,
ops::crypto::keys::op_node_export_secret_key_b64url,
ops::crypto::keys::op_node_export_secret_key,
ops::crypto::keys::op_node_generate_dh_group_key_async,
ops::crypto::keys::op_node_generate_dh_group_key,
ops::crypto::keys::op_node_generate_dh_key_async,
ops::crypto::keys::op_node_generate_dh_key,
ops::crypto::keys::op_node_generate_dsa_key_async,
ops::crypto::keys::op_node_generate_dsa_key,
ops::crypto::keys::op_node_generate_ec_key_async,
ops::crypto::keys::op_node_generate_ec_key,
ops::crypto::keys::op_node_generate_ed25519_key_async,
ops::crypto::keys::op_node_generate_ed25519_key,
ops::crypto::keys::op_node_generate_rsa_key_async,
ops::crypto::keys::op_node_generate_rsa_key,
ops::crypto::keys::op_node_generate_rsa_pss_key,
ops::crypto::keys::op_node_generate_rsa_pss_key_async,
ops::crypto::keys::op_node_generate_secret_key_async,
ops::crypto::keys::op_node_generate_secret_key,
ops::crypto::keys::op_node_generate_x25519_key_async,
ops::crypto::keys::op_node_generate_x25519_key,
ops::crypto::keys::op_node_get_asymmetric_key_details,
ops::crypto::keys::op_node_get_asymmetric_key_type,
ops::crypto::keys::op_node_get_private_key_from_pair,
ops::crypto::keys::op_node_get_public_key_from_pair,
ops::crypto::keys::op_node_get_symmetric_key_size,
ops::crypto::keys::op_node_key_type,
ops::crypto::x509::op_node_x509_parse,
ops::crypto::x509::op_node_x509_ca,
ops::crypto::x509::op_node_x509_check_email,
@ -378,8 +396,6 @@ deno_core::extension!(deno_node,
ops::require::op_require_break_on_next_statement,
ops::util::op_node_guess_handle_type,
ops::worker_threads::op_worker_threads_filename<P>,
ops::crypto::op_node_create_private_key,
ops::crypto::op_node_create_public_key,
ops::ipc::op_node_child_ipc_pipe,
ops::ipc::op_node_ipc_write,
ops::ipc::op_node_ipc_read,

View file

@ -5,14 +5,21 @@ use num_bigint_dig::BigUint;
use num_bigint_dig::RandBigInt;
use num_traits::FromPrimitive;
#[derive(Clone)]
pub struct PublicKey(BigUint);
impl PublicKey {
pub fn from_bytes(bytes: &[u8]) -> Self {
let public_key = BigUint::from_bytes_be(bytes);
Self(public_key)
}
pub fn into_vec(self) -> Vec<u8> {
self.0.to_bytes_be()
}
}
#[derive(Clone)]
pub struct PrivateKey(BigUint);
impl PrivateKey {
@ -22,6 +29,11 @@ impl PrivateKey {
Self(exponent)
}
pub fn from_bytes(bytes: &[u8]) -> Self {
let exponent = BigUint::from_bytes_be(bytes);
Self(exponent)
}
/// Diffie-Hellman modular exponentiation.
/// s = g^x mod p
pub fn compute_public_key(

View file

@ -67,7 +67,7 @@ macro_rules! match_fixed_digest {
type $type = ::blake2::Blake2s256;
$body
}
_ => match_fixed_digest_with_eager_block_buffer!($algorithm_name, fn <$type>() $body, _ => $other)
_ => crate::ops::crypto::digest::match_fixed_digest_with_eager_block_buffer!($algorithm_name, fn <$type>() $body, _ => $other)
}
};
}
@ -84,22 +84,24 @@ macro_rules! match_fixed_digest_with_eager_block_buffer {
type $type = crate::ops::crypto::md5_sha1::Md5Sha1;
$body
}
_ => match_fixed_digest_with_oid!($algorithm_name, fn <$type>() $body, _ => $other)
_ => crate::ops::crypto::digest::match_fixed_digest_with_oid!($algorithm_name, fn <$type>() $body, _ => $other)
}
};
}
pub(crate) use match_fixed_digest_with_eager_block_buffer;
macro_rules! match_fixed_digest_with_oid {
($algorithm_name:expr, fn <$type:ident>() $body:block, _ => $other:block) => {
($algorithm_name:expr, fn $(<$type:ident>)?($($hash_algorithm:ident: Option<RsaPssHashAlgorithm>)?) $body:block, _ => $other:block) => {
match $algorithm_name {
"rsa-md5" | "md5" | "md5withrsaencryption" | "ssl3-md5" => {
type $type = ::md5::Md5;
$(let $hash_algorithm = None;)?
$(type $type = ::md5::Md5;)?
$body
}
"rsa-ripemd160" | "ripemd" | "ripemd160" | "ripemd160withrsa"
| "rmd160" => {
type $type = ::ripemd::Ripemd160;
$(let $hash_algorithm = None;)?
$(type $type = ::ripemd::Ripemd160;)?
$body
}
"rsa-sha1"
@ -108,47 +110,58 @@ macro_rules! match_fixed_digest_with_oid {
| "sha1-2"
| "sha1withrsaencryption"
| "ssl3-sha1" => {
type $type = ::sha1::Sha1;
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha1);)?
$(type $type = ::sha1::Sha1;)?
$body
}
"rsa-sha224" | "sha224" | "sha224withrsaencryption" => {
type $type = ::sha2::Sha224;
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha224);)?
$(type $type = ::sha2::Sha224;)?
$body
}
"rsa-sha256" | "sha256" | "sha256withrsaencryption" => {
type $type = ::sha2::Sha256;
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha256);)?
$(type $type = ::sha2::Sha256;)?
$body
}
"rsa-sha384" | "sha384" | "sha384withrsaencryption" => {
type $type = ::sha2::Sha384;
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha384);)?
$(type $type = ::sha2::Sha384;)?
$body
}
"rsa-sha512" | "sha512" | "sha512withrsaencryption" => {
type $type = ::sha2::Sha512;
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512);)?
$(type $type = ::sha2::Sha512;)?
$body
}
"rsa-sha512/224" | "sha512-224" | "sha512-224withrsaencryption" => {
type $type = ::sha2::Sha512_224;
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512_224);)?
$(type $type = ::sha2::Sha512_224;)?
$body
}
"rsa-sha512/256" | "sha512-256" | "sha512-256withrsaencryption" => {
type $type = ::sha2::Sha512_256;
$(let $hash_algorithm = Some(RsaPssHashAlgorithm::Sha512_256);)?
$(type $type = ::sha2::Sha512_256;)?
$body
}
"rsa-sha3-224" | "id-rsassa-pkcs1-v1_5-with-sha3-224" | "sha3-224" => {
type $type = ::sha3::Sha3_224;
$(let $hash_algorithm = None;)?
$(type $type = ::sha3::Sha3_224;)?
$body
}
"rsa-sha3-256" | "id-rsassa-pkcs1-v1_5-with-sha3-256" | "sha3-256" => {
type $type = ::sha3::Sha3_256;
$(let $hash_algorithm = None;)?
$(type $type = ::sha3::Sha3_256;)?
$body
}
"rsa-sha3-384" | "id-rsassa-pkcs1-v1_5-with-sha3-384" | "sha3-384" => {
type $type = ::sha3::Sha3_384;
$(let $hash_algorithm = None;)?
$(type $type = ::sha3::Sha3_384;)?
$body
}
"rsa-sha3-512" | "id-rsassa-pkcs1-v1_5-with-sha3-512" | "sha3-512" => {
type $type = ::sha3::Sha3_512;
$(let $hash_algorithm = None;)?
$(type $type = ::sha3::Sha3_512;)?
$body
}
_ => $other,

1727
ext/node/ops/crypto/keys.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,6 @@ use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::serde_v8::BigInt as V8BigInt;
use deno_core::unsync::spawn_blocking;
use deno_core::JsBuffer;
use deno_core::OpState;
@ -11,21 +10,12 @@ use deno_core::StringOrBuffer;
use deno_core::ToJsBuffer;
use elliptic_curve::sec1::ToEncodedPoint;
use hkdf::Hkdf;
use keys::KeyObjectHandle;
use num_bigint::BigInt;
use num_bigint_dig::BigUint;
use num_traits::FromPrimitive;
use once_cell::sync::Lazy;
use rand::distributions::Distribution;
use rand::distributions::Uniform;
use rand::thread_rng;
use rand::Rng;
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::pkcs8;
use rsa::pkcs8::der::asn1;
use rsa::pkcs8::der::Decode;
use rsa::pkcs8::der::Encode;
use rsa::pkcs8::der::Reader;
use std::future::Future;
use std::rc::Rc;
@ -34,24 +24,21 @@ use p256::NistP256;
use p384::NistP384;
use rsa::pkcs8::DecodePrivateKey;
use rsa::pkcs8::DecodePublicKey;
use rsa::signature::hazmat::PrehashSigner;
use rsa::signature::hazmat::PrehashVerifier;
use rsa::signature::SignatureEncoding;
use rsa::Oaep;
use rsa::Pkcs1v15Encrypt;
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
use spki::EncodePublicKey;
mod cipher;
mod dh;
mod digest;
pub mod keys;
mod md5_sha1;
mod primes;
mod sign;
pub mod x509;
use self::digest::match_fixed_digest_with_eager_block_buffer;
use self::digest::match_fixed_digest_with_oid;
#[op2(fast)]
pub fn op_node_check_prime(
@ -339,156 +326,23 @@ pub fn op_node_decipheriv_final(
}
#[op2]
#[serde]
#[buffer]
pub fn op_node_sign(
#[cppgc] handle: &KeyObjectHandle,
#[buffer] digest: &[u8],
#[string] digest_type: &str,
#[serde] key: StringOrBuffer,
#[string] _type: &str,
#[string] format: &str,
) -> Result<ToJsBuffer, AnyError> {
let (label, doc) =
pkcs8::SecretDocument::from_pem(std::str::from_utf8(&key).unwrap())?;
let oid;
let pkey = match format {
"pem" => match label {
"PRIVATE KEY" => {
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
oid = pk_info.algorithm.oid;
pk_info.private_key
}
"RSA PRIVATE KEY" => {
oid = RSA_ENCRYPTION_OID;
doc.as_bytes()
}
"EC PRIVATE KEY" => {
let ec_pk = sec1::EcPrivateKey::from_der(doc.as_bytes())?;
match ec_pk.parameters {
Some(sec1::EcParameters::NamedCurve(o)) => {
oid = o;
ec_pk.private_key
}
// https://datatracker.ietf.org/doc/html/rfc5915#section-3
//
// Though the ASN.1 indicates that
// the parameters field is OPTIONAL, implementations that conform to
// this document MUST always include the parameters field.
_ => return Err(type_error("invalid ECPrivateKey params")),
}
}
_ => return Err(type_error("Invalid PEM label")),
},
_ => return Err(type_error("Unsupported key format")),
};
match oid {
RSA_ENCRYPTION_OID => {
use rsa::pkcs1v15::SigningKey;
let key = RsaPrivateKey::from_pkcs1_der(pkey)?;
// md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo
// (so has no prefix).
// See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285
if digest_type == "md5-sha1" {
let signing_key = SigningKey::<md5_sha1::Md5Sha1>::new_unprefixed(key);
let signature = signing_key.sign_prehash(digest)?.to_vec();
return Ok(signature.into());
}
let signature = match_fixed_digest_with_oid!(
digest_type,
fn <D>() {
let signing_key = SigningKey::<D>::new(key);
signing_key.sign_prehash(digest)?.to_vec()
},
_ => {
return Err(type_error(format!(
"digest not allowed for RSA signature: {}",
digest_type
)))
}
);
Ok(signature.into())
}
// signature structure encoding is DER by default for DSA and ECDSA.
//
// TODO(@littledivy): Validate public_key if present
ID_SECP256R1_OID => {
let key = p256::ecdsa::SigningKey::from_slice(pkey)?;
Ok(
key
.sign_prehash(digest)
.map(|sig: p256::ecdsa::Signature| sig.to_der().to_vec().into())?,
)
}
ID_SECP384R1_OID => {
let key = p384::ecdsa::SigningKey::from_slice(pkey)?;
Ok(
key
.sign_prehash(digest)
.map(|sig: p384::ecdsa::Signature| sig.to_der().to_vec().into())?,
)
}
_ => Err(type_error("Unsupported signing key")),
}
) -> Result<Box<[u8]>, AnyError> {
handle.sign_prehashed(digest_type, digest)
}
#[op2]
#[op2(fast)]
pub fn op_node_verify(
#[cppgc] handle: &KeyObjectHandle,
#[buffer] digest: &[u8],
#[string] digest_type: &str,
#[serde] key: StringOrBuffer,
#[string] key_type: &str,
#[string] key_format: &str,
#[buffer] signature: &[u8],
) -> Result<bool, AnyError> {
match key_type {
"rsa" => {
use rsa::pkcs1v15::VerifyingKey;
let key = match key_format {
"pem" => RsaPublicKey::from_public_key_pem((&key).try_into()?)
.map_err(|_| type_error("Invalid RSA public key"))?,
// TODO(kt3k): Support der and jwk formats
_ => {
return Err(type_error(format!(
"Unsupported key format: {}",
key_format
)))
}
};
// md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo
// (so has no prefix).
// See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285
if digest_type == "md5-sha1" {
let verifying_key =
VerifyingKey::<md5_sha1::Md5Sha1>::new_unprefixed(key);
let verified = verifying_key
.verify_prehash(digest, &signature.try_into()?)
.is_ok();
return Ok(verified);
}
Ok(match_fixed_digest_with_oid!(
digest_type,
fn <D>() {
let verifying_key = VerifyingKey::<D>::new(key);
verifying_key.verify_prehash(digest, &signature.try_into()?).is_ok()
},
_ => {
return Err(type_error(format!(
"digest not allowed for RSA signature: {}",
digest_type
)))
}
))
}
_ => Err(type_error(format!(
"Verifying with {} keys is not supported",
key_type
))),
}
handle.verify_prehashed(digest_type, digest, signature)
}
fn pbkdf2_sync(
@ -542,13 +396,13 @@ pub async fn op_node_pbkdf2_async(
}
#[op2(fast)]
pub fn op_node_generate_secret(#[buffer] buf: &mut [u8]) {
pub fn op_node_fill_random(#[buffer] buf: &mut [u8]) {
rand::thread_rng().fill(buf);
}
#[op2(async)]
#[serde]
pub async fn op_node_generate_secret_async(#[smi] len: i32) -> ToJsBuffer {
pub async fn op_node_fill_random_async(#[smi] len: i32) -> ToJsBuffer {
spawn_blocking(move || {
let mut buf = vec![0u8; len as usize];
rand::thread_rng().fill(&mut buf[..]);
@ -560,11 +414,15 @@ pub async fn op_node_generate_secret_async(#[smi] len: i32) -> ToJsBuffer {
fn hkdf_sync(
digest_algorithm: &str,
ikm: &[u8],
handle: &KeyObjectHandle,
salt: &[u8],
info: &[u8],
okm: &mut [u8],
) -> Result<(), AnyError> {
let Some(ikm) = handle.as_secret_key() else {
return Err(type_error("expected secret key"));
};
match_fixed_digest_with_eager_block_buffer!(
digest_algorithm,
fn <D>() {
@ -581,328 +439,32 @@ fn hkdf_sync(
#[op2(fast)]
pub fn op_node_hkdf(
#[string] digest_algorithm: &str,
#[buffer] ikm: &[u8],
#[cppgc] handle: &KeyObjectHandle,
#[buffer] salt: &[u8],
#[buffer] info: &[u8],
#[buffer] okm: &mut [u8],
) -> Result<(), AnyError> {
hkdf_sync(digest_algorithm, ikm, salt, info, okm)
hkdf_sync(digest_algorithm, handle, salt, info, okm)
}
#[op2(async)]
#[serde]
pub async fn op_node_hkdf_async(
#[string] digest_algorithm: String,
#[buffer] ikm: JsBuffer,
#[cppgc] handle: &KeyObjectHandle,
#[buffer] salt: JsBuffer,
#[buffer] info: JsBuffer,
#[number] okm_len: usize,
) -> Result<ToJsBuffer, AnyError> {
let handle = handle.clone();
spawn_blocking(move || {
let mut okm = vec![0u8; okm_len];
hkdf_sync(&digest_algorithm, &ikm, &salt, &info, &mut okm)?;
hkdf_sync(&digest_algorithm, &handle, &salt, &info, &mut okm)?;
Ok(okm.into())
})
.await?
}
use rsa::pkcs1::EncodeRsaPrivateKey;
use rsa::pkcs1::EncodeRsaPublicKey;
use self::primes::Prime;
fn generate_rsa(
modulus_length: usize,
public_exponent: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
let mut rng = rand::thread_rng();
let private_key = RsaPrivateKey::new_with_exp(
&mut rng,
modulus_length,
&rsa::BigUint::from_usize(public_exponent).unwrap(),
)?;
let public_key = private_key.to_public_key();
let private_key_der = private_key.to_pkcs1_der()?.as_bytes().to_vec();
let public_key_der = public_key.to_pkcs1_der()?.to_vec();
Ok((private_key_der.into(), public_key_der.into()))
}
#[op2]
#[serde]
pub fn op_node_generate_rsa(
#[number] modulus_length: usize,
#[number] public_exponent: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
generate_rsa(modulus_length, public_exponent)
}
#[op2(async)]
#[serde]
pub async fn op_node_generate_rsa_async(
#[number] modulus_length: usize,
#[number] public_exponent: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
spawn_blocking(move || generate_rsa(modulus_length, public_exponent)).await?
}
#[op2]
#[string]
pub fn op_node_export_rsa_public_pem(
#[buffer] pkcs1_der: &[u8],
) -> Result<String, AnyError> {
let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?;
let export = public_key.to_public_key_pem(Default::default())?;
Ok(export)
}
#[op2]
#[serde]
pub fn op_node_export_rsa_spki_der(
#[buffer] pkcs1_der: &[u8],
) -> Result<ToJsBuffer, AnyError> {
let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?;
let export = public_key.to_public_key_der()?.to_vec();
Ok(export.into())
}
fn dsa_generate(
modulus_length: usize,
divisor_length: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
let mut rng = rand::thread_rng();
use dsa::pkcs8::EncodePrivateKey;
use dsa::Components;
use dsa::KeySize;
use dsa::SigningKey;
let key_size = match (modulus_length, divisor_length) {
#[allow(deprecated)]
(1024, 160) => KeySize::DSA_1024_160,
(2048, 224) => KeySize::DSA_2048_224,
(2048, 256) => KeySize::DSA_2048_256,
(3072, 256) => KeySize::DSA_3072_256,
_ => return Err(type_error("Invalid modulus_length or divisor_length")),
};
let components = Components::generate(&mut rng, key_size);
let signing_key = SigningKey::generate(&mut rng, components);
let verifying_key = signing_key.verifying_key();
Ok((
signing_key
.to_pkcs8_der()
.map_err(|_| type_error("Not valid pkcs8"))?
.as_bytes()
.to_vec()
.into(),
verifying_key
.to_public_key_der()
.map_err(|_| type_error("Not valid spki"))?
.to_vec()
.into(),
))
}
#[op2]
#[serde]
pub fn op_node_dsa_generate(
#[number] modulus_length: usize,
#[number] divisor_length: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
dsa_generate(modulus_length, divisor_length)
}
#[op2(async)]
#[serde]
pub async fn op_node_dsa_generate_async(
#[number] modulus_length: usize,
#[number] divisor_length: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
spawn_blocking(move || dsa_generate(modulus_length, divisor_length)).await?
}
fn ec_generate(
named_curve: &str,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
let mut rng = rand::thread_rng();
// TODO(@littledivy): Support public key point encoding.
// Default is uncompressed.
match named_curve {
"P-256" | "prime256v1" | "secp256r1" => {
let key = p256::SecretKey::random(&mut rng);
let public_key = key.public_key();
Ok((
key.to_bytes().to_vec().into(),
public_key.to_encoded_point(false).as_ref().to_vec().into(),
))
}
"P-384" | "prime384v1" | "secp384r1" => {
let key = p384::SecretKey::random(&mut rng);
let public_key = key.public_key();
Ok((
key.to_bytes().to_vec().into(),
public_key.to_encoded_point(false).as_ref().to_vec().into(),
))
}
_ => Err(type_error("Unsupported named curve")),
}
}
#[op2]
#[serde]
pub fn op_node_ec_generate(
#[string] named_curve: &str,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
ec_generate(named_curve)
}
#[op2(async)]
#[serde]
pub async fn op_node_ec_generate_async(
#[string] named_curve: String,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
spawn_blocking(move || ec_generate(&named_curve)).await?
}
fn ed25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
use ring::signature::Ed25519KeyPair;
use ring::signature::KeyPair;
let mut rng = thread_rng();
let mut seed = vec![0u8; 32];
rng.fill(seed.as_mut_slice());
let pair = Ed25519KeyPair::from_seed_unchecked(&seed)
.map_err(|_| type_error("Failed to generate Ed25519 key"))?;
let public_key = pair.public_key().as_ref().to_vec();
Ok((seed.into(), public_key.into()))
}
#[op2]
#[serde]
pub fn op_node_ed25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError>
{
ed25519_generate()
}
#[op2(async)]
#[serde]
pub async fn op_node_ed25519_generate_async(
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
spawn_blocking(ed25519_generate).await?
}
fn x25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
// u-coordinate of the base point.
const X25519_BASEPOINT_BYTES: [u8; 32] = [
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0,
];
let mut pkey = [0; 32];
let mut rng = thread_rng();
rng.fill(pkey.as_mut_slice());
let pkey_copy = pkey.to_vec();
// https://www.rfc-editor.org/rfc/rfc7748#section-6.1
// pubkey = x25519(a, 9) which is constant-time Montgomery ladder.
// https://eprint.iacr.org/2014/140.pdf page 4
// https://eprint.iacr.org/2017/212.pdf algorithm 8
// pubkey is in LE order.
let pubkey = x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES);
Ok((pkey_copy.into(), pubkey.to_vec().into()))
}
#[op2]
#[serde]
pub fn op_node_x25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
x25519_generate()
}
#[op2(async)]
#[serde]
pub async fn op_node_x25519_generate_async(
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
spawn_blocking(x25519_generate).await?
}
fn dh_generate_group(
group_name: &str,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
let dh = match group_name {
"modp5" => dh::DiffieHellman::group::<dh::Modp1536>(),
"modp14" => dh::DiffieHellman::group::<dh::Modp2048>(),
"modp15" => dh::DiffieHellman::group::<dh::Modp3072>(),
"modp16" => dh::DiffieHellman::group::<dh::Modp4096>(),
"modp17" => dh::DiffieHellman::group::<dh::Modp6144>(),
"modp18" => dh::DiffieHellman::group::<dh::Modp8192>(),
_ => return Err(type_error("Unsupported group name")),
};
Ok((
dh.private_key.into_vec().into(),
dh.public_key.into_vec().into(),
))
}
#[op2]
#[serde]
pub fn op_node_dh_generate_group(
#[string] group_name: &str,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
dh_generate_group(group_name)
}
#[op2(async)]
#[serde]
pub async fn op_node_dh_generate_group_async(
#[string] group_name: String,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
spawn_blocking(move || dh_generate_group(&group_name)).await?
}
fn dh_generate(
prime: Option<&[u8]>,
prime_len: usize,
generator: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
let prime = prime
.map(|p| p.into())
.unwrap_or_else(|| Prime::generate(prime_len));
let dh = dh::DiffieHellman::new(prime, generator);
Ok((
dh.private_key.into_vec().into(),
dh.public_key.into_vec().into(),
))
}
#[op2]
#[serde]
pub fn op_node_dh_generate(
#[serde] prime: Option<&[u8]>,
#[number] prime_len: usize,
#[number] generator: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
dh_generate(prime, prime_len, generator)
}
// TODO(lev): This duplication should be avoided.
#[op2]
#[serde]
pub fn op_node_dh_generate2(
#[buffer] prime: JsBuffer,
#[number] prime_len: usize,
#[number] generator: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
dh_generate(Some(prime).as_deref(), prime_len, generator)
}
#[op2]
#[serde]
pub fn op_node_dh_compute_secret(
@ -918,17 +480,6 @@ pub fn op_node_dh_compute_secret(
Ok(shared_secret.to_bytes_be().into())
}
#[op2(async)]
#[serde]
pub async fn op_node_dh_generate_async(
#[buffer] prime: Option<JsBuffer>,
#[number] prime_len: usize,
#[number] generator: usize,
) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
spawn_blocking(move || dh_generate(prime.as_deref(), prime_len, generator))
.await?
}
#[op2(fast)]
#[smi]
pub fn op_node_random_int(
@ -1288,392 +839,3 @@ pub async fn op_node_gen_prime_async(
) -> Result<ToJsBuffer, AnyError> {
Ok(spawn_blocking(move || gen_prime(size)).await?)
}
#[derive(serde::Serialize)]
#[serde(tag = "type")]
pub enum AsymmetricKeyDetails {
#[serde(rename = "rsa")]
#[serde(rename_all = "camelCase")]
Rsa {
modulus_length: usize,
public_exponent: V8BigInt,
},
#[serde(rename = "rsa-pss")]
#[serde(rename_all = "camelCase")]
RsaPss {
modulus_length: usize,
public_exponent: V8BigInt,
hash_algorithm: String,
salt_length: u32,
},
#[serde(rename = "ec")]
#[serde(rename_all = "camelCase")]
Ec { named_curve: String },
#[serde(rename = "dh")]
Dh,
}
// https://oidref.com/
const ID_SHA1_OID: rsa::pkcs8::ObjectIdentifier =
rsa::pkcs8::ObjectIdentifier::new_unwrap("1.3.14.3.2.26");
const ID_SHA256_OID: rsa::pkcs8::ObjectIdentifier =
rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
const ID_SHA384_OID: rsa::pkcs8::ObjectIdentifier =
rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2");
const ID_SHA512_OID: rsa::pkcs8::ObjectIdentifier =
rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3");
const ID_MFG1: rsa::pkcs8::ObjectIdentifier =
rsa::pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.8");
pub const ID_SECP256R1_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
pub const ID_SECP384R1_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.34");
pub const ID_SECP521R1_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.35");
// Default HashAlgorithm for RSASSA-PSS-params (sha1)
//
// sha1 HashAlgorithm ::= {
// algorithm id-sha1,
// parameters SHA1Parameters : NULL
// }
//
// SHA1Parameters ::= NULL
static SHA1_HASH_ALGORITHM: Lazy<rsa::pkcs8::AlgorithmIdentifierRef<'static>> =
Lazy::new(|| rsa::pkcs8::AlgorithmIdentifierRef {
// id-sha1
oid: ID_SHA1_OID,
// NULL
parameters: Some(asn1::AnyRef::from(asn1::Null)),
});
// TODO(@littledivy): `pkcs8` should provide AlgorithmIdentifier to Any conversion.
static ENCODED_SHA1_HASH_ALGORITHM: Lazy<Vec<u8>> =
Lazy::new(|| SHA1_HASH_ALGORITHM.to_der().unwrap());
// Default MaskGenAlgrithm for RSASSA-PSS-params (mgf1SHA1)
//
// mgf1SHA1 MaskGenAlgorithm ::= {
// algorithm id-mgf1,
// parameters HashAlgorithm : sha1
// }
static MGF1_SHA1_MASK_ALGORITHM: Lazy<
rsa::pkcs8::AlgorithmIdentifierRef<'static>,
> = Lazy::new(|| rsa::pkcs8::AlgorithmIdentifierRef {
// id-mgf1
oid: ID_MFG1,
// sha1
parameters: Some(
asn1::AnyRef::from_der(&ENCODED_SHA1_HASH_ALGORITHM).unwrap(),
),
});
pub const RSA_ENCRYPTION_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
pub const DH_KEY_AGREEMENT_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.3.1");
pub const RSASSA_PSS_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10");
pub const EC_OID: const_oid::ObjectIdentifier =
const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
// The parameters field associated with OID id-RSASSA-PSS
// Defined in RFC 3447, section A.2.3
//
// RSASSA-PSS-params ::= SEQUENCE {
// hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
// saltLength [2] INTEGER DEFAULT 20,
// trailerField [3] TrailerField DEFAULT trailerFieldBC
// }
pub struct PssPrivateKeyParameters<'a> {
pub hash_algorithm: rsa::pkcs8::AlgorithmIdentifierRef<'a>,
#[allow(dead_code)]
pub mask_gen_algorithm: rsa::pkcs8::AlgorithmIdentifierRef<'a>,
pub salt_length: u32,
}
// Context-specific tag number for hashAlgorithm.
const HASH_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber =
rsa::pkcs8::der::TagNumber::new(0);
// Context-specific tag number for maskGenAlgorithm.
const MASK_GEN_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber =
rsa::pkcs8::der::TagNumber::new(1);
// Context-specific tag number for saltLength.
const SALT_LENGTH_TAG: rsa::pkcs8::der::TagNumber =
rsa::pkcs8::der::TagNumber::new(2);
impl<'a> TryFrom<rsa::pkcs8::der::asn1::AnyRef<'a>>
for PssPrivateKeyParameters<'a>
{
type Error = rsa::pkcs8::der::Error;
fn try_from(
any: rsa::pkcs8::der::asn1::AnyRef<'a>,
) -> rsa::pkcs8::der::Result<PssPrivateKeyParameters> {
any.sequence(|decoder| {
let hash_algorithm = decoder
.context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>(
HASH_ALGORITHM_TAG,
pkcs8::der::TagMode::Explicit,
)?
.map(TryInto::try_into)
.transpose()?
.unwrap_or(*SHA1_HASH_ALGORITHM);
let mask_gen_algorithm = decoder
.context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>(
MASK_GEN_ALGORITHM_TAG,
pkcs8::der::TagMode::Explicit,
)?
.map(TryInto::try_into)
.transpose()?
.unwrap_or(*MGF1_SHA1_MASK_ALGORITHM);
let salt_length = decoder
.context_specific::<u32>(
SALT_LENGTH_TAG,
pkcs8::der::TagMode::Explicit,
)?
.map(TryInto::try_into)
.transpose()?
.unwrap_or(20);
Ok(Self {
hash_algorithm,
mask_gen_algorithm,
salt_length,
})
})
}
}
fn parse_private_key(
key: &[u8],
format: &str,
type_: &str,
) -> Result<pkcs8::SecretDocument, AnyError> {
match format {
"pem" => {
let pem = std::str::from_utf8(key).map_err(|err| {
type_error(format!(
"Invalid PEM private key: not valid utf8 starting at byte {}",
err.valid_up_to()
))
})?;
let (_, doc) = pkcs8::SecretDocument::from_pem(pem)?;
Ok(doc)
}
"der" => {
match type_ {
"pkcs8" => pkcs8::SecretDocument::from_pkcs8_der(key)
.map_err(|_| type_error("Invalid PKCS8 private key")),
"pkcs1" => pkcs8::SecretDocument::from_pkcs1_der(key)
.map_err(|_| type_error("Invalid PKCS1 private key")),
// TODO(@littledivy): sec1 type
_ => Err(type_error(format!("Unsupported key type: {}", type_))),
}
}
_ => Err(type_error(format!("Unsupported key format: {}", format))),
}
}
#[op2]
#[serde]
pub fn op_node_create_private_key(
#[buffer] key: &[u8],
#[string] format: &str,
#[string] type_: &str,
) -> Result<AsymmetricKeyDetails, AnyError> {
use rsa::pkcs1::der::Decode;
let doc = parse_private_key(key, format, type_)?;
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
let alg = pk_info.algorithm.oid;
match alg {
RSA_ENCRYPTION_OID => {
let private_key =
rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?;
let modulus_length = private_key.modulus.as_bytes().len() * 8;
Ok(AsymmetricKeyDetails::Rsa {
modulus_length,
public_exponent: BigInt::from_bytes_be(
num_bigint::Sign::Plus,
private_key.public_exponent.as_bytes(),
)
.into(),
})
}
DH_KEY_AGREEMENT_OID => Ok(AsymmetricKeyDetails::Dh),
RSASSA_PSS_OID => {
let params = PssPrivateKeyParameters::try_from(
pk_info
.algorithm
.parameters
.ok_or_else(|| type_error("Malformed parameters".to_string()))?,
)
.map_err(|_| type_error("Malformed parameters".to_string()))?;
let hash_alg = params.hash_algorithm;
let hash_algorithm = match hash_alg.oid {
ID_SHA1_OID => "sha1",
ID_SHA256_OID => "sha256",
ID_SHA384_OID => "sha384",
ID_SHA512_OID => "sha512",
_ => return Err(type_error("Unsupported hash algorithm")),
};
let private_key =
rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?;
let modulus_length = private_key.modulus.as_bytes().len() * 8;
Ok(AsymmetricKeyDetails::RsaPss {
modulus_length,
public_exponent: BigInt::from_bytes_be(
num_bigint::Sign::Plus,
private_key.public_exponent.as_bytes(),
)
.into(),
hash_algorithm: hash_algorithm.to_string(),
salt_length: params.salt_length,
})
}
EC_OID => {
let named_curve = pk_info
.algorithm
.parameters_oid()
.map_err(|_| type_error("malformed parameters"))?;
let named_curve = match named_curve {
ID_SECP256R1_OID => "p256",
ID_SECP384R1_OID => "p384",
ID_SECP521R1_OID => "p521",
_ => return Err(type_error("Unsupported named curve")),
};
Ok(AsymmetricKeyDetails::Ec {
named_curve: named_curve.to_string(),
})
}
_ => Err(type_error("Unsupported algorithm")),
}
}
fn parse_public_key(
key: &[u8],
format: &str,
type_: &str,
) -> Result<pkcs8::Document, AnyError> {
match format {
"pem" => {
let pem = std::str::from_utf8(key).map_err(|err| {
type_error(format!(
"Invalid PEM private key: not valid utf8 starting at byte {}",
err.valid_up_to()
))
})?;
let (label, doc) = pkcs8::Document::from_pem(pem)?;
if label != "PUBLIC KEY" {
return Err(type_error("Invalid PEM label"));
}
Ok(doc)
}
"der" => match type_ {
"pkcs1" => pkcs8::Document::from_pkcs1_der(key)
.map_err(|_| type_error("Invalid PKCS1 public key")),
_ => Err(type_error(format!("Unsupported key type: {}", type_))),
},
_ => Err(type_error(format!("Unsupported key format: {}", format))),
}
}
#[op2]
#[serde]
pub fn op_node_create_public_key(
#[buffer] key: &[u8],
#[string] format: &str,
#[string] type_: &str,
) -> Result<AsymmetricKeyDetails, AnyError> {
let mut doc = None;
let pk_info = if type_ != "spki" {
doc.replace(parse_public_key(key, format, type_)?);
spki::SubjectPublicKeyInfoRef::try_from(doc.as_ref().unwrap().as_bytes())?
} else {
spki::SubjectPublicKeyInfoRef::try_from(key)?
};
let alg = pk_info.algorithm.oid;
match alg {
RSA_ENCRYPTION_OID => {
let public_key = rsa::pkcs1::RsaPublicKey::from_der(
pk_info.subject_public_key.raw_bytes(),
)?;
let modulus_length = public_key.modulus.as_bytes().len() * 8;
Ok(AsymmetricKeyDetails::Rsa {
modulus_length,
public_exponent: BigInt::from_bytes_be(
num_bigint::Sign::Plus,
public_key.public_exponent.as_bytes(),
)
.into(),
})
}
RSASSA_PSS_OID => {
let params = PssPrivateKeyParameters::try_from(
pk_info
.algorithm
.parameters
.ok_or_else(|| type_error("Malformed parameters".to_string()))?,
)
.map_err(|_| type_error("Malformed parameters".to_string()))?;
let hash_alg = params.hash_algorithm;
let hash_algorithm = match hash_alg.oid {
ID_SHA1_OID => "sha1",
ID_SHA256_OID => "sha256",
ID_SHA384_OID => "sha384",
ID_SHA512_OID => "sha512",
_ => return Err(type_error("Unsupported hash algorithm")),
};
let public_key = rsa::pkcs1::RsaPublicKey::from_der(
pk_info.subject_public_key.raw_bytes(),
)?;
let modulus_length = public_key.modulus.as_bytes().len() * 8;
Ok(AsymmetricKeyDetails::RsaPss {
modulus_length,
public_exponent: BigInt::from_bytes_be(
num_bigint::Sign::Plus,
public_key.public_exponent.as_bytes(),
)
.into(),
hash_algorithm: hash_algorithm.to_string(),
salt_length: params.salt_length,
})
}
EC_OID => {
let named_curve = pk_info
.algorithm
.parameters_oid()
.map_err(|_| type_error("malformed parameters"))?;
let named_curve = match named_curve {
ID_SECP256R1_OID => "p256",
ID_SECP384R1_OID => "p384",
ID_SECP521R1_OID => "p521",
_ => return Err(type_error("Unsupported named curve")),
};
Ok(AsymmetricKeyDetails::Ec {
named_curve: named_curve.to_string(),
})
}
DH_KEY_AGREEMENT_OID => Ok(AsymmetricKeyDetails::Dh),
_ => Err(type_error("Unsupported algorithm")),
}
}

396
ext/node/ops/crypto/sign.rs Normal file
View file

@ -0,0 +1,396 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use digest::Digest;
use digest::FixedOutput;
use digest::FixedOutputReset;
use digest::OutputSizeUser;
use digest::Reset;
use digest::Update;
use rand::rngs::OsRng;
use rsa::signature::hazmat::PrehashSigner as _;
use rsa::signature::hazmat::PrehashVerifier as _;
use rsa::traits::SignatureScheme as _;
use spki::der::Decode;
use crate::ops::crypto::digest::match_fixed_digest;
use crate::ops::crypto::digest::match_fixed_digest_with_oid;
use super::keys::AsymmetricPrivateKey;
use super::keys::AsymmetricPublicKey;
use super::keys::EcPrivateKey;
use super::keys::EcPublicKey;
use super::keys::KeyObjectHandle;
use super::keys::RsaPssHashAlgorithm;
impl KeyObjectHandle {
pub fn sign_prehashed(
&self,
digest_type: &str,
digest: &[u8],
) -> Result<Box<[u8]>, AnyError> {
let private_key = self
.as_private_key()
.ok_or_else(|| type_error("key is not a private key"))?;
match private_key {
AsymmetricPrivateKey::Rsa(key) => {
let signer = if digest_type == "md5-sha1" {
rsa::pkcs1v15::Pkcs1v15Sign::new_unprefixed()
} else {
match_fixed_digest_with_oid!(
digest_type,
fn <D>() {
rsa::pkcs1v15::Pkcs1v15Sign::new::<D>()
},
_ => {
return Err(type_error(format!(
"digest not allowed for RSA signature: {}",
digest_type
)))
}
)
};
let signature = signer
.sign(Some(&mut OsRng), key, digest)
.map_err(|_| generic_error("failed to sign digest with RSA"))?;
Ok(signature.into())
}
AsymmetricPrivateKey::RsaPss(key) => {
let mut hash_algorithm = None;
let mut salt_length = None;
match &key.details {
Some(details) => {
if details.hash_algorithm != details.mf1_hash_algorithm {
return Err(type_error(
"rsa-pss with different mf1 hash algorithm and hash algorithm is not supported",
));
}
hash_algorithm = Some(details.hash_algorithm);
salt_length = Some(details.salt_length as usize);
}
None => {}
};
let pss = match_fixed_digest_with_oid!(
digest_type,
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
if let Some(hash_algorithm) = hash_algorithm.take() {
if Some(hash_algorithm) != algorithm {
return Err(type_error(format!(
"private key does not allow {} to be used, expected {}",
digest_type, hash_algorithm.as_str()
)));
}
}
if let Some(salt_length) = salt_length {
rsa::pss::Pss::new_with_salt::<D>(salt_length)
} else {
rsa::pss::Pss::new::<D>()
}
},
_ => {
return Err(type_error(format!(
"digest not allowed for RSA-PSS signature: {}",
digest_type
)))
}
);
let signature = pss
.sign(Some(&mut OsRng), &key.key, digest)
.map_err(|_| generic_error("failed to sign digest with RSA-PSS"))?;
Ok(signature.into())
}
AsymmetricPrivateKey::Dsa(key) => {
let res = match_fixed_digest!(
digest_type,
fn <D>() {
key.sign_prehashed_rfc6979::<D>(digest)
},
_ => {
return Err(type_error(format!(
"digest not allowed for RSA signature: {}",
digest_type
)))
}
);
let signature =
res.map_err(|_| generic_error("failed to sign digest with DSA"))?;
Ok(signature.into())
}
AsymmetricPrivateKey::Ec(key) => match key {
EcPrivateKey::P224(key) => {
let signing_key = p224::ecdsa::SigningKey::from(key);
let signature: p224::ecdsa::Signature = signing_key
.sign_prehash(digest)
.map_err(|_| type_error("failed to sign digest"))?;
Ok(signature.to_der().to_bytes())
}
EcPrivateKey::P256(key) => {
let signing_key = p256::ecdsa::SigningKey::from(key);
let signature: p256::ecdsa::Signature = signing_key
.sign_prehash(digest)
.map_err(|_| type_error("failed to sign digest"))?;
Ok(signature.to_der().to_bytes())
}
EcPrivateKey::P384(key) => {
let signing_key = p384::ecdsa::SigningKey::from(key);
let signature: p384::ecdsa::Signature = signing_key
.sign_prehash(digest)
.map_err(|_| type_error("failed to sign digest"))?;
Ok(signature.to_der().to_bytes())
}
},
AsymmetricPrivateKey::X25519(_) => {
Err(type_error("x25519 key cannot be used for signing"))
}
AsymmetricPrivateKey::Ed25519(key) => {
if !matches!(
digest_type,
"rsa-sha512" | "sha512" | "sha512withrsaencryption"
) {
return Err(type_error(format!(
"digest not allowed for Ed25519 signature: {}",
digest_type
)));
}
let mut precomputed_digest = PrecomputedDigest([0; 64]);
if digest.len() != precomputed_digest.0.len() {
return Err(type_error("Invalid sha512 digest"));
}
precomputed_digest.0.copy_from_slice(digest);
let signature = key
.sign_prehashed(precomputed_digest, None)
.map_err(|_| generic_error("failed to sign digest with Ed25519"))?;
Ok(signature.to_bytes().into())
}
AsymmetricPrivateKey::Dh(_) => {
Err(type_error("DH key cannot be used for signing"))
}
}
}
pub fn verify_prehashed(
&self,
digest_type: &str,
digest: &[u8],
signature: &[u8],
) -> Result<bool, AnyError> {
let public_key = self
.as_public_key()
.ok_or_else(|| type_error("key is not a public or private key"))?;
match &*public_key {
AsymmetricPublicKey::Rsa(key) => {
let signer = if digest_type == "md5-sha1" {
rsa::pkcs1v15::Pkcs1v15Sign::new_unprefixed()
} else {
match_fixed_digest_with_oid!(
digest_type,
fn <D>() {
rsa::pkcs1v15::Pkcs1v15Sign::new::<D>()
},
_ => {
return Err(type_error(format!(
"digest not allowed for RSA signature: {}",
digest_type
)))
}
)
};
Ok(signer.verify(key, digest, signature).is_ok())
}
AsymmetricPublicKey::RsaPss(key) => {
let mut hash_algorithm = None;
let mut salt_length = None;
match &key.details {
Some(details) => {
if details.hash_algorithm != details.mf1_hash_algorithm {
return Err(type_error(
"rsa-pss with different mf1 hash algorithm and hash algorithm is not supported",
));
}
hash_algorithm = Some(details.hash_algorithm);
salt_length = Some(details.salt_length as usize);
}
None => {}
};
let pss = match_fixed_digest_with_oid!(
digest_type,
fn <D>(algorithm: Option<RsaPssHashAlgorithm>) {
if let Some(hash_algorithm) = hash_algorithm.take() {
if Some(hash_algorithm) != algorithm {
return Err(type_error(format!(
"private key does not allow {} to be used, expected {}",
digest_type, hash_algorithm.as_str()
)));
}
}
if let Some(salt_length) = salt_length {
rsa::pss::Pss::new_with_salt::<D>(salt_length)
} else {
rsa::pss::Pss::new::<D>()
}
},
_ => {
return Err(type_error(format!(
"digest not allowed for RSA-PSS signature: {}",
digest_type
)))
}
);
Ok(pss.verify(&key.key, digest, signature).is_ok())
}
AsymmetricPublicKey::Dsa(key) => {
let signature = dsa::Signature::from_der(signature)
.map_err(|_| type_error("Invalid DSA signature"))?;
Ok(key.verify_prehash(digest, &signature).is_ok())
}
AsymmetricPublicKey::Ec(key) => match key {
EcPublicKey::P224(key) => {
let verifying_key = p224::ecdsa::VerifyingKey::from(key);
let signature = p224::ecdsa::Signature::from_der(signature)
.map_err(|_| type_error("Invalid ECDSA signature"))?;
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
}
EcPublicKey::P256(key) => {
let verifying_key = p256::ecdsa::VerifyingKey::from(key);
let signature = p256::ecdsa::Signature::from_der(signature)
.map_err(|_| type_error("Invalid ECDSA signature"))?;
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
}
EcPublicKey::P384(key) => {
let verifying_key = p384::ecdsa::VerifyingKey::from(key);
let signature = p384::ecdsa::Signature::from_der(signature)
.map_err(|_| type_error("Invalid ECDSA signature"))?;
Ok(verifying_key.verify_prehash(digest, &signature).is_ok())
}
},
AsymmetricPublicKey::X25519(_) => {
Err(type_error("x25519 key cannot be used for verification"))
}
AsymmetricPublicKey::Ed25519(key) => {
if !matches!(
digest_type,
"rsa-sha512" | "sha512" | "sha512withrsaencryption"
) {
return Err(type_error(format!(
"digest not allowed for Ed25519 signature: {}",
digest_type
)));
}
let mut signature_fixed = [0u8; 64];
if signature.len() != signature_fixed.len() {
return Err(type_error("Invalid Ed25519 signature"));
}
signature_fixed.copy_from_slice(signature);
let signature = ed25519_dalek::Signature::from_bytes(&signature_fixed);
let mut precomputed_digest = PrecomputedDigest([0; 64]);
precomputed_digest.0.copy_from_slice(digest);
Ok(
key
.verify_prehashed_strict(precomputed_digest, None, &signature)
.is_ok(),
)
}
AsymmetricPublicKey::Dh(_) => {
Err(type_error("DH key cannot be used for verification"))
}
}
}
}
struct PrecomputedDigest([u8; 64]);
impl OutputSizeUser for PrecomputedDigest {
type OutputSize = <sha2::Sha512 as OutputSizeUser>::OutputSize;
}
impl Digest for PrecomputedDigest {
fn new() -> Self {
unreachable!()
}
fn new_with_prefix(_data: impl AsRef<[u8]>) -> Self {
unreachable!()
}
fn update(&mut self, _data: impl AsRef<[u8]>) {
unreachable!()
}
fn chain_update(self, _data: impl AsRef<[u8]>) -> Self {
unreachable!()
}
fn finalize(self) -> digest::Output<Self> {
self.0.into()
}
fn finalize_into(self, _out: &mut digest::Output<Self>) {
unreachable!()
}
fn finalize_reset(&mut self) -> digest::Output<Self>
where
Self: digest::FixedOutputReset,
{
unreachable!()
}
fn finalize_into_reset(&mut self, _out: &mut digest::Output<Self>)
where
Self: digest::FixedOutputReset,
{
unreachable!()
}
fn reset(&mut self)
where
Self: digest::Reset,
{
unreachable!()
}
fn output_size() -> usize {
unreachable!()
}
fn digest(_data: impl AsRef<[u8]>) -> digest::Output<Self> {
unreachable!()
}
}
impl Reset for PrecomputedDigest {
fn reset(&mut self) {
unreachable!()
}
}
impl FixedOutputReset for PrecomputedDigest {
fn finalize_into_reset(&mut self, _out: &mut digest::Output<Self>) {
unreachable!()
}
}
impl FixedOutput for PrecomputedDigest {
fn finalize_into(self, _out: &mut digest::Output<Self>) {
unreachable!()
}
}
impl Update for PrecomputedDigest {
fn update(&mut self, _data: &[u8]) {
unreachable!()
}
}

View file

@ -93,7 +93,6 @@ impl ContextifyContext {
sandbox_obj: v8::Local<v8::Object>,
) {
let tmp = init_global_template(scope, ContextInitMode::UseSnapshot);
let context = create_v8_context(scope, tmp, ContextInitMode::UseSnapshot);
Self::from_context(scope, context, sandbox_obj);
}

View file

@ -2,20 +2,18 @@
import { EventEmitter } from "ext:deno_node/_events.d.ts";
import { Buffer } from "node:buffer";
/** One of:
* | "ascii"
* | "utf8"
* | "utf-8"
* | "utf16le"
* | "ucs2"
* | "ucs-2"
* | "base64"
* | "base64url"
* | "latin1"
* | "binary"
* | "hex";
*/
export type BufferEncoding = string;
export type BufferEncoding =
| "ascii"
| "utf8"
| "utf-8"
| "utf16le"
| "ucs2"
| "ucs-2"
| "base64"
| "base64url"
| "latin1"
| "binary"
| "hex";
export interface Buffered {
chunk: Buffer;

View file

@ -1,19 +1,25 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This file is here because to break a circular dependency between streams and
// crypto.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { kKeyObject } from "ext:deno_node/internal/crypto/constants.ts";
import type { KeyObject } from "ext:deno_node/internal/crypto/keys.ts";
export const kKeyType = Symbol("kKeyType");
export function isKeyObject(obj: unknown): boolean {
export function isKeyObject(obj: unknown): obj is KeyObject {
return (
obj != null && (obj as Record<symbol, unknown>)[kKeyType] !== undefined
);
}
export function isCryptoKey(obj: unknown): boolean {
export function isCryptoKey(
obj: unknown,
): obj is CryptoKey {
return (
obj != null && (obj as Record<symbol, unknown>)[kKeyObject] !== undefined
);

View file

@ -3,14 +3,9 @@
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import {
op_node_generate_secret,
op_node_generate_secret_async,
} from "ext:core/ops";
import { 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) {
@ -89,7 +76,7 @@ export function randomFillSync(buf, offset = 0, size) {
const bytes = isAnyArrayBuffer(buf)
? new Uint8Array(buf, offset, size)
: new Uint8Array(buf.buffer, buf.byteOffset + offset, size);
op_node_generate_secret(bytes);
op_node_fill_random(bytes);
return buf;
}

View file

@ -41,7 +41,9 @@ import {
isArrayBufferView,
} from "ext:deno_node/internal/util/types.ts";
export function isStringOrBuffer(val) {
export function isStringOrBuffer(
val: unknown,
): val is string | Buffer | ArrayBuffer | ArrayBufferView {
return typeof val === "string" ||
isArrayBufferView(val) ||
isAnyArrayBuffer(val) ||

View file

@ -6,7 +6,7 @@
import {
op_node_dh_compute_secret,
op_node_dh_generate2,
op_node_dh_keys_generate_and_export,
op_node_ecdh_compute_public_key,
op_node_ecdh_compute_secret,
op_node_ecdh_encode_pubkey,
@ -198,7 +198,7 @@ export class DiffieHellman {
generateKeys(encoding: BinaryToTextEncoding): string;
generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string {
const generator = this.#checkGenerator();
const [privateKey, publicKey] = op_node_dh_generate2(
const [privateKey, publicKey] = op_node_dh_keys_generate_and_export(
this.#prime,
this.#primeLength ?? 0,
generator,

View file

@ -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);

View file

@ -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);
}

View file

@ -10,7 +10,6 @@ import {
PrivateKeyObject,
PublicKeyObject,
SecretKeyObject,
setOwnedKey,
} from "ext:deno_node/internal/crypto/keys.ts";
import { notImplemented } from "ext:deno_node/_utils.ts";
import {
@ -32,22 +31,26 @@ import { Buffer } from "node:buffer";
import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
import {
op_node_dh_generate,
op_node_dh_generate_async,
op_node_dh_generate_group,
op_node_dh_generate_group_async,
op_node_dsa_generate,
op_node_dsa_generate_async,
op_node_ec_generate,
op_node_ec_generate_async,
op_node_ed25519_generate,
op_node_ed25519_generate_async,
op_node_generate_rsa,
op_node_generate_rsa_async,
op_node_generate_secret,
op_node_generate_secret_async,
op_node_x25519_generate,
op_node_x25519_generate_async,
op_node_generate_dh_group_key,
op_node_generate_dh_group_key_async,
op_node_generate_dh_key,
op_node_generate_dh_key_async,
op_node_generate_dsa_key,
op_node_generate_dsa_key_async,
op_node_generate_ec_key,
op_node_generate_ec_key_async,
op_node_generate_ed25519_key,
op_node_generate_ed25519_key_async,
op_node_generate_rsa_key,
op_node_generate_rsa_key_async,
op_node_generate_rsa_pss_key,
op_node_generate_rsa_pss_key_async,
op_node_generate_secret_key,
op_node_generate_secret_key_async,
op_node_generate_x25519_key,
op_node_generate_x25519_key_async,
op_node_get_private_key_from_pair,
op_node_get_public_key_from_pair,
} from "ext:core/ops";
function validateGenerateKey(
@ -82,10 +85,11 @@ export function generateKeySync(
validateGenerateKey(type, options);
const { length } = options;
const key = new Uint8Array(Math.floor(length / 8));
op_node_generate_secret(key);
const len = Math.floor(length / 8);
return new SecretKeyObject(setOwnedKey(key));
const handle = op_node_generate_secret_key(len);
return new SecretKeyObject(handle);
}
export function generateKey(
@ -99,11 +103,11 @@ export function generateKey(
validateFunction(callback, "callback");
const { length } = options;
op_node_generate_secret_async(Math.floor(length / 8)).then(
(key) => {
callback(null, new SecretKeyObject(setOwnedKey(key)));
},
);
const len = Math.floor(length / 8);
op_node_generate_secret_key_async(len).then((handle) => {
callback(null, new SecretKeyObject(handle));
});
}
export interface BasePrivateKeyEncodingOptions<T extends KeyFormat> {
@ -565,9 +569,12 @@ export function generateKeyPair(
privateKey: any,
) => void,
) {
createJob(kAsync, type, options).then(([privateKey, publicKey]) => {
privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type });
publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type });
createJob(kAsync, type, options).then((pair) => {
const privateKeyHandle = op_node_get_private_key_from_pair(pair);
const publicKeyHandle = op_node_get_public_key_from_pair(pair);
const privateKey = new PrivateKeyObject(privateKeyHandle);
const publicKey = new PublicKeyObject(publicKeyHandle);
if (typeof options === "object" && options !== null) {
const { publicKeyEncoding, privateKeyEncoding } = options as any;
@ -766,10 +773,13 @@ export function generateKeyPairSync(
):
| KeyPairKeyObjectResult
| KeyPairSyncResult<string | Buffer, string | Buffer> {
let [privateKey, publicKey] = createJob(kSync, type, options);
const pair = createJob(kSync, type, options);
privateKey = new PrivateKeyObject(setOwnedKey(privateKey), { type });
publicKey = new PublicKeyObject(setOwnedKey(publicKey), { type });
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;
@ -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,

View file

@ -14,16 +14,24 @@ const {
import {
op_node_create_private_key,
op_node_create_public_key,
op_node_export_rsa_public_pem,
op_node_export_rsa_spki_der,
op_node_create_secret_key,
op_node_derive_public_key_from_private_key,
op_node_export_private_key_der,
op_node_export_private_key_pem,
op_node_export_public_key_der,
op_node_export_public_key_pem,
op_node_export_secret_key,
op_node_export_secret_key_b64url,
op_node_get_asymmetric_key_details,
op_node_get_asymmetric_key_type,
op_node_get_symmetric_key_size,
op_node_key_type,
} from "ext:core/ops";
import {
kHandle,
kKeyObject,
} from "ext:deno_node/internal/crypto/constants.ts";
import { kHandle } from "ext:deno_node/internal/crypto/constants.ts";
import { isStringOrBuffer } from "ext:deno_node/internal/crypto/cipher.ts";
import {
ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
@ -41,23 +49,21 @@ import {
} from "ext:deno_node/internal/util/types.ts";
import { hideStackFrames } from "ext:deno_node/internal/errors.ts";
import {
isCryptoKey as isCryptoKey_,
isKeyObject as isKeyObject_,
isCryptoKey,
isKeyObject,
kKeyType,
} from "ext:deno_node/internal/crypto/_keys.ts";
import {
validateObject,
validateOneOf,
} from "ext:deno_node/internal/validators.mjs";
import {
forgivingBase64UrlEncode as encodeToBase64Url,
} from "ext:deno_web/00_infra.js";
import { BufferEncoding } from "ext:deno_node/_global.d.ts";
export const getArrayBufferOrView = hideStackFrames(
(
buffer,
name,
encoding,
buffer: ArrayBufferView | ArrayBuffer | string | Buffer,
name: string,
encoding?: BufferEncoding | "buffer",
):
| ArrayBuffer
| SharedArrayBuffer
@ -144,32 +150,30 @@ export interface JwkKeyExportOptions {
format: "jwk";
}
export function isKeyObject(obj: unknown): obj is KeyObject {
return isKeyObject_(obj);
export enum KeyHandleContext {
kConsumePublic = 0,
kConsumePrivate = 1,
kCreatePublic = 2,
kCreatePrivate = 3,
}
export function isCryptoKey(
obj: unknown,
): obj is { type: string; [kKeyObject]: KeyObject } {
return isCryptoKey_(obj);
export const kConsumePublic = KeyHandleContext.kConsumePublic;
export const kConsumePrivate = KeyHandleContext.kConsumePrivate;
export const kCreatePublic = KeyHandleContext.kCreatePublic;
export const kCreatePrivate = KeyHandleContext.kCreatePrivate;
function isJwk(obj: unknown): obj is { kty: unknown } {
// @ts-ignore this is fine
return typeof obj === "object" && obj != null && obj.kty !== undefined;
}
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()
);
}
const KEY_STORE = new WeakMap();
export type KeyObjectHandle = { ___keyObjectHandle: true };
export class KeyObject {
[kKeyType]: KeyObjectType;
[kHandle]: unknown;
[kHandle]: KeyObjectHandle;
constructor(type: KeyObjectType, handle: unknown) {
constructor(type: KeyObjectType, handle: KeyObjectHandle) {
if (type !== "secret" && type !== "public" && type !== "private") {
throw new ERR_INVALID_ARG_VALUE("type", type);
}
@ -184,7 +188,6 @@ export class KeyObject {
get symmetricKeySize(): number | undefined {
notImplemented("crypto.KeyObject.prototype.symmetricKeySize");
return undefined;
}
@ -192,7 +195,6 @@ export class KeyObject {
if (!isCryptoKey(key)) {
throw new ERR_INVALID_ARG_TYPE("key", "CryptoKey", key);
}
notImplemented("crypto.KeyObject.prototype.from");
}
@ -212,12 +214,13 @@ export class KeyObject {
export(options?: KeyExportOptions<"der">): Buffer;
export(options?: JwkKeyExportOptions): JsonWebKey;
export(_options?: unknown): string | Buffer | JsonWebKey {
notImplemented("crypto.KeyObject.prototype.asymmetricKeyType");
notImplemented("crypto.KeyObject.prototype.export");
}
}
ObjectDefineProperties(KeyObject.prototype, {
[SymbolToStringTag]: {
// @ts-expect-error __proto__ is magic
__proto__: null,
configurable: true,
value: "KeyObject",
@ -229,48 +232,356 @@ export interface JsonWebKeyInput {
format: "jwk";
}
export function prepareAsymmetricKey(key) {
if (isStringOrBuffer(key)) {
return { format: "pem", data: getArrayBufferOrView(key, "key") };
} else if (isKeyObject(key)) {
return {
// Assumes that asymmetric keys are stored as PEM.
format: "pem",
data: getKeyMaterial(key),
};
} else if (typeof key == "object") {
const { key: data, encoding, format, type } = key;
if (!isStringOrBuffer(data)) {
throw new TypeError("Invalid key type");
}
return {
data: getArrayBufferOrView(data, "key", encoding),
format: format ?? "pem",
encoding,
type,
};
function getKeyObjectHandle(key: KeyObject, ctx: KeyHandleContext) {
if (ctx === kCreatePrivate) {
throw new ERR_INVALID_ARG_TYPE(
"key",
["string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"],
key,
);
}
throw new TypeError("Invalid key type");
if (key.type !== "private") {
if (ctx === kConsumePrivate || ctx === kCreatePublic) {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "private");
}
if (key.type !== "public") {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(
key.type,
"private or public",
);
}
}
return key[kHandle];
}
export function prepareAsymmetricKey(
key:
| string
| ArrayBuffer
| Buffer
| ArrayBufferView
| KeyObject
| CryptoKey
| PrivateKeyInput
| PublicKeyInput
| JsonWebKeyInput,
ctx: KeyHandleContext,
):
| { handle: KeyObjectHandle; format?: "jwk" }
| {
data: ArrayBuffer | ArrayBufferView;
format: KeyFormat;
type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined;
passphrase: Buffer | ArrayBuffer | ArrayBufferView | undefined;
} {
if (isKeyObject(key)) {
// Best case: A key object, as simple as that.
return {
// @ts-ignore __proto__ is magic
__proto__: null,
handle: getKeyObjectHandle(key, ctx),
};
} else if (isCryptoKey(key)) {
notImplemented("using CryptoKey as input");
} else if (isStringOrBuffer(key)) {
// Expect PEM by default, mostly for backward compatibility.
return {
// @ts-ignore __proto__ is magic
__proto__: null,
format: "pem",
data: getArrayBufferOrView(key, "key"),
};
} else if (typeof key === "object") {
const { key: data, format } = key;
// The 'key' property can be a KeyObject as well to allow specifying
// additional options such as padding along with the key.
if (isKeyObject(data)) {
return {
// @ts-ignore __proto__ is magic
__proto__: null,
handle: getKeyObjectHandle(data, ctx),
};
} else if (isCryptoKey(data)) {
notImplemented("using CryptoKey as input");
} else if (isJwk(data) && format === "jwk") {
notImplemented("using JWK as input");
}
// Either PEM or DER using PKCS#1 or SPKI.
if (!isStringOrBuffer(data)) {
throw new ERR_INVALID_ARG_TYPE(
"key.key",
getKeyTypes(ctx !== kCreatePrivate),
data,
);
}
const isPublic = (ctx === kConsumePrivate || ctx === kCreatePrivate)
? false
: undefined;
return {
data: getArrayBufferOrView(
data,
"key",
(key as PrivateKeyInput | PublicKeyInput).encoding,
),
...parseKeyEncoding(key, undefined, isPublic),
};
}
throw new ERR_INVALID_ARG_TYPE(
"key",
getKeyTypes(ctx !== kCreatePrivate),
key,
);
}
function parseKeyEncoding(
enc: {
cipher?: string;
passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView;
encoding?: BufferEncoding | "buffer";
format?: string;
type?: string;
},
keyType: string | undefined,
isPublic: boolean | undefined,
objName?: string,
): {
format: KeyFormat;
type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined;
passphrase: Buffer | ArrayBuffer | ArrayBufferView | undefined;
cipher: string | undefined;
} {
if (enc === null || typeof enc !== "object") {
throw new ERR_INVALID_ARG_TYPE("options", "object", enc);
}
const isInput = keyType === undefined;
const {
format,
type,
} = parseKeyFormatAndType(enc, keyType, isPublic, objName);
let cipher, passphrase, encoding;
if (isPublic !== true) {
({ cipher, passphrase, encoding } = enc);
if (!isInput) {
if (cipher != null) {
if (typeof cipher !== "string") {
throw new ERR_INVALID_ARG_VALUE(option("cipher", objName), cipher);
}
if (
format === "der" &&
(type === "pkcs1" || type === "sec1")
) {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
type,
"does not support encryption",
);
}
} else if (passphrase !== undefined) {
throw new ERR_INVALID_ARG_VALUE(option("cipher", objName), cipher);
}
}
if (
(isInput && passphrase !== undefined &&
!isStringOrBuffer(passphrase)) ||
(!isInput && cipher != null && !isStringOrBuffer(passphrase))
) {
throw new ERR_INVALID_ARG_VALUE(
option("passphrase", objName),
passphrase,
);
}
}
if (passphrase !== undefined) {
passphrase = getArrayBufferOrView(passphrase, "key.passphrase", encoding);
}
return {
// @ts-ignore __proto__ is magic
__proto__: null,
format,
type,
cipher,
passphrase,
};
}
function option(name: string, objName?: string) {
return objName === undefined
? `options.${name}`
: `options.${objName}.${name}`;
}
function parseKeyFormatAndType(
enc: { format?: string; type?: string },
keyType: string | undefined,
isPublic: boolean | undefined,
objName?: string,
): {
format: KeyFormat;
type: "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined;
} {
const { format: formatStr, type: typeStr } = enc;
const isInput = keyType === undefined;
const format = parseKeyFormat(
formatStr,
isInput ? "pem" : undefined,
option("format", objName),
);
const type = parseKeyType(
typeStr,
!isInput || format === "der",
keyType,
isPublic,
option("type", objName),
);
return {
// @ts-ignore __proto__ is magic
__proto__: null,
format,
type,
};
}
function parseKeyFormat(
formatStr: string | undefined,
defaultFormat: KeyFormat | undefined,
optionName: string,
): KeyFormat {
if (formatStr === undefined && defaultFormat !== undefined) {
return defaultFormat;
} else if (formatStr === "pem") {
return "pem";
} else if (formatStr === "der") {
return "der";
}
throw new ERR_INVALID_ARG_VALUE(optionName, formatStr);
}
function parseKeyType(
typeStr: string | undefined,
required: boolean,
keyType: string | undefined,
isPublic: boolean | undefined,
optionName: string,
): "pkcs1" | "spki" | "pkcs8" | "sec1" | undefined {
if (typeStr === undefined && !required) {
return undefined;
} else if (typeStr === "pkcs1") {
if (keyType !== undefined && keyType !== "rsa") {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
typeStr,
"can only be used for RSA keys",
);
}
return "pkcs1";
} else if (typeStr === "spki" && isPublic !== false) {
return "spki";
} else if (typeStr === "pkcs8" && isPublic !== true) {
return "pkcs8";
} else if (typeStr === "sec1" && isPublic !== true) {
if (keyType !== undefined && keyType !== "ec") {
throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
typeStr,
"can only be used for EC keys",
);
}
return "sec1";
}
throw new ERR_INVALID_ARG_VALUE(optionName, typeStr);
}
// Parses the public key encoding based on an object. keyType must be undefined
// when this is used to parse an input encoding and must be a valid key type if
// used to parse an output encoding.
function parsePublicKeyEncoding(
enc: {
cipher?: string;
passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView;
encoding?: BufferEncoding | "buffer";
format?: string;
type?: string;
},
keyType: string | undefined,
objName?: string,
) {
return parseKeyEncoding(enc, keyType, keyType ? true : undefined, objName);
}
// Parses the private key encoding based on an object. keyType must be undefined
// when this is used to parse an input encoding and must be a valid key type if
// used to parse an output encoding.
function parsePrivateKeyEncoding(
enc: {
cipher?: string;
passphrase?: string | Buffer | ArrayBuffer | ArrayBufferView;
encoding?: BufferEncoding | "buffer";
format?: string;
type?: string;
},
keyType: string | undefined,
objName?: string,
) {
return parseKeyEncoding(enc, keyType, false, objName);
}
export function createPrivateKey(
key: PrivateKeyInput | string | Buffer | JsonWebKeyInput,
): PrivateKeyObject {
const { data, format, type } = prepareAsymmetricKey(key);
const details = op_node_create_private_key(data, format, type);
const handle = setOwnedKey(copyBuffer(data));
return new PrivateKeyObject(handle, details);
const res = prepareAsymmetricKey(key, kCreatePrivate);
if ("handle" in res) {
const type = op_node_key_type(res.handle);
if (type === "private") {
return new PrivateKeyObject(res.handle);
} else {
throw new TypeError(`Can not create private key from ${type} key`);
}
} else {
const handle = op_node_create_private_key(
res.data,
res.format,
res.type ?? "",
res.passphrase,
);
return new PrivateKeyObject(handle);
}
}
export function createPublicKey(
key: PublicKeyInput | string | Buffer | JsonWebKeyInput,
): PublicKeyObject {
const { data, format, type } = prepareAsymmetricKey(key);
const details = op_node_create_public_key(data, format, type);
const handle = setOwnedKey(copyBuffer(data));
return new PublicKeyObject(handle, details);
const res = prepareAsymmetricKey(
key,
kCreatePublic,
);
if ("handle" in res) {
const type = op_node_key_type(res.handle);
if (type === "private") {
const handle = op_node_derive_public_key_from_private_key(res.handle);
return new PublicKeyObject(handle);
} else if (type === "public") {
return new PublicKeyObject(res.handle);
} else {
throw new TypeError(`Can not create private key from ${type} key`);
}
} else {
const handle = op_node_create_public_key(
res.data,
res.format,
res.type ?? "",
);
return new PublicKeyObject(handle);
}
}
function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) {
@ -292,10 +603,10 @@ function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) {
}
export function prepareSecretKey(
key: string | ArrayBufferView | ArrayBuffer | KeyObject,
key: string | ArrayBufferView | ArrayBuffer | KeyObject | CryptoKey,
encoding: string | undefined,
bufferOnly = false,
) {
): Buffer | ArrayBuffer | ArrayBufferView | KeyObjectHandle {
if (!bufferOnly) {
if (isKeyObject(key)) {
if (key.type !== "secret") {
@ -303,10 +614,7 @@ export function prepareSecretKey(
}
return key[kHandle];
} else if (isCryptoKey(key)) {
if (key.type !== "secret") {
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(key.type, "secret");
}
return key[kKeyObject][kHandle];
notImplemented("using CryptoKey as input");
}
}
if (
@ -325,21 +633,20 @@ export function prepareSecretKey(
}
export class SecretKeyObject extends KeyObject {
constructor(handle: unknown) {
constructor(handle: KeyObjectHandle) {
super("secret", handle);
}
get symmetricKeySize() {
return KEY_STORE.get(this[kHandle]).byteLength;
return op_node_get_symmetric_key_size(this[kHandle]);
}
get asymmetricKeyType() {
return undefined;
}
export(): Buffer;
export(options?: JwkKeyExportOptions): JsonWebKey {
const key = KEY_STORE.get(this[kHandle]);
export(options?: { format?: "buffer" | "jwk" }): Buffer | JsonWebKey {
let format: "buffer" | "jwk" = "buffer";
if (options !== undefined) {
validateObject(options, "options");
validateOneOf(
@ -347,111 +654,102 @@ export class SecretKeyObject extends KeyObject {
"options.format",
[undefined, "buffer", "jwk"],
);
if (options.format === "jwk") {
format = options.format ?? "buffer";
}
switch (format) {
case "buffer":
return Buffer.from(op_node_export_secret_key(this[kHandle]));
case "jwk":
return {
kty: "oct",
k: encodeToBase64Url(key),
k: op_node_export_secret_key_b64url(this[kHandle]),
};
}
}
return key.slice();
}
}
const kAsymmetricKeyType = Symbol("kAsymmetricKeyType");
const kAsymmetricKeyDetails = Symbol("kAsymmetricKeyDetails");
class AsymmetricKeyObject extends KeyObject {
constructor(type: KeyObjectType, handle: unknown, details: unknown) {
constructor(type: KeyObjectType, handle: KeyObjectHandle) {
super(type, handle);
this[kAsymmetricKeyType] = details.type;
this[kAsymmetricKeyDetails] = { ...details };
}
get asymmetricKeyType() {
return this[kAsymmetricKeyType];
return op_node_get_asymmetric_key_type(this[kHandle]);
}
get asymmetricKeyDetails() {
return this[kAsymmetricKeyDetails];
return op_node_get_asymmetric_key_details(this[kHandle]);
}
}
export class PrivateKeyObject extends AsymmetricKeyObject {
constructor(handle: unknown, details: unknown) {
super("private", handle, details);
constructor(handle: KeyObjectHandle) {
super("private", handle);
}
export(_options: unknown) {
notImplemented("crypto.PrivateKeyObject.prototype.export");
}
}
export(options: JwkKeyExportOptions | KeyExportOptions<KeyFormat>) {
if (options && options.format === "jwk") {
notImplemented("jwk private key export not implemented");
}
const {
format,
type,
} = parsePrivateKeyEncoding(options, this.asymmetricKeyType);
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`,
);
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 function setOwnedKey(key: Uint8Array): unknown {
const handle = {};
KEY_STORE.set(handle, key);
return handle;
export class PublicKeyObject extends AsymmetricKeyObject {
constructor(handle: KeyObjectHandle) {
super("public", handle);
}
export(options: JwkKeyExportOptions | KeyExportOptions<KeyFormat>) {
if (options && options.format === "jwk") {
notImplemented("jwk public key export not implemented");
}
const {
format,
type,
} = parsePublicKeyEncoding(options, this.asymmetricKeyType);
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 getKeyMaterial(key: KeyObject): Uint8Array {
return KEY_STORE.get(key[kHandle]);
}
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,

View file

@ -4,9 +4,13 @@
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { op_node_sign, op_node_verify } from "ext:core/ops";
import {
op_node_create_private_key,
op_node_create_public_key,
op_node_sign,
op_node_verify,
} from "ext:core/ops";
import { notImplemented } from "ext:deno_node/_utils.ts";
import {
validateFunction,
validateString,
@ -22,12 +26,12 @@ import type {
PublicKeyInput,
} from "ext:deno_node/internal/crypto/types.ts";
import {
kConsumePrivate,
kConsumePublic,
KeyObject,
prepareAsymmetricKey,
} from "ext:deno_node/internal/crypto/keys.ts";
import { createHash, Hash } from "ext:deno_node/internal/crypto/hash.ts";
import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
import { createHash } from "ext:deno_node/internal/crypto/hash.ts";
import { ERR_CRYPTO_SIGN_KEY_REQUIRED } from "ext:deno_node/internal/errors.ts";
export type DSAEncoding = "der" | "ieee-p1363";
@ -72,16 +76,26 @@ export class SignImpl extends Writable {
}
sign(
privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput,
// deno-lint-ignore no-explicit-any
privateKey: any,
encoding?: BinaryToTextEncoding,
): Buffer | string {
const { data, format, type } = prepareAsymmetricKey(privateKey);
const res = prepareAsymmetricKey(privateKey, kConsumePrivate);
let handle;
if ("handle" in res) {
handle = res.handle;
} else {
handle = op_node_create_private_key(
res.data,
res.format,
res.type ?? "",
res.passphrase,
);
}
const ret = Buffer.from(op_node_sign(
handle,
this.hash.digest(),
this.#digestType,
data!,
type,
format,
));
return encoding ? ret.toString(encoding) : ret;
}
@ -127,32 +141,27 @@ export class VerifyImpl extends Writable {
}
verify(
publicKey: BinaryLike | VerifyKeyObjectInput | VerifyPublicKeyInput,
// deno-lint-ignore no-explicit-any
publicKey: any,
signature: BinaryLike,
encoding?: BinaryToTextEncoding,
): boolean {
let keyData: BinaryLike;
let keyType: KeyType;
let keyFormat: KeyFormat;
if (typeof publicKey === "string" || isArrayBufferView(publicKey)) {
// if the key is BinaryLike, interpret it as a PEM encoded RSA key
// deno-lint-ignore no-explicit-any
keyData = publicKey as any;
keyType = "rsa";
keyFormat = "pem";
const res = prepareAsymmetricKey(publicKey, kConsumePublic);
let handle;
if ("handle" in res) {
handle = res.handle;
} else {
// TODO(kt3k): Add support for the case when publicKey is a KeyObject,
// CryptoKey, etc
notImplemented(
"crypto.Verify.prototype.verify with non BinaryLike input",
handle = op_node_create_public_key(
res.data,
res.format,
res.type ?? "",
res.passphrase,
);
}
return op_node_verify(
handle,
this.hash.digest(),
this.#digestType,
keyData!,
keyType,
keyFormat,
Buffer.from(signature, encoding),
);
}

View file

@ -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;
}

View file

@ -0,0 +1,31 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import crypto, { KeyFormat } from "node:crypto";
import path from "node:path";
import { Buffer } from "node:buffer";
import { assert } from "@std/assert/mod.ts";
import asymmetric from "./testdata/asymmetric.json" with { type: "json" };
Deno.test("crypto.createPrivateKey", async (t) => {
for (const key of asymmetric) {
await testCreatePrivateKey(t, key.name, "pem", "pkcs8");
await testCreatePrivateKey(t, key.name, "der", "pkcs8");
}
});
function testCreatePrivateKey(
t: Deno.TestContext,
name: string,
format: KeyFormat,
type: "pkcs8" | "pkcs1" | "sec1",
) {
if (name.includes("dh")) return;
return t.step(`crypto.createPrivateKey ${name} ${format} ${type}`, () => {
const file = path.join(
"./tests/unit_node/crypto/testdata/asymmetric",
`${name}.${type}.${format}`,
);
const key = Buffer.from(Deno.readFileSync(file));
const privateKey = crypto.createPrivateKey({ key, format, type });
assert(privateKey);
});
}

View file

@ -0,0 +1,147 @@
import { writeFileSync } from "node:fs";
import { join } from "node:path";
import crypto from "node:crypto";
import console from "node:console";
const keyTypes = [
{
type: "rsa",
modulusLength: 2048,
},
{
type: "rsa",
modulusLength: 3072,
},
{
type: "rsa-pss",
modulusLength: 2048,
},
{
type: "rsa-pss",
modulusLength: 3072,
},
{
type: "rsa-pss",
modulusLength: 2048,
saltLength: 32,
},
{
type: "rsa-pss",
modulusLength: 2048,
hashAlgorithm: "sha512",
},
{
type: "dsa",
modulusLength: 2048,
},
{
type: "dsa",
modulusLength: 3072,
},
{
type: "ec",
namedCurve: "P-224",
},
{
type: "ec",
namedCurve: "P-256",
},
{
type: "ec",
namedCurve: "P-384",
},
{
type: "x25519",
},
{
type: "ed25519",
},
{
type: "dh",
group: "modp14",
},
];
const data = "Hello, World!";
const entries = [];
for (const keyType of keyTypes) {
console.log(keyType);
const { privateKey, publicKey } = crypto.generateKeyPairSync(keyType.type, {
modulusLength: keyType.modulusLength,
namedCurve: keyType.namedCurve,
group: keyType.group,
saltLength: keyType.saltLength,
hashAlgorithm: keyType.hashAlgorithm,
});
let name = keyType.type;
if (keyType.type === "rsa-pss") {
name += `_${keyType.modulusLength}_${keyType.saltLength ?? "nosalt"}_${
keyType.hashAlgorithm ?? "nohash"
}`;
} else if (keyType.type === "rsa" || keyType.type === "dsa") {
name += `_${keyType.modulusLength}`;
} else if (keyType.type === "ec") {
name += `_${keyType.namedCurve}`;
} else if (keyType.type === "dh") {
name += `_${keyType.group}`;
}
exportAndWrite(name, privateKey, "pem", "pkcs8");
exportAndWrite(name, privateKey, "der", "pkcs8");
exportAndWrite(name, publicKey, "pem", "spki");
exportAndWrite(name, publicKey, "der", "spki");
if (keyType.type === "rsa") {
exportAndWrite(name, privateKey, "pem", "pkcs1");
exportAndWrite(name, privateKey, "der", "pkcs1");
exportAndWrite(name, publicKey, "pem", "pkcs1");
exportAndWrite(name, publicKey, "der", "pkcs1");
}
if (keyType.type === "ec") {
exportAndWrite(name, privateKey, "pem", "sec1");
exportAndWrite(name, privateKey, "der", "sec1");
}
let signed;
if (keyType.type === "ed25519") {
signed = crypto
.sign(null, Buffer.from(data), privateKey)
.toString("base64");
} else if (keyType.type !== "x25519" && keyType.type !== "dh") {
console.log("signing", keyType.type);
signed = crypto
.createSign("sha512")
.update(data)
.sign(privateKey, "base64");
}
entries.push({
name,
keyType: keyType.type,
signed,
});
}
writeFileSync(
join("tests", "unit_node", "crypto", "testdata", "asymmetric.json"),
JSON.stringify(entries, null, 2),
);
function exportAndWrite(name, key, format, type) {
const pem = key.export({
format,
type,
});
const filename = join(
"tests",
"unit_node",
"crypto",
"testdata",
"asymmetric",
`${name}.${type}.${format}`,
);
writeFileSync(filename, pem);
}

View file

@ -0,0 +1,60 @@
[
{
"name": "rsa_2048",
"type": "rsa",
"signed": "FqWl/E1LhnLrSi6coOr0V8I1Skce3VHkxgboZ4DxnBZRZdabVm/nhevBop+cbrdzbHaBRzhrFbi/k2NDAUd4o1D2//g5+bVCoJlCp/t38GJ3oW8eKvAU4cBE8VCrNDCpHi62aEyOIHbFNcKvSvX10Pgvz1jxkU1S+sc4S/mFfc5mWjYZ96JC0dFSjpS5OfdweCzb6uixE+dzTsYtMDaLt5yKddOMMM3ElBITygK2DOlZHS8PqTvR+D2YhJzetB3gdcHGVek1AaDBRbsIzG2PSGT0BekJGDlUJYMD0FeCX456gX22qT+0e/6r9iI8AiPkrcb3dJHKnn0n8SFmflN6bA=="
},
{
"name": "rsa_3072",
"type": "rsa",
"signed": "BreP5Jwjnpfei2wDgETkwMiIjh7zi39Gne/3w1kQH/L2yrlUAA+f0urhBkt8JjMAkisQBvsqqJ6ECFUnc4hvxfMKlPZifGu+DeLMWB0POsCkLSrNvxUYD97mm9WQ3wpVV0hfCQO0q3GPv3OYfTLMB23ugJeWuyTcm/Kbw7QWtrPKBRNysKiguLdAnvyYaRrt/vxQv7kwaNtS0GM3OkgNd/8IPEI2kuUCrRkeAnXH9zCtazOWaqVLoVBJtDuTvbvdy5N5j/im1Z2ZGqviI3rz4+N9iAaS6Xxf8Zo83yBdXftqcz9LtJuqZpORKyxAIb5ShcNLtQUewWH8nesRRY0oU/dx53fctB0vDNpijFsEizKD7uNOT+ZvAwb9DUkxNI2Ex1GPM7kBUzhY9WQ1cAtbzn5djHxJlpLxheiG9aTI/GN9jxMqSwhV0hY+QBd8yvfNi0Ei3IwlHZ4ri58nbt8ZAIBSDAGSN7qpqdVlwG7duqpvUIaX2/wMjXKJQ/Im46XK"
},
{
"name": "rsa-pss_2048",
"type": "rsa-pss",
"signed": "wqguH2sfPcAk2PEca0p9wPBGfil+S3BW61Supa3g2AnJC8seDQL7C2MMnCvyzVD7QOoCLHQ5yMc4snaMoDAZUyC/jCm7kMNgsDNGsVP8Rvs8Pm+mHWEHrtpye8gTJiOQf1vb6Dar4xIk05qi028F8D4lLE/GQkfNUZjxyMCDLTTtFacngDinm39od75tqVtCeJijz8KEslmVnjLNazhxpvXK+3dbudNcnnu50sRxj1F1ogATmDqg01KkmeE47tffvyJXwNACdw5cZPbXOkBqvmY9a2h4xa4hZGBFH06zAxR5baPWlmtv28ZUbl1GoOk6m4Uu6jUyauI+PbeuXays2A=="
},
{
"name": "rsa-pss_3072",
"type": "rsa-pss",
"signed": "GJgbQ6cu7cJnGrjgtY0waTlYG7azhVj1PySwcxp2uubyoch2fQHkRcVYhtppfFdz0SNHH4Nflp5gxdQkTfopyO7fDNTULdGT4/w4fdXOqnB2vq1H88CBQtyVxoW1VwEiu96rkkYX1DOEIyyCI7wCWOt33iTQMUHvYIy/3jOR4pdvqDekoI691e5Uodo16sXLiTDbzmTvplR4uhtlc89KkHGy1NQk4yGOuBS3fC8ksGSr89J1SLwRluNuyn8t4N0n0a/NbBu/DolnltvJzIRA7OhXyCdK1HLg5BM3QNR/LPjxPqXbTYGJ04iC5qErGT9cX9Rz+IqhqtSCBUwNCT93QpPZ7s7v1nj7Xq74HBqenknxhJdwnpxF3RKnIx5DvioeysmNX6Le8LighBR5mJEwOyz2Ls0/afSUazYmV9cA5L2uJDsBYWHjM2OnK6LdV9nTMgXa2h4suoIzo3YOqGxo9NKNPWyJ1q9Uda2dL4CazH1Rhz4NXRZarrv+YQ1958pq"
},
{
"name": "dsa_2048",
"type": "dsa",
"signed": "MD0CHAeYKrUmejhDlvR4hvmHoHru9n5dWg9FeOkAZsUCHQCW5dn/MAFYQJXDCdVaST1tptQYQlM84qJCoaYT"
},
{
"name": "dsa_3072",
"type": "dsa",
"signed": "MD0CHDGw+2G7O5UfjS0IODyGnyV0F18Hx1C3byJ7zHoCHQCOvumjVcvMKui3MNngS7zauiJgsdbDBcB7Un8M"
},
{
"name": "ec_P-224",
"type": "ec",
"signed": "MD0CHQC9Yl4Typq3ql5E9NJTSjW/i2ZK928awxjgk8QwAhwSc5UELwCWK0xllhW5CExLdCI4znemLQ8Qp0+v"
},
{
"name": "ec_P-256",
"type": "ec",
"signed": "MEUCIQDPzGSDwlIZ3Y45Xvpd28OvNGnOlqzT9Dbl5o5tXjq2qgIgJb7MpvTMrcE6ad/VZwaAXppRNN58+1N2LBOHG3+rH3I="
},
{
"name": "ec_P-384",
"type": "ec",
"signed": "MGYCMQDaV74tdPl146lFK1UYFQsDHMNIZGnVvU+LWrNXlsl+D+yBgg6PVpN19G5r6JT4hasCMQDsHnbwCywXFog1judfAyvdkIVJr1YgrlpshxOf0hylyD2PuegWBBU54dwQUblVRZs="
},
{
"name": "x25519",
"type": "x25519"
},
{
"name": "ed25519",
"type": "ed25519",
"signed": "ytAlJrcGJDwT5xW7NbK5Rjv66H0Zow5nUKTBPekzBR8pbXKBbgOHmI+n2cjQsvGalD5L71tJWrWjMbpnlgFPCQ=="
},
{
"name": "dh_modp14",
"type": "dh"
}
]

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEwgIBADASBgkqhkiG9w0BAQowBaIDAgEgBIIEpzCCBKMCAQACggEBAKowWMm2
m7dV5HbzQvMNNtiMahbqcS6P4cfE6bG6TSoiSYhK5q5a+A1hkezzNSOAEd/1zkfc
fU/vgHGlQ21sQZxsgCisFbupXOpNVKcobU0oF9GyyVOniqPXfNLFGOjNiLq6+mRU
poB3gAf9z7my3QUPrO7es6FdEzNXrkHFaIYypBRiwOBVcvsPZXhnxKm8NudnRIin
7xlCLMRsk1422Znl6ovIiC3y+7PcYZgpHTcepzNX7jNKONF13LBJqmTEeJb8PkIR
2/xjn1whThO0mZ4iq9aBwLc/ouqLzo5pN6Ap5fmEoaIoZdc1A6MBB6thMDaLOJdT
HDiGNzMmPMuaYVkCAwEAAQKCAQAED4yBmDgoF3yj3nGYenyc9BSKPxYlgzGDBBBD
HHoy6sr4NxJNpdyRXcSlUHD4hUzxe/9yQHQ9SnS7l9k11/PX612I97P6jIolVvER
xOGlXUZca2ovzLmRzhM7qINSRBvMTQHeHKHbtgFuhchPF3gvPFsj2KY7iWZRghJy
aqcjPL5nt5LQ/DgU5MGNmoPT8rm2YogJgZ1IScbFqcncp577ibRTs7NO/kOT8CvY
l2GiqQlrlh87ogu0d5KMgWyJ38fYYPVZhXUBWdgXzMgNx2hswmBdefSPYaQoFf8l
2AuNrHDPqJYsRdCMVLigpha94uuR3YW5HQtdEQzYJfcBdF8xAoGBAOxPZv+mrXSv
SpbPWH9+nUak5EpmvLIRh+2KuNozVdJA+IhJ3lCixj2v8+rHRWOYNSLKgXOTi2WP
4ioMCvIVnEk4nTIgfOq9CRBm0JiOw9G6TUhx/30PxQbGB6f0Q1TA/0QMmr9TwdYt
oWFk3tDhkyrpHvkn+nZJ6aSjYkZZciPxAoGBALhejGadDVLx8kEQifAyI7JayoP1
kbTTZUmBC7wTENvwEWkEif1YXxFeYzWi3quSUbG4mn3sBrieQPOFH69up/JPk0M9
Zj9ZUYn2Qbdv5tSj6o1bBLZBKb0D/YJA6PDewAJ2VWJbG0/R1qGhLjxwvmGSm9H4
so7rrMH5BK5sAVvpAoGAfI+KGj3Qdo4jggT/gAzMeD1YfINU+YPWI3cY4yNmHHLU
znopblWuqzuBFgM95zaG47TcsYBXXQyPyVwZtOuBOvNVoOORFObZzUR0tcWjIHzU
WdiFNHXIhD6EMJrHlvg4VbVTKIDMzsm0pDLYZEBTI65H/kt9cTaaqobYuX5SdPEC
gYEAl9LEK3wJDNTQeWP1MycW3jiFrET8x7uNHQp3b1kD+RmoPKLQPyAWqWbgq7qD
QyYqv/8Uub0zi7RGKELn5L9q7c85pZVaLbCPxNxVIYm0vEZ/UAgzySHADTbL/AcX
y8Kiu2RWy7fatdBGvrLMMFlnbVIdnrr9z1Oj39gAUuH9/IECgYBjRbnZgI9YhD5C
g3fpuGeHNeS3JLI+4pXReA5+GhiNn7avwd9VbHRfEzrxOlEBC6hBWaU4oOpJTub/
qK76euERcNP7UX4e97/C0A/bxXsVavDJU0pTUdSck9UmPWRZZSIy6cnu+rlSq5mr
9/x7CuqrrtNHWhRAcGAWuzJZWa77iA==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBJzASBgkqhkiG9w0BAQowBaIDAgEgA4IBDwAwggEKAoIBAQCqMFjJtpu3VeR2
80LzDTbYjGoW6nEuj+HHxOmxuk0qIkmISuauWvgNYZHs8zUjgBHf9c5H3H1P74Bx
pUNtbEGcbIAorBW7qVzqTVSnKG1NKBfRsslTp4qj13zSxRjozYi6uvpkVKaAd4AH
/c+5st0FD6zu3rOhXRMzV65BxWiGMqQUYsDgVXL7D2V4Z8SpvDbnZ0SIp+8ZQizE
bJNeNtmZ5eqLyIgt8vuz3GGYKR03HqczV+4zSjjRddywSapkxHiW/D5CEdv8Y59c
IU4TtJmeIqvWgcC3P6Lqi86OaTegKeX5hKGiKGXXNQOjAQerYTA2iziXUxw4hjcz
JjzLmmFZAgMBAAE=
-----END PUBLIC KEY-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEuwIBADALBgkqhkiG9w0BAQoEggSnMIIEowIBAAKCAQEAvM6bE/DSk4I8cNMX
P6ERLuHxLBiM99GHQ3HtVcRjGIzLtLvH3XN5BE32o0uROd11f+bPTNWdNmy9Z1zz
AiM93y+vpf5m/9loi+102XiPc/RBeUDo+dOoSOt8S0P+A1pQUpehsxj917DLseAS
72HpFVEaSDn0cG1jjcWv/gWR4rhluiKHXSD711f/rYLABJWsCxpQHjSkNthxxLyF
A2X9oyuReX3Cw1t7RIgkCoExFx8WfasunSbjXWE5nmqoCo8ED8ae3hBmyWTP1d/p
zWLN6792UwweuTPIPo7nNcrJnn2Pleu5M/Wt0g0ma+FhgED7p5O6mMKZW+Tbv6bx
keaamwIDAQABAoIBAAiCkgppQ+NinJhfQ1vs6mD/8j1jW+WGxRrKoQC23CO6oysG
WSo1U0ciCMGCeM1U0a3E75DppxO/IfqkWRf1W7ARQNC66GgCNh6hC6/BYLjAdk4q
veM7GGUQcLlC5IAuVEl/A28pECP4mGSm//XGFFnq+dY3bMwtVXdTDj+cEV/GRGe8
ORYiEr0k9Mo3aHg/dAEYSnOchakEbCZ3BErmZaXmjm/Qcpd638eaxNWzdiYbULXd
fwQCRt4P7fOnMqQpT6gb76cL9X/99D0p4AUY5wH4Y4otMQMVhoH2gEYBnKTJ2s6g
gGzaBQGGsEubDhPHYqW3i/VVUCdNOesrRJm09iECgYEA+MBG/anB+9/lYLe/SKdu
x48iGKausXNvF/I9aSeH5Mn3jT0wWOl5glqeAhBmC0NDX7lHYkDd8PFJ2F7ErN29
JIFqVPllrUz9c0ZvRNx6MT/OaV4xAY2zMQcrF0FZnHz6IlIopUXJajN+OFAJGZQg
f6+E0ddIoaFx2ER4yYZQXSECgYEAwk8i7wk5E6hcscN5lMwxr2RdetUBWmw4WjwA
gEEUxy3B5NGPhY2EEVLtyk5qxKCxO7zgH03OJF3+upehbKF67xBDHWFXol6SCtHN
dUvzMctyY9NVfgY4lun/elxjIT97FIc9ksBelDHQoYJbW24W4IpH+YxPJPHZvCc3
qPK4pDsCgYB9QBu+yAZj89XEgGDxjVTraLfLX8pgkXYjwZaIZx425jcex+ubKVxE
dapP8b/f9etrvJgj8fOOyX/cUcOII1KEmiFXTgiTXUvVCmcmbjmcqLsfNQ1J8faD
Pk/FMuOTNx6fv2y141DKh8kLQ8mBNqOyh0dCfbsVn3v6YGfNWTMH4QKBgDHRnnBR
Ggw7N4DwUGThMb/5aKpMoKsxYgVoquw3Q3+J8NOdE0I3tWvHqmYbUu6VELvzQjdk
eH0EiIIIzH+Qq3dN4RdQVOqxeppBjn8LeRAETJDhp2LHb4zp8/HIbDYjE4iA4D2X
CujOGOLADHJVuJHbgnauDcC4LY47M98iVErvAoGBAO1gVbqqyJSijMTC+wwGApPR
HIScfFhxuJCyxIxNMviuxO5vPIWZGcAYYBkiL08IVLuRyPlyurFUf69aU0jGjpqS
ncjwoSmMCyUvEppsC33rcgCJkP5eFSe/UgJLibCVv44BTsrcJaRtl+m1EwFODJnQ
WlHPWp95bdo70yfOFeme
-----END PRIVATE KEY-----

View file

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIDALBgkqhkiG9w0BAQoDggEPADCCAQoCggEBALzOmxPw0pOCPHDTFz+hES7h
8SwYjPfRh0Nx7VXEYxiMy7S7x91zeQRN9qNLkTnddX/mz0zVnTZsvWdc8wIjPd8v
r6X+Zv/ZaIvtdNl4j3P0QXlA6PnTqEjrfEtD/gNaUFKXobMY/dewy7HgEu9h6RVR
Gkg59HBtY43Fr/4FkeK4Zboih10g+9dX/62CwASVrAsaUB40pDbYccS8hQNl/aMr
kXl9wsNbe0SIJAqBMRcfFn2rLp0m411hOZ5qqAqPBA/Gnt4QZslkz9Xf6c1izeu/
dlMMHrkzyD6O5zXKyZ59j5XruTP1rdINJmvhYYBA+6eTupjCmVvk27+m8ZHmmpsC
AwEAAQ==
-----END PUBLIC KEY-----

View file

@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
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
W8yoba6AEekotJ+M+A==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBoDALBgkqhkiG9w0BAQoDggGPADCCAYoCggGBAI/9ITIs1Wo+9dz5ZT1drh5r
0+o8DTOy6lIP7cSQwxNiPjDslGqCJ7Wg5cZeIHYg6lXwdxOZx/FSGMtYqw3NBl6D
K/rNxmeLyXD67HJMggRzhpynO9Q0NscGZ5/A3ReiSPdhkhRp0pgOG2Ul3eyhejXb
hp0PFErnAw1BDRtWjM/jpe27ROQV1J4DhFVj3OKhMb2pnpQHNX/SdlS0LPFFlTvy
Rsgmd2JSNy/3wJzRCLX35sv3T3zWnDSTzPHoECJoO0RyAv/RGQXjYtN/ceVzYZ03
C1u3V69ncjlMoZOxpjnWeHWLaJUbBaSPAuiFh5k62QFqORSAM35IE3jFX2JGIOod
Y/H7b5Rr+205g38Q0+YuYgzRuzNzn2KbM7Eqfuw4btlK6ON0ouf2IrAsh2IkWewv
vG52EIgqF5B1Iep8epabjbE8MUyRUKgM0NICJ+5x6jHdqXM2D6YYbrNqBy+oa29w
N9Um4tznd75+eyPksntqqVEWtJ2PQM7afS0VrbiIBQIDAQAB
-----END PUBLIC KEY-----

Binary file not shown.

View file

@ -0,0 +1,8 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAmenZwtjzGH5KudheRB7fkoQhKqnD8NL0PxH9KX9tZY61+U9hQXr0
NtwQ/yuRAP162NgVS9WrnSDXCMWVQyf3OXzGDSmQBNUbTYvs6VLJJxzeiZ+bCyfL
EOR49SUtJmzgwgdRItXFOY4Vjuen1tLt1SKLErHhJ9c1vnaQPpBlL0hpYoot3KoZ
GF/Ji/v0MIa9sPRPIYTY3IVm1KaPDrW7Vnlv8vm2b1bfd1WEEUZwx7nnQDoOHe65
q8JOiz1vSd7sxt6sl5ffPfPpgW8Uwv+nzWdGfULmoXnT8aEorv69Mw8k+YBonxSN
xVvH/ScUsrPXb2WbFL4Aqvu2jaTMC3/ZhwIDAQAB
-----END RSA PUBLIC KEY-----

Binary file not shown.

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZ6dnC2PMYfkq5
2F5EHt+ShCEqqcPw0vQ/Ef0pf21ljrX5T2FBevQ23BD/K5EA/XrY2BVL1audINcI
xZVDJ/c5fMYNKZAE1RtNi+zpUsknHN6Jn5sLJ8sQ5Hj1JS0mbODCB1Ei1cU5jhWO
56fW0u3VIosSseEn1zW+dpA+kGUvSGliii3cqhkYX8mL+/Qwhr2w9E8hhNjchWbU
po8OtbtWeW/y+bZvVt93VYQRRnDHuedAOg4d7rmrwk6LPW9J3uzG3qyXl9898+mB
bxTC/6fNZ0Z9QuahedPxoSiu/r0zDyT5gGifFI3FW8f9JxSys9dvZZsUvgCq+7aN
pMwLf9mHAgMBAAECggEABL0SfPFUYENfH8qkC4fbZtwV8B83tTd7DC5zGch4QNsW
Ciqh+jPQk5g1FvR4UeX78Fol8BndEm9BssQjT/585r9LLHn3mtZ7l5vm7tk88GA7
3IcJhtxL4BjD12Ea7nOffDz6bmg52rqsbXDML4zhHdTWIEmFGGA27nnuMsCCUcrM
kPebv9u/voQkRGdwyYH3+z8rO+IzQI/jGL0FkNi2czPHH3i5kZlCEAnqWlA2rfwz
dzCxycfRgTJ6HL6rQ4ru8pjlDqVsdlcbAaB8oqhiWZ1pXKo1IMhSal8C6sZ0eXQz
lJkdTcmuky9fgSsk91ggn+WeTYqaXAOVBNXtUyk8sQKBgQDXj6uLwP8novKr35CO
t2lBeqAfQHGxsTWa5Bj+1N1Jz+li6YMmvGqUWWUWaJLkV5B+P7Q7JjnBHlIAeICV
SEwjYJQSQy1+fLzdl5LxcPg4W2mz8RHMofpTDgO542lPrPNFRVwULN3nBuu0fI/y
YF7oNEl1BWgBIatjVU+9R2UjVwKBgQC2yYwitwdscfObl+CLDEkbYKyPNUDjPnPV
wbhlhex6UPqcoVd4p7Dk+zP3VGiQqc3zYBd4FrPBOCq5bxGNvSLen0q8SiDTgcsR
dDUlpjjkG+JFfEXIjo7x1gXkaXxgQFrZ7kpUTmkaOA5ekfqgcF1JdnumLEAf3M5s
kV5p3YXNUQKBgQCmfYbnqCbqvEZmTYRfVnXrZwTpXmLx9YcLnQVZPZu0+OqvxN/R
OVGwRuN2zUo3JxKpEBbqYHnXGM4JIwldQ7vazytOd6hZu4o8NGgAJ1rwXFpl6tnu
jWTEZVynZGfgbBpw9ENMKeMyHvxKKLMdZyWmf0wFICnWReUUEb5G2S/afQKBgFj8
O6W21v1baE4qHR10SK70XG2HbmRyxe+dVIjQLvTJMYhJH41UjdCb3ouc4x7yG5pN
AH/tBWueTWZjBPesySn6AGcz61EskdCYczs19eJPFNPhERP3Gu3u1IWDORKeodwQ
nsz2M0KZYZ12kb3DlhaqgL3AMyOP2kqOZplBR99RAoGBAMF0Q12eFamvQuIajv2O
oK9LNcRyFhAwBxl+whzRaXanLNLDgPaofNBLEVi0pltcreELP79YvYeMvwB8bv8k
6062d6Icxv8LbiLMg52ce1tCH542Vcl+IUWBYtxdj3juSG0tFHoREEVum+hIiZOa
SkHtWuAaYJ+eYANAuTU2tddK
-----END PRIVATE KEY-----

Binary file not shown.

View file

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmenZwtjzGH5KudheRB7f
koQhKqnD8NL0PxH9KX9tZY61+U9hQXr0NtwQ/yuRAP162NgVS9WrnSDXCMWVQyf3
OXzGDSmQBNUbTYvs6VLJJxzeiZ+bCyfLEOR49SUtJmzgwgdRItXFOY4Vjuen1tLt
1SKLErHhJ9c1vnaQPpBlL0hpYoot3KoZGF/Ji/v0MIa9sPRPIYTY3IVm1KaPDrW7
Vnlv8vm2b1bfd1WEEUZwx7nnQDoOHe65q8JOiz1vSd7sxt6sl5ffPfPpgW8Uwv+n
zWdGfULmoXnT8aEorv69Mw8k+YBonxSNxVvH/ScUsrPXb2WbFL4Aqvu2jaTMC3/Z
hwIDAQAB
-----END PUBLIC KEY-----

Binary file not shown.

View file

@ -0,0 +1,11 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBigKCAYEAoHs4aVryR35C5J4lN+QwS5wsxLJHfV+bMC0j339jPAMja0B203bB
HnGDbb23Natodh6HwjhD7qhWe5gGWIGR2QfskYOgswznjfo5zhdgQCwUsZ9sSYL5
aBvD/8XHoaEPAX03e78RKRPmIdIVExQw/cMewtpPtnxa6rS7sCd5AFRQ1/yN3Orj
mY3+nDmaw2XmvL9t13Icacixk5DqmGZ51CXDkv546gQug2frRospyKQzn1tBf1Be
aeG95XcLBYih7lj39MQ317ocF4e0tRzHGzzWVEhbx8ewpmPzt7yqQUpoNExRzrh6
9KMVwS08YQRXVrnmiOgg9tYN7S3wNcrIGzkpEITqFk5JNcwxaDggmnt4D7rH5C6W
1Q1R09IMehZL4zNuWUjzaHS7Tazuoh0635VXk26MXNnlWdLJHMq+UvX/pJMQ4smD
P15A3DUPYgFN8fCAbabXDaxb9gO4QaPMqFGbXBGWVcb4T72+k4zLxkeSzxaJ++J5
DVXp2ij6kS/1AgMBAAE=
-----END RSA PUBLIC KEY-----

Binary file not shown.

View file

@ -0,0 +1,40 @@
-----BEGIN PRIVATE KEY-----
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
GoAA1ZpHWKqWYvM6EJsH/qQ=
-----END PRIVATE KEY-----

Binary file not shown.

View file

@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAoHs4aVryR35C5J4lN+Qw
S5wsxLJHfV+bMC0j339jPAMja0B203bBHnGDbb23Natodh6HwjhD7qhWe5gGWIGR
2QfskYOgswznjfo5zhdgQCwUsZ9sSYL5aBvD/8XHoaEPAX03e78RKRPmIdIVExQw
/cMewtpPtnxa6rS7sCd5AFRQ1/yN3OrjmY3+nDmaw2XmvL9t13Icacixk5DqmGZ5
1CXDkv546gQug2frRospyKQzn1tBf1BeaeG95XcLBYih7lj39MQ317ocF4e0tRzH
GzzWVEhbx8ewpmPzt7yqQUpoNExRzrh69KMVwS08YQRXVrnmiOgg9tYN7S3wNcrI
GzkpEITqFk5JNcwxaDggmnt4D7rH5C6W1Q1R09IMehZL4zNuWUjzaHS7Tazuoh06
35VXk26MXNnlWdLJHMq+UvX/pJMQ4smDP15A3DUPYgFN8fCAbabXDaxb9gO4QaPM
qFGbXBGWVcb4T72+k4zLxkeSzxaJ++J5DVXp2ij6kS/1AgMBAAE=
-----END PUBLIC KEY-----

View file

@ -1,5 +1,7 @@
{
"imports": {
"ext:core/mod.js": "../../deno_core/core/core.d.ts",
"ext:core/ops": "./ops.d.ts",
"ext:deno_broadcast_channel/01_broadcast_channel.js": "../ext/broadcast_channel/01_broadcast_channel.js",
"ext:deno_cache/01_cache.js": "../ext/cache/01_cache.js",
"ext:deno_canvas/01_image.js": "../ext/canvas/01_image.js",

4
tools/ops.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This file is intentionally empty - that puts this file into script mode,
// which then allows all symbols to be imported from the file.