webcrypto_validator.rs (17919B)
1 // Copyright 2022 Adobe. All rights reserved. 2 // This file is licensed to you under the Apache License, 3 // Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) 4 // or the MIT license (http://opensource.org/licenses/MIT), 5 // at your option. 6 7 // Unless required by applicable law or agreed to in writing, 8 // this software is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or 10 // implied. See the LICENSE-MIT and LICENSE-APACHE files for the 11 // specific language governing permissions and limitations under 12 // each license. 13 14 use std::convert::TryFrom; 15 16 use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; 17 use rsa::{BigUint, PaddingScheme, PublicKey, RsaPublicKey}; 18 use sha2::{Sha256, Sha384, Sha512}; 19 use spki::SubjectPublicKeyInfo; 20 use wasm_bindgen::prelude::*; 21 use wasm_bindgen_futures::JsFuture; 22 use web_sys::{CryptoKey, SubtleCrypto}; 23 use x509_parser::der_parser::ber::{parse_ber_sequence, BerObject}; 24 25 use crate::{ 26 utils::hash_utils::hash_by_alg, wasm::context::WindowOrWorker, Error, Result, SigningAlg, 27 }; 28 29 pub struct RsaHashedImportParams { 30 name: String, 31 hash: String, 32 } 33 34 impl RsaHashedImportParams { 35 pub fn new(name: &str, hash: &str) -> Self { 36 RsaHashedImportParams { 37 name: name.to_owned(), 38 hash: hash.to_owned(), 39 } 40 } 41 42 pub fn as_js_object(&self) -> Object { 43 let obj = Object::new(); 44 Reflect::set(&obj, &"name".into(), &self.name.clone().into()).expect("not valid name"); 45 46 let inner_obj = Object::new(); 47 Reflect::set(&inner_obj, &"name".into(), &self.hash.clone().into()) 48 .expect("not valid name"); 49 50 Reflect::set(&obj, &"hash".into(), &inner_obj).expect("not valid name"); 51 52 obj 53 } 54 } 55 56 pub struct EcKeyImportParams { 57 name: String, 58 named_curve: String, 59 hash: String, 60 } 61 62 impl EcKeyImportParams { 63 pub fn new(name: &str, hash: &str, named_curve: &str) -> Self { 64 EcKeyImportParams { 65 name: name.to_owned(), 66 named_curve: named_curve.to_owned(), 67 hash: hash.to_owned(), 68 } 69 } 70 71 pub fn as_js_object(&self) -> Object { 72 let obj = Object::new(); 73 Reflect::set(&obj, &"name".into(), &self.name.clone().into()).expect("not valid name"); 74 Reflect::set(&obj, &"namedCurve".into(), &self.named_curve.clone().into()) 75 .expect("not valid name"); 76 77 let inner_obj = Object::new(); 78 Reflect::set(&inner_obj, &"name".into(), &self.hash.clone().into()) 79 .expect("not valid name"); 80 81 Reflect::set(&obj, &"hash".into(), &inner_obj).expect("not valid name"); 82 83 obj 84 } 85 } 86 87 pub struct EcdsaParams { 88 name: String, 89 hash: String, 90 } 91 92 impl EcdsaParams { 93 pub fn new(name: &str, hash: &str) -> Self { 94 EcdsaParams { 95 name: name.to_owned(), 96 hash: hash.to_owned(), 97 } 98 } 99 100 pub fn as_js_object(&self) -> Object { 101 let obj = Object::new(); 102 Reflect::set(&obj, &"name".into(), &self.name.clone().into()).expect("not valid name"); 103 104 let inner_obj = Object::new(); 105 Reflect::set(&inner_obj, &"name".into(), &self.hash.clone().into()) 106 .expect("not valid name"); 107 108 Reflect::set(&obj, &"hash".into(), &inner_obj).expect("not valid name"); 109 110 obj 111 } 112 } 113 114 fn data_as_array_buffer(data: &[u8]) -> ArrayBuffer { 115 let typed_array = Uint8Array::new_with_length(data.len() as u32); 116 typed_array.copy_from(data); 117 typed_array.buffer() 118 } 119 120 async fn crypto_is_verified( 121 subtle_crypto: &SubtleCrypto, 122 alg: &Object, 123 key: &CryptoKey, 124 sig: &Object, 125 data: &Object, 126 ) -> Result<bool> { 127 let promise = subtle_crypto 128 .verify_with_object_and_buffer_source_and_buffer_source(alg, key, sig, data) 129 .map_err(|_err| Error::WasmVerifier)?; 130 let verified: JsValue = JsFuture::from(promise) 131 .await 132 .map_err(|_err| Error::WasmVerifier)? 133 .into(); 134 let result = verified.is_truthy(); 135 web_sys::console::debug_2(&"verified".into(), &result.into()); 136 Ok(result) 137 } 138 139 // Conversion utility from num-bigint::BigUint (used by x509_parser) 140 // to num-bigint-dig::BigUint (used by rsa) 141 fn biguint_val(ber_object: &BerObject) -> BigUint { 142 ber_object 143 .as_biguint() 144 .map(|x| x.to_u32_digits()) 145 .map(BigUint::new) 146 .unwrap_or_default() 147 } 148 149 fn pss_padding_from_hash(hash: &str, salt_len: &u32) -> Result<PaddingScheme> { 150 let salt_len = usize::try_from(salt_len.clone()) 151 .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; 152 let rng = rand::thread_rng(); 153 154 match hash { 155 "SHA-256" => Ok(PaddingScheme::new_pss_with_salt::<Sha256, _>(rng, salt_len)), 156 "SHA-384" => Ok(PaddingScheme::new_pss_with_salt::<Sha384, _>(rng, salt_len)), 157 "SHA-512" => Ok(PaddingScheme::new_pss_with_salt::<Sha512, _>(rng, salt_len)), 158 &_ => Err(Error::WasmRsaKeyImport(format!( 159 "Invalid PSS hash supplied for padding: {}", 160 hash 161 ))), 162 } 163 } 164 165 // Validate an Ed25519 signature for the provided data. The pkey must 166 // be the raw bytes representing CompressedEdwardsY. The length must 32 bytes. 167 fn ed25519_validate(sig: Vec<u8>, data: Vec<u8>, pkey: Vec<u8>) -> Result<bool> { 168 use ed25519_dalek::{Signature, Verifier, VerifyingKey, PUBLIC_KEY_LENGTH}; 169 170 if pkey.len() == PUBLIC_KEY_LENGTH { 171 let ed_sig = Signature::from_slice(&sig).map_err(|_| Error::CoseInvalidCert)?; 172 173 // convert to VerifyingKey 174 let mut cert_slice: [u8; 32] = Default::default(); 175 cert_slice.copy_from_slice(&pkey[0..PUBLIC_KEY_LENGTH]); 176 177 let vk = VerifyingKey::from_bytes(&cert_slice).map_err(|_| Error::CoseInvalidCert)?; 178 179 match vk.verify(&data, &ed_sig) { 180 Ok(_) => Ok(true), 181 Err(_) => Ok(false), 182 } 183 } else { 184 web_sys::console::debug_2( 185 &"Ed25519 public key incorrect length: ".into(), 186 &pkey.len().to_string().into(), 187 ); 188 Err(Error::CoseInvalidCert) 189 } 190 } 191 192 pub(crate) async fn async_validate( 193 algo: String, 194 hash: String, 195 salt_len: u32, 196 pkey: Vec<u8>, 197 sig: Vec<u8>, 198 data: Vec<u8>, 199 ) -> Result<bool> { 200 let context = WindowOrWorker::new(); 201 let subtle_crypto = context?.subtle_crypto()?; 202 let sig_array_buf = data_as_array_buffer(&sig); 203 let data_array_buf = data_as_array_buffer(&data); 204 205 match algo.as_ref() { 206 "RSASSA-PKCS1-v1_5" => { 207 // used for certificate validation 208 // Create Key 209 let algorithm = RsaHashedImportParams::new(&algo, &hash).as_js_object(); 210 let key_array_buf = data_as_array_buffer(&pkey); 211 let usages = Array::new(); 212 usages.push(&"verify".into()); 213 214 let promise = subtle_crypto 215 .import_key_with_object("spki", &key_array_buf, &algorithm, true, &usages) 216 .map_err(|_err| Error::WasmKey)?; 217 let crypto_key: CryptoKey = JsFuture::from(promise) 218 .await 219 .map_err(|_err| Error::WasmKey)? 220 .into(); 221 web_sys::console::debug_2(&"CryptoKey".into(), &crypto_key); 222 223 // Create verifier 224 crypto_is_verified( 225 &subtle_crypto, 226 &algorithm, 227 &crypto_key, 228 &sig_array_buf, 229 &data_array_buf, 230 ) 231 .await 232 } 233 "RSA-PSS" => { 234 let spki = SubjectPublicKeyInfo::try_from(pkey.as_ref()) 235 .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; 236 let (_, seq) = parse_ber_sequence(spki.subject_public_key) 237 .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; 238 // We need to normalize this from SHA-256 (the format WebCrypto uses) to sha256 239 // (the format the util function expects) so that it maps correctly 240 let normalized_hash = hash.clone().replace("-", "").to_lowercase(); 241 let hashed_data = hash_by_alg(&normalized_hash, &data, None); 242 let modulus = biguint_val(&seq[0]); 243 let exp = biguint_val(&seq[1]); 244 let public_key = RsaPublicKey::new(modulus, exp) 245 .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; 246 let padding = pss_padding_from_hash(&hash, &salt_len)?; 247 let result = public_key.verify(padding, &hashed_data, &sig); 248 249 match result { 250 Ok(()) => Ok(true), 251 Err(err) => { 252 web_sys::console::debug_2( 253 &"RSA-PSS validation failed:".into(), 254 &err.to_string().into(), 255 ); 256 Ok(false) 257 } 258 } 259 } 260 "ECDSA" => { 261 // Create Key 262 let named_curve = match hash.as_ref() { 263 "SHA-256" => "P-256".to_string(), 264 "SHA-384" => "P-384".to_string(), 265 "SHA-512" => "P-521".to_string(), 266 _ => return Err(Error::UnsupportedType), 267 }; 268 let mut algorithm = EcKeyImportParams::new(&algo, &hash, &named_curve).as_js_object(); 269 let key_array_buf = data_as_array_buffer(&pkey); 270 let usages = Array::new(); 271 usages.push(&"verify".into()); 272 273 let promise = subtle_crypto 274 .import_key_with_object("spki", &key_array_buf, &algorithm, true, &usages) 275 .map_err(|_err| Error::WasmKey)?; 276 let crypto_key: CryptoKey = JsFuture::from(promise) 277 .await 278 .map_err(|_| Error::CoseInvalidCert)? 279 .into(); 280 web_sys::console::debug_2(&"CryptoKey".into(), &crypto_key); 281 282 // Create verifier 283 algorithm = EcdsaParams::new(&algo, &hash).as_js_object(); 284 crypto_is_verified( 285 &subtle_crypto, 286 &algorithm, 287 &crypto_key, 288 &sig_array_buf, 289 &data_array_buf, 290 ) 291 .await 292 } 293 "ED25519" => { 294 use x509_parser::{prelude::*, public_key::PublicKey}; 295 296 // pull out raw Ed code points 297 if let Ok((_, certificate_public_key)) = SubjectPublicKeyInfo::from_der(&pkey) { 298 match certificate_public_key.parsed() { 299 Ok(key) => match key { 300 PublicKey::Unknown(raw_key) => { 301 ed25519_validate(sig, data, raw_key.to_vec()) 302 } 303 _ => Err(Error::OtherError( 304 "could not unwrap Ed25519 public key".into(), 305 )), 306 }, 307 Err(_) => Err(Error::OtherError( 308 "could not recognize Ed25519 public key".into(), 309 )), 310 } 311 } else { 312 Err(Error::OtherError( 313 "could not parse Ed25519 public key".into(), 314 )) 315 } 316 } 317 _ => Err(Error::UnsupportedType), 318 } 319 } 320 321 // This interface is called from CoseValidator. RSA validation not supported here. 322 pub async fn validate_async(alg: SigningAlg, sig: &[u8], data: &[u8], pkey: &[u8]) -> Result<bool> { 323 web_sys::console::debug_2(&"Validating with algorithm".into(), &alg.to_string().into()); 324 325 match alg { 326 SigningAlg::Ps256 => { 327 async_validate( 328 "RSA-PSS".to_string(), 329 "SHA-256".to_string(), 330 32, 331 pkey.to_vec(), 332 sig.to_vec(), 333 data.to_vec(), 334 ) 335 .await 336 } 337 SigningAlg::Ps384 => { 338 async_validate( 339 "RSA-PSS".to_string(), 340 "SHA-384".to_string(), 341 48, 342 pkey.to_vec(), 343 sig.to_vec(), 344 data.to_vec(), 345 ) 346 .await 347 } 348 SigningAlg::Ps512 => { 349 async_validate( 350 "RSA-PSS".to_string(), 351 "SHA-512".to_string(), 352 64, 353 pkey.to_vec(), 354 sig.to_vec(), 355 data.to_vec(), 356 ) 357 .await 358 } 359 // "rs256" => { 360 // async_validate( 361 // "RSASSA-PKCS1-v1_5".to_string(), 362 // "SHA-256".to_string(), 363 // 0, 364 // pkey.to_vec(), 365 // sig.to_vec(), 366 // data.to_vec(), 367 // ) 368 // .await 369 // } 370 // "rs384" => { 371 // async_validate( 372 // "RSASSA-PKCS1-v1_5".to_string(), 373 // "SHA-384".to_string(), 374 // 0, 375 // pkey.to_vec(), 376 // sig.to_vec(), 377 // data.to_vec(), 378 // ) 379 // .await 380 // } 381 // "rs512" => { 382 // async_validate( 383 // "RSASSA-PKCS1-v1_5".to_string(), 384 // "SHA-512".to_string(), 385 // 0, 386 // pkey.to_vec(), 387 // sig.to_vec(), 388 // data.to_vec(), 389 // ) 390 // .await 391 // } 392 SigningAlg::Es256 => { 393 async_validate( 394 "ECDSA".to_string(), 395 "SHA-256".to_string(), 396 0, 397 pkey.to_vec(), 398 sig.to_vec(), 399 data.to_vec(), 400 ) 401 .await 402 } 403 SigningAlg::Es384 => { 404 async_validate( 405 "ECDSA".to_string(), 406 "SHA-384".to_string(), 407 0, 408 pkey.to_vec(), 409 sig.to_vec(), 410 data.to_vec(), 411 ) 412 .await 413 } 414 SigningAlg::Es512 => { 415 async_validate( 416 "ECDSA".to_string(), 417 "SHA-512".to_string(), 418 0, 419 pkey.to_vec(), 420 sig.to_vec(), 421 data.to_vec(), 422 ) 423 .await 424 } 425 SigningAlg::Ed25519 => { 426 async_validate( 427 "ED25519".to_string(), 428 "SHA-512".to_string(), 429 0, 430 pkey.to_vec(), 431 sig.to_vec(), 432 data.to_vec(), 433 ) 434 .await 435 } 436 } 437 } 438 439 #[cfg(test)] 440 pub mod tests { 441 #![allow(clippy::unwrap_used)] 442 443 #[cfg(target_arch = "wasm32")] 444 use wasm_bindgen_test::*; 445 446 use super::*; 447 use crate::SigningAlg; 448 449 #[cfg(target_arch = "wasm32")] 450 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); 451 452 #[cfg_attr(not(target_arch = "wasm32"), test)] 453 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 454 #[wasm_bindgen_test] 455 async fn test_async_verify_rsa_pss() { 456 // PS signatures 457 let sig_bytes = include_bytes!("../../tests/fixtures/sig_ps256.data"); 458 let data_bytes = include_bytes!("../../tests/fixtures/data_ps256.data"); 459 let key_bytes = include_bytes!("../../tests/fixtures/key_ps256.data"); 460 461 let validated = validate_async(SigningAlg::Ps256, sig_bytes, data_bytes, key_bytes) 462 .await 463 .unwrap(); 464 465 assert_eq!(validated, true); 466 } 467 468 #[cfg_attr(not(target_arch = "wasm32"), test)] 469 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 470 #[wasm_bindgen_test] 471 async fn test_async_verify_ecdsa() { 472 // EC signatures 473 let sig_es384_bytes = include_bytes!("../../tests/fixtures/sig_es384.data"); 474 let data_es384_bytes = include_bytes!("../../tests/fixtures/data_es384.data"); 475 let key_es384_bytes = include_bytes!("../../tests/fixtures/key_es384.data"); 476 477 let mut validated = validate_async( 478 SigningAlg::Es384, 479 sig_es384_bytes, 480 data_es384_bytes, 481 key_es384_bytes, 482 ) 483 .await 484 .unwrap(); 485 486 assert_eq!(validated, true); 487 488 let sig_es512_bytes = include_bytes!("../../tests/fixtures/sig_es512.data"); 489 let data_es512_bytes = include_bytes!("../../tests/fixtures/data_es512.data"); 490 let key_es512_bytes = include_bytes!("../../tests/fixtures/key_es512.data"); 491 492 validated = validate_async( 493 SigningAlg::Es512, 494 sig_es512_bytes, 495 data_es512_bytes, 496 key_es512_bytes, 497 ) 498 .await 499 .unwrap(); 500 501 assert_eq!(validated, true); 502 503 let sig_es256_bytes = include_bytes!("../../tests/fixtures/sig_es256.data"); 504 let data_es256_bytes = include_bytes!("../../tests/fixtures/data_es256.data"); 505 let key_es256_bytes = include_bytes!("../../tests/fixtures/key_es256.data"); 506 507 let validated = validate_async( 508 SigningAlg::Es256, 509 sig_es256_bytes, 510 data_es256_bytes, 511 key_es256_bytes, 512 ) 513 .await 514 .unwrap(); 515 516 assert_eq!(validated, true); 517 } 518 519 #[cfg_attr(not(target_arch = "wasm32"), test)] 520 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] 521 #[wasm_bindgen_test] 522 #[ignore] 523 async fn test_async_verify_bad() { 524 let sig_bytes = include_bytes!("../../tests/fixtures/sig_ps256.data"); 525 let data_bytes = include_bytes!("../../tests/fixtures/data_ps256.data"); 526 let key_bytes = include_bytes!("../../tests/fixtures/key_ps256.data"); 527 528 let mut bad_bytes = data_bytes.to_vec(); 529 bad_bytes[0] = b'c'; 530 bad_bytes[1] = b'2'; 531 bad_bytes[2] = b'p'; 532 bad_bytes[3] = b'a'; 533 534 let validated = validate_async(SigningAlg::Ps256, sig_bytes, &bad_bytes, key_bytes) 535 .await 536 .unwrap(); 537 538 assert_eq!(validated, false); 539 } 540 }