| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| #![allow(clippy::uninlined_format_args)] | |
| use std::rc::Rc; | |
| #[cfg(feature = "_subtle-full")] | |
| use der::{ | |
| asn1::{BitStringRef, OctetString, OctetStringRef}, | |
| Decode, Encode, | |
| }; | |
| #[cfg(feature = "_subtle-full")] | |
| use ed25519_dalek::SigningKey; | |
| #[cfg(feature = "_subtle-full")] | |
| use llrt_encoding::bytes_from_b64_url_safe; | |
| use llrt_exceptions::DOMException; | |
| use llrt_utils::{bytes::ObjectBytes, object::ObjectExt, result::ResultExt, str_enum}; | |
| #[cfg(feature = "_subtle-full")] | |
| use pkcs8::PrivateKeyInfoRef; | |
| use rquickjs::{ | |
| atom::PredefinedAtom, Array, Ctx, Exception, FromJs, Object, Result, TypedArray, Value, | |
| }; | |
| #[cfg(feature = "_subtle-full")] | |
| use spki::{AlgorithmIdentifier, ObjectIdentifier}; | |
| #[cfg(feature = "_subtle-full")] | |
| use x25519_dalek::{PublicKey, StaticSecret}; | |
| use crate::{hash::HashAlgorithm, provider::parse_rsa_public_exponent}; | |
| #[cfg(feature = "_subtle-full")] | |
| use super::{algorithm_mismatch_error, util::DataError}; | |
| use super::{ | |
| algorithm_not_supported_error, | |
| crypto_key::KeyKind, | |
| normalize_algorithm_name, to_name_and_maybe_object, | |
| util::{NotSupportedError, ResultDomExt}, | |
| EllipticCurve, | |
| }; | |
| #[derive(Clone, Copy, PartialEq)] | |
| pub enum KeyUsage { | |
| //7 values, can be max 255 (u8) 0b11111111 | |
| Encrypt, | |
| Decrypt, | |
| WrapKey, | |
| UnwrapKey, | |
| Sign, | |
| Verify, | |
| DeriveKey, | |
| DeriveBits, | |
| } | |
| impl TryFrom<&str> for KeyUsage { | |
| type Error = String; | |
| fn try_from(s: &str) -> std::result::Result<Self, Self::Error> { | |
| Ok(match s { | |
| "encrypt" => KeyUsage::Encrypt, | |
| "decrypt" => KeyUsage::Decrypt, | |
| "wrapKey" => KeyUsage::WrapKey, | |
| "unwrapKey" => KeyUsage::UnwrapKey, | |
| "sign" => KeyUsage::Sign, | |
| "verify" => KeyUsage::Verify, | |
| "deriveKey" => KeyUsage::DeriveKey, | |
| "deriveBits" => KeyUsage::DeriveBits, | |
| _ => return Err(["Invalid key usage: ", s].concat()), | |
| }) | |
| } | |
| } | |
| impl KeyUsage { | |
| fn classify_and_check_usages<'js>( | |
| ctx: &Ctx<'js>, | |
| key_usage_algorithm: KeyUsageAlgorithm, | |
| key_usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| kind: Option<&KeyKind>, | |
| ) -> Result<()> { | |
| let (mut private_usages_mask, mut public_usages_mask) = key_usage_algorithm.masks(); | |
| match kind { | |
| Some(KeyKind::Private) => public_usages_mask = 0, | |
| Some(KeyKind::Secret) | Some(KeyKind::Public) => private_usages_mask = 0, | |
| None => {}, | |
| }; | |
| let allowed_usages = private_usages_mask | public_usages_mask; | |
| let mut generated_public_usages = Vec::with_capacity(4); | |
| let mut generated_private_usages = Vec::with_capacity(4); | |
| let mut has_any_usages = false; | |
| for usage in key_usages.iter::<String>() { | |
| has_any_usages = true; | |
| let value = usage?; | |
| let usage = KeyUsage::try_from(value.as_str()).or_throw(ctx)?; | |
| let usage = usage.mask(); | |
| if allowed_usages & usage != usage { | |
| return Err(Exception::throw_syntax( | |
| ctx, | |
| &["Invalid key usage '", &value, "'"].concat(), | |
| )); | |
| } | |
| if private_usages_mask == public_usages_mask { | |
| generated_private_usages.push(value.clone()); | |
| generated_public_usages.push(value); | |
| } else if private_usages_mask & usage == usage { | |
| generated_private_usages.push(value); | |
| } else if public_usages_mask & usage == usage { | |
| generated_public_usages.push(value); | |
| } | |
| } | |
| *private_usages = generated_private_usages; | |
| *public_usages = generated_public_usages; | |
| if !has_any_usages | |
| && key_usage_algorithm.requires_non_empty_usages() | |
| && !matches!(kind, Some(KeyKind::Public)) | |
| { | |
| return Err(Exception::throw_syntax(ctx, "Key usages empty")); | |
| } | |
| if private_usages != public_usages { | |
| let valid_usage = match kind { | |
| Some(KeyKind::Secret) | Some(KeyKind::Public) => { | |
| private_usages.is_empty() && !public_usages.is_empty() | |
| }, | |
| Some(KeyKind::Private) => !private_usages.is_empty() && public_usages.is_empty(), | |
| None => true, | |
| }; | |
| if !valid_usage { | |
| return Err(Exception::throw_syntax(ctx, "Invalid key usage")); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| const fn mask(self) -> u16 { | |
| 1 << self as u16 | |
| } | |
| } | |
| #[repr(u16)] | |
| #[derive(Clone, Copy)] | |
| pub enum KeyUsageAlgorithm { | |
| //single mask algorithms (symmetric) | |
| AesKw = KeyUsage::WrapKey.mask() | KeyUsage::UnwrapKey.mask(), | |
| //all non-KW AES | |
| Symmetric = (KeyUsage::Encrypt.mask()) | |
| | (KeyUsage::Decrypt.mask()) | |
| | (KeyUsage::WrapKey.mask()) | |
| | (KeyUsage::UnwrapKey.mask()), | |
| Hmac = (KeyUsage::Sign.mask()) | (KeyUsage::Verify.mask()), | |
| // asymmetric derive algorithms - use high bits as private usages | |
| // ECDH/X25519 | |
| DeriveAsymmetric = ((KeyUsage::DeriveKey.mask() | KeyUsage::DeriveBits.mask()) << 8), | |
| // HKDF/PBKDF2 | |
| DeriveSymmetric = KeyUsage::DeriveKey.mask() | KeyUsage::DeriveBits.mask(), | |
| RsaOaep = ((KeyUsage::Decrypt.mask() | KeyUsage::UnwrapKey.mask()) << 8) //private | |
| | KeyUsage::Encrypt.mask() | KeyUsage::WrapKey.mask(), //public | |
| //ECDSA, ED25519, all non-OEAP RSA | |
| Sign = (KeyUsage::Sign.mask() << 8) //private | |
| | KeyUsage::Verify.mask(), //public | |
| } | |
| impl KeyUsageAlgorithm { | |
| fn masks(&self) -> (u16, u16) { | |
| let value = *self as u16; | |
| let private_mask = value >> 8; | |
| let public_mask = value & 0xFF; | |
| (private_mask, public_mask) | |
| } | |
| fn requires_non_empty_usages(self) -> bool { | |
| matches!( | |
| self, | |
| Self::Symmetric | |
| | Self::AesKw | |
| | Self::Hmac | |
| | Self::DeriveAsymmetric | |
| | Self::DeriveSymmetric | |
| | Self::Sign | |
| | Self::RsaOaep | |
| ) | |
| } | |
| } | |
| #[derive(Debug, Clone)] | |
| pub enum KeyDerivation { | |
| Hkdf { | |
| hash: HashAlgorithm, | |
| salt: Box<[u8]>, | |
| info: Box<[u8]>, | |
| }, | |
| Pbkdf2 { | |
| hash: HashAlgorithm, | |
| salt: Box<[u8]>, | |
| iterations: u32, | |
| }, | |
| } | |
| impl KeyDerivation { | |
| pub fn for_hkdf_object<'js>(ctx: &Ctx<'js>, obj: Object<'js>) -> Result<Self> { | |
| let hash = extract_sha_hash(ctx, &obj)?; | |
| let salt = obj | |
| .get_required::<_, ObjectBytes>("salt", "algorithm")? | |
| .into_bytes(ctx)? | |
| .into_boxed_slice(); | |
| let info = obj | |
| .get_required::<_, ObjectBytes>("info", "algorithm")? | |
| .into_bytes(ctx)? | |
| .into_boxed_slice(); | |
| Ok(KeyDerivation::Hkdf { hash, salt, info }) | |
| } | |
| pub fn for_pbkf2_object<'js>(ctx: &&Ctx<'js>, obj: Object<'js>) -> Result<Self> { | |
| let hash = extract_sha_hash(ctx, &obj)?; | |
| let salt = obj | |
| .get_required::<_, ObjectBytes>("salt", "algorithm")? | |
| .into_bytes(ctx)? | |
| .into_boxed_slice(); | |
| let iterations = obj.get_required("iterations", "algorithm")?; | |
| Ok(KeyDerivation::Pbkdf2 { | |
| hash, | |
| salt, | |
| iterations, | |
| }) | |
| } | |
| } | |
| #[derive(Debug, Clone)] | |
| pub enum EcAlgorithm { | |
| Ecdh, | |
| Ecdsa, | |
| } | |
| #[derive(PartialEq, Debug, Clone)] | |
| pub enum AesAlgorithm { | |
| Cbc, | |
| Ctr, | |
| Gcm, | |
| Kw, | |
| } | |
| #[derive(Debug, Clone)] | |
| pub enum KeyAlgorithm { | |
| Aes { | |
| length: u16, | |
| algorithm: AesAlgorithm, | |
| }, | |
| Ec { | |
| curve: EllipticCurve, | |
| algorithm: EcAlgorithm, | |
| }, | |
| X25519, | |
| Ed25519, | |
| Hmac { | |
| hash: HashAlgorithm, | |
| length: u16, | |
| }, | |
| Rsa { | |
| modulus_length: u32, | |
| public_exponent: Rc<Box<[u8]>>, | |
| hash: HashAlgorithm, | |
| }, | |
| Derive(KeyDerivation), | |
| HkdfImport, | |
| Pbkdf2Import, | |
| } | |
| pub enum KeyFormat { | |
| Jwk, | |
| Raw, | |
| Spki, | |
| Pkcs8, | |
| } | |
| str_enum!(KeyFormat, Jwk => "jwk", Raw => "raw", Spki => "spki", Pkcs8 => "pkcs8"); | |
| impl<'js> FromJs<'js> for KeyFormat { | |
| fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> { | |
| if let Some(string) = value.as_string() { | |
| let string = string.to_string()?; | |
| match string.as_str() { | |
| "jwk" => return Ok(KeyFormat::Jwk), | |
| "raw" => return Ok(KeyFormat::Raw), | |
| "spki" => return Ok(KeyFormat::Spki), | |
| "pkcs8" => return Ok(KeyFormat::Pkcs8), | |
| _ => {}, | |
| }; | |
| } | |
| Err(DOMException::not_supported_error( | |
| ctx, | |
| "Key import/export format must be 'jwk','raw','spki' or 'pkcs8'", | |
| )) | |
| } | |
| } | |
| #[derive(PartialEq)] | |
| pub enum KeyFormatData<'js> { | |
| Jwk(Object<'js>), | |
| Raw(ObjectBytes<'js>), | |
| Spki(ObjectBytes<'js>), | |
| Pkcs8(ObjectBytes<'js>), | |
| } | |
| #[derive(PartialEq)] | |
| pub enum KeyAlgorithmMode<'a, 'js> { | |
| Import { | |
| format: KeyFormatData<'js>, | |
| kind: &'a mut KeyKind, | |
| data: &'a mut Vec<u8>, | |
| }, | |
| Generate, | |
| Derive, | |
| } | |
| pub struct KeyAlgorithmWithUsages { | |
| pub name: String, | |
| pub algorithm: KeyAlgorithm, | |
| pub public_usages: Vec<String>, | |
| pub private_usages: Vec<String>, | |
| } | |
| fn from_ed25519<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| algorithm_name: &str, | |
| usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| ) -> Result<KeyAlgorithm> { | |
| #[cfg(feature = "_subtle-full")] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| algorithm_name: &str, | |
| ) -> Result<Option<KeyKind>> { | |
| if let KeyAlgorithmMode::Import { format, kind, data } = mode { | |
| import_okp_key( | |
| ctx, | |
| format, | |
| kind, | |
| data, | |
| const_oid::db::rfc8410::ID_ED_25519, | |
| algorithm_name, | |
| true, | |
| )?; | |
| Ok(Some(*kind)) | |
| } else { | |
| Ok(None) | |
| } | |
| } | |
| #[cfg(not(feature = "_subtle-full"))] | |
| #[inline] | |
| fn import<'js>( | |
| _ctx: &Ctx<'js>, | |
| _mode: KeyAlgorithmMode<'_, 'js>, | |
| _algorithm_name: &str, | |
| ) -> Result<Option<KeyKind>> { | |
| Ok(None) | |
| } | |
| let key_kind = import(ctx, mode, algorithm_name)?; | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| KeyUsageAlgorithm::Sign, | |
| usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_ref(), | |
| )?; | |
| Ok(KeyAlgorithm::Ed25519) | |
| } | |
| fn from_x25519<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| algorithm_name: &str, | |
| usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| ) -> Result<KeyAlgorithm> { | |
| #[cfg(feature = "_subtle-full")] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| algorithm_name: &str, | |
| ) -> Result<Option<KeyKind>> { | |
| if let KeyAlgorithmMode::Import { format, kind, data } = mode { | |
| import_okp_key( | |
| ctx, | |
| format, | |
| kind, | |
| data, | |
| const_oid::db::rfc8410::ID_X_25519, | |
| algorithm_name, | |
| false, | |
| )?; | |
| Ok(Some(*kind)) | |
| } else { | |
| Ok(None) | |
| } | |
| } | |
| #[cfg(not(feature = "_subtle-full"))] | |
| #[inline] | |
| fn import<'js>( | |
| _ctx: &Ctx<'js>, | |
| _mode: KeyAlgorithmMode<'_, 'js>, | |
| _algorithm_name: &str, | |
| ) -> Result<Option<KeyKind>> { | |
| Ok(None) | |
| } | |
| let key_kind = import(ctx, mode, algorithm_name)?; | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| KeyUsageAlgorithm::DeriveAsymmetric, | |
| usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_ref(), | |
| )?; | |
| Ok(KeyAlgorithm::X25519) | |
| } | |
| fn from_aes<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| ) -> Result<KeyAlgorithm> { | |
| #[cfg(feature = "_subtle-full")] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| ) -> Result<(u16, Option<KeyKind>)> { | |
| if let KeyAlgorithmMode::Import { data, format, kind } = mode { | |
| let length = | |
| import_symmetric_key(ctx, format, kind, data, algorithm_name, None)? as u16; | |
| Ok((length, Some(*kind))) | |
| } else { | |
| let length: u16 = obj?.get_required("length", "algorithm")?; | |
| Ok((length, None)) | |
| } | |
| } | |
| #[cfg(not(feature = "_subtle-full"))] | |
| #[inline] | |
| fn import<'js>( | |
| _ctx: &Ctx<'js>, | |
| _mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| _algorithm_name: &str, | |
| ) -> Result<(u16, Option<KeyKind>)> { | |
| let length: u16 = obj?.get_required("length", "algorithm")?; | |
| Ok((length, None)) | |
| } | |
| let (length, key_kind) = import(ctx, mode, obj, algorithm_name)?; | |
| if !matches!(length, 128 | 192 | 256) { | |
| return Err(DOMException::operation_error( | |
| ctx, | |
| format!( | |
| "Algorithm 'length' must be one of: 128, 192, or 256 = {}", | |
| length | |
| ), | |
| )); | |
| } | |
| let algorithm = match algorithm_name { | |
| "AES-CBC" => AesAlgorithm::Cbc, | |
| "AES-CTR" => AesAlgorithm::Ctr, | |
| "AES-GCM" => AesAlgorithm::Gcm, | |
| "AES-KW" => AesAlgorithm::Kw, | |
| _ => return Err(DOMException::operation_error(ctx, "Invalid algorithm name")), | |
| }; | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| if algorithm == AesAlgorithm::Kw { | |
| KeyUsageAlgorithm::AesKw | |
| } else { | |
| KeyUsageAlgorithm::Symmetric | |
| }, | |
| usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_ref(), | |
| )?; | |
| Ok(KeyAlgorithm::Aes { length, algorithm }) | |
| } | |
| fn from_hmac<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| ) -> Result<KeyAlgorithm> { | |
| let obj = obj?; | |
| let hash = extract_sha_hash(ctx, &obj)?; | |
| if !matches!( | |
| hash, | |
| HashAlgorithm::Sha1 | HashAlgorithm::Sha256 | HashAlgorithm::Sha384 | HashAlgorithm::Sha512 | |
| ) { | |
| return Err(DOMException::not_supported_error( | |
| ctx, | |
| "Unsupported HMAC hash algorithm", | |
| )); | |
| } | |
| let mut length = match obj.get_optional::<_, u16>("length")? { | |
| Some(length) => length, | |
| None => match mode { | |
| KeyAlgorithmMode::Import { .. } => 0, | |
| _ => (hash.block_len() * 8) as u16, | |
| }, | |
| }; | |
| #[cfg(feature = "_subtle-full")] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| algorithm_name: &str, | |
| hash: &HashAlgorithm, | |
| length: &mut u16, | |
| ) -> Result<Option<KeyKind>> { | |
| if let KeyAlgorithmMode::Import { data, format, kind } = mode { | |
| let data_length = | |
| import_symmetric_key(ctx, format, kind, data, algorithm_name, Some(hash))?; | |
| if *length == 0 { | |
| *length = data_length as u16; | |
| } | |
| Ok(Some(*kind)) | |
| } else { | |
| Ok(None) | |
| } | |
| } | |
| #[cfg(not(feature = "_subtle-full"))] | |
| #[inline] | |
| fn import<'js>( | |
| _ctx: &Ctx<'js>, | |
| _mode: KeyAlgorithmMode<'_, 'js>, | |
| _algorithm_name: &str, | |
| _hash: &HashAlgorithm, | |
| _length: &mut u16, | |
| ) -> Result<Option<KeyKind>> { | |
| Ok(None) | |
| } | |
| let key_kind = import(ctx, mode, algorithm_name, &hash, &mut length)?; | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| KeyUsageAlgorithm::Hmac, | |
| usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_ref(), | |
| )?; | |
| Ok(KeyAlgorithm::Hmac { hash, length }) | |
| } | |
| fn from_rsa<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| ) -> Result<KeyAlgorithm> { | |
| let obj = obj?; | |
| let hash = extract_sha_hash(ctx, &obj)?; | |
| let is_generate = mode == KeyAlgorithmMode::Generate; | |
| #[cfg(feature = "_subtle-full")] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: &Object<'js>, | |
| algorithm_name: &str, | |
| hash: &HashAlgorithm, | |
| ) -> Result<(u32, Box<[u8]>, Option<KeyKind>)> { | |
| if let KeyAlgorithmMode::Import { format, kind, data } = mode { | |
| let (mod_length, exp) = import_rsa_key(ctx, format, kind, data, algorithm_name, hash)?; | |
| Ok((mod_length, exp, Some(*kind))) | |
| } else { | |
| let modulus_length = obj.get_required("modulusLength", "algorithm")?; | |
| let public_exponent: TypedArray<u8> = | |
| obj.get_required("publicExponent", "algorithm")?; | |
| let public_exponent = public_exponent | |
| .as_bytes() | |
| .ok_or_else(|| { | |
| DOMException::not_supported_error(ctx, "Array buffer has been detached") | |
| })? | |
| .to_owned() | |
| .into_boxed_slice(); | |
| Ok((modulus_length, public_exponent, None)) | |
| } | |
| } | |
| #[cfg(not(feature = "_subtle-full"))] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| _mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: &Object<'js>, | |
| _algorithm_name: &str, | |
| _hash: &HashAlgorithm, | |
| ) -> Result<(u32, Box<[u8]>, Option<KeyKind>)> { | |
| let modulus_length = obj.get_required("modulusLength", "algorithm")?; | |
| let public_exponent: TypedArray<u8> = obj.get_required("publicExponent", "algorithm")?; | |
| let public_exponent = public_exponent | |
| .as_bytes() | |
| .ok_or_else(|| { | |
| DOMException::not_supported_error(ctx, "Array buffer has been detached") | |
| })? | |
| .to_owned() | |
| .into_boxed_slice(); | |
| Ok((modulus_length, public_exponent, None)) | |
| } | |
| let (modulus_length, public_exponent, key_kind) = | |
| import(ctx, mode, &obj, algorithm_name, &hash)?; | |
| if is_generate { | |
| parse_rsa_public_exponent(&public_exponent).or_throw_dom(ctx)?; | |
| } | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| if algorithm_name == "RSA-OAEP" { | |
| KeyUsageAlgorithm::RsaOaep | |
| } else { | |
| KeyUsageAlgorithm::Sign | |
| }, | |
| usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_ref(), | |
| )?; | |
| Ok(KeyAlgorithm::Rsa { | |
| modulus_length, | |
| public_exponent: Rc::new(public_exponent), | |
| hash, | |
| }) | |
| } | |
| fn from_hkdf<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| ) -> Result<KeyAlgorithm> { | |
| #[cfg(feature = "_subtle-full")] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| ) -> Result<(KeyAlgorithm, Option<KeyKind>)> { | |
| match mode { | |
| KeyAlgorithmMode::Import { format, kind, data } => { | |
| import_derive_key(ctx, format, kind, data, algorithm_name)?; | |
| Ok((KeyAlgorithm::HkdfImport, Some(*kind))) | |
| }, | |
| KeyAlgorithmMode::Derive => { | |
| let obj = obj?; | |
| Ok(( | |
| KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), | |
| None, | |
| )) | |
| }, | |
| _ => algorithm_not_supported_error(ctx), | |
| } | |
| } | |
| #[cfg(not(feature = "_subtle-full"))] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| _algorithm_name: &str, | |
| ) -> Result<(KeyAlgorithm, Option<KeyKind>)> { | |
| match mode { | |
| KeyAlgorithmMode::Derive => { | |
| let obj = obj?; | |
| Ok(( | |
| KeyAlgorithm::Derive(KeyDerivation::for_hkdf_object(ctx, obj)?), | |
| None, | |
| )) | |
| }, | |
| _ => algorithm_not_supported_error(ctx), | |
| } | |
| } | |
| let (algorithm, key_kind) = import(ctx, mode, obj, algorithm_name)?; | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| KeyUsageAlgorithm::DeriveSymmetric, | |
| usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_ref(), | |
| )?; | |
| Ok(algorithm) | |
| } | |
| fn from_pbkdf2<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| ) -> Result<KeyAlgorithm> { | |
| #[cfg(feature = "_subtle-full")] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| algorithm_name: &str, | |
| ) -> Result<(KeyAlgorithm, Option<KeyKind>)> { | |
| match mode { | |
| KeyAlgorithmMode::Import { format, kind, data } => { | |
| import_derive_key(ctx, format, kind, data, algorithm_name)?; | |
| Ok((KeyAlgorithm::Pbkdf2Import, Some(*kind))) | |
| }, | |
| KeyAlgorithmMode::Derive => { | |
| let obj = obj?; | |
| Ok(( | |
| KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), | |
| None, | |
| )) | |
| }, | |
| _ => algorithm_not_supported_error(ctx), | |
| } | |
| } | |
| #[cfg(not(feature = "_subtle-full"))] | |
| #[inline] | |
| fn import<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| _algorithm_name: &str, | |
| ) -> Result<(KeyAlgorithm, Option<KeyKind>)> { | |
| match mode { | |
| KeyAlgorithmMode::Derive => { | |
| let obj = obj?; | |
| Ok(( | |
| KeyAlgorithm::Derive(KeyDerivation::for_pbkf2_object(&ctx, obj)?), | |
| None, | |
| )) | |
| }, | |
| _ => algorithm_not_supported_error(ctx), | |
| } | |
| } | |
| let (algorithm, key_kind) = import(ctx, mode, obj, algorithm_name)?; | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| KeyUsageAlgorithm::DeriveSymmetric, | |
| usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_ref(), | |
| )?; | |
| Ok(algorithm) | |
| } | |
| impl KeyAlgorithm { | |
| pub fn from_js<'js>( | |
| ctx: &Ctx<'js>, | |
| mode: KeyAlgorithmMode<'_, 'js>, | |
| value: Value<'js>, | |
| usages: Array<'js>, | |
| ) -> Result<KeyAlgorithmWithUsages> { | |
| // When _subtle-full is not enabled, Import mode is not supported | |
| #[cfg(not(feature = "_subtle-full"))] | |
| if matches!(mode, KeyAlgorithmMode::Import { .. }) { | |
| return Err(DOMException::not_supported_error( | |
| ctx, | |
| "Key import is not supported with this crypto provider", | |
| )); | |
| } | |
| let (name, obj) = to_name_and_maybe_object(ctx, value)?; | |
| let name = normalize_algorithm_name(&name); | |
| let mut public_usages = vec![]; | |
| let mut private_usages = vec![]; | |
| let algorithm_name = name.as_ref(); | |
| let algorithm = match algorithm_name { | |
| "Ed25519" => from_ed25519( | |
| ctx, | |
| mode, | |
| algorithm_name, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| )?, | |
| "X25519" => from_x25519( | |
| ctx, | |
| mode, | |
| algorithm_name, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| )?, | |
| "AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-KW" => from_aes( | |
| ctx, | |
| mode, | |
| obj, | |
| algorithm_name, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| )?, | |
| "ECDH" => Self::from_ec( | |
| ctx, | |
| mode, | |
| obj, | |
| algorithm_name, | |
| EcAlgorithm::Ecdh, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| KeyUsageAlgorithm::DeriveAsymmetric, | |
| )?, | |
| "ECDSA" => Self::from_ec( | |
| ctx, | |
| mode, | |
| obj, | |
| algorithm_name, | |
| EcAlgorithm::Ecdsa, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| KeyUsageAlgorithm::Sign, | |
| )?, | |
| "HMAC" => from_hmac( | |
| ctx, | |
| mode, | |
| obj, | |
| algorithm_name, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| )?, | |
| "RSA-OAEP" | "RSA-PSS" | "RSASSA-PKCS1-v1_5" => from_rsa( | |
| ctx, | |
| mode, | |
| obj, | |
| algorithm_name, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| )?, | |
| "HKDF" => from_hkdf( | |
| ctx, | |
| mode, | |
| obj, | |
| algorithm_name, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| )?, | |
| "PBKDF2" => from_pbkdf2( | |
| ctx, | |
| mode, | |
| obj, | |
| algorithm_name, | |
| &usages, | |
| &mut private_usages, | |
| &mut public_usages, | |
| )?, | |
| _ => return algorithm_not_supported_error(ctx), | |
| }; | |
| Ok(KeyAlgorithmWithUsages { | |
| name, | |
| algorithm, | |
| public_usages, | |
| private_usages, | |
| }) | |
| } | |
| pub fn as_object<'js, T: AsRef<str>>(&self, ctx: &Ctx<'js>, name: T) -> Result<Object<'js>> { | |
| let obj = Object::new(ctx.clone())?; | |
| obj.set(PredefinedAtom::Name, name.as_ref())?; | |
| match self { | |
| KeyAlgorithm::Aes { length, .. } => { | |
| obj.set(PredefinedAtom::Length, length)?; | |
| }, | |
| KeyAlgorithm::Ec { curve, .. } => { | |
| obj.set("namedCurve", curve.as_str())?; | |
| }, | |
| KeyAlgorithm::Hmac { hash, length } => { | |
| let hash_obj = create_hash_object(ctx, hash)?; | |
| obj.set("hash", hash_obj)?; | |
| obj.set(PredefinedAtom::Length, length)?; | |
| }, | |
| KeyAlgorithm::Rsa { | |
| modulus_length, | |
| public_exponent, | |
| hash, | |
| } => { | |
| let public_exponent = public_exponent.as_ref().to_vec(); | |
| let array = TypedArray::new(ctx.clone(), public_exponent)?; | |
| let hash_obj = create_hash_object(ctx, hash)?; | |
| obj.set("hash", hash_obj)?; | |
| obj.set("modulusLength", modulus_length)?; | |
| obj.set("publicExponent", array)?; | |
| }, | |
| KeyAlgorithm::Derive(KeyDerivation::Hkdf { hash, salt, info }) => { | |
| let salt = TypedArray::<u8>::new(ctx.clone(), salt.to_vec())?; | |
| let info = TypedArray::<u8>::new(ctx.clone(), info.to_vec())?; | |
| obj.set("hash", hash.as_str())?; | |
| obj.set("salt", salt)?; | |
| obj.set("info", info)?; | |
| }, | |
| KeyAlgorithm::Derive(KeyDerivation::Pbkdf2 { | |
| hash, | |
| salt, | |
| iterations, | |
| }) => { | |
| let salt = TypedArray::<u8>::new(ctx.clone(), salt.to_vec())?; | |
| obj.set("hash", hash.as_str())?; | |
| obj.set("salt", salt)?; | |
| obj.set("iterations", iterations)?; | |
| }, | |
| _ => {}, | |
| }; | |
| Ok(obj) | |
| } | |
| #[allow(clippy::too_many_arguments)] | |
| fn from_ec<'js>( | |
| ctx: &Ctx<'js>, | |
| #[allow(unused_variables)] mode: KeyAlgorithmMode<'_, 'js>, | |
| obj: Result<Object<'js>>, | |
| #[allow(unused_variables)] algorithm_name: &str, | |
| algorithm: EcAlgorithm, | |
| key_usages: &Array<'js>, | |
| private_usages: &mut Vec<String>, | |
| public_usages: &mut Vec<String>, | |
| key_usage_algorithm: KeyUsageAlgorithm, | |
| ) -> Result<KeyAlgorithm> { | |
| let obj = obj?; | |
| let curve_name: String = obj.get_required("namedCurve", "algorithm")?; | |
| let curve = EllipticCurve::try_from(curve_name.as_str()) | |
| .map_err(NotSupportedError) | |
| .or_throw_dom(ctx)?; | |
| #[cfg(feature = "_subtle-full")] | |
| let key_kind = if let KeyAlgorithmMode::Import { format, kind, data } = mode { | |
| import_ec_key(ctx, format, kind, data, algorithm_name, &curve, &curve_name)?; | |
| Some(kind) | |
| } else { | |
| None | |
| }; | |
| #[cfg(not(feature = "_subtle-full"))] | |
| let key_kind: Option<&KeyKind> = None; | |
| KeyUsage::classify_and_check_usages( | |
| ctx, | |
| key_usage_algorithm, | |
| key_usages, | |
| private_usages, | |
| public_usages, | |
| key_kind.as_deref(), | |
| )?; | |
| Ok(KeyAlgorithm::Ec { curve, algorithm }) | |
| } | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn import_derive_key<'js>( | |
| ctx: &Ctx<'js>, | |
| format: KeyFormatData<'js>, | |
| kind: &mut KeyKind, | |
| data: &mut Vec<u8>, | |
| algorithm_name: &str, | |
| ) -> Result<()> { | |
| if let KeyFormatData::Raw(object_bytes) = format { | |
| *data = object_bytes.into_bytes(ctx)?; | |
| *kind = KeyKind::Secret; | |
| } else { | |
| return Err(DOMException::not_supported_error( | |
| ctx, | |
| [algorithm_name, " only supports 'raw' import format"].concat(), | |
| )); | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn import_rsa_key<'js>( | |
| ctx: &Ctx<'js>, | |
| format: KeyFormatData<'js>, | |
| kind: &mut KeyKind, | |
| data: &mut Vec<u8>, | |
| algorithm_name: &str, | |
| hash: &HashAlgorithm, | |
| ) -> Result<(u32, Box<[u8]>)> { | |
| use crate::{ | |
| provider::{CryptoProvider, RsaJwkImport}, | |
| CRYPTO_PROVIDER, | |
| }; | |
| let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { | |
| if other_oid != const_oid::db::rfc5912::RSA_ENCRYPTION { | |
| return algorithm_mismatch_error(ctx, algorithm_name); | |
| } | |
| Ok(()) | |
| }; | |
| let (modulus_length, public_exponent) = match format { | |
| KeyFormatData::Jwk(object) => { | |
| validate_jwk_kty(ctx, &object, "RSA")?; | |
| if let Some(alg) = object.get_optional::<_, String>("alg")? { | |
| let numeric_hash_str = match algorithm_name { | |
| "RSASSA-PKCS1-v1_5" => alg.strip_prefix("RS"), | |
| "RSA-PSS" => alg.strip_prefix("PS"), | |
| "RSA-OAEP" => alg.strip_prefix("RSA-OAEP-"), | |
| _ => None, | |
| }; | |
| let Some(numeric_hash_str) = numeric_hash_str else { | |
| return algorithm_mismatch_error(ctx, algorithm_name); | |
| }; | |
| if numeric_hash_str != hash.as_numeric_str() { | |
| return hash_mismatch_error(ctx, hash); | |
| } | |
| } | |
| let n_bytes = get_jwk_required_bytes(ctx, &object, "n")?; | |
| let e_bytes = get_jwk_required_bytes(ctx, &object, "e")?; | |
| let d_bytes = get_jwk_optional_bytes(ctx, &object, "d")?; | |
| let result = if let Some(ref d_bytes) = d_bytes { | |
| let p_bytes = get_jwk_required_bytes(ctx, &object, "p")?; | |
| let q_bytes = get_jwk_required_bytes(ctx, &object, "q")?; | |
| let dp_bytes = get_jwk_required_bytes(ctx, &object, "dp")?; | |
| let dq_bytes = get_jwk_required_bytes(ctx, &object, "dq")?; | |
| let qi_bytes = get_jwk_required_bytes(ctx, &object, "qi")?; | |
| let jwk = RsaJwkImport { | |
| n: &n_bytes, | |
| e: &e_bytes, | |
| d: Some(d_bytes), | |
| p: Some(&p_bytes), | |
| q: Some(&q_bytes), | |
| dp: Some(&dp_bytes), | |
| dq: Some(&dq_bytes), | |
| qi: Some(&qi_bytes), | |
| }; | |
| CRYPTO_PROVIDER.import_rsa_jwk(jwk).or_throw_dom(ctx)? | |
| } else { | |
| let jwk = RsaJwkImport { | |
| n: &n_bytes, | |
| e: &e_bytes, | |
| d: None, | |
| p: None, | |
| q: None, | |
| dp: None, | |
| dq: None, | |
| qi: None, | |
| }; | |
| CRYPTO_PROVIDER.import_rsa_jwk(jwk).or_throw_dom(ctx)? | |
| }; | |
| *data = result.key_data; | |
| *kind = if result.is_private { | |
| KeyKind::Private | |
| } else { | |
| KeyKind::Public | |
| }; | |
| (result.modulus_length as usize, result.public_exponent) | |
| }, | |
| KeyFormatData::Raw(object_bytes) => { | |
| let result = CRYPTO_PROVIDER | |
| .import_rsa_public_key_pkcs1(object_bytes.as_bytes(ctx)?) | |
| .or_throw_dom(ctx)?; | |
| *data = result.key_data; | |
| *kind = KeyKind::Public; | |
| (result.modulus_length as usize, result.public_exponent) | |
| }, | |
| KeyFormatData::Pkcs8(object_bytes) => { | |
| let pk_info = PrivateKeyInfoRef::from_der(object_bytes.as_bytes(ctx)?).or_throw(ctx)?; | |
| validate_oid(pk_info.algorithm.oid)?; | |
| let result = CRYPTO_PROVIDER | |
| .import_rsa_private_key_pkcs8(object_bytes.as_bytes(ctx)?) | |
| .or_throw_dom(ctx)?; | |
| *data = result.key_data; | |
| *kind = KeyKind::Private; | |
| (result.modulus_length as usize, result.public_exponent) | |
| }, | |
| KeyFormatData::Spki(object_bytes) => { | |
| let pk_info = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) | |
| .or_throw(ctx)?; | |
| validate_oid(pk_info.algorithm.oid)?; | |
| let result = CRYPTO_PROVIDER | |
| .import_rsa_public_key_spki(object_bytes.as_bytes(ctx)?) | |
| .or_throw_dom(ctx)?; | |
| *data = result.key_data; | |
| *kind = KeyKind::Public; | |
| (result.modulus_length as usize, result.public_exponent) | |
| }, | |
| }; | |
| let public_exponent = public_exponent.into_boxed_slice(); | |
| Ok((modulus_length as u32, public_exponent)) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn import_symmetric_key<'js>( | |
| ctx: &Ctx<'js>, | |
| format: KeyFormatData<'js>, | |
| kind: &mut KeyKind, | |
| data: &mut Vec<u8>, | |
| algorithm_name: &str, | |
| hash: Option<&HashAlgorithm>, | |
| ) -> Result<usize> { | |
| *kind = KeyKind::Secret; | |
| match format { | |
| KeyFormatData::Jwk(object) => { | |
| validate_jwk_kty(ctx, &object, "oct")?; | |
| let k: String = get_jwk_required_string(ctx, &object, "k")?; | |
| let alg: String = get_jwk_required_string(ctx, &object, "alg")?; | |
| let prefix = &alg[..1]; | |
| match (prefix, hash) { | |
| //HMAC - HS256, HS512 etc | |
| ("H", Some(hash)) => { | |
| if &alg[2..] != hash.as_numeric_str() { | |
| return hash_mismatch_error(ctx, hash); | |
| } | |
| }, | |
| //AES - A256KW, A256GCM, A256CRT, A512CBC etc | |
| ("A", None) => { | |
| //extract AES-{suffix} | |
| let aes_variant = &alg[4..]; | |
| if !algorithm_name.ends_with(aes_variant) { | |
| return algorithm_mismatch_error(ctx, algorithm_name); | |
| } | |
| }, | |
| _ => return algorithm_mismatch_error(ctx, algorithm_name), | |
| } | |
| *data = bytes_from_b64_url_safe(k.as_bytes()).or_throw(ctx)?; | |
| Ok(data.len() * 8) | |
| }, | |
| KeyFormatData::Raw(object_bytes) => { | |
| let bytes = object_bytes.into_bytes(ctx)?; | |
| *data = bytes; | |
| Ok(data.len() * 8) | |
| }, | |
| _ => algorithm_mismatch_error(ctx, algorithm_name), | |
| } | |
| } | |
| // EC algorithm OID for validation | |
| #[cfg(feature = "_subtle-full")] | |
| const EC_ALGORITHM_OID: const_oid::ObjectIdentifier = | |
| const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); | |
| #[cfg(feature = "_subtle-full")] | |
| fn import_ec_key<'js>( | |
| ctx: &Ctx<'js>, | |
| format: KeyFormatData<'js>, | |
| kind: &mut KeyKind, | |
| data: &mut Vec<u8>, | |
| algorithm_name: &str, | |
| curve: &EllipticCurve, | |
| curve_name: &str, | |
| ) -> Result<()> { | |
| use crate::{ | |
| provider::{CryptoProvider, EcJwkImport}, | |
| CRYPTO_PROVIDER, | |
| }; | |
| let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { | |
| if other_oid != EC_ALGORITHM_OID { | |
| return algorithm_mismatch_error(ctx, algorithm_name); | |
| } | |
| Ok(()) | |
| }; | |
| // Get expected coordinate length for the curve | |
| let coord_len = match curve { | |
| EllipticCurve::P256 => 32, | |
| EllipticCurve::P384 => 48, | |
| EllipticCurve::P521 => 66, | |
| }; | |
| match format { | |
| KeyFormatData::Jwk(object) => { | |
| validate_jwk_kty(ctx, &object, "EC")?; | |
| validate_jwk_use(ctx, &object, true)?; | |
| validate_jwk_crv(ctx, &object, curve_name)?; | |
| let x_bytes = get_jwk_required_bytes(ctx, &object, "x")?; | |
| validate_jwk_bytes_len(ctx, algorithm_name, "x coordinate", &x_bytes, coord_len)?; | |
| let y_bytes = get_jwk_required_bytes(ctx, &object, "y")?; | |
| validate_jwk_bytes_len(ctx, algorithm_name, "y coordinate", &y_bytes, coord_len)?; | |
| let d_bytes = get_jwk_optional_bytes(ctx, &object, "d")?; | |
| if let Some(ref d_bytes) = d_bytes { | |
| validate_jwk_bytes_len(ctx, algorithm_name, "private key", d_bytes, coord_len)?; | |
| } | |
| let jwk = EcJwkImport { | |
| x: &x_bytes, | |
| y: &y_bytes, | |
| d: d_bytes.as_deref(), | |
| }; | |
| let result = CRYPTO_PROVIDER | |
| .import_ec_jwk(jwk, *curve) | |
| .or_throw_dom(ctx)?; | |
| *data = result.key_data; | |
| *kind = if result.is_private { | |
| KeyKind::Private | |
| } else { | |
| KeyKind::Public | |
| }; | |
| }, | |
| KeyFormatData::Raw(object_bytes) => { | |
| let bytes = object_bytes.as_bytes(ctx)?; | |
| let result = CRYPTO_PROVIDER | |
| .import_ec_public_key_sec1(bytes, *curve) | |
| .or_throw_dom(ctx)?; | |
| *data = result.key_data; | |
| *kind = KeyKind::Public; | |
| }, | |
| KeyFormatData::Spki(object_bytes) => { | |
| let spki = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) | |
| .or_throw_data_error(ctx)?; | |
| validate_oid(spki.algorithm.oid)?; | |
| let result = CRYPTO_PROVIDER | |
| .import_ec_public_key_spki(object_bytes.as_bytes(ctx)?, *curve) | |
| .or_throw_dom(ctx)?; | |
| *data = result.key_data; | |
| *kind = KeyKind::Public; | |
| }, | |
| KeyFormatData::Pkcs8(object_bytes) => { | |
| let pkcs8 = PrivateKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) | |
| .or_throw_data_error(ctx)?; | |
| validate_oid(pkcs8.algorithm.oid)?; | |
| let result = CRYPTO_PROVIDER | |
| .import_ec_private_key_pkcs8(object_bytes.as_bytes(ctx)?) | |
| .or_throw_dom(ctx)?; | |
| *data = result.key_data; | |
| *kind = KeyKind::Private; | |
| }, | |
| }; | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn import_okp_key<'js>( | |
| ctx: &Ctx<'js>, | |
| format: KeyFormatData<'js>, | |
| kind: &mut KeyKind, | |
| data: &mut Vec<u8>, | |
| oid: ObjectIdentifier, | |
| algorithm_name: &str, | |
| is_ed25519: bool, | |
| ) -> Result<()> { | |
| let validate_oid = |other_oid: const_oid::ObjectIdentifier| -> Result<()> { | |
| if other_oid != oid { | |
| return algorithm_mismatch_error(ctx, algorithm_name); | |
| } | |
| Ok(()) | |
| }; | |
| match format { | |
| KeyFormatData::Jwk(object) => { | |
| validate_jwk_kty(ctx, &object, "OKP")?; | |
| validate_jwk_crv(ctx, &object, algorithm_name)?; | |
| if is_ed25519 { | |
| validate_jwk_alg(ctx, &object)?; | |
| } | |
| validate_jwk_use(ctx, &object, is_ed25519)?; | |
| let public_key = get_jwk_required_bytes(ctx, &object, "x")?; | |
| validate_jwk_bytes_len(ctx, algorithm_name, "public key", &public_key, 32)?; | |
| let private_key = get_jwk_optional_bytes(ctx, &object, "d")?; | |
| if let Some(private_key) = private_key { | |
| validate_jwk_bytes_len(ctx, algorithm_name, "private key", &private_key, 32)?; | |
| validate_okp_jwk_key_pair(ctx, &private_key, &public_key, is_ed25519)?; | |
| if is_ed25519 { | |
| // Ed25519 internal representation is the complete PKCS#8 DER. | |
| let inner = OctetStringRef::new(private_key.as_slice()).or_throw(ctx)?; | |
| let inner_der = inner.to_der().or_throw(ctx)?; | |
| let pk_info = PrivateKeyInfoRef { | |
| algorithm: AlgorithmIdentifier { | |
| oid, | |
| parameters: None, | |
| }, | |
| private_key: OctetStringRef::new(&inner_der).or_throw(ctx)?, | |
| public_key: Some(BitStringRef::from_bytes(&public_key).or_throw(ctx)?), | |
| }; | |
| *data = pk_info.to_der().or_throw(ctx)?; | |
| } else { | |
| // X25519 internal representation is raw 32-byte scalar. | |
| *data = private_key; | |
| } | |
| *kind = KeyKind::Private; | |
| } else { | |
| *data = public_key; | |
| *kind = KeyKind::Public; | |
| } | |
| }, | |
| KeyFormatData::Raw(object_bytes) => { | |
| let bytes = object_bytes.into_bytes(ctx)?; | |
| if bytes.len() != 32 { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| [algorithm_name, " keys must be 32 bytes long"].concat(), | |
| )); | |
| } | |
| *data = bytes; | |
| *kind = KeyKind::Public; | |
| }, | |
| KeyFormatData::Spki(object_bytes) => { | |
| let spki = spki::SubjectPublicKeyInfoRef::try_from(object_bytes.as_bytes(ctx)?) | |
| .or_throw_data_error(ctx)?; | |
| validate_oid(spki.algorithm.oid)?; | |
| let public_key = spki.subject_public_key.raw_bytes(); | |
| if public_key.len() != 32 { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| [algorithm_name, " public key must be 32 bytes"].concat(), | |
| )); | |
| } | |
| *data = public_key.to_vec(); | |
| *kind = KeyKind::Public; | |
| }, | |
| KeyFormatData::Pkcs8(object_bytes) => { | |
| let bytes = object_bytes.into_bytes(ctx)?; | |
| let pkcs8 = PrivateKeyInfoRef::try_from(bytes.as_slice()).or_throw_data_error(ctx)?; | |
| validate_oid(pkcs8.algorithm.oid)?; | |
| if is_ed25519 { | |
| // Ed25519 internal representation is the complete PKCS#8 DER. | |
| *data = bytes; | |
| } else { | |
| // X25519 internal representation is the inner OCTET STRING. | |
| *data = OctetString::from_der(pkcs8.private_key.as_bytes()) | |
| .or_throw(ctx)? | |
| .as_bytes() | |
| .to_vec(); | |
| if data.len() != 32 { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| [algorithm_name, " private key must be 32 bytes"].concat(), | |
| )); | |
| } | |
| } | |
| *kind = KeyKind::Private; | |
| }, | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn get_jwk_required_string<'js>( | |
| ctx: &Ctx<'js>, | |
| object: &Object<'js>, | |
| name: &str, | |
| ) -> Result<String> { | |
| object | |
| .get_required(name, "keyData") | |
| .or_throw_data_error(ctx) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn get_jwk_required_bytes<'js>( | |
| ctx: &Ctx<'js>, | |
| object: &Object<'js>, | |
| name: &str, | |
| ) -> Result<Vec<u8>> { | |
| let value = get_jwk_required_string(ctx, object, name)?; | |
| bytes_from_b64_url_safe(value.as_bytes()).or_throw_data_error(ctx) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn get_jwk_optional_bytes<'js>( | |
| ctx: &Ctx<'js>, | |
| object: &Object<'js>, | |
| name: &str, | |
| ) -> Result<Option<Vec<u8>>> { | |
| let value = object.get_optional::<_, String>(name)?; | |
| value | |
| .map(|value| bytes_from_b64_url_safe(value.as_bytes()).or_throw_data_error(ctx)) | |
| .transpose() | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn validate_jwk_kty<'js>(ctx: &Ctx<'js>, object: &Object<'js>, expected: &str) -> Result<()> { | |
| let kty = get_jwk_required_string(ctx, object, "kty")?; | |
| if kty != expected { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| ["JWK 'kty' parameter must be '", expected, "'"].concat(), | |
| )); | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn validate_jwk_crv<'js>(ctx: &Ctx<'js>, object: &Object<'js>, expected: &str) -> Result<()> { | |
| let crv = get_jwk_required_string(ctx, object, "crv")?; | |
| if crv != expected { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| ["JWK 'crv' parameter must be '", expected, "'"].concat(), | |
| )); | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn validate_jwk_use(ctx: &Ctx<'_>, object: &Object<'_>, is_ed25519: bool) -> Result<()> { | |
| if let Some(use_) = object.get_optional::<_, String>("use")? { | |
| let expected = if is_ed25519 { "sig" } else { "enc" }; | |
| if use_ != expected { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| "JWK 'use' parameter is invalid", | |
| )); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn validate_jwk_alg(ctx: &Ctx<'_>, object: &Object<'_>) -> Result<()> { | |
| if let Some(alg) = object.get_optional::<_, String>("alg")? { | |
| if alg != "Ed25519" && alg != "EdDSA" { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| "JWK 'alg' parameter is invalid", | |
| )); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn validate_jwk_bytes_len( | |
| ctx: &Ctx<'_>, | |
| algorithm_name: &str, | |
| field: &str, | |
| bytes: &[u8], | |
| expected: usize, | |
| ) -> Result<()> { | |
| if bytes.len() != expected { | |
| return Err(DOMException::data_error( | |
| ctx, | |
| [algorithm_name, " JWK ", field, " has invalid length"].concat(), | |
| )); | |
| } | |
| Ok(()) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| fn validate_okp_jwk_key_pair<'js>( | |
| ctx: &Ctx<'js>, | |
| private_key: &[u8], | |
| public_key: &[u8], | |
| is_ed25519: bool, | |
| ) -> Result<()> { | |
| let derived_public_key = if is_ed25519 { | |
| let secret_key: [u8; 32] = private_key.try_into().or_throw_data_error(ctx)?; | |
| SigningKey::from_bytes(&secret_key) | |
| .verifying_key() | |
| .to_bytes() | |
| .to_vec() | |
| } else { | |
| let secret_key: [u8; 32] = private_key.try_into().or_throw_data_error(ctx)?; | |
| let secret = StaticSecret::from(secret_key); | |
| PublicKey::from(&secret).as_bytes().to_vec() | |
| }; | |
| if derived_public_key.as_slice() != public_key { | |
| return Err(DOMException::data_error(ctx, "JWK key pair is invalid")); | |
| } | |
| Ok(()) | |
| } | |
| pub fn extract_sha_hash<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> Result<HashAlgorithm> { | |
| let hash: Value = obj.get_required("hash", "algorithm")?; | |
| let hash = if let Some(string) = hash.as_string() { | |
| string.to_string() | |
| } else if let Some(obj) = hash.into_object() { | |
| obj.get_required("name", "hash") | |
| } else { | |
| return Err(DOMException::not_supported_error( | |
| ctx, | |
| "hash must be a string or an object", | |
| )); | |
| }?; | |
| let hash = normalize_algorithm_name(&hash); | |
| HashAlgorithm::from_strict_str(hash.as_str()).or_throw_dom(ctx) | |
| } | |
| fn create_hash_object<'js>(ctx: &Ctx<'js>, hash: &HashAlgorithm) -> Result<Object<'js>> { | |
| let hash_obj = Object::new(ctx.clone())?; | |
| hash_obj.set(PredefinedAtom::Name, hash.as_str())?; | |
| Ok(hash_obj) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| pub fn hash_mismatch_error<T>(ctx: &Ctx<'_>, hash: &HashAlgorithm) -> Result<T> { | |
| Err(DOMException::type_mismatch_error( | |
| ctx, | |
| ["Algorithm hash expected to be ", hash.as_str()].concat(), | |
| )) | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| trait DataErrorResultExt<T> { | |
| fn or_throw_data_error(self, ctx: &Ctx<'_>) -> Result<T>; | |
| } | |
| #[cfg(feature = "_subtle-full")] | |
| impl<T, E> DataErrorResultExt<T> for std::result::Result<T, E> | |
| where | |
| E: std::fmt::Display, | |
| { | |
| fn or_throw_data_error(self, ctx: &Ctx<'_>) -> Result<T> { | |
| self.map_err(|e| DataError(e.to_string())).or_throw_dom(ctx) | |
| } | |
| } |