use std::convert::TryFrom;
use crate::crypto::{webcrypto::WindowOrWorker, SigningAlg};
use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array};
use spki::SubjectPublicKeyInfoRef;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{CryptoKey, SubtleCrypto};
use x509_parser::der_parser::ber::{parse_ber_sequence, BerObject};
use crate::{Error, Result};
pub struct EcKeyImportParams {
name: String,
named_curve: String,
hash: String,
}
impl EcKeyImportParams {
pub fn new(name: &str, hash: &str, named_curve: &str) -> Self {
EcKeyImportParams {
name: name.to_owned(),
named_curve: named_curve.to_owned(),
hash: hash.to_owned(),
}
}
pub fn as_js_object(&self) -> Object {
let obj = Object::new();
Reflect::set(&obj, &"name".into(), &self.name.clone().into()).expect("not valid name");
Reflect::set(&obj, &"namedCurve".into(), &self.named_curve.clone().into())
.expect("not valid name");
let inner_obj = Object::new();
Reflect::set(&inner_obj, &"name".into(), &self.hash.clone().into())
.expect("not valid name");
Reflect::set(&obj, &"hash".into(), &inner_obj).expect("not valid name");
obj
}
}
pub struct EcdsaParams {
name: String,
hash: String,
}
impl EcdsaParams {
pub fn new(name: &str, hash: &str) -> Self {
EcdsaParams {
name: name.to_owned(),
hash: hash.to_owned(),
}
}
pub fn as_js_object(&self) -> Object {
let obj = Object::new();
Reflect::set(&obj, &"name".into(), &self.name.clone().into()).expect("not valid name");
let inner_obj = Object::new();
Reflect::set(&inner_obj, &"name".into(), &self.hash.clone().into())
.expect("not valid name");
Reflect::set(&obj, &"hash".into(), &inner_obj).expect("not valid name");
obj
}
}
fn data_as_array_buffer(data: &[u8]) -> ArrayBuffer {
let typed_array = Uint8Array::new_with_length(data.len() as u32);
typed_array.copy_from(data);
typed_array.buffer()
}
async fn crypto_is_verified(
subtle_crypto: &SubtleCrypto,
alg: &Object,
key: &CryptoKey,
sig: &Object,
data: &Object,
) -> Result<bool> {
let promise = subtle_crypto
.verify_with_object_and_buffer_source_and_buffer_source(alg, key, sig, data)
.map_err(|_err| Error::WasmVerifier)?;
let verified: JsValue = JsFuture::from(promise)
.await
.map_err(|_err| Error::WasmVerifier)?
.into();
let result = verified.is_truthy();
web_sys::console::debug_2(&"verified".into(), &result.into());
Ok(result)
}
fn biguint_val(ber_object: &BerObject) -> rsa::BigUint {
ber_object
.as_biguint()
.map(|x| x.to_u32_digits())
.map(rsa::BigUint::new)
.unwrap_or_default()
}
fn ed25519_validate(sig: Vec<u8>, data: Vec<u8>, pkey: Vec<u8>) -> Result<bool> {
use ed25519_dalek::{Signature, Verifier, VerifyingKey, PUBLIC_KEY_LENGTH};
if pkey.len() == PUBLIC_KEY_LENGTH {
let ed_sig = Signature::from_slice(&sig).map_err(|_| Error::CoseInvalidCert)?;
let mut cert_slice: [u8; 32] = Default::default();
cert_slice.copy_from_slice(&pkey[0..PUBLIC_KEY_LENGTH]);
let vk = VerifyingKey::from_bytes(&cert_slice).map_err(|_| Error::CoseInvalidCert)?;
match vk.verify(&data, &ed_sig) {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
} else {
web_sys::console::debug_2(
&"Ed25519 public key incorrect length: ".into(),
&pkey.len().to_string().into(),
);
Err(Error::CoseInvalidCert)
}
}
pub(crate) async fn async_validate(
algo: String,
hash: String,
_salt_len: u32,
pkey: Vec<u8>,
sig: Vec<u8>,
data: Vec<u8>,
) -> Result<bool> {
use rsa::{
sha2::{Sha256, Sha384, Sha512},
RsaPublicKey,
};
let context = WindowOrWorker::new();
let subtle_crypto = context?.subtle_crypto()?;
let sig_array_buf = data_as_array_buffer(&sig);
let data_array_buf = data_as_array_buffer(&data);
match algo.as_ref() {
"RSASSA-PKCS1-v1_5" => {
use rsa::{pkcs1v15::Signature, signature::Verifier};
let spki = SubjectPublicKeyInfoRef::try_from(pkey.as_ref())
.map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?;
let (_, seq) = parse_ber_sequence(&spki.subject_public_key.raw_bytes())
.map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?;
let modulus = biguint_val(&seq[0]);
let exp = biguint_val(&seq[1]);
let public_key = RsaPublicKey::new(modulus, exp)
.map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?;
let normalized_hash = hash.clone().replace("-", "").to_lowercase();
let result = match normalized_hash.as_ref() {
"sha256" => {
let vk = rsa::pkcs1v15::VerifyingKey::<Sha256>::new(public_key);
let signature: Signature = sig.as_slice().try_into().map_err(|_e| {
Error::WasmRsaKeyImport("could no process RSA signature".to_string())
})?;
vk.verify(&data, &signature)
}
"sha384" => {
let vk = rsa::pkcs1v15::VerifyingKey::<Sha384>::new(public_key);
let signature: Signature = sig.as_slice().try_into().map_err(|_e| {
Error::WasmRsaKeyImport("could no process RSA signature".to_string())
})?;
vk.verify(&data, &signature)
}
"sha512" => {
let vk = rsa::pkcs1v15::VerifyingKey::<Sha512>::new(public_key);
let signature: Signature = sig.as_slice().try_into().map_err(|_e| {
Error::WasmRsaKeyImport("could no process RSA signature".to_string())
})?;
vk.verify(&data, &signature)
}
_ => return Err(Error::UnknownAlgorithm),
};
match result {
Ok(()) => {
web_sys::console::debug_1(&"RSA validation success:".into());
Ok(true)
}
Err(err) => {
web_sys::console::debug_2(
&"RSA validation failed:".into(),
&err.to_string().into(),
);
Ok(false)
}
}
}
"RSA-PSS" => {
use rsa::{pss::Signature, signature::Verifier};
let spki = SubjectPublicKeyInfoRef::try_from(pkey.as_ref())
.map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?;
let (_, seq) = parse_ber_sequence(&spki.subject_public_key.raw_bytes())
.map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?;
let normalized_hash = hash.clone().replace("-", "").to_lowercase();
let modulus = biguint_val(&seq[0]);
let exp = biguint_val(&seq[1]);
let public_key = RsaPublicKey::new(modulus, exp)
.map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?;
let result = match normalized_hash.as_ref() {
"sha256" => {
let vk = rsa::pss::VerifyingKey::<Sha256>::new(public_key);
let signature: Signature = sig.as_slice().try_into().map_err(|_e| {
Error::WasmRsaKeyImport("could no process RSA signature".to_string())
})?;
vk.verify(&data, &signature)
}
"sha384" => {
let vk = rsa::pss::VerifyingKey::<Sha384>::new(public_key);
let signature: Signature = sig.as_slice().try_into().map_err(|_e| {
Error::WasmRsaKeyImport("could no process RSA signature".to_string())
})?;
vk.verify(&data, &signature)
}
"sha512" => {
let vk = rsa::pss::VerifyingKey::<Sha512>::new(public_key);
let signature: Signature = sig.as_slice().try_into().map_err(|_e| {
Error::WasmRsaKeyImport("could no process RSA signature".to_string())
})?;
vk.verify(&data, &signature)
}
_ => return Err(Error::UnknownAlgorithm),
};
match result {
Ok(()) => {
web_sys::console::debug_1(&"RSA-PSS validation success:".into());
Ok(true)
}
Err(err) => {
web_sys::console::debug_2(
&"RSA-PSS validation failed:".into(),
&err.to_string().into(),
);
Ok(false)
}
}
}
"ECDSA" => {
let named_curve = match hash.as_ref() {
"SHA-256" => "P-256".to_string(),
"SHA-384" => "P-384".to_string(),
"SHA-512" => "P-521".to_string(),
_ => return Err(Error::UnsupportedType),
};
let mut algorithm = EcKeyImportParams::new(&algo, &hash, &named_curve).as_js_object();
let key_array_buf = data_as_array_buffer(&pkey);
let usages = Array::new();
usages.push(&"verify".into());
let promise = subtle_crypto
.import_key_with_object("spki", &key_array_buf, &algorithm, true, &usages)
.map_err(|_err| Error::WasmKey)?;
let crypto_key: CryptoKey = JsFuture::from(promise)
.await
.map_err(|_| {
web_sys::console::debug_1(&"bad EC key".into());
Error::CoseInvalidCert
})?
.into();
web_sys::console::debug_2(&"CryptoKey".into(), &crypto_key);
algorithm = EcdsaParams::new(&algo, &hash).as_js_object();
crypto_is_verified(
&subtle_crypto,
&algorithm,
&crypto_key,
&sig_array_buf,
&data_array_buf,
)
.await
}
"ED25519" => {
use x509_parser::{prelude::*, public_key::PublicKey};
if let Ok((_, certificate_public_key)) = SubjectPublicKeyInfo::from_der(&pkey) {
match certificate_public_key.parsed() {
Ok(key) => match key {
PublicKey::Unknown(raw_key) => {
ed25519_validate(sig, data, raw_key.to_vec())
}
_ => Err(Error::OtherError(
"could not unwrap Ed25519 public key".into(),
)),
},
Err(_) => Err(Error::OtherError(
"could not recognize Ed25519 public key".into(),
)),
}
} else {
Err(Error::OtherError(
"could not parse Ed25519 public key".into(),
))
}
}
_ => Err(Error::UnsupportedType),
}
}
pub async fn validate_async(alg: SigningAlg, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result<bool> {
web_sys::console::debug_2(&"Validating with algorithm".into(), &alg.to_string().into());
match alg {
SigningAlg::Ps256 => {
async_validate(
"RSA-PSS".to_string(),
"SHA-256".to_string(),
32,
pkey.to_vec(),
sig.to_vec(),
data.to_vec(),
)
.await
}
SigningAlg::Ps384 => {
async_validate(
"RSA-PSS".to_string(),
"SHA-384".to_string(),
48,
pkey.to_vec(),
sig.to_vec(),
data.to_vec(),
)
.await
}
SigningAlg::Ps512 => {
async_validate(
"RSA-PSS".to_string(),
"SHA-512".to_string(),
64,
pkey.to_vec(),
sig.to_vec(),
data.to_vec(),
)
.await
}
SigningAlg::Es256 => {
async_validate(
"ECDSA".to_string(),
"SHA-256".to_string(),
0,
pkey.to_vec(),
sig.to_vec(),
data.to_vec(),
)
.await
}
SigningAlg::Es384 => {
async_validate(
"ECDSA".to_string(),
"SHA-384".to_string(),
0,
pkey.to_vec(),
sig.to_vec(),
data.to_vec(),
)
.await
}
SigningAlg::Es512 => {
async_validate(
"ECDSA".to_string(),
"SHA-512".to_string(),
0,
pkey.to_vec(),
sig.to_vec(),
data.to_vec(),
)
.await
}
SigningAlg::Ed25519 => {
async_validate(
"ED25519".to_string(),
"SHA-512".to_string(),
0,
pkey.to_vec(),
sig.to_vec(),
data.to_vec(),
)
.await
}
}
}
#[cfg(test)]
pub mod tests {
#![allow(clippy::unwrap_used)]
use crate::crypto::SigningAlg;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
use super::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[wasm_bindgen_test]
async fn test_async_verify_rsa_pss() {
let sig_bytes = include_bytes!("../../tests/fixtures/sig_ps256.data");
let data_bytes = include_bytes!("../../tests/fixtures/data_ps256.data");
let key_bytes = include_bytes!("../../tests/fixtures/key_ps256.data");
let validated = validate_async(SigningAlg::Ps256, sig_bytes, data_bytes, key_bytes)
.await
.unwrap();
assert_eq!(validated, true);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[wasm_bindgen_test]
async fn test_async_verify_ecdsa() {
let sig_es384_bytes = include_bytes!("../../tests/fixtures/sig_es384.data");
let data_es384_bytes = include_bytes!("../../tests/fixtures/data_es384.data");
let key_es384_bytes = include_bytes!("../../tests/fixtures/key_es384.data");
let mut validated = validate_async(
SigningAlg::Es384,
sig_es384_bytes,
data_es384_bytes,
key_es384_bytes,
)
.await
.unwrap();
assert_eq!(validated, true);
let sig_es512_bytes = include_bytes!("../../tests/fixtures/sig_es512.data");
let data_es512_bytes = include_bytes!("../../tests/fixtures/data_es512.data");
let key_es512_bytes = include_bytes!("../../tests/fixtures/key_es512.data");
validated = validate_async(
SigningAlg::Es512,
sig_es512_bytes,
data_es512_bytes,
key_es512_bytes,
)
.await
.unwrap();
assert_eq!(validated, true);
let sig_es256_bytes = include_bytes!("../../tests/fixtures/sig_es256.data");
let data_es256_bytes = include_bytes!("../../tests/fixtures/data_es256.data");
let key_es256_bytes = include_bytes!("../../tests/fixtures/key_es256.data");
let validated = validate_async(
SigningAlg::Es256,
sig_es256_bytes,
data_es256_bytes,
key_es256_bytes,
)
.await
.unwrap();
assert_eq!(validated, true);
}
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[wasm_bindgen_test]
#[ignore]
async fn test_async_verify_bad() {
let sig_bytes = include_bytes!("../../tests/fixtures/sig_ps256.data");
let data_bytes = include_bytes!("../../tests/fixtures/data_ps256.data");
let key_bytes = include_bytes!("../../tests/fixtures/key_ps256.data");
let mut bad_bytes = data_bytes.to_vec();
bad_bytes[0] = b'c';
bad_bytes[1] = b'2';
bad_bytes[2] = b'p';
bad_bytes[3] = b'a';
let validated = validate_async(SigningAlg::Ps256, sig_bytes, &bad_bytes, key_bytes)
.await
.unwrap();
assert_eq!(validated, false);
}
}