ferrijs-std 0.2.2

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;
use crate::utils::{bytes::ObjectBytes, object::ObjectExt};
use rquickjs::{Ctx, FromJs, Result, Value};

use super::{
    algorithm_not_supported_error, enforce_range_u8, get_optional_dictionary_value,
    get_required_dictionary_value, normalize_algorithm_name, to_name_and_maybe_object,
};

#[derive(Debug)]
pub enum EncryptionAlgorithm {
    AesCbc {
        iv: Box<[u8]>,
    },
    AesCtr {
        counter: Box<[u8]>,
        length: u32,
    },
    AesGcm {
        iv: Box<[u8]>,
        tag_length: u8,
        additional_data: Option<Box<[u8]>>,
    },
    ChaCha20Poly1305 {
        iv: Box<[u8]>,
        tag_length: u8,
        additional_data: Option<Box<[u8]>>,
    },
    RsaOaep {
        label: Option<Box<[u8]>>,
    },
    AesKw,
}

impl<'js> FromJs<'js> for EncryptionAlgorithm {
    fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
        let (name, obj) = to_name_and_maybe_object(ctx, value)?;
        let name = normalize_algorithm_name(&name);

        match name.as_str() {
            "AES-CBC" => {
                let obj = obj?;
                let iv = obj
                    .get_required::<_, ObjectBytes>("iv", "algorithm")?
                    .into_bytes(ctx)?
                    .into_boxed_slice();

                if iv.len() != 16 {
                    return Err(DOMException::operation_error(
                        ctx,
                        "invalid length of iv. Currently supported 16 bytes",
                    ));
                }

                Ok(EncryptionAlgorithm::AesCbc { iv })
            },
            "AES-CTR" => {
                let obj = obj?;
                let counter = obj
                    .get_required::<_, ObjectBytes>("counter", "algorithm")?
                    .into_bytes(ctx)?
                    .into_boxed_slice();

                let value = get_required_dictionary_value(&obj, "length", "algorithm")?;
                let length = u32::from(enforce_range_u8(ctx, value, "length")?);

                if !matches!(length, 32 | 64 | 128) {
                    return Err(DOMException::operation_error(
                        ctx,
                        "invalid counter length. Currently supported 32/64/128 bits",
                    ));
                }

                Ok(EncryptionAlgorithm::AesCtr { counter, length })
            },
            "AES-GCM" => {
                let obj = obj?;
                let iv = obj
                    .get_required::<_, ObjectBytes>("iv", "algorithm")?
                    .into_bytes(ctx)?
                    .into_boxed_slice();

                let additional_data = obj
                    .get_optional::<_, ObjectBytes>("additionalData")?
                    .map(|v| v.into_bytes(ctx))
                    .transpose()?
                    .map(|vec| vec.into_boxed_slice());

                let tag_length = get_optional_dictionary_value(&obj, "tagLength")?
                    .map(|value| enforce_range_u8(ctx, value, "tagLength"))
                    .transpose()?
                    .unwrap_or(128);

                //ensure tag length is supported using a match statement 32, 64, 96, 104, 112, 120, or 128
                if !matches!(tag_length, 32 | 64 | 96 | 104 | 112 | 120 | 128) {
                    return Err(DOMException::operation_error(ctx, "Invalid tagLength"));
                }

                Ok(EncryptionAlgorithm::AesGcm {
                    iv,
                    additional_data,
                    tag_length,
                })
            },
            "ChaCha20-Poly1305" => {
                let obj = obj?;
                let iv = obj
                    .get_required::<_, ObjectBytes>("iv", "algorithm")?
                    .into_bytes(ctx)?
                    .into_boxed_slice();
                let additional_data = obj
                    .get_optional::<_, ObjectBytes>("additionalData")?
                    .map(|value| value.into_bytes(ctx))
                    .transpose()?
                    .map(Vec::into_boxed_slice);
                let tag_length = get_optional_dictionary_value(&obj, "tagLength")?
                    .map(|value| enforce_range_u8(ctx, value, "tagLength"))
                    .transpose()?
                    .unwrap_or(128);

                Ok(EncryptionAlgorithm::ChaCha20Poly1305 {
                    iv,
                    tag_length,
                    additional_data,
                })
            },
            "RSA-OAEP" => {
                let label = if let Ok(obj) = obj {
                    let value: Value = obj.get("label")?;
                    if value.is_undefined() {
                        None
                    } else if value.is_null() {
                        return Err(rquickjs::Exception::throw_type(
                            ctx,
                            "RSA-OAEP label must be a BufferSource",
                        ));
                    } else {
                        Some(
                            ObjectBytes::from_js(ctx, value)?
                                .into_bytes(ctx)?
                                .into_boxed_slice(),
                        )
                    }
                } else {
                    None
                };

                Ok(EncryptionAlgorithm::RsaOaep { label })
            },
            "AES-KW" => Ok(EncryptionAlgorithm::AesKw),
            _ => algorithm_not_supported_error(ctx),
        }
    }
}