test.rs (19018B)
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 thema 11 // specific language governing permissions and limitations under 12 // each license. 13 14 #![allow(clippy::unwrap_used)] 15 16 use std::path::PathBuf; 17 #[cfg(feature = "file_io")] 18 use std::{ 19 io::{Cursor, Read, Write}, 20 path::Path, 21 }; 22 23 use tempfile::TempDir; 24 25 use crate::{ 26 assertions::{labels, Action, Actions, Ingredient, ReviewRating, SchemaDotOrg, Thumbnail}, 27 claim::Claim, 28 salt::DefaultSalt, 29 store::Store, 30 RemoteSigner, Result, Signer, SigningAlg, 31 }; 32 #[cfg(feature = "file_io")] 33 use crate::{ 34 asset_io::CAIReadWrite, create_signer, hash_utils::Hasher, 35 jumbf_io::get_assetio_handler_from_path, 36 }; 37 #[cfg(feature = "openssl_sign")] 38 use crate::{ 39 openssl::{AsyncSignerAdapter, RsaSigner}, 40 signer::ConfigurableSigner, 41 }; 42 43 pub const TEST_SMALL_JPEG: &str = "earth_apollo17.jpg"; 44 45 pub const TEST_WEBP: &str = "mars.webp"; 46 47 pub const TEST_VC: &str = r#"{ 48 "@context": [ 49 "https://www.w3.org/2018/credentials/v1", 50 "http://schema.org" 51 ], 52 "type": [ 53 "VerifiableCredential", 54 "NPPACredential" 55 ], 56 "issuer": "https://nppa.org/", 57 "credentialSubject": { 58 "id": "did:nppa:eb1bb9934d9896a374c384521410c7f14", 59 "name": "Bob Ross", 60 "memberOf": "https://nppa.org/" 61 }, 62 "proof": { 63 "type": "RsaSignature2018", 64 "created": "2021-06-18T21:19:10Z", 65 "proofPurpose": "assertionMethod", 66 "verificationMethod": 67 "did:nppa:eb1bb9934d9896a374c384521410c7f14#_Qq0UL2Fq651Q0Fjd6TvnYE-faHiOpRlPVQcY_-tA4A", 68 "jws": "eyJhbGciOiJQUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19DJBMvvFAIC00nSGB6Tn0XKbbF9XrsaJZREWvR2aONYTQQxnyXirtXnlewJMBBn2h9hfcGZrvnC1b6PgWmukzFJ1IiH1dWgnDIS81BH-IxXnPkbuYDeySorc4QU9MJxdVkY5EL4HYbcIfwKj6X4LBQ2_ZHZIu1jdqLcRZqHcsDF5KKylKc1THn5VRWy5WhYg_gBnyWny8E6Qkrze53MR7OuAmmNJ1m1nN8SxDrG6a08L78J0-Fbas5OjAQz3c17GY8mVuDPOBIOVjMEghBlgl3nOi1ysxbRGhHLEK4s0KKbeRogZdgt1DkQxDFxxn41QWDw_mmMCjs9qxg0zcZzqEJw" 69 } 70 }"#; 71 72 /// creates a claim for testing 73 pub fn create_test_claim() -> Result<Claim> { 74 let mut claim = Claim::new("adobe unit test", Some("adobe")); 75 76 // add some data boxes 77 let _db_uri = claim.add_databox("text/plain", "this is a test".as_bytes().to_vec(), None)?; 78 let _db_uri_1 = 79 claim.add_databox("text/plain", "this is more text".as_bytes().to_vec(), None)?; 80 81 // add VC entry 82 let _hu = claim.add_verifiable_credential(TEST_VC)?; 83 84 // Add assertions. 85 let actions = Actions::new() 86 .add_action( 87 Action::new("c2pa.cropped") 88 .set_parameter( 89 "name".to_owned(), 90 r#"{ 91 "left": 0, 92 "right": 2000, 93 "top": 1000, 94 "bottom": 4000 95 }"#, 96 ) 97 .unwrap(), 98 ) 99 .add_action( 100 Action::new("c2pa.filtered") 101 .set_parameter("name".to_owned(), "gaussian blur")? 102 .set_when("2015-06-26T16:43:23+0200"), 103 ); 104 // add a binary thumbnail assertion ('deadbeefadbeadbe') 105 let some_binary_data: Vec<u8> = vec![ 106 0x0d, 0x0e, 0x0a, 0x0d, 0x0b, 0x0e, 0x0e, 0x0f, 0x0a, 0x0d, 0x0b, 0x0e, 0x0a, 0x0d, 0x0b, 107 0x0e, 108 ]; 109 110 // create a schema.org claim 111 let cr = r#"{ 112 "@context": "https://schema.org", 113 "@type": "ClaimReview", 114 "claimReviewed": "The world is flat", 115 "reviewRating": { 116 "@type": "Rating", 117 "ratingValue": "1", 118 "bestRating": "5", 119 "worstRating": "1", 120 "alternateName": "False" 121 } 122 }"#; 123 let claim_review = SchemaDotOrg::from_json_str(cr)?; 124 125 let thumbnail_claim = Thumbnail::new(labels::JPEG_CLAIM_THUMBNAIL, some_binary_data.clone()); 126 127 let thumbnail_ingred = Thumbnail::new(labels::JPEG_INGREDIENT_THUMBNAIL, some_binary_data); 128 129 claim.add_assertion(&actions)?; 130 claim.add_assertion(&claim_review)?; 131 claim.add_assertion(&thumbnail_claim)?; 132 133 let thumb_uri = claim.add_assertion_with_salt(&thumbnail_ingred, &DefaultSalt::default())?; 134 135 let review = ReviewRating::new( 136 "a 3rd party plugin was used", 137 Some("actions.unknownActionsPerformed".to_string()), 138 1, 139 ); 140 141 //let data_path = claim.add_ingredient_data("some data".as_bytes()); 142 let ingredient = Ingredient::new( 143 "image 1.jpg", 144 "image/jpeg", 145 "xmp.iid:7b57930e-2f23-47fc-affe-0400d70b738d", 146 Some("xmp.did:87d51599-286e-43b2-9478-88c79f49c347"), 147 ) 148 .set_thumbnail(Some(&thumb_uri)) 149 //.set_manifest_data(&data_path) 150 .add_review(review); 151 152 let ingredient2 = Ingredient::new( 153 "image 2.png", 154 "image/png", 155 "xmp.iid:7b57930e-2f23-47fc-affe-0400d70b738c", 156 Some("xmp.did:87d51599-286e-43b2-9478-88c79f49c346"), 157 ) 158 .set_thumbnail(Some(&thumb_uri)); 159 160 claim.add_assertion_with_salt(&ingredient, &DefaultSalt::default())?; 161 claim.add_assertion_with_salt(&ingredient2, &DefaultSalt::default())?; 162 163 Ok(claim) 164 } 165 166 /// Creates a store with an unsigned claim for testing 167 pub fn create_test_store() -> Result<Store> { 168 // Create claims store. 169 let mut store = Store::new(); 170 171 let claim = create_test_claim()?; 172 store.commit_claim(claim).unwrap(); 173 Ok(store) 174 } 175 176 /// returns a path to a file in the fixtures folder 177 pub fn fixture_path(file_name: &str) -> PathBuf { 178 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 179 path.push("tests/fixtures"); 180 path.push(file_name); 181 path 182 } 183 184 /// returns a path to a file in the temp_dir folder 185 // note, you must pass TempDir from the caller's context 186 pub fn temp_dir_path(temp_dir: &TempDir, file_name: &str) -> PathBuf { 187 let mut path = PathBuf::from(temp_dir.path()); 188 path.push(file_name); 189 path 190 } 191 192 // copies a fixture to a temp file and returns path to copy 193 pub fn temp_fixture_path(temp_dir: &TempDir, file_name: &str) -> PathBuf { 194 let fixture_src = fixture_path(file_name); 195 let fixture_copy = temp_dir_path(temp_dir, file_name); 196 std::fs::copy(fixture_src, &fixture_copy).unwrap(); 197 fixture_copy 198 } 199 200 /// Create a [`Signer`] instance that can be used for testing purposes. 201 /// 202 /// This is a suitable default for use when you need a [`Signer`], but 203 /// don't care what the format is. 204 /// 205 /// # Returns 206 /// 207 /// Returns a boxed [`Signer`] instance. 208 /// 209 /// # Panics 210 /// 211 /// Can panic if the certs cannot be read. (This function should only 212 /// be used as part of testing infrastructure.) 213 #[cfg(feature = "file_io")] 214 pub fn temp_signer_file() -> RsaSigner { 215 #![allow(clippy::expect_used)] 216 let mut sign_cert_path = fixture_path("certs"); 217 sign_cert_path.push("ps256"); 218 sign_cert_path.set_extension("pub"); 219 220 let mut pem_key_path = fixture_path("certs"); 221 pem_key_path.push("ps256"); 222 pem_key_path.set_extension("pem"); 223 224 RsaSigner::from_files(&sign_cert_path, &pem_key_path, SigningAlg::Ps256, None) 225 .expect("get_temp_signer") 226 } 227 228 /// Utility to create a test file with a placeholder for a manifest 229 #[cfg(feature = "file_io")] 230 pub fn write_jpeg_placeholder_file( 231 placeholder: &[u8], 232 input: &Path, 233 output_file: &mut dyn CAIReadWrite, 234 mut hasher: Option<&mut Hasher>, 235 ) -> Result<usize> { 236 // get where we will put the data 237 let mut f = std::fs::File::open(input).unwrap(); 238 let jpeg_io = get_assetio_handler_from_path(input).unwrap(); 239 let box_mapper = jpeg_io.asset_box_hash_ref().unwrap(); 240 let boxes = box_mapper.get_box_map(&mut f).unwrap(); 241 let sof = boxes.iter().find(|b| b.names[0] == "SOF0").unwrap(); 242 243 // build new asset with hole for new manifest 244 let outbuf = Vec::new(); 245 let mut out_stream = Cursor::new(outbuf); 246 let mut input_file = std::fs::File::open(input).unwrap(); 247 248 // write before 249 let mut before = vec![0u8; sof.range_start]; 250 input_file.read_exact(before.as_mut_slice()).unwrap(); 251 if let Some(hasher) = hasher.as_deref_mut() { 252 hasher.update(&before); 253 } 254 out_stream.write_all(&before).unwrap(); 255 256 // write placeholder 257 out_stream.write_all(placeholder).unwrap(); 258 259 // write bytes after 260 let mut after_buf = Vec::new(); 261 input_file.read_to_end(&mut after_buf).unwrap(); 262 if let Some(hasher) = hasher { 263 hasher.update(&after_buf); 264 } 265 out_stream.write_all(&after_buf).unwrap(); 266 267 // save to output file 268 output_file.write_all(&out_stream.into_inner()).unwrap(); 269 270 Ok(sof.range_start) 271 } 272 273 pub(crate) struct TestGoodSigner {} 274 impl crate::Signer for TestGoodSigner { 275 fn sign(&self, _data: &[u8]) -> Result<Vec<u8>> { 276 Ok(b"not a valid signature".to_vec()) 277 } 278 279 fn alg(&self) -> SigningAlg { 280 SigningAlg::Ps256 281 } 282 283 fn certs(&self) -> Result<Vec<Vec<u8>>> { 284 Ok(Vec::new()) 285 } 286 287 fn reserve_size(&self) -> usize { 288 1024 289 } 290 291 fn send_timestamp_request(&self, _message: &[u8]) -> Option<crate::error::Result<Vec<u8>>> { 292 Some(Ok(Vec::new())) 293 } 294 } 295 296 pub(crate) struct AsyncTestGoodSigner {} 297 298 #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] 299 #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] 300 impl crate::AsyncSigner for AsyncTestGoodSigner { 301 async fn sign(&self, _data: Vec<u8>) -> Result<Vec<u8>> { 302 Ok(b"not a valid signature".to_vec()) 303 } 304 305 fn alg(&self) -> SigningAlg { 306 SigningAlg::Ps256 307 } 308 309 fn certs(&self) -> Result<Vec<Vec<u8>>> { 310 Ok(Vec::new()) 311 } 312 313 fn reserve_size(&self) -> usize { 314 1024 315 } 316 317 async fn send_timestamp_request( 318 &self, 319 _message: &[u8], 320 ) -> Option<crate::error::Result<Vec<u8>>> { 321 Some(Ok(Vec::new())) 322 } 323 } 324 325 /// Create a [`Signer`] instance that can be used for testing purposes using ps256 alg. 326 /// 327 /// # Returns 328 /// 329 /// Returns a boxed [`Signer`] instance. 330 #[cfg(test)] 331 pub(crate) fn temp_signer() -> Box<dyn Signer> { 332 #[cfg(feature = "openssl_sign")] 333 { 334 #![allow(clippy::expect_used)] 335 let sign_cert = include_bytes!("../../tests/fixtures/certs/ps256.pub").to_vec(); 336 let pem_key = include_bytes!("../../tests/fixtures/certs/ps256.pem").to_vec(); 337 338 let signer = 339 RsaSigner::from_signcert_and_pkey(&sign_cert, &pem_key, SigningAlg::Ps256, None) 340 .expect("get_temp_signer"); 341 342 Box::new(signer) 343 } 344 345 // todo: the will be a RustTLS signer shortly 346 #[cfg(not(feature = "openssl_sign"))] 347 { 348 Box::new(TestGoodSigner {}) 349 } 350 } 351 352 #[cfg(any(target_arch = "wasm32", feature = "openssl_sign"))] 353 pub fn temp_async_signer() -> Box<dyn crate::signer::AsyncSigner> { 354 #[cfg(feature = "openssl_sign")] 355 { 356 Box::new(AsyncSignerAdapter::new(SigningAlg::Es256)) 357 } 358 359 #[cfg(target_arch = "wasm32")] 360 { 361 let sign_cert = include_str!("../../tests/fixtures/certs/es256.pub"); 362 let pem_key = include_str!("../../tests/fixtures/certs/es256.pem"); 363 let signer = WebCryptoSigner::new("es256", sign_cert, pem_key); 364 Box::new(signer) 365 } 366 } 367 368 /// Create a [`Signer`] instance for a specific algorithm that can be used for testing purposes. 369 /// 370 /// # Returns 371 /// 372 /// Returns a boxed [`Signer`] instance. 373 /// 374 /// # Panics 375 /// 376 /// Can panic if the certs cannot be read. (This function should only 377 /// be used as part of testing infrastructure.) 378 #[cfg(feature = "file_io")] 379 pub fn temp_signer_with_alg(alg: SigningAlg) -> Box<dyn Signer> { 380 #![allow(clippy::expect_used)] 381 // sign and embed into the target file 382 let mut sign_cert_path = fixture_path("certs"); 383 sign_cert_path.push(alg.to_string()); 384 sign_cert_path.set_extension("pub"); 385 386 let mut pem_key_path = fixture_path("certs"); 387 pem_key_path.push(alg.to_string()); 388 pem_key_path.set_extension("pem"); 389 390 create_signer::from_files(sign_cert_path.clone(), pem_key_path, alg, None) 391 .expect("get_temp_signer_with_alg") 392 } 393 394 struct TempRemoteSigner {} 395 396 #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] 397 #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] 398 impl crate::signer::RemoteSigner for TempRemoteSigner { 399 async fn sign_remote(&self, claim_bytes: &[u8]) -> crate::error::Result<Vec<u8>> { 400 #[cfg(feature = "openssl_sign")] 401 { 402 let signer = 403 crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); 404 405 // this would happen on some remote server 406 crate::cose_sign::cose_sign_async(&signer, claim_bytes, self.reserve_size()).await 407 } 408 #[cfg(not(feature = "openssl_sign"))] 409 { 410 use std::io::{Seek, Write}; 411 412 let mut sign_bytes = std::io::Cursor::new(vec![0u8; self.reserve_size()]); 413 414 sign_bytes.rewind()?; 415 sign_bytes.write_all(claim_bytes)?; 416 417 // fake sig 418 Ok(sign_bytes.into_inner()) 419 } 420 } 421 422 fn reserve_size(&self) -> usize { 423 10000 424 } 425 } 426 427 #[cfg(target_arch = "wasm32")] 428 struct WebCryptoSigner { 429 signing_alg: SigningAlg, 430 signing_alg_name: String, 431 certs: Vec<Vec<u8>>, 432 key: Vec<u8>, 433 } 434 435 #[cfg(target_arch = "wasm32")] 436 impl WebCryptoSigner { 437 pub fn new(alg: &str, cert: &str, key: &str) -> Self { 438 static START_CERTIFICATE: &str = "-----BEGIN CERTIFICATE-----"; 439 static END_CERTIFICATE: &str = "-----END CERTIFICATE-----"; 440 static START_KEY: &str = "-----BEGIN PRIVATE KEY-----"; 441 static END_KEY: &str = "-----END PRIVATE KEY-----"; 442 443 let mut name = alg.to_owned().to_uppercase(); 444 name.insert(2, '-'); 445 446 let key = key 447 .replace("\n", "") 448 .replace(START_KEY, "") 449 .replace(END_KEY, ""); 450 let key = crate::utils::base64::decode(&key).unwrap(); 451 452 let certs = cert 453 .replace("\n", "") 454 .replace(START_CERTIFICATE, "") 455 .split(END_CERTIFICATE) 456 .map(|x| crate::utils::base64::decode(x).unwrap()) 457 .collect(); 458 459 Self { 460 signing_alg: alg.parse().unwrap(), 461 signing_alg_name: name, 462 certs, 463 key, 464 } 465 } 466 } 467 468 #[cfg(target_arch = "wasm32")] 469 #[async_trait::async_trait(?Send)] 470 impl crate::signer::AsyncSigner for WebCryptoSigner { 471 fn alg(&self) -> SigningAlg { 472 self.signing_alg 473 } 474 475 fn certs(&self) -> Result<Vec<Vec<u8>>> { 476 Ok(self.certs.clone()) 477 } 478 479 async fn sign(&self, claim_bytes: Vec<u8>) -> crate::error::Result<Vec<u8>> { 480 use js_sys::{Array, Object, Reflect, Uint8Array}; 481 use wasm_bindgen_futures::JsFuture; 482 use web_sys::CryptoKey; 483 484 use crate::wasm::context::WindowOrWorker; 485 let context = WindowOrWorker::new().unwrap(); 486 let crypto = context.subtle_crypto().unwrap(); 487 488 let mut data = claim_bytes.clone(); 489 let promise = crypto 490 .digest_with_str_and_u8_array("SHA-256", &mut data) 491 .unwrap(); 492 let result = JsFuture::from(promise).await.unwrap(); 493 let mut digest = Uint8Array::new(&result).to_vec(); 494 495 let key = Uint8Array::new_with_length(self.key.len() as u32); 496 key.copy_from(&self.key); 497 let usages = Array::new(); 498 usages.push(&"sign".into()); 499 let alg = Object::new(); 500 Reflect::set(&alg, &"name".into(), &"ECDSA".into()).unwrap(); 501 Reflect::set(&alg, &"namedCurve".into(), &"P-256".into()).unwrap(); 502 503 let promise = crypto 504 .import_key_with_object("pkcs8", &key, &alg, true, &usages) 505 .unwrap(); 506 let key: CryptoKey = JsFuture::from(promise).await.unwrap().into(); 507 508 let alg = Object::new(); 509 Reflect::set(&alg, &"name".into(), &"ECDSA".into()).unwrap(); 510 Reflect::set(&alg, &"hash".into(), &"SHA-256".into()).unwrap(); 511 let promise = crypto 512 .sign_with_object_and_u8_array(&alg, &key, &mut digest) 513 .unwrap(); 514 let result = JsFuture::from(promise).await.unwrap(); 515 Ok(Uint8Array::new(&result).to_vec()) 516 } 517 518 fn reserve_size(&self) -> usize { 519 10000 520 } 521 522 async fn send_timestamp_request(&self, _: &[u8]) -> Option<Result<Vec<u8>>> { 523 None 524 } 525 } 526 527 /// Create a [`RemoteSigner`] instance that can be used for testing purposes. 528 /// 529 /// # Returns 530 /// 531 /// Returns a boxed [`RemoteSigner`] instance. 532 pub fn temp_remote_signer() -> Box<dyn RemoteSigner> { 533 Box::new(TempRemoteSigner {}) 534 } 535 536 /// Create an AsyncSigner that acts as a RemoteSigner 537 struct TempAsyncRemoteSigner { 538 signer: TempRemoteSigner, 539 } 540 541 #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] 542 #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] 543 impl crate::signer::AsyncSigner for TempAsyncRemoteSigner { 544 // this will not be called but requires an implementation 545 async fn sign(&self, claim_bytes: Vec<u8>) -> Result<Vec<u8>> { 546 #[cfg(feature = "openssl_sign")] 547 { 548 let signer = 549 crate::openssl::temp_signer_async::AsyncSignerAdapter::new(SigningAlg::Ps256); 550 551 // this would happen on some remote server 552 crate::cose_sign::cose_sign_async(&signer, &claim_bytes, self.reserve_size()).await 553 } 554 #[cfg(not(feature = "openssl_sign"))] 555 { 556 use std::io::{Seek, Write}; 557 558 let mut sign_bytes = std::io::Cursor::new(vec![0u8; self.reserve_size()]); 559 560 sign_bytes.rewind()?; 561 sign_bytes.write_all(&claim_bytes)?; 562 563 // fake sig 564 Ok(sign_bytes.into_inner()) 565 } 566 } 567 568 // signer will return a COSE structure 569 fn direct_cose_handling(&self) -> bool { 570 true 571 } 572 573 fn alg(&self) -> SigningAlg { 574 SigningAlg::Ps256 575 } 576 577 fn certs(&self) -> Result<Vec<Vec<u8>>> { 578 Ok(Vec::new()) 579 } 580 581 fn reserve_size(&self) -> usize { 582 10000 583 } 584 585 async fn send_timestamp_request( 586 &self, 587 _message: &[u8], 588 ) -> Option<crate::error::Result<Vec<u8>>> { 589 Some(Ok(Vec::new())) 590 } 591 } 592 593 /// Create a [`AsyncSigner`] that does it's own COSE handling for testing. 594 /// 595 /// # Returns 596 /// 597 /// Returns a boxed [`RemoteSigner`] instance. 598 pub fn temp_async_remote_signer() -> Box<dyn crate::signer::AsyncSigner> { 599 Box::new(TempAsyncRemoteSigner { 600 signer: TempRemoteSigner {}, 601 }) 602 } 603 604 #[test] 605 fn test_create_test_store() { 606 #[allow(clippy::expect_used)] 607 let store = create_test_store().expect("create test store"); 608 609 assert_eq!(store.claims().len(), 1); 610 }