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 {
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 } => {
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))
}