diff --git a/Cargo.lock b/Cargo.lock index 7fae2daba9..6666c7b02e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -682,6 +682,32 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + [[package]] name = "deadpool" version = "0.10.0" @@ -707,10 +733,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "der_derive", "pem-rfc7468", "zeroize", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] + [[package]] name = "digest" version = "0.10.7" @@ -845,6 +883,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "flate2" version = "1.0.35" @@ -1691,9 +1735,11 @@ dependencies = [ "aes", "aes-gcm", "cbc", + "const-oid", "crc32c", "crc32fast", "ctr", + "der", "llrt_buffer", "llrt_context", "llrt_encoding", @@ -1704,13 +1750,16 @@ dependencies = [ "once_cell", "p256", "p384", + "pkcs8", "rand", "ring", "rquickjs", "rsa", + "spki", "tokio", "uuid", "uuid-simd", + "x25519-dalek", ] [[package]] @@ -3683,6 +3732,18 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + [[package]] name = "yoke" version = "0.7.5" @@ -3754,6 +3815,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.90", +] [[package]] name = "zerovec" diff --git a/libs/llrt_encoding/src/lib.rs b/libs/llrt_encoding/src/lib.rs index 254fbeef79..38c50e2690 100644 --- a/libs/llrt_encoding/src/lib.rs +++ b/libs/llrt_encoding/src/lib.rs @@ -137,6 +137,10 @@ pub fn bytes_to_b64_string(bytes: &[u8]) -> String { base64_simd::STANDARD.encode_to_string(bytes) } +pub fn bytes_to_b64_url_safe_string(bytes: &[u8]) -> String { + base64_simd::URL_SAFE_NO_PAD.encode_to_string(bytes) +} + pub fn bytes_from_b64(bytes: &[u8]) -> Result, String> { base64_simd::forgiving_decode_to_vec(bytes).map_err(|e| e.to_string()) } diff --git a/libs/llrt_utils/src/bytes.rs b/libs/llrt_utils/src/bytes.rs index 5e6c2c7106..5f0f02d58d 100644 --- a/libs/llrt_utils/src/bytes.rs +++ b/libs/llrt_utils/src/bytes.rs @@ -6,7 +6,7 @@ use rquickjs::{ use crate::error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED; -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub enum ObjectBytes<'js> { U8Array(TypedArray<'js, u8>), I8Array(TypedArray<'js, i8>), diff --git a/modules/llrt_crypto/Cargo.toml b/modules/llrt_crypto/Cargo.toml index dd695f906f..4f6c832c79 100644 --- a/modules/llrt_crypto/Cargo.toml +++ b/modules/llrt_crypto/Cargo.toml @@ -37,6 +37,15 @@ ctr = "0.9" rsa = { version = "0.9", features = ["std", "sha2"], default-features = false } p256 = { version = "0.13", features = ["ecdh"] } p384 = "0.13" +x25519-dalek = { version = "2", features = [ + "static_secrets", + "zeroize", + "getrandom", +] } +spki = { version = "0.7", features = ["std"] } +pkcs8 = { version = "0.10", features = ["std"] } +der = { version = "0.7", features = ["derive"] } +const-oid = { version = "0.9", features = ["db"] } [target.'cfg(target_os = "windows")'.dependencies] memchr = "2" diff --git a/modules/llrt_crypto/src/subtle/crypto_key.rs b/modules/llrt_crypto/src/subtle/crypto_key.rs index 21eaf29447..27014f9042 100644 --- a/modules/llrt_crypto/src/subtle/crypto_key.rs +++ b/modules/llrt_crypto/src/subtle/crypto_key.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::rc::Rc; +use llrt_utils::str_enum; use rquickjs::{ class::{Trace, Tracer}, Ctx, Result, Value, @@ -9,20 +10,29 @@ use rquickjs::{ use super::key_algorithm::KeyAlgorithm; +#[derive(PartialEq)] +pub enum KeyKind { + Secret, + Private, + Public, +} + +str_enum!(KeyKind,Secret => "secret", Private => "private", Public => "public"); + #[rquickjs::class] #[derive(rquickjs::JsLifetime)] pub struct CryptoKey { - type_name: &'static str, + pub kind: KeyKind, pub extractable: bool, pub algorithm: KeyAlgorithm, pub name: Box, - usages: Vec, + pub usages: Vec, pub handle: Rc<[u8]>, } impl CryptoKey { pub fn new( - type_name: &'static str, + kind: KeyKind, name: N, extractable: bool, algorithm: KeyAlgorithm, @@ -34,7 +44,7 @@ impl CryptoKey { H: Into>, { Self { - type_name, + kind, extractable, algorithm, name: name.into(), @@ -52,7 +62,7 @@ impl<'js> Trace<'js> for CryptoKey { impl CryptoKey { #[qjs(get, rename = "type")] pub fn get_type(&self) -> &str { - self.type_name + self.kind.as_str() } #[qjs(get)] @@ -69,7 +79,7 @@ impl CryptoKey { #[qjs(get)] pub fn usages(&self) -> Vec { - self.usages.iter().map(|u| u.to_string()).collect() + self.usages.clone() } } diff --git a/modules/llrt_crypto/src/subtle/derive.rs b/modules/llrt_crypto/src/subtle/derive.rs index 551ea0878e..f229a028c1 100644 --- a/modules/llrt_crypto/src/subtle/derive.rs +++ b/modules/llrt_crypto/src/subtle/derive.rs @@ -9,6 +9,7 @@ use rquickjs::{Array, ArrayBuffer, Class, Ctx, Exception, Result, Value}; use super::{ algorithm_not_supported_error, + crypto_key::KeyKind, derive_algorithm::DeriveAlgorithm, key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyDerivation}, }; @@ -139,7 +140,7 @@ pub async fn subtle_derive_key<'js>( key_usages, )?; - let length: u16 = match &derived_key_algorithm { + let length = match &derived_key_algorithm { KeyAlgorithm::Aes { length } => *length, KeyAlgorithm::Hmac { length, .. } => *length, KeyAlgorithm::Derive { .. } => 0, @@ -153,7 +154,7 @@ pub async fn subtle_derive_key<'js>( let bytes = derive_bits(&ctx, &algorithm, handle, length as u32)?; let key = CryptoKey::new( - "secret", + KeyKind::Secret, name, extractable, derived_key_algorithm, diff --git a/modules/llrt_crypto/src/subtle/derive_algorithm.rs b/modules/llrt_crypto/src/subtle/derive_algorithm.rs index e6c2c2db3b..40dd2d228c 100644 --- a/modules/llrt_crypto/src/subtle/derive_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/derive_algorithm.rs @@ -27,7 +27,7 @@ impl<'js> FromJs<'js> for DeriveAlgorithm { "ECDH" | "X25519" => { let public_key: Class = obj.get_required("public", "algorithm")?; let public_key = public_key.borrow(); - let curve = if let KeyAlgorithm::Ec { curve } = &public_key.algorithm { + let curve = if let KeyAlgorithm::Ec { curve, .. } = &public_key.algorithm { curve.clone() } else { return Err(Exception::throw_message( diff --git a/modules/llrt_crypto/src/subtle/export_key.rs b/modules/llrt_crypto/src/subtle/export_key.rs index 15254308a7..3d237109d4 100644 --- a/modules/llrt_crypto/src/subtle/export_key.rs +++ b/modules/llrt_crypto/src/subtle/export_key.rs @@ -1,18 +1,35 @@ -use llrt_utils::result::ResultExt; -use ring::signature::{EcdsaKeyPair, Ed25519KeyPair, KeyPair}; // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Result}; + +use der::{ + asn1::{self, BitString}, + Encode, SecretDocument, +}; +use llrt_encoding::bytes_to_b64_url_safe_string; +use llrt_utils::result::ResultExt; +use p256::elliptic_curve; +use pkcs8::{AssociatedOid, PrivateKeyInfo}; +use ring::signature::{EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair}; + +use pkcs8::EncodePrivateKey; +use rquickjs::{ArrayBuffer, Class, Ctx, Exception, Object, Result}; +use rsa::{pkcs1::DecodeRsaPrivateKey, RsaPrivateKey}; +use spki::{AlgorithmIdentifier, AlgorithmIdentifierOwned, SubjectPublicKeyInfo}; use crate::{subtle::CryptoKey, SYSTEM_RANDOM}; -use super::{algorithm_not_supported_error, key_algorithm::KeyAlgorithm}; +use super::{ + algorithm_not_supported_error, + crypto_key::KeyKind, + key_algorithm::{EcAlgorithm, KeyAlgorithm}, + EllipticCurve, +}; pub async fn subtle_export_key<'js>( ctx: Ctx<'js>, format: String, key: Class<'js, CryptoKey>, -) -> Result> { +) -> Result> { let key = key.borrow(); if !key.extractable { @@ -22,26 +39,35 @@ pub async fn subtle_export_key<'js>( )); }; - //TODO add more formats - if format != "raw" { - return Err(Exception::throw_type( + match format.as_str() { + "raw" => export_raw(ctx, &key), + "pkcs8" => export_pkcs8(ctx, &key), + "spki" => export_spki(ctx, &key), + "jwk" => export_jwk(ctx, &key), + _ => Err(Exception::throw_type( &ctx, &["Format '", &format, "' is not implemented"].concat(), - )); + )), } - export_raw(ctx, &key) } -fn export_raw<'js>(ctx: Ctx<'js>, key: &CryptoKey) -> Result> { +fn export_raw<'js>(ctx: Ctx<'js>, key: &CryptoKey) -> Result> { + if matches!(key.kind, KeyKind::Private) { + return Err(Exception::throw_type( + &ctx, + "Private Crypto keys can't be exported as raw format", + )); + }; let handle = key.handle.as_ref(); let bytes: Vec = match &key.algorithm { KeyAlgorithm::Aes { .. } | KeyAlgorithm::Hmac { .. } => handle.into(), - KeyAlgorithm::Ec { curve } => { + KeyAlgorithm::Ec { curve, .. } => { let alg = curve.as_signing_algorithm(); let rng = &(*SYSTEM_RANDOM); let key_pair = EcdsaKeyPair::from_pkcs8(alg, &key.handle, rng).or_throw(&ctx)?; key_pair.public_key().as_ref().into() }, + KeyAlgorithm::X25519 => handle[32..].into(), //public key last 32 bytes KeyAlgorithm::Ed25519 => { let key_pair = Ed25519KeyPair::from_pkcs8(handle).or_throw(&ctx)?; key_pair.public_key().as_ref().into() @@ -53,5 +79,231 @@ fn export_raw<'js>(ctx: Ctx<'js>, key: &CryptoKey) -> Result> { _ => return algorithm_not_supported_error(&ctx), }; - ArrayBuffer::new(ctx, bytes) + Ok(ArrayBuffer::new(ctx, bytes)?.into_object()) +} + +fn export_pkcs8<'js>(ctx: Ctx<'js>, key: &CryptoKey) -> Result> { + let handle = key.handle.as_ref(); + + if !matches!(key.kind, KeyKind::Private) { + return Err(Exception::throw_type( + &ctx, + "Public or Secret Crypto keys can't be exported as pkcs8 format", + )); + } + + let bytes: Vec = match &key.algorithm { + KeyAlgorithm::Ec { .. } | KeyAlgorithm::Ed25519 => handle.into(), + KeyAlgorithm::X25519 => PrivateKeyInfo::new( + AlgorithmIdentifier { + oid: const_oid::db::rfc8410::ID_X_25519, + parameters: None, + }, + &handle[0..32], //private key lengths + ) + .to_der() + .or_throw(&ctx)?, + KeyAlgorithm::Rsa { .. } => rsa_der_pkcs1_to_pkcs8(&ctx, handle)?.as_bytes().to_vec(), + _ => return algorithm_not_supported_error(&ctx), + }; + + Ok(ArrayBuffer::new(ctx, bytes)?.into_object()) +} + +fn rsa_der_pkcs1_to_pkcs8(ctx: &Ctx, handle: &[u8]) -> Result { + let private_key = RsaPrivateKey::from_pkcs1_der(handle).or_throw(ctx)?; + private_key.to_pkcs8_der().or_throw(ctx) +} + +fn export_spki<'js>(ctx: Ctx<'js>, key: &CryptoKey) -> Result> { + if !matches!(key.kind, KeyKind::Public) { + return Err(Exception::throw_type( + &ctx, + "Private or Secret Crypto keys can't be exported as spki format", + )); + } + + let handle = key.handle.as_ref(); + let bytes: Vec = match &key.algorithm { + KeyAlgorithm::X25519 => { + let public_key = &handle[32..]; //public key last 32 bytes + + let key_info = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifierRef { + oid: const_oid::db::rfc8410::ID_X_25519, + parameters: None, + }, + subject_public_key: BitString::from_bytes(public_key).unwrap(), + }; + + key_info.to_der().unwrap() + }, + KeyAlgorithm::Ec { curve, algorithm } => { + let alg = curve.as_signing_algorithm(); + let rng = &(*SYSTEM_RANDOM); + let key_pair = EcdsaKeyPair::from_pkcs8(alg, &key.handle, rng).or_throw(&ctx)?; + let public_key_bytes = key_pair.public_key().as_ref().to_vec(); + + let alg_id = match curve { + EllipticCurve::P256 => AlgorithmIdentifierOwned { + oid: elliptic_curve::ALGORITHM_OID, + parameters: Some((&p256::NistP256::OID).into()), + }, + EllipticCurve::P384 => AlgorithmIdentifierOwned { + oid: elliptic_curve::ALGORITHM_OID, + parameters: Some((&p384::NistP384::OID).into()), + }, + }; + + let alg_id = match algorithm { + EcAlgorithm::Ecdh { .. } => AlgorithmIdentifier { + oid: const_oid::db::rfc5912::ID_EC_PUBLIC_KEY, + parameters: alg_id.parameters, + }, + _ => alg_id, + }; + + //unwrap ok, key is always valid after this stage + let key_info = SubjectPublicKeyInfo { + algorithm: alg_id, + + subject_public_key: BitString::from_bytes(&public_key_bytes).unwrap(), + }; + + key_info.to_der().unwrap() + }, + KeyAlgorithm::Ed25519 => { + let key_pair = Ed25519KeyPair::from_pkcs8(handle).or_throw(&ctx)?; + + let key_info = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifierOwned { + oid: const_oid::db::rfc8410::ID_ED_25519, + parameters: None, + }, + subject_public_key: BitString::from_bytes(key_pair.public_key().as_ref()).unwrap(), + }; + key_info.to_der().unwrap() + }, + + KeyAlgorithm::Rsa { .. } => { + let pkcs8 = rsa_der_pkcs1_to_pkcs8(&ctx, handle)?; + let pkcs8 = pkcs8.as_bytes(); + let key_pair = RsaKeyPair::from_pkcs8(pkcs8).or_throw(&ctx)?; + let public_key = key_pair.public().as_ref(); + + //unwrap ok, key is always valid after this stage + let key_info = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifier { + oid: const_oid::db::rfc5912::RSA_ENCRYPTION, + parameters: Some(asn1::AnyRef::from(asn1::Null)), + }, + subject_public_key: BitString::from_bytes(public_key).unwrap(), + }; + + key_info.to_der().unwrap() + }, + _ => return algorithm_not_supported_error(&ctx), + }; + + Ok(ArrayBuffer::new(ctx, bytes)?.into_object()) +} + +fn export_jwk<'js>(ctx: Ctx<'js>, key: &CryptoKey) -> Result> { + let _name = key.name.as_ref(); + let handle = key.handle.as_ref(); + let obj = Object::new(ctx.clone())?; + obj.set("key_ops", key.usages())?; + obj.set("ext", true)?; + match &key.algorithm { + KeyAlgorithm::Aes { .. } => { + let k = bytes_to_b64_url_safe_string(handle); + obj.set("kty", "oct")?; + obj.set("k", k)?; + }, + KeyAlgorithm::Hmac { hash, .. } => { + let k = bytes_to_b64_url_safe_string(handle); + obj.set("kty", "oct")?; + obj.set("alg", format!("HS{}", hash.as_str()))?; + obj.set("k", k)?; + }, + KeyAlgorithm::Ec { curve, .. } => { + let key_data = EcKeyData::new(&ctx, curve, handle)?; + + let (x, y) = key_data.coordinates(); + + obj.set("kty", "EC")?; + obj.set("crv", curve.as_str())?; + obj.set("x", bytes_to_b64_url_safe_string(x))?; + obj.set("y", bytes_to_b64_url_safe_string(y))?; + + if matches!(key.kind, KeyKind::Private) { + let d = key_data.private_key(handle); + + obj.set("d", bytes_to_b64_url_safe_string(d))?; + } + }, + KeyAlgorithm::Ed25519 => { + let key_pair = Ed25519KeyPair::from_pkcs8(handle).or_throw(&ctx)?; + let pub_key = key_pair.public_key().as_ref(); + let x = bytes_to_b64_url_safe_string(pub_key); + obj.set("kty", "OKP")?; + obj.set("crv", "Ed25519")?; + obj.set("x", x)?; + }, + KeyAlgorithm::Rsa { .. } => { + let pkcs8 = rsa_der_pkcs1_to_pkcs8(&ctx, handle)?; + + let pkcs8 = pkcs8.as_bytes(); + let key_pair = ring::signature::RsaKeyPair::from_pkcs8(pkcs8).or_throw(&ctx)?; + let pub_key = key_pair.public_key().as_ref(); + let n = bytes_to_b64_url_safe_string(pub_key); + obj.set("kty", "RSA")?; + obj.set("n", n)?; + obj.set("e", "AQAB")?; // Default RSA public exponent (65537) + }, + _ => return algorithm_not_supported_error(&ctx), + }; + + Ok(obj) +} + +struct EcKeyData { + key_pair: EcdsaKeyPair, + byte_length: usize, +} + +impl EcKeyData { + fn new(ctx: &Ctx, curve: &EllipticCurve, handle: &[u8]) -> Result { + let alg = curve.as_signing_algorithm(); + let rng = &(*SYSTEM_RANDOM); + let key_pair = EcdsaKeyPair::from_pkcs8(alg, handle, rng).or_throw(ctx)?; + + let byte_length = match curve { + EllipticCurve::P256 => 32, + EllipticCurve::P384 => 48, + }; + + Ok(Self { + key_pair, + byte_length, + }) + } + + fn private_key<'a>(&self, pkcs8: &'a [u8]) -> &'a [u8] { + let start_key = 36; + let end_key = start_key + self.byte_length; + &pkcs8[start_key..end_key] + } + + fn coordinates(&self) -> (&[u8], &[u8]) { + let pub_key = self.key_pair.public_key().as_ref(); + + let start_x = 1; + let end_x = start_x + self.byte_length; + let start_y = end_x; + let end_y = start_y + self.byte_length; + let x = &pub_key[start_x..end_x]; + let y = &pub_key[start_y..end_y]; + (x, y) + } } diff --git a/modules/llrt_crypto/src/subtle/generate_key.rs b/modules/llrt_crypto/src/subtle/generate_key.rs index b55db63f3d..87661cf84e 100644 --- a/modules/llrt_crypto/src/subtle/generate_key.rs +++ b/modules/llrt_crypto/src/subtle/generate_key.rs @@ -15,6 +15,7 @@ use crate::{sha_hash::ShaAlgorithm, CryptoKey, SYSTEM_RANDOM}; use super::{ algorithm_not_supported_error, + crypto_key::KeyKind, key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages}, }; @@ -40,7 +41,7 @@ pub async fn subtle_generate_key<'js>( return Ok(Class::instance( ctx, CryptoKey::new( - "secret", + KeyKind::Secret, name, extractable, key_algorithm, @@ -55,9 +56,9 @@ pub async fn subtle_generate_key<'js>( let private_key = Class::instance( ctx.clone(), CryptoKey::new( - "private", + KeyKind::Private, name.clone(), - false, + extractable, key_algorithm.clone(), private_usages, bytes.clone(), @@ -67,7 +68,7 @@ pub async fn subtle_generate_key<'js>( let public_key = Class::instance( ctx.clone(), CryptoKey::new( - "public", + KeyKind::Public, name, extractable, key_algorithm, @@ -95,11 +96,10 @@ fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result> { key }, - KeyAlgorithm::Ec { curve } => { + KeyAlgorithm::Ec { curve, .. } => { let rng = &(*SYSTEM_RANDOM); let curve = curve.as_signing_algorithm(); let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, rng).or_throw(ctx)?; - pkcs8.as_ref().to_vec() }, KeyAlgorithm::Ed25519 => { @@ -115,7 +115,18 @@ fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result> { key }, - // KeyAlgorithm::X25519 => {}, //TODO + KeyAlgorithm::X25519 => { + let secret_key = x25519_dalek::StaticSecret::random(); + let public_key = x25519_dalek::PublicKey::from(&secret_key); + + let secret_key_bytes = secret_key.as_bytes(); + let public_key_bytes = public_key.as_bytes(); + + let mut merged = Vec::with_capacity(secret_key_bytes.len() + public_key_bytes.len()); + merged.extend_from_slice(secret_key_bytes); + merged.extend_from_slice(public_key_bytes); + merged + }, KeyAlgorithm::Rsa { modulus_length, public_exponent, @@ -135,12 +146,9 @@ fn generate_key(ctx: &Ctx<'_>, algorithm: &KeyAlgorithm) -> Result> { }; let mut rng = OsRng; - let private_key = RsaPrivateKey::new_with_exp( - &mut rng, - *modulus_length as usize, - &BigUint::from(exponent), - ) - .or_throw(ctx)?; + let exp = BigUint::from(exponent); + let private_key = RsaPrivateKey::new_with_exp(&mut rng, *modulus_length as usize, &exp) + .or_throw(ctx)?; let pkcs = private_key.to_pkcs1_der().or_throw(ctx)?; pkcs.as_bytes().to_vec() }, diff --git a/modules/llrt_crypto/src/subtle/import_key.rs b/modules/llrt_crypto/src/subtle/import_key.rs index b68401fdfd..1453655a8d 100644 --- a/modules/llrt_crypto/src/subtle/import_key.rs +++ b/modules/llrt_crypto/src/subtle/import_key.rs @@ -1,59 +1,58 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 use llrt_utils::{bytes::ObjectBytes, object::ObjectExt}; -use rquickjs::{Array, Class, Ctx, Exception, Result, Value}; +use rquickjs::{Array, Class, Ctx, Exception, FromJs, Result, Value}; use crate::subtle::CryptoKey; -use super::key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages}; +use super::{ + crypto_key::KeyKind, + key_algorithm::{KeyAlgorithm, KeyAlgorithmMode, KeyAlgorithmWithUsages, KeyFormat}, +}; #[allow(dead_code)] pub async fn subtle_import_key<'js>( ctx: Ctx<'js>, format: String, - key_data: ObjectBytes<'js>, + key_data: Value<'js>, algorithm: Value<'js>, extractable: bool, key_usages: Array<'js>, ) -> Result> { - if format != "raw" { - return Err(Exception::throw_type( - &ctx, - &["Format '", &format, "' is not implemented"].concat(), - )); - }; - - let data = key_data.into_bytes(); - - if let Some(obj) = algorithm.as_object() { - let name: String = obj.get_required("name", "algorithm")?; - if name.starts_with("AES") || name == "HMAC" { - obj.set("length", data.len())?; - } - if name.starts_with("RSA") { + let format: KeyFormat = match format.as_str() { + "raw" => KeyFormat::Raw(ObjectBytes::from_js(&ctx, key_data)?), + "pkcs8" => KeyFormat::Pkcs8(ObjectBytes::from_js(&ctx, key_data)?), + "spki" => KeyFormat::Spki(ObjectBytes::from_js(&ctx, key_data)?), + "jwk" => KeyFormat::Jwk(key_data.into_object_or_throw(&ctx, "keyData")?), + _ => { return Err(Exception::throw_type( &ctx, - "RSA keys are not supported for import yet", - )); - } - } + &["Invalid format: ", &format].concat(), + )) + }, + }; + + let mut kind = KeyKind::Public; + let mut data = Vec::new(); let KeyAlgorithmWithUsages { name, algorithm: key_algorithm, public_usages, .. - } = KeyAlgorithm::from_js(&ctx, KeyAlgorithmMode::Import, algorithm, key_usages)?; + } = KeyAlgorithm::from_js( + &ctx, + KeyAlgorithmMode::Import { + format, + kind: &mut kind, + data: &mut data, + }, + algorithm, + key_usages, + )?; Class::instance( ctx, - CryptoKey::new( - "secret", - name, - extractable, - key_algorithm, - public_usages, - data, - ), + CryptoKey::new(kind, name, extractable, key_algorithm, public_usages, data), ) } diff --git a/modules/llrt_crypto/src/subtle/key_algorithm.rs b/modules/llrt_crypto/src/subtle/key_algorithm.rs index 8f868eeebe..2a1140937b 100644 --- a/modules/llrt_crypto/src/subtle/key_algorithm.rs +++ b/modules/llrt_crypto/src/subtle/key_algorithm.rs @@ -5,14 +5,16 @@ use std::rc::Rc; use crate::sha_hash::ShaAlgorithm; -use super::{algorithm_not_supported_error, to_name_and_maybe_object, EllipticCurve}; +use super::{ + algorithm_not_supported_error, crypto_key::KeyKind, to_name_and_maybe_object, EllipticCurve, +}; static SYMMETRIC_USAGES: &[&str] = &["encrypt", "decrypt", "wrapKey", "unwrapKey"]; static SIGNATURE_USAGES: &[&str] = &["sign", "verify"]; static EMPTY_USAGES: &[&str] = &[]; static SIGN_USAGES: &[&str] = &["sign"]; static RSA_OAEP_USAGES: &[&str] = &["decrypt", "unwrapKey"]; -static ECDH_USAGES: &[&str] = &["deriveKey", "deriveBits"]; +static ECDH_X25519_USAGES: &[&str] = &["deriveKey", "deriveBits"]; static AES_KW_USAGES: &[&str] = &["wrapKey", "unwrapKey"]; #[derive(Debug, Clone)] @@ -63,6 +65,12 @@ impl KeyDerivation { } } +#[derive(Debug, Clone)] +pub enum EcAlgorithm { + Ecdh, + Ecdsa, +} + #[derive(Debug, Clone)] pub enum KeyAlgorithm { Aes { @@ -70,7 +78,12 @@ pub enum KeyAlgorithm { }, Ec { curve: EllipticCurve, + algorithm: EcAlgorithm, }, + AesKw { + length: u16, + }, + X25519, Ed25519, Hmac { hash: ShaAlgorithm, @@ -81,15 +94,28 @@ pub enum KeyAlgorithm { public_exponent: Rc>, hash: ShaAlgorithm, }, - X25519, Derive(KeyDerivation), HkdfImport, Pbkdf2Import, } #[derive(PartialEq)] -pub enum KeyAlgorithmMode { - Import, +pub enum KeyFormat<'js> { + Jwk(Object<'js>), + Raw(ObjectBytes<'js>), + Spki(ObjectBytes<'js>), + Pkcs8(ObjectBytes<'js>), +} + +impl KeyFormat<'_> {} + +#[derive(PartialEq)] +pub enum KeyAlgorithmMode<'a, 'js> { + Import { + format: KeyFormat<'js>, + kind: &'a mut KeyKind, + data: &'a mut Vec, + }, Generate, Derive, } @@ -114,6 +140,60 @@ impl KeyAlgorithm { let name_ref = name.as_str(); let mut is_symmetric = false; let algorithm = match name_ref { + "Ed25519" => { + // if let KeyAlgorithmMode::Import { format, data, kind } = mode { + // match format { + // KeyFormat::Raw => { + // if data.len() != 32 { + // return Err(Exception::throw_type( + // ctx, + // "Ed25519 raw key must be 32 bytes", + // )); + // } + // *kind = KeyKind::Secret; + // }, + // KeyFormat::Pkcs8 => { + // // PKCS8 format is for private keys + // *kind = KeyKind::Private; + // }, + // KeyFormat::Jwk => { + // // JWK format can be either public or private + // // kind will be set based on JWK fields + // *kind = if data.contains(&b"d"[0]) { + // KeyKind::Private + // } else { + // KeyKind::Public + // }; + // }, + // KeyFormat::Spki => { + // // SPKI format is for public keys + // *kind = KeyKind::Public; + // }, + // } + // } + Self::classify_and_check_signature_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )?; + KeyAlgorithm::Ed25519 + }, + "X25519" => { + if !matches!(mode, KeyAlgorithmMode::Import { .. }) { + Self::classify_and_check_ecdh_x25519_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )? + } + KeyAlgorithm::X25519 + }, "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => { is_symmetric = true; if name_ref == "AES-KW" { @@ -138,7 +218,11 @@ impl KeyAlgorithm { )?; } - let length: u16 = obj.or_throw(ctx)?.get_required("length", "algorithm")?; + let length = if let KeyAlgorithmMode::Import { data, .. } = mode { + data.len() as u16 + } else { + obj.or_throw(ctx)?.get_required("length", "algorithm")? + }; if !matches!(length, 128 | 192 | 256) { return Err(Exception::throw_type( @@ -153,55 +237,47 @@ impl KeyAlgorithm { let obj = obj.or_throw(ctx)?; let curive: String = obj.get_required("namedCurve", "algorithm")?; let curve = EllipticCurve::try_from(curive.as_str()).or_throw(ctx)?; - if !matches!(mode, KeyAlgorithmMode::Import) { + let mut algorithm = EcAlgorithm::Ecdh; + if !matches!(mode, KeyAlgorithmMode::Import { .. }) { match name_ref { "ECDH" => match mode { - KeyAlgorithmMode::Generate => Self::classify_and_check_usages( + KeyAlgorithmMode::Generate => { + Self::classify_and_check_ecdh_x25519_usages( + ctx, + name_ref, + &usages, + is_symmetric, + &mut private_usages, + &mut public_usages, + )? + }, + KeyAlgorithmMode::Derive => Self::classify_and_check_symmetric_usages( ctx, name_ref, &usages, - ECDH_USAGES, - EMPTY_USAGES, is_symmetric, &mut private_usages, &mut public_usages, )?, - KeyAlgorithmMode::Derive => Self::classify_and_check_symmetric_usages( + _ => unreachable!(), + }, + "ECDSA" => { + algorithm = EcAlgorithm::Ecdsa; + Self::classify_and_check_signature_usages( ctx, name_ref, &usages, is_symmetric, &mut private_usages, &mut public_usages, - )?, - _ => unreachable!(), + )? }, - "ECDSA" => Self::classify_and_check_signature_usages( - ctx, - name_ref, - &usages, - is_symmetric, - &mut private_usages, - &mut public_usages, - )?, _ => unreachable!(), } } - KeyAlgorithm::Ec { curve } - }, - "Ed25519" => { - if !matches!(mode, KeyAlgorithmMode::Import) { - Self::classify_and_check_signature_usages( - ctx, - name_ref, - &usages, - is_symmetric, - &mut private_usages, - &mut public_usages, - )?; - } - KeyAlgorithm::Ed25519 + KeyAlgorithm::Ec { curve, algorithm } }, + "HMAC" => { is_symmetric = true; Self::classify_and_check_usages( @@ -217,12 +293,19 @@ impl KeyAlgorithm { let obj = obj.or_throw(ctx)?; let hash = extract_sha_hash(ctx, &obj)?; - let length = obj.get_optional("length")?.unwrap_or_default(); + + let mut length = obj.get_optional("length")?.unwrap_or_default(); + + if length == 0 { + if let KeyAlgorithmMode::Import { data, .. } = mode { + length = data.len() as u16 + } + } KeyAlgorithm::Hmac { hash, length } }, "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-v1_5" => { - if !matches!(mode, KeyAlgorithmMode::Import) { + if !matches!(mode, KeyAlgorithmMode::Import { .. }) { if name == "RSA-OAEP" { Self::classify_and_check_usages( ctx, @@ -264,21 +347,8 @@ impl KeyAlgorithm { hash, } }, - "X25519" => { - if !matches!(mode, KeyAlgorithmMode::Import) { - Self::classify_and_check_symmetric_usages( - ctx, - name_ref, - &usages, - is_symmetric, - &mut private_usages, - &mut public_usages, - )?; - } - KeyAlgorithm::X25519 - }, "HKDF" => match mode { - KeyAlgorithmMode::Import => KeyAlgorithm::HkdfImport, + KeyAlgorithmMode::Import { .. } => KeyAlgorithm::HkdfImport, KeyAlgorithmMode::Derive => { Self::classify_and_check_symmetric_usages( ctx, @@ -296,8 +366,9 @@ impl KeyAlgorithm { return algorithm_not_supported_error(ctx); }, }, + "PBKDF2" => match mode { - KeyAlgorithmMode::Import => KeyAlgorithm::Pbkdf2Import, + KeyAlgorithmMode::Import { .. } => KeyAlgorithm::Pbkdf2Import, KeyAlgorithmMode::Derive => { Self::classify_and_check_symmetric_usages( ctx, @@ -341,7 +412,7 @@ impl KeyAlgorithm { KeyAlgorithm::Aes { length } => { obj.set(PredefinedAtom::Length, length)?; }, - KeyAlgorithm::Ec { curve } => { + KeyAlgorithm::Ec { curve, .. } => { obj.set("namedCurve", curve.as_str())?; }, @@ -408,6 +479,26 @@ impl KeyAlgorithm { ) } + fn classify_and_check_ecdh_x25519_usages<'js>( + ctx: &Ctx<'js>, + name: &str, + usages: &Array<'js>, + is_symmetric: bool, + private_usages: &mut Vec, + public_usages: &mut Vec, + ) -> Result<()> { + Self::classify_and_check_usages( + ctx, + name, + usages, + ECDH_X25519_USAGES, + EMPTY_USAGES, + is_symmetric, + private_usages, + public_usages, + ) + } + fn classify_and_check_symmetric_usages<'js>( ctx: &Ctx<'js>, name: &str, diff --git a/modules/llrt_crypto/src/subtle/mod.rs b/modules/llrt_crypto/src/subtle/mod.rs index 62ab3fc5b9..c422c63476 100644 --- a/modules/llrt_crypto/src/subtle/mod.rs +++ b/modules/llrt_crypto/src/subtle/mod.rs @@ -23,6 +23,7 @@ pub use digest::subtle_digest; pub use encrypt::subtle_encrypt; pub use export_key::subtle_export_key; pub use generate_key::subtle_generate_key; +use rsa::pkcs1::DecodeRsaPrivateKey; pub use sign::subtle_sign; pub use verify::subtle_verify; @@ -44,7 +45,7 @@ use ctr::{Ctr128BE, Ctr32BE, Ctr64BE}; use llrt_utils::{object::ObjectExt, result::ResultExt, str_enum}; use ring::signature; use rquickjs::{Ctx, Exception, Object, Result, Value}; -use rsa::{pkcs1::DecodeRsaPrivateKey, Oaep, RsaPrivateKey}; +use rsa::{Oaep, RsaPrivateKey}; use crate::sha_hash::ShaAlgorithm; diff --git a/tests/unit/crypto.subtle.test.ts b/tests/unit/crypto.subtle.test.ts index 46088accdd..247f8fe046 100644 --- a/tests/unit/crypto.subtle.test.ts +++ b/tests/unit/crypto.subtle.test.ts @@ -54,58 +54,27 @@ describe("SubtleCrypto digest", () => { }); describe("SubtleCrypto generateKey/sign/verify", () => { - it("should be processing AES-CBC/AES-CTR/AES-GCM/AES-KW algorithm", async () => { - const parameters = [ - { - name: "AES-CBC", - length: 128, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CBC", - length: 192, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CBC", - length: 256, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 128, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 192, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 256, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-GCM", - length: 128, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-GCM", - length: 192, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-GCM", - length: 256, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { name: "AES-KW", length: 128, usages: ["wrapKey", "unwrapKey"] }, - { name: "AES-KW", length: 192, usages: ["wrapKey", "unwrapKey"] }, - { name: "AES-KW", length: 256, usages: ["wrapKey", "unwrapKey"] }, - ]; + // Common test parameters + const keyLengths = [128, 192, 256]; + const hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; + const curves = ["P-256", "P-384"]; + const rsaParams = { + modulusLength: 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + }; + it("should be processing AES-CBC/AES-CTR/AES-GCM/AES-KW algorithm", async () => { + const aesAlgorithms = ["AES-CBC", "AES-CTR", "AES-GCM"]; + const aesUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"]; + const kwUsages = ["wrapKey", "unwrapKey"]; + + const parameters = aesAlgorithms.flatMap((name) => + keyLengths.map((length) => ({ + name, + length, + usages: name === "AES-KW" ? kwUsages : aesUsages, + })) + ); for (const t of parameters) { const algorithm = { name: t.name, length: t.length }; @@ -122,12 +91,11 @@ describe("SubtleCrypto generateKey/sign/verify", () => { }); it("should be processing HMAC algorithm", async () => { - const parameters = [ - { name: "HMAC", hash: "SHA-1", usages: ["sign", "verify"] }, - { name: "HMAC", hash: "SHA-256", usages: ["sign", "verify"] }, - { name: "HMAC", hash: "SHA-384", usages: ["sign", "verify"] }, - { name: "HMAC", hash: "SHA-512", usages: ["sign", "verify"] }, - ]; + const parameters = hashAlgorithms.map((hash) => ({ + name: "HMAC", + hash, + usages: ["sign", "verify"], + })); for (const t of parameters) { const algorithm = { name: t.name, hash: t.hash }; @@ -145,29 +113,23 @@ describe("SubtleCrypto generateKey/sign/verify", () => { }); it("should be processing ECDH/ECDSA algorithm", async () => { - const parameters = [ - { - name: "ECDH", - namedCurve: "P-256", - usages: ["deriveKey", "deriveBits"], - }, - { - name: "ECDH", - namedCurve: "P-384", - usages: ["deriveKey", "deriveBits"], - }, - { - name: "ECDSA", - namedCurve: "P-256", - usages: ["sign", "verify"], - hash: "SHA-256", - }, - { + const parameters: { + name: string; + namedCurve: string; + usages: string[]; + hash?: string; + }[] = [ + ...curves.map((curve, i) => ({ name: "ECDSA", - namedCurve: "P-384", + namedCurve: curve, usages: ["sign", "verify"], - hash: "SHA-384", - }, + hash: i === 0 ? "SHA-256" : "SHA-384", + })), + ...curves.map((curve) => ({ + name: "ECDH", + namedCurve: curve, + usages: ["deriveKey", "deriveBits"], + })), ]; for (const t of parameters) { @@ -183,10 +145,7 @@ describe("SubtleCrypto generateKey/sign/verify", () => { expect(keyAlgorithm.name).toEqual(algorithm.name); expect(keyAlgorithm.namedCurve).toEqual(algorithm.namedCurve); - expect(privateKey.extractable).toEqual(false); - - expect(keyAlgorithm.name).toEqual(algorithm.name); - expect(keyAlgorithm.namedCurve).toEqual(algorithm.namedCurve); + expect(privateKey.extractable).toEqual(true); expect(publicKey.extractable).toEqual(true); if (t.usages.includes("sign")) { @@ -231,23 +190,17 @@ describe("SubtleCrypto generateKey/sign/verify", () => { )) as webcrypto.CryptoKeyPair; expect(privateKey.algorithm.name).toEqual(algorithm.name); - expect(privateKey.extractable).toEqual(false); - - expect(publicKey.algorithm.name).toEqual(algorithm.name); + expect(privateKey.extractable).toEqual(true); expect(publicKey.extractable).toEqual(true); if (t.usages.includes("sign")) { const signature = await crypto.subtle.sign( - { - name: t.name, - }, + { name: t.name }, privateKey, ENCODED_DATA ); const isValid = await crypto.subtle.verify( - { - name: t.name, - }, + { name: t.name }, publicKey, signature, ENCODED_DATA @@ -259,69 +212,31 @@ describe("SubtleCrypto generateKey/sign/verify", () => { }); it.skip("should be processing RSA-PSS/RSA-OAEP/RSASSA-PKCS1-v1_5 algorithm", async () => { - const parameters = [ - { - name: "RSA-PSS", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-256", - usages: ["sign", "verify"], - }, + const rsaAlgorithms = [ { name: "RSA-PSS", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-384", - usages: ["sign", "verify"], - }, - { - name: "RSA-PSS", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-512", usages: ["sign", "verify"], }, { name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-256", usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], }, - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-384", - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-512", - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "RSASSA-PKCS1-v1_5", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-256", - }, { name: "RSASSA-PKCS1-v1_5", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-384", - }, - { - name: "RSASSA-PKCS1-v1_5", - modulusLength: 2048, - publicExponent: new Uint8Array([0x01, 0x00, 0x01]), - hash: "SHA-512", + usages: ["sign", "verify"], }, ]; + const parameters = rsaAlgorithms.flatMap((algo) => + hashAlgorithms + .filter((h) => h !== "SHA-1") + .map((hash) => ({ + ...algo, + ...rsaParams, + hash, + })) + ); + for (const t of parameters) { const algorithm = { name: t.name, @@ -341,7 +256,7 @@ describe("SubtleCrypto generateKey/sign/verify", () => { expect(privateKey.algorithm.name).toEqual(t.name); expect(privateKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); - expect(privateKey.extractable).toEqual(false); + expect(privateKey.extractable).toEqual(true); expect(publicKey.algorithm.name).toEqual(algorithm.name); expect(publicKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); @@ -349,16 +264,12 @@ describe("SubtleCrypto generateKey/sign/verify", () => { if (t.usages?.includes("sign")) { const signature = await crypto.subtle.sign( - { - name: t.name, - }, + { name: t.name }, privateKey, ENCODED_DATA ); const isValid = await crypto.subtle.verify( - { - name: t.name, - }, + { name: t.name }, publicKey, signature, ENCODED_DATA @@ -371,27 +282,17 @@ describe("SubtleCrypto generateKey/sign/verify", () => { }); describe("SubtleCrypto generateKey/encrypt/decrypt", () => { + // Common key lengths and usages for AES algorithms + const keyLengths = [128, 192, 256]; + const commonUsages = ["encrypt", "decrypt", "wrapKey", "unwrapKey"]; + it("should be processing AES-CBC algorithm", async () => { - const parameters = [ - { - name: "AES-CBC", - length: 128, - iv: crypto.getRandomValues(new Uint8Array(16)), - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CBC", - length: 192, - iv: crypto.getRandomValues(new Uint8Array(16)), - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CBC", - length: 256, - iv: crypto.getRandomValues(new Uint8Array(16)), - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - ]; + const parameters = keyLengths.map((length) => ({ + name: "AES-CBC", + length, + iv: crypto.getRandomValues(new Uint8Array(16)), + usages: commonUsages, + })); for (const t of parameters) { const algorithm = { name: t.name, length: t.length }; @@ -431,71 +332,16 @@ describe("SubtleCrypto generateKey/encrypt/decrypt", () => { }); it("should be processing AES-CTR algorithm", async () => { - const parameters = [ - { - name: "AES-CTR", - length: 128, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 32, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 128, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 64, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 128, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 128, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 192, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 32, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 192, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 64, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 192, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 128, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 256, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 32, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { + const counterLengths = [32, 64, 128]; + const parameters = keyLengths.flatMap((length) => + counterLengths.map((counterLength) => ({ name: "AES-CTR", - length: 256, + length, counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 64, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-CTR", - length: 256, - counter: crypto.getRandomValues(new Uint8Array(16)), - counterLength: 128, - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - ]; + counterLength, + usages: commonUsages, + })) + ); for (const t of parameters) { const algorithm = { name: t.name, length: t.length }; @@ -539,26 +385,12 @@ describe("SubtleCrypto generateKey/encrypt/decrypt", () => { }); it("should be processing AES-GCM algorithm", async () => { - const parameters = [ - { - name: "AES-GCM", - length: 128, - iv: crypto.getRandomValues(new Uint8Array(12)), - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-GCM", - length: 192, - iv: crypto.getRandomValues(new Uint8Array(12)), - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "AES-GCM", - length: 256, - iv: crypto.getRandomValues(new Uint8Array(12)), - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - ]; + const parameters = keyLengths.map((length) => ({ + name: "AES-GCM", + length, + iv: crypto.getRandomValues(new Uint8Array(12)), + usages: commonUsages, + })); for (const t of parameters) { const algorithm = { name: t.name, length: t.length }; @@ -601,29 +433,14 @@ describe("SubtleCrypto generateKey/encrypt/decrypt", () => { // Caveat: The current RSA implementation is too slow to complete the test within the time limit. it.skip("should be processing RSA-OAEP algorithm", async () => { - const parameters = [ - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-384", - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - { - name: "RSA-OAEP", - modulusLength: 2048, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-512", - usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], - }, - ]; + const hashAlgorithms = ["SHA-256", "SHA-384", "SHA-512"]; + const parameters = hashAlgorithms.map((hash) => ({ + name: "RSA-OAEP", + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash, + usages: commonUsages, + })); for (const t of parameters) { const algorithm = { @@ -644,7 +461,7 @@ describe("SubtleCrypto generateKey/encrypt/decrypt", () => { expect(privateKey.algorithm.name).toEqual(t.name); expect(privateKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); - expect(privateKey.extractable).toEqual(false); + expect(privateKey.extractable).toEqual(true); expect(publicKey.algorithm.name).toEqual(algorithm.name); expect(publicKeyAlgorithm.hash).toEqual({ name: algorithm.hash }); @@ -675,94 +492,57 @@ describe("SubtleCrypto generateKey/encrypt/decrypt", () => { describe("SubtleCrypto deriveBits/deriveKey", () => { it("should be processing ECDH algorithm", async () => { - const generatedParams = [ - { - name: "ECDH", - namedCurve: "P-256", - }, - { - name: "ECDH", - namedCurve: "P-384", - }, - ]; - const derivedParams = [ - { - name: "AES-CBC", - length: 128, - }, - { - name: "AES-CBC", - length: 192, - }, - { - name: "AES-CBC", - length: 256, - }, - { - name: "AES-CTR", - length: 128, - }, - { - name: "AES-CTR", - length: 192, - }, - { - name: "AES-CTR", - length: 256, - }, - { - name: "AES-GCM", - length: 128, - }, - { - name: "AES-GCM", - length: 192, - }, - { - name: "AES-GCM", - length: 256, - }, - ]; + const keyLengths = [128, 192, 256]; + const algorithms = ["AES-CBC", "AES-CTR", "AES-GCM"]; - // 1. Generate Alice's key pair - const aliceKeyPair = await crypto.subtle.generateKey( - { - name: "ECDH", - namedCurve: "P-256", - }, - true, // whether the key is extractable (i.e. can be used in exportKey) - ["deriveKey", "deriveBits"] // can be any combination of "deriveKey" and "deriveBits" - ); + const namedCurves = ["P-256", "P-384"]; - // 2. Generate Bob's key pair - const bobKeyPair = await crypto.subtle.generateKey( - { - name: "ECDH", - namedCurve: "P-256", - }, - true, - ["deriveKey", "deriveBits"] + const derivedParams = algorithms.flatMap((name) => + keyLengths.map((length) => ({ + name, + length, + })) ); - // 3. Export Bob's public key to share with Alice - // const bobPublicKey = await crypto.subtle.exportKey( - // "raw", - // bobKeyPair.publicKey - // ); + for (const namedCurve of namedCurves) { + // 1. Generate Alice's key pair + const aliceKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve, + }, + true, // whether the key is extractable (i.e. can be used in exportKey) + ["deriveKey", "deriveBits"] // can be any combination of "deriveKey" and "deriveBits" + ); - // 3.5. Alice imports Bob's public key - // const bobImportKey = await crypto.subtle.importKey( - // "raw", - // bobPublicKey, - // { - // name: "ECDH", - // namedCurve: "P-256", - // }, - // true, - // [] - // ); + // 2. Generate Bob's key pair + const bobKeyPair = await crypto.subtle.generateKey( + { + name: "ECDH", + namedCurve, + }, + true, + ["deriveKey", "deriveBits"] + ); + + // 3. Export Bob's public key to share with Alice + // const bobPublicKey = await crypto.subtle.exportKey( + // "raw", + // bobKeyPair.publicKey + // ); + + // 3.5. Alice imports Bob's public key + // const bobImportKey = await crypto.subtle.importKey( + // "raw", + // bobPublicKey, + // { + // name: "ECDH", + // namedCurve: "P-256", + // }, + // true, + // [] + // ); - for (const generated of generatedParams) { for (const derived of derivedParams) { // 4. Alice derives a shared key using Bob's public key const aliceDerivedKey = await crypto.subtle.deriveKey( @@ -821,74 +601,27 @@ describe("SubtleCrypto deriveBits/deriveKey", () => { } }); - it.skip("should be processing HKDF algorithm", async () => { + it("should be processing HKDF algorithm", async () => { const hkdfSalt = new Uint8Array(16); // Salt value (can be random, but here it's set to all zeros) const hkdfInfo = new TextEncoder().encode("HKDF info"); // Info parameter, can be any label string - const generatedParams = [ - { - name: "HKDF", - salt: hkdfSalt, - info: hkdfInfo, - hash: "SHA-1", - }, - { - name: "HKDF", - salt: hkdfSalt, - info: hkdfInfo, - hash: "SHA-256", - }, - { - name: "HKDF", - salt: hkdfSalt, - info: hkdfInfo, - hash: "SHA-384", - }, - { - name: "HKDF", - salt: hkdfSalt, - info: hkdfInfo, - hash: "SHA-512", - }, - ]; - const derivedParams = [ - { - name: "AES-CBC", - length: 128, - }, - { - name: "AES-CBC", - length: 192, - }, - { - name: "AES-CBC", - length: 256, - }, - { - name: "AES-CTR", - length: 128, - }, - { - name: "AES-CTR", - length: 192, - }, - { - name: "AES-CTR", - length: 256, - }, - { - name: "AES-GCM", - length: 128, - }, - { - name: "AES-GCM", - length: 192, - }, - { - name: "AES-GCM", - length: 256, - }, - ]; + const keyLengths = [128, 192, 256]; + const algorithms = ["AES-CBC", "AES-CTR", "AES-GCM"]; + const hashAlgorithms = ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]; + + const generatedParams = hashAlgorithms.map((hash) => ({ + name: "HKDF", + salt: hkdfSalt, + info: hkdfInfo, + hash, + })); + + const derivedParams = algorithms.flatMap((name) => + keyLengths.map((length) => ({ + name, + length, + })) + ); // 1. Generate Alice's key pair const aliceKeyPair = await crypto.subtle.generateKey( @@ -1017,9 +750,9 @@ describe("SubtleCrypto deriveBits/deriveKey", () => { } }); - it.skip("should be processing PBKDF2 algorithm", async () => { + it("should be processing PBKDF2 algorithm", async () => { const pbkdf2Salt = new Uint8Array(16); // Salt value (can be random, but here it's set to all zeros) - const pbkdf2Iterations = 50000; // Number of iterations for PBKDF2 + const pbkdf2Iterations = 50; // Number of iterations for PBKDF2 // We skip some tests because they run slowly in CI. const generatedParams = [