c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

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 }