diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 2005e4c315e7a6..8f4adfbd18bb16 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -226,6 +226,7 @@ deno_core::extension!(deno_node, ops::crypto::op_node_cipheriv_encrypt, ops::crypto::op_node_cipheriv_final, ops::crypto::op_node_cipheriv_set_aad, + ops::crypto::op_node_cipheriv_take, ops::crypto::op_node_create_cipheriv, ops::crypto::op_node_create_decipheriv, ops::crypto::op_node_create_hash, @@ -260,7 +261,9 @@ deno_core::extension!(deno_node, ops::crypto::op_node_scrypt_async, ops::crypto::op_node_scrypt_sync, ops::crypto::op_node_sign, + ops::crypto::op_node_sign_ed25519, ops::crypto::op_node_verify, + ops::crypto::op_node_verify_ed25519, ops::crypto::keys::op_node_create_private_key, ops::crypto::keys::op_node_create_public_key, ops::crypto::keys::op_node_create_secret_key, diff --git a/ext/node/ops/crypto/cipher.rs b/ext/node/ops/crypto/cipher.rs index 0c1218d31a8f31..ca13fdcd8fa5a0 100644 --- a/ext/node/ops/crypto/cipher.rs +++ b/ext/node/ops/crypto/cipher.rs @@ -64,6 +64,10 @@ impl CipherContext { self.cipher.borrow_mut().encrypt(input, output); } + pub fn take_tag(self) -> Tag { + Rc::try_unwrap(self.cipher).ok()?.into_inner().take_tag() + } + pub fn r#final( self, auto_pad: bool, @@ -290,6 +294,15 @@ impl Cipher { } } } + + fn take_tag(self) -> Tag { + use Cipher::*; + match self { + Aes128Gcm(cipher) => Some(cipher.finish().to_vec()), + Aes256Gcm(cipher) => Some(cipher.finish().to_vec()), + _ => None, + } + } } impl Decipher { diff --git a/ext/node/ops/crypto/keys.rs b/ext/node/ops/crypto/keys.rs index 5f634b35fd9c7e..87e0e543916c16 100644 --- a/ext/node/ops/crypto/keys.rs +++ b/ext/node/ops/crypto/keys.rs @@ -496,14 +496,9 @@ impl KeyObjectHandle { AsymmetricPrivateKey::X25519(x25519_dalek::StaticSecret::from(bytes)) } ED25519_OID => { - let string_ref = OctetStringRef::from_der(pk_info.private_key) + let signing_key = ed25519_dalek::SigningKey::try_from(pk_info) .map_err(|_| type_error("invalid Ed25519 private key"))?; - if string_ref.as_bytes().len() != 32 { - return Err(type_error("Ed25519 private key is the wrong length")); - } - let mut bytes = [0; 32]; - bytes.copy_from_slice(string_ref.as_bytes()); - AsymmetricPrivateKey::Ed25519(ed25519_dalek::SigningKey::from(bytes)) + AsymmetricPrivateKey::Ed25519(signing_key) } DH_KEY_AGREEMENT_OID => { let params = pk_info @@ -643,16 +638,8 @@ impl KeyObjectHandle { AsymmetricPublicKey::X25519(x25519_dalek::PublicKey::from(bytes)) } ED25519_OID => { - let mut bytes = [0; 32]; - let data = spki.subject_public_key.as_bytes().ok_or_else(|| { - type_error("malformed or missing public key in ed25519 spki") - })?; - if data.len() < 32 { - return Err(type_error("ed25519 public key is too short")); - } - bytes.copy_from_slice(&data[0..32]); - let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&bytes) - .map_err(|_| type_error("ed25519 public key is malformed"))?; + let verifying_key = ed25519_dalek::VerifyingKey::try_from(spki) + .map_err(|_| type_error("invalid Ed25519 private key"))?; AsymmetricPublicKey::Ed25519(verifying_key) } DH_KEY_AGREEMENT_OID => { diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index 8780495a489701..05501fa87b1267 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -20,6 +20,7 @@ use num_bigint_dig::BigUint; use rand::distributions::Distribution; use rand::distributions::Uniform; use rand::Rng; +use ring::signature::Ed25519KeyPair; use std::future::Future; use std::rc::Rc; @@ -272,6 +273,18 @@ pub fn op_node_cipheriv_final( context.r#final(auto_pad, input, output) } +#[op2] +#[buffer] +pub fn op_node_cipheriv_take( + state: &mut OpState, + #[smi] rid: u32, +) -> Result>, AnyError> { + let context = state.resource_table.take::(rid)?; + let context = Rc::try_unwrap(context) + .map_err(|_| type_error("Cipher context is already in use"))?; + Ok(context.take_tag()) +} + #[op2(fast)] #[smi] pub fn op_node_create_decipheriv( @@ -938,3 +951,50 @@ pub fn op_node_diffie_hellman( Ok(res) } + +#[op2(fast)] +pub fn op_node_sign_ed25519( + #[cppgc] key: &KeyObjectHandle, + #[buffer] data: &[u8], + #[buffer] signature: &mut [u8], +) -> Result<(), AnyError> { + let private = key + .as_private_key() + .ok_or_else(|| type_error("Expected private key"))?; + + let ed25519 = match private { + AsymmetricPrivateKey::Ed25519(private) => private, + _ => return Err(type_error("Expected Ed25519 private key")), + }; + + let pair = Ed25519KeyPair::from_seed_unchecked(ed25519.as_bytes().as_slice()) + .map_err(|_| type_error("Invalid Ed25519 private key"))?; + signature.copy_from_slice(pair.sign(data).as_ref()); + + Ok(()) +} + +#[op2(fast)] +pub fn op_node_verify_ed25519( + #[cppgc] key: &KeyObjectHandle, + #[buffer] data: &[u8], + #[buffer] signature: &[u8], +) -> Result { + let public = key + .as_public_key() + .ok_or_else(|| type_error("Expected public key"))?; + + let ed25519 = match &*public { + AsymmetricPublicKey::Ed25519(public) => public, + _ => return Err(type_error("Expected Ed25519 public key")), + }; + + let verified = ring::signature::UnparsedPublicKey::new( + &ring::signature::ED25519, + ed25519.as_bytes().as_slice(), + ) + .verify(data, signature) + .is_ok(); + + Ok(verified) +} diff --git a/ext/node/ops/crypto/sign.rs b/ext/node/ops/crypto/sign.rs index 9aea3aab71d2a9..2dba15abad77a0 100644 --- a/ext/node/ops/crypto/sign.rs +++ b/ext/node/ops/crypto/sign.rs @@ -2,12 +2,6 @@ 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 _; @@ -146,29 +140,9 @@ impl KeyObjectHandle { 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::Ed25519(_) => Err(type_error( + "Ed25519 key cannot be used for prehashed signing", + )), AsymmetricPrivateKey::Dh(_) => { Err(type_error("DH key cannot be used for signing")) } @@ -275,122 +249,12 @@ impl KeyObjectHandle { 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::Ed25519(_) => Err(type_error( + "Ed25519 key cannot be used for prehashed verification", + )), AsymmetricPublicKey::Dh(_) => { Err(type_error("DH key cannot be used for verification")) } } } } - -struct PrecomputedDigest([u8; 64]); - -impl OutputSizeUser for PrecomputedDigest { - type OutputSize = ::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.0.into() - } - - fn finalize_into(self, _out: &mut digest::Output) { - unreachable!() - } - - fn finalize_reset(&mut self) -> digest::Output - where - Self: digest::FixedOutputReset, - { - unreachable!() - } - - fn finalize_into_reset(&mut self, _out: &mut digest::Output) - 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 { - unreachable!() - } -} - -impl Reset for PrecomputedDigest { - fn reset(&mut self) { - unreachable!() - } -} - -impl FixedOutputReset for PrecomputedDigest { - fn finalize_into_reset(&mut self, _out: &mut digest::Output) { - unreachable!() - } -} - -impl FixedOutput for PrecomputedDigest { - fn finalize_into(self, _out: &mut digest::Output) { - unreachable!() - } -} - -impl Update for PrecomputedDigest { - fn update(&mut self, _data: &[u8]) { - unreachable!() - } -} diff --git a/ext/node/polyfills/internal/crypto/cipher.ts b/ext/node/polyfills/internal/crypto/cipher.ts index 0a0a1ca0647ad7..2141edc76bf9ca 100644 --- a/ext/node/polyfills/internal/crypto/cipher.ts +++ b/ext/node/polyfills/internal/crypto/cipher.ts @@ -12,6 +12,7 @@ import { op_node_cipheriv_encrypt, op_node_cipheriv_final, op_node_cipheriv_set_aad, + op_node_cipheriv_take, op_node_create_cipheriv, op_node_create_decipheriv, op_node_decipheriv_decrypt, @@ -194,7 +195,11 @@ export class Cipheriv extends Transform implements Cipher { final(encoding: string = getDefaultEncoding()): Buffer | string { const buf = new Buffer(16); - + if (this.#cache.cache.byteLength == 0) { + const maybeTag = op_node_cipheriv_take(this.#context); + if (maybeTag) this.#authTag = Buffer.from(maybeTag); + return encoding === "buffer" ? Buffer.from([]) : ""; + } if (!this.#autoPadding && this.#cache.cache.byteLength != 16) { throw new Error("Invalid final block size"); } diff --git a/ext/node/polyfills/internal/crypto/sig.ts b/ext/node/polyfills/internal/crypto/sig.ts index c711c7193d3152..3dd6b7c589e559 100644 --- a/ext/node/polyfills/internal/crypto/sig.ts +++ b/ext/node/polyfills/internal/crypto/sig.ts @@ -7,8 +7,11 @@ import { op_node_create_private_key, op_node_create_public_key, + op_node_get_asymmetric_key_type, op_node_sign, + op_node_sign_ed25519, op_node_verify, + op_node_verify_ed25519, } from "ext:core/ops"; import { @@ -30,6 +33,8 @@ import { kConsumePublic, KeyObject, prepareAsymmetricKey, + PrivateKeyObject, + PublicKeyObject, } from "ext:deno_node/internal/crypto/keys.ts"; import { createHash } from "ext:deno_node/internal/crypto/hash.ts"; import { ERR_CRYPTO_SIGN_KEY_REQUIRED } from "ext:deno_node/internal/errors.ts"; @@ -191,7 +196,34 @@ export function signOneShot( throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); } - const result = Sign(algorithm!).update(data).sign(key); + const res = prepareAsymmetricKey(key, kConsumePrivate); + let handle; + if ("handle" in res) { + handle = res.handle; + } else { + handle = op_node_create_private_key( + res.data, + res.format, + res.type ?? "", + res.passphrase, + ); + } + + let result: Buffer; + if (op_node_get_asymmetric_key_type(handle) === "ed25519") { + if (algorithm != null && algorithm !== "sha512") { + throw new TypeError("Only 'sha512' is supported for Ed25519 keys"); + } + result = new Buffer(64); + op_node_sign_ed25519(handle, data, result); + } else if (algorithm == null) { + throw new TypeError( + "Algorithm must be specified when using non-Ed25519 keys", + ); + } else { + result = Sign(algorithm!).update(data) + .sign(new PrivateKeyObject(handle)); + } if (callback) { setTimeout(() => callback(null, result)); @@ -219,7 +251,33 @@ export function verifyOneShot( throw new ERR_CRYPTO_SIGN_KEY_REQUIRED(); } - const result = Verify(algorithm!).update(data).verify(key, signature); + const res = prepareAsymmetricKey(key, kConsumePublic); + let handle; + if ("handle" in res) { + handle = res.handle; + } else { + handle = op_node_create_public_key( + res.data, + res.format, + res.type ?? "", + res.passphrase, + ); + } + + let result: boolean; + if (op_node_get_asymmetric_key_type(handle) === "ed25519") { + if (algorithm != null && algorithm !== "sha512") { + throw new TypeError("Only 'sha512' is supported for Ed25519 keys"); + } + result = op_node_verify_ed25519(handle, data, signature); + } else if (algorithm == null) { + throw new TypeError( + "Algorithm must be specified when using non-Ed25519 keys", + ); + } else { + result = Verify(algorithm!).update(data) + .verify(new PublicKeyObject(handle), signature); + } if (callback) { setTimeout(() => callback(null, result));