ferrijs-std 0.2.1

Node and web standard library for the ferrijs QuickJS runtime: WHATWG Streams, Events, AbortController, Buffer, crypto, fs, os, url, zlib and the capability model they enforce (partly derived from awslabs/llrt, Apache-2.0).
Documentation
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
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 {
    // Keep preparation outside the async block: Rust async function bodies are deferred until
    // polled, while WebCrypto requires call-time algorithm normalization and input snapshotting.
    // Retaining the Result lets preparation failures reject the rquickjs-created Promise.
    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),
        }
    }
}