/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/crypto/subtle_verify.rs
416 строк
12 KB
em
feat(ext/crypto): support remaining modern WebCrypto algorithms (#35223)
15 июн 2026, 13:18
Не верифицирован
15 июн 2026, 13:18
9fb684d
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. //! `SubtleCrypto.verify()` body in Rust — symmetric mirror of //! [`crate::subtle_sign`]. The per-algorithm parameter dictionary //! (`saltLength` for RSA-PSS, `hash` for ECDSA, optional `context` for //! ML-DSA) is parsed by [`SubtleVerifyParams`], and the dispatch lands //! in the existing per-algorithm verify helpers exported from //! [`crate::lib`], [`crate::ed25519`], and [`crate::mldsa`]. use std::borrow::Cow; use deno_core::v8; use deno_core::webidl::ContextFn; use deno_core::webidl::WebIdlConverter; use deno_core::webidl::WebIdlError; use deno_error::JsErrorBox; use crate::CryptoError; use crate::KeyData; use crate::VerifyArg; use crate::crypto_key::CryptoKeyType; use crate::ed25519::ed25519_verify; use crate::key::Algorithm; use crate::key::CryptoHash; use crate::key::CryptoNamedCurve; use crate::mldsa::mldsa_verify; use crate::shared::ShaHash; use crate::subtle_encrypt::extract_name_and_obj; use crate::subtle_key::SubtleKey; use crate::subtle_sign::read_optional_buffer_source; use crate::subtle_sign::read_required_hash; use crate::subtle_sign::read_required_u32; use crate::verify_key_sync; pub enum SubtleVerifyParams { RsassaPkcs1v15, RsaPss { salt_length: u32, }, Ecdsa { /// Raw `.name` string; resolved to `ShaHash` at `run` time so an /// unknown / mis-cased name throws `DOMException NotSupportedError` /// (see `crate::subtle_sign::read_required_hash`). hash: String, }, Hmac, Kmac { name: &'static str, output_length: u32, customization: Option<Vec<u8>>, }, Ed25519, MlDsa { variant: u8, context: Option<Vec<u8>>, }, SlhDsa { variant: crate::slhdsa::SlhDsaVariantId, context: Option<Vec<u8>>, }, Unknown(String), } impl SubtleVerifyParams { pub fn canonical_name(&self) -> &str { match self { Self::RsassaPkcs1v15 => "RSASSA-PKCS1-v1_5", Self::RsaPss { .. } => "RSA-PSS", Self::Ecdsa { .. } => "ECDSA", Self::Hmac => "HMAC", Self::Kmac { name, .. } => name, Self::Ed25519 => "Ed25519", Self::MlDsa { variant, .. } => match variant { 0 => "ML-DSA-44", 1 => "ML-DSA-65", _ => "ML-DSA-87", }, Self::SlhDsa { variant, .. } => crate::slhdsa::params(*variant).name, Self::Unknown(n) => n, } } } impl<'a> WebIdlConverter<'a> for SubtleVerifyParams { type Options = (); fn convert<'b>( scope: &mut v8::PinScope<'a, '_>, value: v8::Local<'a, v8::Value>, prefix: Cow<'static, str>, context: ContextFn<'b>, _options: &Self::Options, ) -> Result<Self, WebIdlError> { let (name_str, maybe_obj) = extract_name_and_obj(scope, value, prefix.clone(), context.borrowed())?; let Some(canonical) = canonical_verify_name(&name_str) else { return Ok(Self::Unknown(name_str)); }; match canonical { "RSASSA-PKCS1-v1_5" => Ok(Self::RsassaPkcs1v15), "RSA-PSS" => { let obj = maybe_obj.ok_or_else(|| missing_dict(prefix.clone(), &context))?; let salt_length = read_required_u32( scope, obj, "saltLength", prefix.clone(), &context, )?; Ok(Self::RsaPss { salt_length }) } "ECDSA" => { let obj = maybe_obj.ok_or_else(|| missing_dict(prefix.clone(), &context))?; let hash = read_required_hash(scope, obj, "hash", prefix.clone(), &context)?; Ok(Self::Ecdsa { hash }) } "HMAC" => Ok(Self::Hmac), "KMAC128" | "KMAC256" => { let obj = maybe_obj.ok_or_else(|| missing_dict(prefix.clone(), &context))?; let output_length = read_required_u32( scope, obj, "outputLength", prefix.clone(), &context, )?; let customization = read_optional_buffer_source( scope, obj, "customization", prefix.clone(), &context, )?; Ok(Self::Kmac { name: canonical, output_length, customization, }) } "Ed25519" => Ok(Self::Ed25519), "ML-DSA-44" | "ML-DSA-65" | "ML-DSA-87" => { let variant = match canonical { "ML-DSA-44" => 0, "ML-DSA-65" => 1, _ => 2, }; let context_bytes = match maybe_obj { Some(o) => read_optional_buffer_source( scope, o, "context", prefix.clone(), &context, )?, None => None, }; Ok(Self::MlDsa { variant, context: context_bytes, }) } _ if let Some(variant) = crate::slhdsa::variant_from_name(canonical) => { let context_bytes = match maybe_obj { Some(o) => read_optional_buffer_source( scope, o, "context", prefix.clone(), &context, )?, None => None, }; Ok(Self::SlhDsa { variant, context: context_bytes, }) } _ => unreachable!(), } } } fn canonical_verify_name(name: &str) -> Option<&'static str> { const NAMES: &[&str] = &[ "RSASSA-PKCS1-v1_5", "RSA-PSS", "ECDSA", "HMAC", "KMAC128", "KMAC256", "Ed25519", "ML-DSA-44", "ML-DSA-65", "ML-DSA-87", "SLH-DSA-SHA2-128s", "SLH-DSA-SHA2-128f", "SLH-DSA-SHA2-192s", "SLH-DSA-SHA2-192f", "SLH-DSA-SHA2-256s", "SLH-DSA-SHA2-256f", "SLH-DSA-SHAKE-128s", "SLH-DSA-SHAKE-128f", "SLH-DSA-SHAKE-192s", "SLH-DSA-SHAKE-192f", "SLH-DSA-SHAKE-256s", "SLH-DSA-SHAKE-256f", ]; NAMES.iter().copied().find(|n| n.eq_ignore_ascii_case(name)) } fn missing_dict( prefix: Cow<'static, str>, context: &ContextFn<'_>, ) -> WebIdlError { WebIdlError::other( prefix, context.borrowed(), JsErrorBox::type_error("Algorithm requires a parameter dictionary"), ) } const SUPPORTED_NAMED_CURVES: &[&str] = &["P-256", "P-384", "P-521"]; pub fn run( params: SubtleVerifyParams, key: SubtleKey, signature: Vec<u8>, data: Vec<u8>, ) -> Result<bool, CryptoError> { if params.canonical_name() != key.algorithm_name { return Err(invalid_access(format!( "Verifying algorithm '{}' does not match key algorithm", params.canonical_name() ))); } if !key.has_usage("verify") { return Err(invalid_access( "The requested operation is not valid for the provided key".to_string(), )); } match params { SubtleVerifyParams::RsassaPkcs1v15 => { if key.key_type != CryptoKeyType::Public { return Err(invalid_access("Key type not supported".to_string())); } let hash = key.algorithm_hash.ok_or_else(|| { op_error("RSASSA-PKCS1-v1_5 key is missing 'hash'".to_string()) })?; let key_data: KeyData = (&key.raw).into(); let args = VerifyArg::new( Algorithm::RsassaPkcs1v15, None, Some(sha_to_crypto_hash(hash)), signature, None, ); verify_key_sync(key_data, args, &data) } SubtleVerifyParams::RsaPss { salt_length } => { if key.key_type != CryptoKeyType::Public { return Err(invalid_access("Key type not supported".to_string())); } let hash = key .algorithm_hash .ok_or_else(|| op_error("RSA-PSS key is missing 'hash'".to_string()))?; let key_data: KeyData = (&key.raw).into(); let args = VerifyArg::new( Algorithm::RsaPss, Some(salt_length), Some(sha_to_crypto_hash(hash)), signature, None, ); verify_key_sync(key_data, args, &data) } SubtleVerifyParams::Ecdsa { hash } => { // Resolve the raw hash string here so that an unknown / mis-cased // name throws `DOMException NotSupportedError`, matching the // ECDSA `verification failure due to bad hash name` WPT cases. let hash = crate::subtle_generate_key::sha_from_name(&hash).ok_or_else(|| { not_supported(format!("Unrecognized hash algorithm: {hash}")) })?; if key.key_type != CryptoKeyType::Public { return Err(invalid_access("Key type not supported".to_string())); } let curve_name = key.algorithm_named_curve.as_deref().ok_or_else(|| { op_error("ECDSA key is missing 'namedCurve'".to_string()) })?; if !SUPPORTED_NAMED_CURVES.contains(&curve_name) { return Err(not_supported("Curve not supported".to_string())); } let named_curve = parse_named_curve(curve_name) .ok_or_else(|| not_supported("Curve not supported".to_string()))?; let key_data: KeyData = (&key.raw).into(); let args = VerifyArg::new( Algorithm::Ecdsa, None, Some(sha_to_crypto_hash(hash)), signature, Some(named_curve), ); verify_key_sync(key_data, args, &data) } SubtleVerifyParams::Hmac => { let hash = key .algorithm_hash .ok_or_else(|| op_error("HMAC key is missing 'hash'".to_string()))?; let key_data: KeyData = (&key.raw).into(); let args = VerifyArg::new( Algorithm::Hmac, None, Some(sha_to_crypto_hash(hash)), signature, None, ); verify_key_sync(key_data, args, &data) } SubtleVerifyParams::Kmac { output_length, customization, .. } => { let computed = crate::subtle_sign::run_kmac( &key, &data, output_length, customization, )?; Ok(constant_time_eq(&computed, &signature)) } SubtleVerifyParams::Ed25519 => { if key.key_type != CryptoKeyType::Public { return Err(invalid_access("Key type not supported".to_string())); } Ok(ed25519_verify(key.raw.bytes(), &data, &signature)) } SubtleVerifyParams::MlDsa { variant, context } => { if key.key_type != CryptoKeyType::Public { return Err(invalid_access("Key type not supported".to_string())); } Ok(mldsa_verify( variant, key.raw.bytes(), &data, &signature, context.as_deref(), )) } SubtleVerifyParams::SlhDsa { variant, context } => { if key.key_type != CryptoKeyType::Public { return Err(invalid_access("Key type not supported".to_string())); } Ok(crate::slhdsa::verify( variant, key.raw.bytes(), &data, &signature, context.as_deref(), )) } SubtleVerifyParams::Unknown(name) => Err(not_supported(format!( "Algorithm '{name}' is not supported" ))), } } fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { return false; } let mut diff = 0u8; for (&x, &y) in a.iter().zip(b) { diff |= x ^ y; } diff == 0 } fn sha_to_crypto_hash(h: ShaHash) -> CryptoHash { match h { ShaHash::Sha1 => CryptoHash::Sha1, ShaHash::Sha256 => CryptoHash::Sha256, ShaHash::Sha384 => CryptoHash::Sha384, ShaHash::Sha512 => CryptoHash::Sha512, ShaHash::Sha3_256 => CryptoHash::Sha3_256, ShaHash::Sha3_384 => CryptoHash::Sha3_384, ShaHash::Sha3_512 => CryptoHash::Sha3_512, } } fn parse_named_curve(name: &str) -> Option<CryptoNamedCurve> { match name { "P-256" => Some(CryptoNamedCurve::P256), "P-384" => Some(CryptoNamedCurve::P384), "P-521" => Some(CryptoNamedCurve::P521), _ => None, } } fn invalid_access(msg: String) -> CryptoError { CryptoError::Other(JsErrorBox::new("DOMExceptionInvalidAccessError", msg)) } fn op_error(msg: String) -> CryptoError { CryptoError::Other(JsErrorBox::new("DOMExceptionOperationError", msg)) } fn not_supported(msg: String) -> CryptoError { CryptoError::Other(JsErrorBox::new("DOMExceptionNotSupportedError", msg)) }