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