use std::future::Future;
use crate::crypto::provider::{modern, CryptoError, CryptoProvider, HmacProvider};
use ctutils::CtEq;
use crate::utils::bytes::ObjectBytes;
use rquickjs::{Class, Ctx, FromJs, Result, Value};
use crate::crypto::CRYPTO_PROVIDER;
use super::{
algorithm_invalid_access_error,
crypto_key::{CryptoKey, KeyKind},
digest,
key_algorithm::KeyAlgorithm,
rsa_hash_digest, rsa_pss_salt_length_is_valid,
sign_algorithm::SigningAlgorithm,
util::ResultDomExt,
};
pub fn subtle_verify<'js>(
ctx: Ctx<'js>,
algorithm: Value<'js>,
key: Class<'js, CryptoKey<'js>>,
signature: ObjectBytes<'js>,
data: ObjectBytes<'js>,
) -> impl Future<Output = Result<bool>> + 'js {
let prepared = prepare_verify(&ctx, algorithm, key, signature, data);
async move {
let PreparedVerify {
algorithm,
key,
signature,
data,
} = prepared?;
let key = key.borrow();
if key.name.as_ref() != algorithm.name() {
return algorithm_invalid_access_error(&ctx, algorithm.name());
}
key.check_validity("verify").or_throw_dom(&ctx)?;
let expected_kind = match &algorithm {
SigningAlgorithm::Hmac => KeyKind::Secret,
_ => KeyKind::Public,
};
key.check_kind(expected_kind).or_throw_dom(&ctx)?;
verify(&ctx, &algorithm, &key, &signature, &data)
}
}
struct PreparedVerify<'js> {
algorithm: SigningAlgorithm,
key: Class<'js, CryptoKey<'js>>,
signature: Vec<u8>,
data: Vec<u8>,
}
fn prepare_verify<'js>(
ctx: &Ctx<'js>,
algorithm: Value<'js>,
key: Class<'js, CryptoKey<'js>>,
signature: ObjectBytes<'js>,
data: ObjectBytes<'js>,
) -> Result<PreparedVerify<'js>> {
let algorithm = SigningAlgorithm::from_js(ctx, algorithm)?;
let signature = signature.as_bytes_opt().unwrap_or_default().to_vec();
let data = data.as_bytes_opt().unwrap_or_default().to_vec();
Ok(PreparedVerify {
algorithm,
key,
signature,
data,
})
}
fn verify(
ctx: &Ctx<'_>,
algorithm: &SigningAlgorithm,
key: &CryptoKey,
signature: &[u8],
data: &[u8],
) -> Result<bool> {
let handle = key.handle.as_ref();
Ok(match algorithm {
SigningAlgorithm::Ecdsa { hash } => {
let curve = match &key.algorithm {
KeyAlgorithm::Ec { curve, .. } => curve,
_ => return algorithm_invalid_access_error(ctx, "ECDSA"),
};
let digest = digest::digest(hash, data);
crate::crypto::CRYPTO_PROVIDER
.ecdsa_verify(*curve, handle, signature, &digest)
.into_verification(ctx)?
},
SigningAlgorithm::Ed25519 => {
if !matches!(&key.algorithm, KeyAlgorithm::Ed25519) {
return algorithm_invalid_access_error(ctx, "Ed25519");
}
crate::crypto::CRYPTO_PROVIDER
.ed25519_verify(handle, signature, data)
.into_verification(ctx)?
},
SigningAlgorithm::Hmac => {
let hash = match &key.algorithm {
KeyAlgorithm::Hmac { hash, .. } => hash,
_ => return algorithm_invalid_access_error(ctx, "HMAC"),
};
let mut hmac = CRYPTO_PROVIDER.hmac(*hash, handle);
hmac.update(data);
let computed_signature = hmac.finalize();
computed_signature.as_slice().ct_eq(signature).to_bool()
},
SigningAlgorithm::MlDsa { variant, context } => {
if !matches!(&key.algorithm, KeyAlgorithm::MlDsa(key_variant) if key_variant == variant)
{
return algorithm_invalid_access_error(ctx, variant.as_str());
}
modern::ml_dsa_verify(*variant, handle, signature, data, context)
.into_verification(ctx)?
},
SigningAlgorithm::RsaPss { salt_length } => {
let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSA-PSS")?;
if !rsa_pss_salt_length_is_valid(key, hash, *salt_length) {
false
} else {
crate::crypto::CRYPTO_PROVIDER
.rsa_pss_verify(
&key.handle,
signature,
digest.as_ref(),
*salt_length as usize,
*hash,
)
.into_verification(ctx)?
}
},
SigningAlgorithm::RsassaPkcs1v15 => {
let (hash, digest) = rsa_hash_digest(ctx, key, data, "RSASSA-PKCS1-v1_5")?;
crate::crypto::CRYPTO_PROVIDER
.rsa_pkcs1v15_verify(&key.handle, signature, digest.as_ref(), *hash)
.into_verification(ctx)?
},
})
}
trait VerificationResultExt {
fn into_verification(self, ctx: &Ctx<'_>) -> Result<bool>;
}
impl VerificationResultExt for std::result::Result<bool, CryptoError> {
fn into_verification(self, ctx: &Ctx<'_>) -> Result<bool> {
match self {
Err(CryptoError::InvalidSignature(_)) => Ok(false),
result => result.or_throw_dom(ctx),
}
}
}