ferrijs-std 0.2.4

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 crate::exceptions::DOMException;
#[cfg(feature = "_subtle-full")]
use crate::utils::bytes::ObjectBytes;
use crate::utils::object::ObjectExt;
#[cfg(feature = "_subtle-full")]
use rquickjs::FromJs;
use rquickjs::{Array, Class, Ctx, Result, Value};

#[cfg(feature = "_subtle-full")]
use super::key_algorithm::{key_format_not_supported_error, KeyFormat};
use super::{
    crypto_key::{CryptoKey, KeyKind},
    key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyFormatData},
};

#[cfg(feature = "_subtle-full")]
pub async fn subtle_import_key<'js>(
    ctx: Ctx<'js>,
    format: Value<'js>,
    key_data: Value<'js>,
    algorithm: Value<'js>,
    extractable: bool,
    key_usages: Array<'js>,
) -> Result<Class<'js, CryptoKey<'js>>> {
    let format = KeyFormat::from_js(&ctx, format)?;
    validate_import_algorithm(
        &ctx,
        &format,
        algorithm.clone(),
        extractable,
        key_usages.clone(),
    )?;
    let format = match format {
        KeyFormat::Raw => KeyFormatData::Raw(ObjectBytes::from_js(&ctx, key_data)?),
        KeyFormat::RawPrivate => KeyFormatData::RawPrivate(ObjectBytes::from_js(&ctx, key_data)?),
        KeyFormat::RawPublic => KeyFormatData::RawPublic(ObjectBytes::from_js(&ctx, key_data)?),
        KeyFormat::RawSecret => KeyFormatData::RawSecret(ObjectBytes::from_js(&ctx, key_data)?),
        KeyFormat::RawSeed => KeyFormatData::RawSeed(ObjectBytes::from_js(&ctx, key_data)?),
        KeyFormat::Pkcs8 => KeyFormatData::Pkcs8(ObjectBytes::from_js(&ctx, key_data)?),
        KeyFormat::Spki => KeyFormatData::Spki(ObjectBytes::from_js(&ctx, key_data)?),
        KeyFormat::Jwk => KeyFormatData::Jwk(key_data.into_object_or_throw(&ctx, "keyData")?),
    };

    import_key(ctx, format, algorithm, extractable, key_usages)
}

pub fn import_key<'js>(
    ctx: Ctx<'js>,
    format: KeyFormatData<'js>,
    algorithm: Value<'js>,
    extractable: bool,
    key_usages: Array<'js>,
) -> Result<Class<'js, CryptoKey<'js>>> {
    let jwk = match &format {
        KeyFormatData::Jwk(value) => Some(value.clone()),
        _ => None,
    };

    let mut kind = KeyKind::Public;
    let mut data = Vec::new();

    let KeyAlgorithmWithUsages {
        name,
        algorithm: key_algorithm,
        public_usages,
        private_usages,
    } = KeyAlgorithm::from_js(
        &ctx,
        KeyAlgorithmMode::Import {
            kind: &mut kind,
            data: &mut data,
            format,
        },
        algorithm,
        key_usages,
    )?;
    if extractable
        && matches!(
            &key_algorithm,
            KeyAlgorithm::HkdfImport | KeyAlgorithm::Pbkdf2Import
        )
    {
        return Err(DOMException::syntax_error(
            &ctx,
            format!("{name} keys must not be extractable"),
        ));
    }

    let usages = match kind {
        KeyKind::Public | KeyKind::Secret => public_usages,
        KeyKind::Private => private_usages,
    };
    if let Some(jwk) = jwk {
        if let Some(key_ops) = parse_jwk_key_ops(&ctx, &jwk)? {
            validate_requested_jwk_key_ops(&ctx, &key_ops, &usages)?;
        }
        validate_jwk_extractable(&ctx, &jwk, extractable)?;
    }

    Class::instance(
        ctx,
        CryptoKey::new(kind, name, extractable, key_algorithm, usages, data),
    )
}

#[cfg(feature = "_subtle-full")]
fn validate_import_algorithm<'js>(
    ctx: &Ctx<'js>,
    format: &KeyFormat,
    algorithm: Value<'js>,
    extractable: bool,
    key_usages: Array<'js>,
) -> Result<()> {
    let normalized =
        KeyAlgorithm::from_js(ctx, KeyAlgorithmMode::ValidateImport, algorithm, key_usages)?;
    if matches!(
        normalized.algorithm,
        super::key_algorithm::KeyAlgorithm::HkdfImport
            | super::key_algorithm::KeyAlgorithm::Pbkdf2Import
    ) {
        if !matches!(format, KeyFormat::Raw | KeyFormat::RawSecret) {
            return key_format_not_supported_error(ctx, &normalized.name, format.as_str());
        }
        if extractable {
            return Err(DOMException::syntax_error(
                ctx,
                format!("{} keys must not be extractable", normalized.name),
            ));
        }
    }
    Ok(())
}

fn validate_jwk_extractable(
    ctx: &Ctx<'_>,
    jwk: &rquickjs::Object<'_>,
    extractable: bool,
) -> Result<()> {
    if extractable && matches!(jwk.get_optional::<_, bool>("ext")?, Some(false)) {
        return Err(DOMException::data_error(ctx, "JWK is not extractable"));
    }
    Ok(())
}

fn parse_jwk_key_ops(ctx: &Ctx<'_>, jwk: &rquickjs::Object<'_>) -> Result<Option<Vec<String>>> {
    let Some(key_ops) = jwk.get_optional::<_, Array>("key_ops")? else {
        return Ok(None);
    };
    let mut operations = Vec::with_capacity(key_ops.len());
    for operation in key_ops.iter::<String>() {
        let operation = operation?;
        if operations.contains(&operation) {
            return Err(DOMException::data_error(
                ctx,
                "JWK 'key_ops' contains a duplicate operation",
            ));
        }
        operations.push(operation);
    }
    Ok(Some(operations))
}

fn validate_requested_jwk_key_ops(
    ctx: &Ctx<'_>,
    operations: &[String],
    requested_usages: &[String],
) -> Result<()> {
    if requested_usages
        .iter()
        .any(|usage| !operations.contains(usage))
    {
        return Err(DOMException::data_error(
            ctx,
            "JWK 'key_ops' does not contain all requested usages",
        ));
    }
    Ok(())
}