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

commit ca88432afa984037b9105bbf24923da87077801e
parent 9ccdb2be5a9ba8585072f12e5528fa5e5d2e8aad
Author: mauricefisher64 <92736594+mauricefisher64@users.noreply.github.com>
Date:   Fri,  8 Mar 2024 10:07:30 -0500

Trust support (#415)

* Push to keep things moving

* Add ManifestStoreOptions to async bytes functions
ManifestStore::from_bytes_async()
ManifestStore::from_manifest_and_asset_bytes_async
Also some fmt/clippy fixes
Builds but unit tests do not run yet

* WASM build

* WASM fix

* Expose ManifestStoreOptions

* Fix missing anchors

* Fix misspelled var

* remove check that may be wrong

* remove issuer check

* switch to x509_parser because x509-certificate has issues

* Add support DER encoded EC signatures when validating trust lists

* Update Adobe trust root

* Small fixes for Wasm building (#354)

* Remove trust anchors from build, only leave the test certs

* Add ManifestStore::from_file_with_options

* Minor fixes

* Trust fixes

* Wasm compiler fixes

* Restored more tests

* EC sig fix

* Add ARW & NEF support

* Fix for wrong sig algoritm

* Support for allowed list

* Add Wasm compilation fixes for trust_support branch (#401)

* Fix wasm trust handler

* Fix reading lines inside PEM certificate blocks

* Wire in settings support
Remove feature flags covered by settings

* Build fixes
simplified check_trust

* Build fixes

* Test fixes

* Build fix

* another try to fix build issue

* Build fixes
Cleanup

* Fix preflight check

* Remove wrong cert from test anchors bundle

* Remove trust feature flag

* simplify manifest_store changes

* revert unintended change

* Remove ManifestStore changes
Update integration example to use loading settings from JSON

* Change feature flag setting

* Change more feature flags

* enhance trust pass through to support configuration of allowed_list and EKU config
Check signing cert before signing so that it gets checked independent of whether we validate after signing

* A little code cleanup

* Make sure ED25519 tests are disabled for WASM

* Remove Ed25519 from trust list until we can get it working in WASM

* Enable Ed25519 support

* Added debug logging for ed25519

* wasm build fix

* fix broken check

* Fixes for wasm

* Fix build failure

* Fix comment

* Code cleanup

* OCSP fix

---------

Co-authored-by: Gavin Peacock <gpeacock@adobe.com>
Co-authored-by: Dave Kozma <dkozma@adobe.com>
Diffstat:
M.gitignore | 1+
MREADME.md | 1-
Msdk/Cargo.toml | 4++--
Msdk/src/claim.rs | 17++++++++++++++---
Msdk/src/cose_sign.rs | 40+++++++++++++++++++++++++++++++++++++---
Msdk/src/cose_validator.rs | 293+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msdk/src/error.rs | 9+++++++++
Msdk/src/lib.rs | 3++-
Msdk/src/ocsp_utils.rs | 51++++++++++++++++++++++++++++++++++-----------------
Msdk/src/openssl/ec_validator.rs | 45+++++++++++++++++++++++++--------------------
Msdk/src/openssl/mod.rs | 23+++++++++++++++--------
Asdk/src/openssl/openssl_trust_handler.rs | 436+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msdk/src/settings.rs | 133++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Msdk/src/store.rs | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Asdk/src/trust_handler.rs | 202+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msdk/src/utils/hash_utils.rs | 8+++-----
Msdk/src/wasm/mod.rs | 4++++
Msdk/src/wasm/webcrypto_validator.rs | 126++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Asdk/src/wasm/webpki_trust_handler.rs | 657+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msdk/tests/fixtures/certs/ed25519.pub | 15+--------------
Msdk/tests/fixtures/certs/es256.pub | 15---------------
Msdk/tests/fixtures/certs/es384.pub | 16----------------
Msdk/tests/fixtures/certs/es512.pub | 18------------------
Msdk/tests/fixtures/certs/ps256.pub | 38--------------------------------------
Msdk/tests/fixtures/certs/ps384.pub | 38+-------------------------------------
Msdk/tests/fixtures/certs/ps512.pub | 37-------------------------------------
Asdk/tests/fixtures/certs/trust/allowed_list.hash | 7+++++++
Asdk/tests/fixtures/certs/trust/allowed_list.pem | 187+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asdk/tests/fixtures/certs/trust/store.cfg | 12++++++++++++
Asdk/tests/fixtures/certs/trust/test_cert_root_bundle.pem | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msdk/tests/fixtures/cloud_manifest.c2pa | 0
Msdk/tests/fixtures/video1.mp4 | 0
Msdk/tests/integration.rs | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
33 files changed, 2463 insertions(+), 401 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -6,6 +6,7 @@ Cargo.lock .DS_Store .idea +.cargo/config.toml .vscode diff --git a/README.md b/README.md @@ -89,7 +89,6 @@ The Rust library crate provides: * `no_interleaved_io` forces fully-synchronous I/O; otherwise, the library uses threaded I/O for some operations to improve performance. * `fetch_remote_manifests` enables the verification step to retrieve externally referenced manifest stores. External manifests are only fetched if there is no embedded manifest store and no locally adjacent .c2pa manifest store file of the same name. * `json_schema` is used by `make schema` to produce a JSON schema document that represents the `ManifestStore` data structures. -* `fetch_ocsp_response` if the feature is enabled, during manifest validation if an OCSP response is not present in the manifest we will attempt a network call to fetch it. OCSP is used to check the revocation status of the manifest signing certificate. * `psxxx_ocsp_stapling_experimental` this is an demonstration feature that will attempt to fetch the OCSP data from the OCSP responders listed in the manifest signing certificate. The response becomes part of the manifest and is used to prove the certificate was not revoked at the time of signing. This is only implemented for PS256, PS384 and PS512 signatures and is intended as a demonstration. diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml @@ -27,7 +27,6 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] add_thumbnails = ["image"] -fetch_ocsp_response = [] psxxx_ocsp_stapling_experimental = [] file_io = ["openssl_sign"] serialize_thumbnails = [] @@ -110,7 +109,7 @@ thiserror = "1.0.40" treeline = "0.1.0" url = "2.2.2" uuid = { version = "1.3.1", features = ["serde", "v4", "wasm-bindgen"] } -x509-parser = "0.15.0" +x509-parser = "0.15.1" x509-certificate = "0.19.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] @@ -125,6 +124,7 @@ xmp_toolkit = { version = "1.7.1", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] console_log = { version = "1.0.0", features = ["color"] } +ed25519-dalek = "2.1.1" getrandom = { version = "0.2.7", features = ["js"] } # We need to use the `inaccurate` flag here to ensure usage of the JavaScript Date API # to handle certificate timestamp checking correctly. diff --git a/sdk/src/claim.rs b/sdk/src/claim.rs @@ -45,6 +45,7 @@ use crate::{ jumbf_io::{get_assetio_handler, get_assetio_handler_from_path}, salt::{DefaultSalt, SaltGenerator, NO_SALT}, status_tracker::{log_item, OneShotStatusTracker, StatusTracker}, + trust_handler::TrustHandlerConfig, utils::{ base64, hash_utils::{hash_by_alg, vec_compare, verify_by_alg}, @@ -1018,10 +1019,11 @@ impl Claim { /// Verify claim signature, assertion store and asset hashes /// claim - claim to be verified /// asset_bytes - reference to bytes of the asset - pub async fn verify_claim_async<'a>( + pub(crate) async fn verify_claim_async<'a>( claim: &Claim, asset_data: &mut ClaimAssetData<'_>, is_provenance: bool, + th: &dyn TrustHandlerConfig, validation_log: &mut impl StatusTracker, ) -> Result<()> { // Parse COSE signed data (signature) and validate it. @@ -1055,6 +1057,7 @@ impl Claim { claim_data, additional_bytes, !is_provenance, + th, validation_log, ) .await; @@ -1064,10 +1067,11 @@ impl Claim { /// Verify claim signature, assertion store and asset hashes /// claim - claim to be verified /// asset_bytes - reference to bytes of the asset - pub fn verify_claim( + pub(crate) fn verify_claim( claim: &Claim, asset_data: &mut ClaimAssetData<'_>, is_provenance: bool, + th: &dyn TrustHandlerConfig, validation_log: &mut impl StatusTracker, ) -> Result<()> { // Parse COSE signed data (signature) and validate it. @@ -1096,7 +1100,14 @@ impl Claim { return Err(Error::ClaimDecoding); }; - let verified = verify_cose(sig, data, &additional_bytes, !is_provenance, validation_log); + let verified = verify_cose( + sig, + data, + &additional_bytes, + !is_provenance, + th, + validation_log, + ); Claim::verify_internal(claim, asset_data, is_provenance, verified, validation_log) } diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs @@ -15,6 +15,8 @@ #![deny(missing_docs)] +use std::io::Cursor; + use async_generic::async_generic; use ciborium::value::Value; use coset::{ @@ -25,11 +27,13 @@ use coset::{ use crate::{ claim::Claim, - cose_validator::verify_cose, + cose_validator::{check_cert, verify_cose}, + settings::get_settings_value, status_tracker::OneShotStatusTracker, time_stamp::{ cose_timestamp_countersign, cose_timestamp_countersign_async, make_cose_timestamp, }, + trust_handler::TrustHandlerConfig, AsyncSigner, Error, Result, Signer, SigningAlg, }; @@ -70,8 +74,16 @@ pub fn sign_claim(claim_bytes: &[u8], signer: &dyn Signer, box_size: usize) -> R Ok(signed_bytes) => { // Sanity check: Ensure that this signature is valid. let mut cose_log = OneShotStatusTracker::new(); - - match verify_cose(&signed_bytes, claim_bytes, b"", false, &mut cose_log) { + let passthrough_tb = crate::trust_handler::TrustPassThrough::new(); + + match verify_cose( + &signed_bytes, + claim_bytes, + b"", + true, + &passthrough_tb, + &mut cose_log, + ) { Ok(r) => { if !r.validated { Err(Error::CoseSignature) @@ -86,6 +98,20 @@ pub fn sign_claim(claim_bytes: &[u8], signer: &dyn Signer, box_size: usize) -> R } } +fn signing_cert_valid(signing_cert: &[u8]) -> Result<()> { + // make sure signer certs are valid + let mut cose_log = OneShotStatusTracker::default(); + let mut passthrough_tb = crate::trust_handler::TrustPassThrough::new(); + + // allow user EKUs through this check if configured + if let Ok(Some(trust_config)) = get_settings_value::<Option<String>>("trust.trust_config") { + let mut reader = Cursor::new(trust_config.as_bytes()); + passthrough_tb.load_configuration(&mut reader)?; + } + + check_cert(signing_cert, &passthrough_tb, &mut cose_log, None) +} + /// Returns signed Cose_Sign1 bytes for `data`. /// The Cose_Sign1 will be signed with the algorithm from [`Signer`]. #[async_generic(async_signature( @@ -116,6 +142,14 @@ pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Re string. */ + // make sure the signing cert is valid + let certs = signer.certs()?; + if let Some(signing_cert) = certs.first() { + signing_cert_valid(signing_cert)?; + } else { + return Err(Error::CoseNoCerts); + } + let alg = signer.alg(); // build complete header diff --git a/sdk/src/cose_validator.rs b/sdk/src/cose_validator.rs @@ -12,6 +12,7 @@ // each license. use asn1_rs::{Any, Class, Header, Tag}; +use async_generic::async_generic; use ciborium::value::Value; use conv::*; use coset::{ @@ -25,40 +26,43 @@ use x509_parser::{ prelude::*, }; +#[cfg(feature = "openssl")] +use crate::openssl::verify_trust; #[cfg(not(target_arch = "wasm32"))] use crate::validator::{get_validator, CoseValidator}; -#[cfg(target_arch = "wasm32")] -use crate::wasm::webcrypto_validator::validate_async; use crate::{ asn1::rfc3161::TstInfo, error::{Error, Result}, ocsp_utils::{check_ocsp_response, OcspData}, + settings::get_settings_value, status_tracker::{log_item, StatusTracker}, time_stamp::gt_to_datetime, + trust_handler::{has_allowed_oid, TrustHandlerConfig}, validation_status, validator::ValidationInfo, SigningAlg, }; +#[cfg(target_arch = "wasm32")] +use crate::{ + wasm::webcrypto_validator::validate_async, wasm::webpki_trust_handler::verify_trust_async, +}; -const RSA_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .1); -const EC_PUBLICKEY_OID: Oid<'static> = oid!(1.2.840 .10045 .2 .1); -const ECDSA_WITH_SHA256_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .2); -const ECDSA_WITH_SHA384_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .3); -const ECDSA_WITH_SHA512_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .4); -const RSASSA_PSS_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .10); -const SHA256_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .11); -const SHA384_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .12); -const SHA512_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .13); -const ED25519_OID: Oid<'static> = oid!(1.3.101 .112); -const SHA256_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .1); -const SHA384_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .2); -const SHA512_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .3); -const SECP521R1_OID: Oid<'static> = oid!(1.3.132 .0 .35); -const SECP384R1_OID: Oid<'static> = oid!(1.3.132 .0 .34); -const PRIME256V1_OID: Oid<'static> = oid!(1.2.840 .10045 .3 .1 .7); - -const DOCUMENT_SIGNING_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .36); -const MS_C2PA_SIGNING_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .311 .76 .59 .1 .9); +pub(crate) const RSA_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .1); +pub(crate) const EC_PUBLICKEY_OID: Oid<'static> = oid!(1.2.840 .10045 .2 .1); +pub(crate) const ECDSA_WITH_SHA256_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .2); +pub(crate) const ECDSA_WITH_SHA384_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .3); +pub(crate) const ECDSA_WITH_SHA512_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .4); +pub(crate) const RSASSA_PSS_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .10); +pub(crate) const SHA256_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .11); +pub(crate) const SHA384_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .12); +pub(crate) const SHA512_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .13); +pub(crate) const ED25519_OID: Oid<'static> = oid!(1.3.101 .112); +pub(crate) const SHA256_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .1); +pub(crate) const SHA384_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .2); +pub(crate) const SHA512_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .3); +pub(crate) const SECP521R1_OID: Oid<'static> = oid!(1.3.132 .0 .35); +pub(crate) const SECP384R1_OID: Oid<'static> = oid!(1.3.132 .0 .34); +pub(crate) const PRIME256V1_OID: Oid<'static> = oid!(1.2.840 .10045 .3 .1 .7); /********************** Supported Validators *************************************** RS256 RSASSA-PKCS1-v1_5 using SHA-256 - not recommended @@ -100,12 +104,9 @@ fn get_cose_sign1( } } -fn has_oid(eku: &ExtendedKeyUsage, oid_val: &Oid) -> bool { - eku.other.iter().any(|v| v == oid_val) -} - -fn check_cert( +pub(crate) fn check_cert( ca_der_bytes: &[u8], + th: &dyn TrustHandlerConfig, validation_log: &mut impl StatusTracker, _tst_info_opt: Option<&TstInfo>, ) -> Result<()> { @@ -138,7 +139,7 @@ fn check_cert( // check for cert expiration if let Some(tst_info) = _tst_info_opt { - // was there a time stamp associtation with this signature, is verify against that time + // was there a time stamp association with this signature, is verify against that time let signing_time = gt_to_datetime(tst_info.gen_time.clone()); if !signcert.validity().is_valid_at( x509_parser::time::ASN1Time::from_timestamp(signing_time.timestamp()) @@ -360,6 +361,20 @@ fn check_cert( return Err(Error::CoseInvalidCert); } + // unique ids are not allowed + if signcert.issuer_uid.is_some() || signcert.subject_uid.is_some() { + let log_item = log_item!( + "Cose_Sign1", + "certificate issuer/subject unique ids are not allowed", + "check_cert_alg" + ) + .error(Error::CoseInvalidCert) + .validation_status(validation_status::SIGNING_CREDENTIAL_INVALID); + validation_log.log_silent(log_item); + + return Err(Error::CoseInvalidCert); + } + let mut aki_good = false; let mut ski_good = false; let mut key_usage_good = false; @@ -382,12 +397,7 @@ fn check_cert( return Err(Error::CoseInvalidCert); } - if !(eku.email_protection - || eku.ocsp_signing - || eku.time_stamping - || has_oid(eku, &DOCUMENT_SIGNING_OID) - || has_oid(eku, &MS_C2PA_SIGNING_OID)) - { + if has_allowed_oid(eku, &th.get_auxillary_ekus()).is_none() { let log_item = log_item!( "Cose_Sign1", "certificate missing required EKU", @@ -407,7 +417,7 @@ fn check_cert( | eku.code_signing | eku.email_protection | eku.server_auth - | has_oid(eku, &DOCUMENT_SIGNING_OID))) + | !eku.other.is_empty())) { let log_item = log_item!( "Cose_Sign1", @@ -680,6 +690,7 @@ fn get_ocsp_der(sign1: &coset::CoseSign1) -> Option<Vec<u8>> { pub(crate) fn check_ocsp_status( cose_bytes: &[u8], data: &[u8], + th: &dyn TrustHandlerConfig, validation_log: &mut impl StatusTracker, ) -> Result<OcspData> { let sign1 = get_cose_sign1(cose_bytes, data, validation_log)?; @@ -700,43 +711,46 @@ pub(crate) fn check_ocsp_status( // if we get a valid response validate the certs if ocsp_data.revoked_at.is_none() { if let Some(ocsp_certs) = &ocsp_data.ocsp_certs { - check_cert(&ocsp_certs[0], validation_log, None)?; + check_cert(&ocsp_certs[0], th, validation_log, None)?; } } result = Ok(ocsp_data); } } } else { - // fetch OCSP response if feature "fetch_ocsp_response" - // only support fetching with the op - #[cfg(feature = "fetch_ocsp_response")] + #[cfg(not(target_arch = "wasm32"))] { - // get the cert chain - let certs = get_sign_certs(&sign1)?; - - if let Some(ocsp_der) = crate::ocsp_utils::fetch_ocsp_response(&certs) { - // fetch_ocsp_response(&certs) { - let ocsp_response_der = ocsp_der; - - let signing_time = match &time_stamp_info { - Ok(tst_info) => { - let signing_time = gt_to_datetime(tst_info.gen_time.clone()); - Some(signing_time) - } - Err(_) => None, - }; - - // Check the OCSP response, only use if not malformed. Revocation errors are reported in the validation log - if let Ok(ocsp_data) = - check_ocsp_response(&ocsp_response_der, signing_time, validation_log) - { - // if we get a valid response validate the certs - if ocsp_data.revoked_at.is_none() { - if let Some(ocsp_certs) = &ocsp_data.ocsp_certs { - check_cert(&ocsp_certs[0], validation_log, None)?; + // only support fetching with the enabled + if let Ok(ocsp_fetch) = get_settings_value::<bool>("verify.ocsp_fetch") { + if ocsp_fetch { + // get the cert chain + let certs = get_sign_certs(&sign1)?; + + if let Some(ocsp_der) = crate::ocsp_utils::fetch_ocsp_response(&certs) { + // fetch_ocsp_response(&certs) { + let ocsp_response_der = ocsp_der; + + let signing_time = match &time_stamp_info { + Ok(tst_info) => { + let signing_time = gt_to_datetime(tst_info.gen_time.clone()); + Some(signing_time) + } + Err(_) => None, + }; + + // Check the OCSP response, only use if not malformed. Revocation errors are reported in the validation log + if let Ok(ocsp_data) = + check_ocsp_response(&ocsp_response_der, signing_time, validation_log) + { + // if we get a valid response validate the certs + if ocsp_data.revoked_at.is_none() { + if let Some(ocsp_certs) = &ocsp_data.ocsp_certs { + check_cert(&ocsp_certs[0], th, validation_log, None)?; + } + } + result = Ok(ocsp_data); } } - result = Ok(ocsp_data); } } } @@ -748,7 +762,7 @@ pub(crate) fn check_ocsp_status( // internal util function to dump the cert chain in PEM format #[allow(unused_variables)] fn dump_cert_chain(certs: &[Vec<u8>], output_path: Option<&std::path::Path>) -> Result<Vec<u8>> { - #[cfg(feature = "openssl_sign")] + #[cfg(feature = "openssl")] { let mut out_buf: Vec<u8> = Vec::new(); @@ -766,7 +780,7 @@ fn dump_cert_chain(certs: &[Vec<u8>], output_path: Option<&std::path::Path>) -> Ok(out_buf) } - #[cfg(not(feature = "openssl_sign"))] + #[cfg(not(feature = "openssl"))] { let out_buf: Vec<u8> = Vec::new(); Ok(out_buf) @@ -814,6 +828,86 @@ fn get_timestamp_info(sign1: &coset::CoseSign1, data: &[u8]) -> Result<TstInfo> Err(Error::NotFound) } +#[async_generic(async_signature( th: &dyn TrustHandlerConfig, chain_der: &[Vec<u8>], cert_der: &[u8], validation_log: &mut impl StatusTracker))] +#[allow(unused)] +fn check_trust( + th: &dyn TrustHandlerConfig, + chain_der: &[Vec<u8>], + cert_der: &[u8], + validation_log: &mut impl StatusTracker, +) -> Result<()> { + // just return is trust checks are disabled or misconfigured + match get_settings_value::<bool>("verify.verify_trust") { + Ok(verify_trust) => { + if !verify_trust { + return Ok(()); + } + } + Err(e) => return Err(e), + } + + // is the certificate trusted + + let verify_result: Result<bool> = if _sync { + #[cfg(not(feature = "openssl"))] + { + Err(Error::NotImplemented( + "no trust handler for this feature".to_string(), + )) + } + + #[cfg(feature = "openssl")] + { + verify_trust(th, chain_der, cert_der) + } + } else { + #[cfg(target_arch = "wasm32")] + { + verify_trust_async(th, chain_der, cert_der).await + } + + #[cfg(feature = "openssl")] + { + verify_trust(th, chain_der, cert_der) + } + + #[cfg(all(not(feature = "openssl"), not(target_arch = "wasm32")))] + { + Err(Error::NotImplemented( + "no trust handler for this feature".to_string(), + )) + } + }; + + match verify_result { + Ok(trusted) => { + if trusted { + let log_item = + log_item!("Cose_Sign1", "signing certificate trusted", "verify_cose") + .validation_status(validation_status::SIGNING_CREDENTIAL_TRUSTED); + validation_log.log_silent(log_item); + Ok(()) + } else { + let log_item = + log_item!("Cose_Sign1", "signing certificate untrusted", "verify_cose") + .error(Error::CoseCertUntrusted) + .validation_status(validation_status::SIGNING_CREDENTIAL_UNTRUSTED); + validation_log.log(log_item, Some(Error::CoseCertUntrusted))?; + Err(Error::CoseCertUntrusted) + } + } + Err(e) => { + let log_item = log_item!("Cose_Sign1", "signing certificate untrusted", "verify_cose") + .error(Error::CoseCertUntrusted) + .validation_status(validation_status::SIGNING_CREDENTIAL_UNTRUSTED) + .set_error(&e); + + validation_log.log(log_item, Some(Error::CoseCertUntrusted))?; + Err(e) + } + } +} + /// A wrapper containing information of the signing cert. pub(crate) struct CertInfo { /// The name of the identity the certificate is issued to. @@ -842,11 +936,12 @@ fn extract_serial_from_cert(cert: &X509Certificate) -> BigUint { /// data: data that was used to create the cose_bytes, these must match /// addition_data: additional optional data that may have been used during signing /// returns - Ok on success -pub async fn verify_cose_async( +pub(crate) async fn verify_cose_async( cose_bytes: Vec<u8>, data: Vec<u8>, additional_data: Vec<u8>, signature_only: bool, + th: &dyn TrustHandlerConfig, validation_log: &mut impl StatusTracker, ) -> Result<ValidationInfo> { let mut sign1 = get_cose_sign1(&cose_bytes, &data, validation_log)?; @@ -856,7 +951,7 @@ pub async fn verify_cose_async( Err(_) => { let log_item = log_item!( "Cose_Sign1", - "unsupported or missing Cose algorithhm", + "unsupported or missing Cose algorithm", "verify_cose_async" ) .error(Error::CoseSignatureAlgorithmNotSupported) @@ -871,18 +966,21 @@ pub async fn verify_cose_async( // build result structure let mut result = ValidationInfo::default(); + // get the cert chain + let certs = get_sign_certs(&sign1)?; + // get the public key der - let der_bytes = get_sign_cert(&sign1)?; + let der_bytes = &certs[0]; // verify cert matches requested algorithm if !signature_only { // verify certs match get_timestamp_info(&sign1, &data) { - Ok(tst_info) => check_cert(&der_bytes, validation_log, Some(&tst_info))?, + Ok(tst_info) => check_cert(der_bytes, th, validation_log, Some(&tst_info))?, Err(e) => { // log timestamp errors match e { - Error::NotFound => check_cert(&der_bytes, validation_log, None)?, + Error::NotFound => check_cert(der_bytes, th, validation_log, None)?, Error::CoseTimeStampMismatch => { let log_item = log_item!( "Cose_Sign1", @@ -911,10 +1009,19 @@ pub async fn verify_cose_async( } } } - } - // check certificate revocation - check_ocsp_status(&cose_bytes, &data, validation_log)?; + // is the certificate trusted + #[cfg(target_arch = "wasm32")] + check_trust_async(th, &certs[1..], der_bytes, validation_log).await?; + + #[cfg(not(target_arch = "wasm32"))] + check_trust(th, &certs[1..], der_bytes, validation_log)?; + + // check certificate revocation + check_ocsp_status(&cose_bytes, &data, th, validation_log)?; + + // todo: check TSA certs against trust list + } // Check the signature, which needs to have the same `additional_data` provided, by // providing a closure that can do the verify operation. @@ -933,7 +1040,7 @@ pub async fn verify_cose_async( if let Ok(CertInfo { subject, serial_number, - }) = validate_with_cert_async(alg, &sign1.signature, &tbs, &der_bytes).await + }) = validate_with_cert_async(alg, &sign1.signature, &tbs, der_bytes).await { result.issuer_org = Some(subject); result.cert_serial_number = Some(serial_number); @@ -1019,11 +1126,12 @@ pub(crate) fn get_signing_info( /// addition_data: additional optional data that may have been used during signing /// returns - Ok on success #[cfg(not(target_arch = "wasm32"))] -pub fn verify_cose( +pub(crate) fn verify_cose( cose_bytes: &[u8], data: &[u8], additional_data: &[u8], signature_only: bool, + th: &dyn TrustHandlerConfig, validation_log: &mut impl StatusTracker, ) -> Result<ValidationInfo> { let sign1 = get_cose_sign1(cose_bytes, data, validation_log)?; @@ -1033,7 +1141,7 @@ pub fn verify_cose( Err(_) => { let log_item = log_item!( "Cose_Sign1", - "unsupported or missing Cose algorithhm", + "unsupported or missing Cose algorithm", "verify_cose" ) .error(Error::CoseSignatureAlgorithmNotSupported) @@ -1061,11 +1169,11 @@ pub fn verify_cose( if !signature_only { // verify certs match &time_stamp_info { - Ok(tst_info) => check_cert(der_bytes, validation_log, Some(tst_info))?, + Ok(tst_info) => check_cert(der_bytes, th, validation_log, Some(tst_info))?, Err(e) => { // log timestamp errors match e { - Error::NotFound => check_cert(der_bytes, validation_log, None)?, + Error::NotFound => check_cert(der_bytes, th, validation_log, None)?, Error::CoseTimeStampMismatch => { let log_item = log_item!( "Cose_Sign1", @@ -1092,10 +1200,15 @@ pub fn verify_cose( } } } - } - // check certificate revocation - check_ocsp_status(cose_bytes, data, validation_log)?; + // is the certificate trusted + check_trust(th, &certs[1..], der_bytes, validation_log)?; + + // check certificate revocation + check_ocsp_status(cose_bytes, data, th, validation_log)?; + + // todo: check TSA certs against trust list + } // Check the signature, which needs to have the same `additional_data` provided, by // providing a closure that can do the verify operation. @@ -1126,11 +1239,12 @@ pub fn verify_cose( } #[cfg(target_arch = "wasm32")] -pub fn verify_cose( +pub(crate) fn verify_cose( _cose_bytes: &[u8], _data: &[u8], _additional_data: &[u8], _signature_only: bool, + _th: &dyn TrustHandlerConfig, _validation_log: &mut impl StatusTracker, ) -> Result<ValidationInfo> { Err(Error::CoseVerifier) @@ -1215,13 +1329,15 @@ pub mod tests { use super::*; use crate::{ - signer::ConfigurableSigner, status_tracker::DetailedStatusTracker, Signer, SigningAlg, + openssl::temp_signer, signer::ConfigurableSigner, status_tracker::DetailedStatusTracker, + Signer, SigningAlg, }; #[test] #[cfg(feature = "file_io")] fn test_expired_cert() { let mut validation_log = DetailedStatusTracker::new(); + let th = crate::openssl::OpenSSLTrustHandlerConfig::new(); let mut cert_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); cert_path.push("tests/fixtures/rsa-pss256_key-expired.pub"); @@ -1230,7 +1346,7 @@ pub mod tests { if let Ok(signcert) = openssl::x509::X509::from_pem(&expired_cert) { let der_bytes = signcert.to_der().unwrap(); - assert!(check_cert(&der_bytes, &mut validation_log, None).is_err()); + assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_err()); assert!(!validation_log.get_log().is_empty()); @@ -1301,8 +1417,7 @@ pub mod tests { #[cfg(feature = "openssl_sign")] fn test_cert_algorithms() { let cert_dir = crate::utils::test::fixture_path("certs"); - - use crate::openssl::temp_signer; + let th = crate::openssl::OpenSSLTrustHandlerConfig::new(); let mut validation_log = DetailedStatusTracker::new(); @@ -1320,22 +1435,22 @@ pub mod tests { if let Ok(signcert) = openssl::x509::X509::from_pem(&es256_cert) { let der_bytes = signcert.to_der().unwrap(); - assert!(check_cert(&der_bytes, &mut validation_log, None).is_ok()); + assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } if let Ok(signcert) = openssl::x509::X509::from_pem(&es384_cert) { let der_bytes = signcert.to_der().unwrap(); - assert!(check_cert(&der_bytes, &mut validation_log, None).is_ok()); + assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } if let Ok(signcert) = openssl::x509::X509::from_pem(&es512_cert) { let der_bytes = signcert.to_der().unwrap(); - assert!(check_cert(&der_bytes, &mut validation_log, None).is_ok()); + assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } if let Ok(signcert) = openssl::x509::X509::from_pem(&rsa_pss256_cert) { let der_bytes = signcert.to_der().unwrap(); - assert!(check_cert(&der_bytes, &mut validation_log, None).is_ok()); + assert!(check_cert(&der_bytes, &th, &mut validation_log, None).is_ok()); } } diff --git a/sdk/src/error.rs b/sdk/src/error.rs @@ -129,6 +129,9 @@ pub enum Error { #[error("COSE certificate has been revoked")] CoseCertRevoked, + #[error("COSE certificate not trusted")] + CoseCertUntrusted, + /// Unable to parse the time stamp from this signature. #[error("COSE time stamp could not be parsed")] CoseInvalidTimeStamp, @@ -150,6 +153,9 @@ pub enum Error { #[error("COSE Signature too big for JUMBF box")] CoseSigboxTooSmall, + #[error("COSE Signer does not contain signing certificate")] + CoseNoCerts, + #[error("WASM verifier error")] WasmVerifier, @@ -252,6 +258,9 @@ pub enum Error { #[error("could not generate XML")] XmlWriteError, + #[error("unknown algorithm")] + UnknownAlgorithm, + // --- third-party errors --- #[error(transparent)] IoError(#[from] std::io::Error), diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs @@ -140,10 +140,11 @@ pub(crate) mod hashed_uri; #[allow(dead_code)] pub(crate) mod jumbf; pub(crate) mod salt; -pub(crate) mod settings; +pub mod settings; pub(crate) mod status_tracker; pub(crate) mod store; pub(crate) mod time_stamp; +pub(crate) mod trust_handler; pub(crate) mod utils; pub mod validation_status; pub use hash_utils::HashRange; diff --git a/sdk/src/ocsp_utils.rs b/sdk/src/ocsp_utils.rs @@ -52,7 +52,7 @@ impl Default for OcspData { } } -#[cfg(feature = "fetch_ocsp_response")] +#[cfg(not(target_arch = "wasm32"))] fn extract_aia_responders(cert: &x509_parser::certificate::X509Certificate) -> Option<Vec<String>> { use x509_parser::der_parser::{oid, Oid}; @@ -84,7 +84,7 @@ fn extract_aia_responders(cert: &x509_parser::certificate::X509Certificate) -> O /// retrieve the OCSPResponse. If successful returns OcspData containing the DER encoded OCSPResponse and /// the DateTime for when this cached response should be refreshed, and the OCSP signer certificate chain. /// None otherwise. -#[cfg(feature = "fetch_ocsp_response")] +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn fetch_ocsp_response(certs: &[Vec<u8>]) -> Option<Vec<u8>> { use std::io::Read; @@ -361,21 +361,38 @@ pub(crate) fn check_ocsp_response( Utc, ); - let msg = - format!("certificate revoked at: {}", utc_with_offset); - let log_item = - log_item!("OCSP_RESPONSE", &msg, "check_ocsp_response") - .error(Error::CoseCertRevoked) - .validation_status( - validation_status::SIGNING_CREDENTIAL_REVOKED, - ); - validation_log.log_silent(log_item); + // check to see if cert was signed before revocation + let in_range = if let Some(st) = signing_time { + st.timestamp() < utc_with_offset.timestamp() + } else { + false + }; - output.revoked_at = - Some(DateTime::from_naive_utc_and_offset( - revoked_at_native, - Utc, - )); + if !in_range { + let msg = format!( + "certificate revoked at: {}", + utc_with_offset + ); + let log_item = log_item!( + "OCSP_RESPONSE", + &msg, + "check_ocsp_response" + ) + .error(Error::CoseCertRevoked) + .validation_status( + validation_status::SIGNING_CREDENTIAL_REVOKED, + ); + validation_log.log_silent(log_item); + + output.revoked_at = + Some(DateTime::from_naive_utc_and_offset( + revoked_at_native, + Utc, + )); + } else { + found_good = true; + break; // found good match so break + } } } else { let log_item = log_item!( @@ -437,7 +454,7 @@ pub mod tests { let mut validation_log = DetailedStatusTracker::default(); - let test_time = Utc.with_ymd_and_hms(2023, 2, 1, 8, 0, 0).unwrap(); + let test_time = Utc.with_ymd_and_hms(2024, 2, 1, 8, 0, 0).unwrap(); let ocsp_data = check_ocsp_response(rsp_data, Some(test_time), &mut validation_log).unwrap(); diff --git a/sdk/src/openssl/ec_validator.rs b/sdk/src/openssl/ec_validator.rs @@ -38,27 +38,32 @@ impl CoseValidator for EcValidator { }; // is this an expected P1363 sig size - if sig.len() - != match self.alg { - SigningAlg::Es256 => 64, - SigningAlg::Es384 => 96, - SigningAlg::Es512 => 132, - _ => return Err(Error::UnsupportedType), + let sig_der = if sig.len() == 64 || sig.len() == 96 || sig.len() == 132 { + if sig.len() + != match self.alg { + SigningAlg::Es256 => 64, + SigningAlg::Es384 => 96, + SigningAlg::Es512 => 132, + _ => return Err(Error::UnsupportedType), + } + { + return Err(Error::CoseSignature); } - { - return Err(Error::CoseSignature); - } - - // convert P1363 sig to DER sig - let sig_len = sig.len() / 2; - let r = openssl::bn::BigNum::from_slice(&sig[0..sig_len]) - .map_err(|_err| Error::CoseSignature)?; - let s = openssl::bn::BigNum::from_slice(&sig[sig_len..]) - .map_err(|_err| Error::CoseSignature)?; - - let ecdsa_sig = openssl::ecdsa::EcdsaSig::from_private_components(r, s) - .map_err(|_err| Error::CoseSignature)?; - let sig_der = ecdsa_sig.to_der().map_err(|_err| Error::CoseSignature)?; + + // convert P1363 sig to DER sig + let sig_len = sig.len() / 2; + let r = openssl::bn::BigNum::from_slice(&sig[0..sig_len]) + .map_err(|_err| Error::CoseSignature)?; + let s = openssl::bn::BigNum::from_slice(&sig[sig_len..]) + .map_err(|_err| Error::CoseSignature)?; + + let ecdsa_sig = openssl::ecdsa::EcdsaSig::from_private_components(r, s) + .map_err(|_err| Error::CoseSignature)?; + + ecdsa_sig.to_der().map_err(|_err| Error::CoseSignature)? + } else { + sig.to_vec() + }; verifier.update(data).map_err(wrap_openssl_err)?; verifier diff --git a/sdk/src/openssl/mod.rs b/sdk/src/openssl/mod.rs @@ -15,7 +15,7 @@ mod rsa_signer; pub(crate) use rsa_signer::RsaSigner; -#[cfg(feature = "openssl_sign")] +#[cfg(feature = "openssl")] mod rsa_validator; pub(crate) use rsa_validator::RsaValidator; @@ -23,7 +23,7 @@ pub(crate) use rsa_validator::RsaValidator; mod ec_signer; pub(crate) use ec_signer::EcSigner; -#[cfg(feature = "openssl_sign")] +#[cfg(feature = "openssl")] mod ec_validator; pub(crate) use ec_validator::EcValidator; @@ -31,13 +31,20 @@ pub(crate) use ec_validator::EcValidator; mod ed_signer; pub(crate) use ed_signer::EdSigner; -#[cfg(feature = "openssl_sign")] +#[cfg(feature = "openssl")] mod ed_validator; pub(crate) use ed_validator::EdValidator; +#[cfg(feature = "openssl")] +mod openssl_trust_handler; #[cfg(test)] pub(crate) mod temp_signer; +#[cfg(feature = "openssl")] +pub(crate) use openssl_trust_handler::verify_trust; +#[cfg(feature = "openssl")] +pub(crate) use openssl_trust_handler::OpenSSLTrustHandlerConfig; + #[cfg(test)] pub(crate) mod temp_signer_async; @@ -45,9 +52,9 @@ pub(crate) mod temp_signer_async; use openssl::x509::X509; #[cfg(test)] #[allow(unused_imports)] -#[cfg(feature = "openssl_sign")] +#[cfg(feature = "openssl")] pub(crate) use temp_signer_async::AsyncSignerAdapter; -#[cfg(feature = "openssl_sign")] +#[cfg(feature = "openssl")] pub(crate) fn check_chain_order(certs: &[X509]) -> bool { { if certs.len() > 1 { @@ -71,12 +78,12 @@ pub(crate) fn check_chain_order(certs: &[X509]) -> bool { } } -#[cfg(not(feature = "openssl_sign"))] +#[cfg(not(feature = "openssl"))] pub(crate) fn check_chain_order(certs: &[X509]) -> bool { true } -#[cfg(feature = "openssl_sign")] +#[cfg(feature = "openssl")] #[allow(dead_code)] pub(crate) fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool { let mut certs: Vec<X509> = Vec::new(); @@ -91,7 +98,7 @@ pub(crate) fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool { check_chain_order(&certs) } -#[cfg(not(feature = "openssl_sign"))] +#[cfg(not(feature = "openssl"))] pub(crate) fn check_chain_order_der(cert_ders: &[Vec<u8>]) -> bool { true } diff --git a/sdk/src/openssl/openssl_trust_handler.rs b/sdk/src/openssl/openssl_trust_handler.rs @@ -0,0 +1,436 @@ +// Copyright 2022 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use std::{ + collections::HashSet, + io::{BufRead, BufReader, Cursor, Read}, + str::FromStr, +}; + +use asn1_rs::Oid; + +use crate::{ + error::{wrap_openssl_err, Result}, + hash_utils::hash_sha256, + trust_handler::{load_eku_configuration, TrustHandlerConfig}, + utils::base64, + Error, +}; + +fn certs_der_to_x509(ders: &[Vec<u8>]) -> Result<Vec<openssl::x509::X509>> { + let mut certs: Vec<openssl::x509::X509> = Vec::new(); + + for d in ders { + let cert = openssl::x509::X509::from_der(d).map_err(wrap_openssl_err)?; + certs.push(cert); + } + + Ok(certs) +} + +fn load_trust_from_pem_data(trust_data: &[u8]) -> Result<Vec<openssl::x509::X509>> { + openssl::x509::X509::stack_from_pem(trust_data).map_err(wrap_openssl_err) +} + +// Struct to handle verification of trust chains +pub(crate) struct OpenSSLTrustHandlerConfig { + trust_anchors: Vec<openssl::x509::X509>, + private_anchors: Vec<openssl::x509::X509>, + allowed_cert_set: HashSet<String>, + trust_store: Option<openssl::x509::store::X509Store>, + config_store: Vec<u8>, +} + +impl OpenSSLTrustHandlerConfig { + pub fn load_default_trust(&mut self) -> Result<()> { + // load config store + let config = include_bytes!("../../tests/fixtures/certs/trust/store.cfg"); + let mut config_reader = Cursor::new(config); + self.load_configuration(&mut config_reader)?; + + // load debug/test private trust anchors + if cfg!(test) { + let pa = include_bytes!("../../tests/fixtures/certs/trust/test_cert_root_bundle.pem"); + let mut pa_reader = Cursor::new(pa); + + self.append_private_trust_data(&mut pa_reader)?; + } + + Ok(()) + } + + fn update_store(&mut self) -> Result<()> { + let mut builder = + openssl::x509::store::X509StoreBuilder::new().map_err(wrap_openssl_err)?; + + // add trust anchors + for t in &self.trust_anchors { + builder.add_cert(t.clone())?; + } + + // add private anchors + for t in &self.private_anchors { + builder.add_cert(t.clone())?; + } + + self.trust_store = Some(builder.build()); + + Ok(()) + } +} + +impl std::fmt::Debug for OpenSSLTrustHandlerConfig { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{} trust anchors, {} private anchors.", + self.trust_anchors.len(), + self.private_anchors.len() + ) + } +} + +#[allow(dead_code)] +impl TrustHandlerConfig for OpenSSLTrustHandlerConfig { + fn new() -> Self { + let mut th = OpenSSLTrustHandlerConfig { + trust_anchors: Vec::new(), + private_anchors: Vec::new(), + allowed_cert_set: HashSet::new(), + trust_store: None, + config_store: Vec::new(), + }; + if th.load_default_trust().is_err() { + th.clear(); // just use empty trust handler to fail automatically + } + + th + } + + // add trust anchors + fn load_trust_anchors_from_data(&mut self, trust_data_reader: &mut dyn Read) -> Result<()> { + let mut trust_data = Vec::new(); + trust_data_reader.read_to_end(&mut trust_data)?; + + self.trust_anchors = load_trust_from_pem_data(&trust_data)?; + if self.trust_anchors.is_empty() { + return Err(Error::NotFound); // catch silent failure + } + + self.update_store() + } + + // add allowed list entries + fn load_allowed_list(&mut self, allowed_list: &mut dyn Read) -> Result<()> { + let mut buffer = Vec::new(); + allowed_list.read_to_end(&mut buffer)?; + + if let Ok(cert_list) = openssl::x509::X509::stack_from_pem(&buffer) { + for cert in &cert_list { + let cert_der = cert.to_der().map_err(wrap_openssl_err)?; + let cert_sha256 = hash_sha256(&cert_der); + let cert_hash_base64 = base64::encode(&cert_sha256); + + self.allowed_cert_set.insert(cert_hash_base64); + } + } + + // try to load the of base64 encoded encoding of the sha256 hash of the certificate DER encoding + let reader = Cursor::new(buffer); + let buf_reader = BufReader::new(reader); + + let mut inside_cert_block = false; + for l in buf_reader.lines().map_while(|v| v.ok()) { + if l.contains("-----BEGIN") { + inside_cert_block = true; + } + if l.contains("-----END") { + inside_cert_block = false; + } + + // sanity check that that is is base64 encoded and outside of certificate block + if !inside_cert_block && base64::decode(&l).is_ok() && !l.is_empty() { + self.allowed_cert_set.insert(l); + } + } + + Ok(()) + } + + // append private trust anchors + fn append_private_trust_data(&mut self, private_anchors_reader: &mut dyn Read) -> Result<()> { + let mut private_anchors_data = Vec::new(); + private_anchors_reader.read_to_end(&mut private_anchors_data)?; + + let mut pa = load_trust_from_pem_data(&private_anchors_data)?; + self.private_anchors.append(&mut pa); + self.update_store() + } + + fn clear(&mut self) { + self.trust_anchors = Vec::new(); + self.private_anchors = Vec::new(); + self.trust_store = None; + } + + // load EKU configuration + fn load_configuration(&mut self, config_data: &mut dyn Read) -> Result<()> { + config_data.read_to_end(&mut self.config_store)?; + Ok(()) + } + + // list off auxillary allowed EKU Oid + fn get_auxillary_ekus(&self) -> Vec<Oid> { + let mut oids = Vec::new(); + if let Ok(oid_strings) = load_eku_configuration(&mut Cursor::new(&self.config_store)) { + for oid_str in &oid_strings { + if let Ok(oid) = Oid::from_str(oid_str) { + oids.push(oid); + } + } + } + oids + } + + fn get_anchors(&self) -> Vec<Vec<u8>> { + let mut anchors = Vec::new(); + + for a in &self.private_anchors { + if let Ok(der) = a.to_der() { + anchors.push(der) + } + } + + for a in &self.trust_anchors { + if let Ok(der) = a.to_der() { + anchors.push(der) + } + } + anchors + } + + // set of allowed cert hashes + fn get_allowed_list(&self) -> &HashSet<String> { + &self.allowed_cert_set + } +} + +// verify certificate and trust chain +pub(crate) fn verify_trust( + th: &dyn TrustHandlerConfig, + chain_der: &[Vec<u8>], + cert_der: &[u8], +) -> Result<bool> { + // check the cert against the allowed list first + let cert_sha256 = hash_sha256(cert_der); + let cert_hash_base64 = base64::encode(&cert_sha256); + if th.get_allowed_list().contains(&cert_hash_base64) { + return Ok(true); + } + + let mut cert_chain = openssl::stack::Stack::new().map_err(wrap_openssl_err)?; + let mut store_ctx = openssl::x509::X509StoreContext::new().map_err(wrap_openssl_err)?; + + let chain = certs_der_to_x509(chain_der)?; + for c in chain { + cert_chain.push(c).map_err(wrap_openssl_err)?; + } + let cert = openssl::x509::X509::from_der(cert_der).map_err(wrap_openssl_err)?; + + let mut builder = openssl::x509::store::X509StoreBuilder::new().map_err(wrap_openssl_err)?; + + // todo: figure out the passthrough case + if th.get_anchors().is_empty() { + return Ok(false); + } + + // add trust anchors + for d in th.get_anchors() { + let c = openssl::x509::X509::from_der(&d).map_err(wrap_openssl_err)?; + builder.add_cert(c)?; + } + // finalize store + let store = builder.build(); + + match store_ctx.init(&store, cert.as_ref(), &cert_chain, |f| f.verify_cert()) { + Ok(trust) => Ok(trust), + Err(_) => Ok(false), + } +} +#[cfg(test)] +pub mod tests { + #![allow(clippy::expect_used)] + #![allow(clippy::panic)] + #![allow(clippy::unwrap_used)] + + use super::*; + use crate::{ + openssl::temp_signer::{self}, + Signer, SigningAlg, + }; + + #[test] + fn test_trust_store() { + let cert_dir = crate::utils::test::fixture_path("certs"); + + let mut th = OpenSSLTrustHandlerConfig::new(); + th.clear(); + + th.load_default_trust().unwrap(); + + // test all the certs + let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); + let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); + let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); + let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); + let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); + let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); + let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + + let ps256_certs = ps256.certs().unwrap(); + let ps384_certs = ps384.certs().unwrap(); + let ps512_certs = ps512.certs().unwrap(); + let es256_certs = es256.certs().unwrap(); + let es384_certs = es384.certs().unwrap(); + let es512_certs = es512.certs().unwrap(); + let ed25519_certs = ed25519.certs().unwrap(); + + assert!(verify_trust(&th, &ps256_certs[1..], &ps256_certs[0]).unwrap()); + assert!(verify_trust(&th, &ps384_certs[1..], &ps384_certs[0]).unwrap()); + assert!(verify_trust(&th, &ps512_certs[1..], &ps512_certs[0]).unwrap()); + assert!(verify_trust(&th, &es256_certs[1..], &es256_certs[0]).unwrap()); + assert!(verify_trust(&th, &es384_certs[1..], &es384_certs[0]).unwrap()); + assert!(verify_trust(&th, &es512_certs[1..], &es512_certs[0]).unwrap()); + assert!(verify_trust(&th, &ed25519_certs[1..], &ed25519_certs[0]).unwrap()); + } + + #[test] + fn test_broken_trust_chain() { + let cert_dir = crate::utils::test::fixture_path("certs"); + let ta = include_bytes!("../../tests/fixtures/certs/trust/test_cert_root_bundle.pem"); + + let mut th = OpenSSLTrustHandlerConfig::new(); + th.clear(); + + // load the trust store + let mut reader = Cursor::new(ta); + th.load_trust_anchors_from_data(&mut reader).unwrap(); + + // test all the certs + let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); + let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); + let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); + let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); + let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); + let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); + let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + + let ps256_certs = ps256.certs().unwrap(); + let ps384_certs = ps384.certs().unwrap(); + let ps512_certs = ps512.certs().unwrap(); + let es256_certs = es256.certs().unwrap(); + let es384_certs = es384.certs().unwrap(); + let es512_certs = es512.certs().unwrap(); + let ed25519_certs = ed25519.certs().unwrap(); + + assert!(!verify_trust(&th, &ps256_certs[2..], &ps256_certs[0]).unwrap()); + assert!(!verify_trust(&th, &ps384_certs[2..], &ps384_certs[0]).unwrap()); + assert!(!verify_trust(&th, &ps512_certs[2..], &ps512_certs[0]).unwrap()); + assert!(!verify_trust(&th, &es256_certs[2..], &es256_certs[0]).unwrap()); + assert!(!verify_trust(&th, &es384_certs[2..], &es384_certs[0]).unwrap()); + assert!(!verify_trust(&th, &es512_certs[2..], &es512_certs[0]).unwrap()); + assert!(!verify_trust(&th, &ed25519_certs[2..], &ed25519_certs[0]).unwrap()); + } + + #[test] + fn test_allowed_list() { + let cert_dir = crate::utils::test::fixture_path("certs"); + + let mut th = OpenSSLTrustHandlerConfig::new(); + th.clear(); + + let mut allowed_list_path = crate::utils::test::fixture_path("certs"); + allowed_list_path = allowed_list_path.join("trust"); + allowed_list_path = allowed_list_path.join("allowed_list.pem"); + + let mut allowed_list = std::fs::File::open(&allowed_list_path).unwrap(); + + th.load_allowed_list(&mut allowed_list).unwrap(); + + // test all the certs + let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); + let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); + let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); + let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); + let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); + let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); + let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + + let ps256_certs = ps256.certs().unwrap(); + let ps384_certs = ps384.certs().unwrap(); + let ps512_certs = ps512.certs().unwrap(); + let es256_certs = es256.certs().unwrap(); + let es384_certs = es384.certs().unwrap(); + let es512_certs = es512.certs().unwrap(); + let ed25519_certs = ed25519.certs().unwrap(); + + assert!(verify_trust(&th, &ps256_certs[1..], &ps256_certs[0]).unwrap()); + assert!(verify_trust(&th, &ps384_certs[1..], &ps384_certs[0]).unwrap()); + assert!(verify_trust(&th, &ps512_certs[1..], &ps512_certs[0]).unwrap()); + assert!(verify_trust(&th, &es256_certs[1..], &es256_certs[0]).unwrap()); + assert!(verify_trust(&th, &es384_certs[1..], &es384_certs[0]).unwrap()); + assert!(verify_trust(&th, &es512_certs[1..], &es512_certs[0]).unwrap()); + assert!(verify_trust(&th, &ed25519_certs[1..], &ed25519_certs[0]).unwrap()); + } + + #[test] + fn test_allowed_list_hashes() { + let cert_dir = crate::utils::test::fixture_path("certs"); + + let mut th = OpenSSLTrustHandlerConfig::new(); + th.clear(); + + let mut allowed_list_path = crate::utils::test::fixture_path("certs"); + allowed_list_path = allowed_list_path.join("trust"); + allowed_list_path = allowed_list_path.join("allowed_list.hash"); + + let mut allowed_list = std::fs::File::open(&allowed_list_path).unwrap(); + + th.load_allowed_list(&mut allowed_list).unwrap(); + + // test all the certs + let (ps256, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps256, None); + let (ps384, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps384, None); + let (ps512, _) = temp_signer::get_rsa_signer(&cert_dir, SigningAlg::Ps512, None); + let (es256, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es256, None); + let (es384, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es384, None); + let (es512, _) = temp_signer::get_ec_signer(&cert_dir, SigningAlg::Es512, None); + let (ed25519, _) = temp_signer::get_ed_signer(&cert_dir, SigningAlg::Ed25519, None); + + let ps256_certs = ps256.certs().unwrap(); + let ps384_certs = ps384.certs().unwrap(); + let ps512_certs = ps512.certs().unwrap(); + let es256_certs = es256.certs().unwrap(); + let es384_certs = es384.certs().unwrap(); + let es512_certs = es512.certs().unwrap(); + let ed25519_certs = ed25519.certs().unwrap(); + + assert!(verify_trust(&th, &ps256_certs[1..], &ps256_certs[0]).unwrap()); + assert!(verify_trust(&th, &ps384_certs[1..], &ps384_certs[0]).unwrap()); + assert!(verify_trust(&th, &ps512_certs[1..], &ps512_certs[0]).unwrap()); + assert!(verify_trust(&th, &es256_certs[1..], &es256_certs[0]).unwrap()); + assert!(verify_trust(&th, &es384_certs[1..], &es384_certs[0]).unwrap()); + assert!(verify_trust(&th, &es512_certs[1..], &es512_certs[0]).unwrap()); + assert!(verify_trust(&th, &ed25519_certs[1..], &ed25519_certs[0]).unwrap()); + } +} diff --git a/sdk/src/settings.rs b/sdk/src/settings.rs @@ -11,13 +11,17 @@ // specific language governing permissions and limitations under // each license. -use std::{path::Path, sync::RwLock}; +use std::{ + io::{BufRead, BufReader, Cursor}, + path::Path, + sync::RwLock, +}; use config::{Config, FileFormat}; use lazy_static::lazy_static; use serde_derive::{Deserialize, Serialize}; -use crate::{Error, Result}; +use crate::{utils::base64, Error, Result}; lazy_static! { static ref SETTINGS: RwLock<Config> = @@ -25,7 +29,7 @@ lazy_static! { } // trait used to validate user input to make sure user supplied configurations are valid -pub trait SettingsValidate { +pub(crate) trait SettingsValidate { // returns error if settings are invalid fn validate(&self) -> Result<()> { Ok(()) @@ -35,19 +39,84 @@ pub trait SettingsValidate { // Settings for trust list feature #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[allow(unused)] -pub struct Trust { +pub(crate) struct Trust { private_anchors: Option<String>, trust_anchors: Option<String>, trust_config: Option<String>, allowed_list: Option<String>, } -impl SettingsValidate for Trust {} +impl Trust { + // load PEMs + fn load_trust_from_data(&self, trust_data: &[u8]) -> Result<Vec<Vec<u8>>> { + let mut certs = Vec::new(); + + for pem_result in x509_parser::pem::Pem::iter_from_buffer(trust_data) { + let pem = pem_result.map_err(|_e| Error::CoseInvalidCert)?; + certs.push(pem.contents); + } + Ok(certs) + } + + // sanity check to see if can parse trust settings + fn test_load_trust(&self, allowed_list: &[u8]) -> Result<()> { + // check pems + if let Ok(cert_list) = self.load_trust_from_data(allowed_list) { + if !cert_list.is_empty() { + return Ok(()); + } + } + + // try to load the of base64 encoded encoding of the sha256 hash of the certificate DER encoding + let reader = Cursor::new(allowed_list); + let buf_reader = BufReader::new(reader); + let mut found_der_hash = false; + + let mut inside_cert_block = false; + for l in buf_reader.lines().map_while(|v| v.ok()) { + if l.contains("-----BEGIN") { + inside_cert_block = true; + } + if l.contains("-----END") { + inside_cert_block = false; + } + + // sanity check that that is is base64 encoded and outside of certificate block + if !inside_cert_block && base64::decode(&l).is_ok() && !l.is_empty() { + found_der_hash = true; + } + } + + if found_der_hash { + Ok(()) + } else { + Err(Error::NotFound) + } + } +} + +impl SettingsValidate for Trust { + fn validate(&self) -> Result<()> { + if let Some(ta) = &self.trust_anchors { + self.test_load_trust(ta.as_bytes())?; + } + + if let Some(pa) = &self.private_anchors { + self.test_load_trust(pa.as_bytes())?; + } + + if let Some(al) = &self.allowed_list { + self.test_load_trust(al.as_bytes())?; + } + + Ok(()) + } +} // Settings for core C2PA-RS functionality #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[allow(unused)] -pub struct Core { +pub(crate) struct Core { debug: bool, hash_alg: String, salt_jumbf_boxes: bool, @@ -83,7 +152,7 @@ impl SettingsValidate for Core { // Settings for verification options #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[allow(unused)] -pub struct Verify { +pub(crate) struct Verify { verify_after_sign: bool, verify_trust: bool, ocsp_fetch: bool, @@ -93,7 +162,7 @@ pub struct Verify { impl Default for Verify { fn default() -> Self { Self { - verify_after_sign: false, + verify_after_sign: true, verify_trust: false, ocsp_fetch: false, remote_manifest_fetch: true, @@ -106,7 +175,7 @@ impl SettingsValidate for Verify {} // Settings for manifest API options #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[allow(unused)] -pub struct Manifest { +pub(crate) struct Manifest { auto_thumbnail: bool, } @@ -237,7 +306,7 @@ pub fn load_settings<P: AsRef<Path>>(settings_path: P) -> Result<()> { load_settings_from_str(&String::from_utf8_lossy(&setting_buf), &ext) } -// Load settings form string representation of the configuration. Format of configuration must be supplied. +/// Load settings form string representation of the configuration. Format of configuration must be supplied. #[allow(unused)] pub fn load_settings_from_str(settings_str: &str, format: &str) -> Result<()> { Settings::from_string(settings_str, format).map(|_| ()) @@ -267,9 +336,20 @@ pub(crate) fn set_settings_value<T: Into<config::Value>>(value_path: &str, value .set_override(value_path, value); if let Ok(updated) = update_config { - *c = updated + let update_config = updated .build() .map_err(|_e| Error::OtherError("could not update configuration".into()))?; + + let settings = update_config + .clone() + .try_deserialize::<Settings>() + .map_err(|_e| { + Error::BadParam("configuration file contains unrecognized param".into()) + })?; + settings.validate()?; + + *c = update_config; + Ok(()) } else { Err(Error::OtherError("could not save settings".into())) @@ -294,7 +374,7 @@ pub(crate) fn get_settings_value<'de, T: serde::de::Deserialize<'de>>( // Set settings back to the default values. Current use case is for testing. #[allow(unused)] -pub(crate) fn reset_default_settings() -> Result<()> { +pub fn reset_default_settings() -> Result<()> { if let Ok(default_settings) = Config::try_from(&Settings::default()) { match SETTINGS.write() { Ok(mut current_settings) => { @@ -400,13 +480,15 @@ pub mod tests { fn test_set_val_by_direct_path() { let _protect = PROTECT.lock().unwrap(); + let ts = include_bytes!("../tests/fixtures/certs/trust/test_cert_root_bundle.pem"); + // test updating values set_settings_value("core.hash_alg", "sha512").unwrap(); set_settings_value("verify.remote_manifest_fetch", false).unwrap(); set_settings_value("manifest.auto_thumbnail", false).unwrap(); set_settings_value( "trust.private_anchors", - Some("path/to/my/content".to_string()), + Some(String::from_utf8(ts.to_vec()).unwrap()), ) .unwrap(); @@ -418,7 +500,7 @@ pub mod tests { assert!(!get_settings_value::<bool>("manifest.auto_thumbnail").unwrap()); assert_eq!( get_settings_value::<Option<String>>("trust.private_anchors").unwrap(), - Some("path/to/my/content".to_string()) + Some(String::from_utf8(ts.to_vec()).unwrap()) ); // the current config should be different from the defaults @@ -482,16 +564,9 @@ pub mod tests { let _protect = PROTECT.lock().unwrap(); // we support just changing the fields you are interested in changing - // here are two examples of incomplete structures only overriding specific + // here is an example of incomplete structures only overriding specific // fields - let modified_trust = r#"{ - "trust": { - "private_anchors": "this is a test", - "allowed_list": "another test" - } - }"#; - let modified_core = r#"{ "core": { "debug": true, @@ -500,18 +575,9 @@ pub mod tests { } }"#; - load_settings_from_str(modified_trust, "json").unwrap(); load_settings_from_str(modified_core, "json").unwrap(); // see if updated values match - assert_eq!( - get_settings_value::<Option<String>>("trust.private_anchors").unwrap(), - Some("this is a test".to_string()) - ); - assert_eq!( - get_settings_value::<Option<String>>("trust.allowed_list").unwrap(), - Some("another test".to_string()) - ); assert!(get_settings_value::<bool>("core.debug").unwrap()); assert_eq!( get_settings_value::<String>("core.hash_alg").unwrap(), @@ -527,10 +593,7 @@ pub mod tests { get_settings_value::<bool>("manifest.auto_thumbnail").unwrap(), Manifest::default().auto_thumbnail ); - assert_eq!( - get_settings_value::<Option<String>>("trust.trust_anchors").unwrap(), - Trust::default().trust_anchors - ); + assert_eq!( get_settings_value::<bool>("core.salt_jumbf_boxes").unwrap(), Core::default().salt_jumbf_boxes diff --git a/sdk/src/store.rs b/sdk/src/store.rs @@ -46,9 +46,11 @@ use crate::{ save_jumbf_to_memory, save_jumbf_to_stream, }, salt::DefaultSalt, + settings::get_settings_value, status_tracker::{log_item, OneShotStatusTracker, StatusTracker}, + trust_handler::TrustHandlerConfig, utils::{ - hash_utils::{hash256, HashRange}, + hash_utils::{hash_sha256, HashRange}, patch::patch_bytes, }, validation_status, AsyncSigner, ManifestStoreReport, RemoteSigner, Signer, @@ -75,6 +77,7 @@ pub struct Store { claims: Vec<Claim>, label: String, provenance_path: Option<String>, + trust_handler: Box<dyn TrustHandlerConfig>, } struct ManifestInfo<'a> { @@ -113,13 +116,46 @@ impl Store { /// /// In most cases, calling [`Store::new()`] is preferred. pub fn new_with_label(label: &str) -> Self { - Store { + let mut store = Store { claims_map: HashMap::new(), manifest_box_hash_cache: HashMap::new(), claims: Vec::new(), label: label.to_string(), + #[cfg(feature = "openssl")] + trust_handler: Box::new(crate::openssl::OpenSSLTrustHandlerConfig::new()), + #[cfg(all(not(feature = "openssl"), target_arch = "wasm32"))] + trust_handler: Box::new(crate::wasm::WebTrustHandlerConfig::new()), + #[cfg(all(not(feature = "openssl"), not(target_arch = "wasm32")))] + trust_handler: Box::new(crate::trust_handler::TrustPassThrough::new()), provenance_path: None, - } + }; + + // load the trust handler settings, don't worry about status as these are checked during setting generation + let _ = get_settings_value::<Option<String>>("trust.trust_anchors").map(|ta_opt| { + if let Some(ta) = ta_opt { + let _v = store.add_trust(ta.as_bytes()); + } + }); + + let _ = get_settings_value::<Option<String>>("trust.private_anchors").map(|pa_opt| { + if let Some(pa) = pa_opt { + let _v = store.add_private_trust_anchors(pa.as_bytes()); + } + }); + + let _ = get_settings_value::<Option<String>>("trust.trust_config").map(|tc_opt| { + if let Some(tc) = tc_opt { + let _v = store.add_trust_config(tc.as_bytes()); + } + }); + + let _ = get_settings_value::<Option<String>>("trust.allowed_list").map(|al_opt| { + if let Some(al) = al_opt { + let _v = store.add_trust_allowed_list(al.as_bytes()); + } + }); + + store } /// Return label for the store @@ -127,6 +163,42 @@ impl Store { &self.label } + /// Load set of trust anchors used for certificate validation. [u8] containing the + /// trust anchors is passed in the trust_vec variable. + pub fn add_trust(&mut self, trust_vec: &[u8]) -> Result<()> { + let mut trust_reader = Cursor::new(trust_vec); + self.trust_handler + .load_trust_anchors_from_data(&mut trust_reader) + } + + // Load set of private trust anchors used for certificate validation. [u8] to the + /// private trust anchors is passed in the trust_vec variable. This can be called multiple times + /// if there are additional trust stores. + pub fn add_private_trust_anchors(&mut self, trust_vec: &[u8]) -> Result<()> { + let mut trust_reader = Cursor::new(trust_vec); + self.trust_handler + .append_private_trust_data(&mut trust_reader) + } + + pub fn add_trust_config(&mut self, trust_vec: &[u8]) -> Result<()> { + let mut trust_reader = Cursor::new(trust_vec); + self.trust_handler.load_configuration(&mut trust_reader) + } + + pub fn add_trust_allowed_list(&mut self, allowed_vec: &[u8]) -> Result<()> { + let mut trust_reader = Cursor::new(allowed_vec); + self.trust_handler.load_allowed_list(&mut trust_reader) + } + + /// Clear all existing trust anchors + pub fn clear_trust_anchors(&mut self) { + self.trust_handler.clear(); + } + + fn trust_handler(&self) -> &dyn TrustHandlerConfig { + self.trust_handler.as_ref() + } + /// Get the provenance if available. /// If loaded from an existing asset it will be provenance from the last claim. /// If a new claim is committed that will be the provenance claim @@ -369,7 +441,7 @@ impl Store { // with actual signature data. fn sign_claim_placeholder(claim: &Claim, min_reserve_size: usize) -> Vec<u8> { let placeholder_str = format!("signature placeholder:{}", claim.label()); - let mut placeholder = hash256(placeholder_str.as_bytes()).as_bytes().to_vec(); + let mut placeholder = hash_sha256(placeholder_str.as_bytes()); use std::cmp::max; placeholder.resize(max(placeholder.len(), min_reserve_size), 0); @@ -401,7 +473,7 @@ impl Store { let data = claim.data().ok()?; let mut validation_log = OneShotStatusTracker::new(); - if let Ok(info) = check_ocsp_status(sig, &data, &mut validation_log) { + if let Ok(info) = check_ocsp_status(sig, &data, self.trust_handler(), &mut validation_log) { if let Some(revoked_at) = &info.revoked_at { Some(format!( "Certificate Status: Revoked, revoked at: {}", @@ -429,18 +501,26 @@ impl Store { cose_sign(signer, &claim_bytes, box_size).and_then(|sig| { // Sanity check: Ensure that this signature is valid. - - let mut cose_log = OneShotStatusTracker::new(); - match verify_cose(&sig, &claim_bytes, b"", false, &mut cose_log) { - Ok(_) => Ok(sig), - Err(err) => { - error!( - "Signature that was just generated does not validate: {:#?}", - err - ); - Err(err) + if let Ok(verify_after_sign) = get_settings_value::<bool>("verify.verify_after_sign") { + if verify_after_sign { + let mut cose_log = OneShotStatusTracker::new(); + if let Err(err) = verify_cose( + &sig, + &claim_bytes, + b"", + false, + self.trust_handler(), + &mut cose_log, + ) { + error!( + "Signature that was just generated does not validate: {:#?}", + err + ); + return Err(err); + } } } + Ok(sig) }) } @@ -458,25 +538,30 @@ impl Store { match cose_sign_async(signer, &claim_bytes, box_size).await { // Sanity check: Ensure that this signature is valid. Ok(sig) => { - let mut cose_log = OneShotStatusTracker::new(); - match verify_cose_async( - sig.clone(), - claim_bytes, - b"".to_vec(), - false, - &mut cose_log, - ) - .await + if let Ok(verify_after_sign) = + get_settings_value::<bool>("verify.verify_after_sign") { - Ok(_) => Ok(sig), - Err(err) => { - error!( - "Signature that was just generated does not validate: {:#?}", - err - ); - Err(err) + if verify_after_sign { + let mut cose_log = OneShotStatusTracker::new(); + if let Err(err) = verify_cose_async( + sig.clone(), + claim_bytes, + b"".to_vec(), + false, + self.trust_handler(), + &mut cose_log, + ) + .await + { + error!( + "Signature that was just generated does not validate: {:#?}", + err + ); + return Err(err); + } } } + Ok(sig) } Err(e) => Err(e), } @@ -1235,7 +1320,13 @@ impl Store { // make sure // verify the ingredient claim - Claim::verify_claim(ingredient, asset_data, false, validation_log)?; + Claim::verify_claim( + ingredient, + asset_data, + false, + store.trust_handler(), + validation_log, + )?; } else { let log_item = log_item!( &c2pa_manifest.url(), @@ -1345,8 +1436,14 @@ impl Store { )?; } // verify the ingredient claim - Claim::verify_claim_async(ingredient, asset_data, false, validation_log) - .await?; + Claim::verify_claim_async( + ingredient, + asset_data, + false, + store.trust_handler(), + validation_log, + ) + .await?; } else { let log_item = log_item!( &c2pa_manifest.url(), @@ -1394,7 +1491,14 @@ impl Store { }; // verify the provenance claim - Claim::verify_claim_async(claim, asset_data, true, validation_log).await?; + Claim::verify_claim_async( + claim, + asset_data, + true, + store.trust_handler(), + validation_log, + ) + .await?; Store::ingredient_checks_async(store, claim, asset_data, validation_log).await?; @@ -1425,7 +1529,13 @@ impl Store { }; // verify the provenance claim - Claim::verify_claim(claim, asset_data, true, validation_log)?; + Claim::verify_claim( + claim, + asset_data, + true, + store.trust_handler(), + validation_log, + )?; Store::ingredient_checks(store, claim, asset_data, validation_log)?; @@ -2771,7 +2881,7 @@ impl Store { }) } - fn get_store_from_memory( + pub fn get_store_from_memory( asset_type: &str, data: &[u8], validation_log: &mut impl StatusTracker, diff --git a/sdk/src/trust_handler.rs b/sdk/src/trust_handler.rs @@ -0,0 +1,202 @@ +// Copyright 2023 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use std::{ + collections::HashSet, + io::{read_to_string, Cursor, Read}, + str::FromStr, +}; + +use asn1_rs::{oid, Oid}; + +use crate::{hash_utils::hash_sha256, utils::base64, Error, Result}; + +pub(crate) static EMAIL_PROTECTION_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .4); +pub(crate) static TIMESTAMPING_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .8); +pub(crate) static OCSP_SIGNING_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .9); +pub(crate) static DOCUMENT_SIGNING_OID: Oid<'static> = oid!(1.3.6 .1 .5 .5 .7 .3 .36); + +// Trait for supply configuration and handling of trust lists and EKU configuration store +pub(crate) trait TrustHandlerConfig { + fn new() -> Self + where + Self: Sized; + + // add trust anchors + fn load_trust_anchors_from_data(&mut self, trust_data: &mut dyn Read) -> Result<()>; + + // add allowed list + fn load_allowed_list(&mut self, allowed_list: &mut dyn Read) -> Result<()>; + + // append private trust anchors + fn append_private_trust_data(&mut self, private_anchors_data: &mut dyn Read) -> Result<()>; + + // clear all entries in trust handler list + fn clear(&mut self); + + // load EKU configuration + fn load_configuration(&mut self, config_data: &mut dyn Read) -> Result<()>; + + // list off auxillary allowed EKU Oid + fn get_auxillary_ekus(&self) -> Vec<Oid>; + + // list of all anchors + fn get_anchors(&self) -> Vec<Vec<u8>>; + + // set of allowed cert hashes + fn get_allowed_list(&self) -> &HashSet<String>; +} + +impl std::fmt::Debug for dyn TrustHandlerConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "TrustHandler Installed") + } +} + +pub(crate) fn has_allowed_oid<'a>( + eku: &x509_parser::extensions::ExtendedKeyUsage, + allowed_ekus: &'a [Oid], +) -> Option<&'a Oid<'a>> { + if eku.email_protection { + return Some(&EMAIL_PROTECTION_OID); + } + + if eku.time_stamping { + return Some(&TIMESTAMPING_OID); + } + + if eku.ocsp_signing { + return Some(&OCSP_SIGNING_OID); + } + + let mut last_oid = None; + if eku.other.iter().any(|v| { + allowed_ekus.iter().any(|oid| { + if oid == v { + last_oid = Some(oid); + true + } else { + false + } + }) + }) { + return last_oid; + } + None +} + +// load set of validation EKUs, ignoring unrecognized Oid lines +#[allow(dead_code)] +pub(crate) fn load_eku_configuration(config_data: &mut dyn Read) -> Result<Vec<String>> { + let mut oid_vec = Vec::new(); + + for line in read_to_string(config_data)?.lines() { + if Oid::from_str(line).is_ok() { + oid_vec.push(line.to_owned()); + } + } + Ok(oid_vec) +} + +pub(crate) fn load_trust_from_data(trust_data: &[u8]) -> Result<Vec<Vec<u8>>> { + let mut certs = Vec::new(); + + for pem_result in x509_parser::pem::Pem::iter_from_buffer(trust_data) { + let pem = pem_result.map_err(|_e| Error::CoseInvalidCert)?; + certs.push(pem.contents); + } + Ok(certs) +} + +// Pass through trust for the case of claim signer usage since it has known trust with context +// configured to all email protection, timestamping, ocsp signing and document signing +pub(crate) struct TrustPassThrough { + allowed_cert_set: HashSet<String>, + config_store: Vec<u8>, +} + +impl TrustHandlerConfig for TrustPassThrough { + fn new() -> Self + where + Self: Sized, + { + TrustPassThrough { + allowed_cert_set: HashSet::new(), + config_store: Vec::new(), + } + } + + fn load_trust_anchors_from_data(&mut self, _trust_data: &mut dyn std::io::Read) -> Result<()> { + Ok(()) + } + + fn append_private_trust_data( + &mut self, + _private_anchors_data: &mut dyn std::io::Read, + ) -> Result<()> { + Ok(()) + } + + fn clear(&mut self) {} + + fn load_configuration(&mut self, config_data: &mut dyn Read) -> Result<()> { + config_data.read_to_end(&mut self.config_store)?; + Ok(()) + } + + // list off auxillary allowed EKU Oid + fn get_auxillary_ekus(&self) -> Vec<Oid> { + let mut oids = Vec::new(); + if let Ok(oid_strings) = load_eku_configuration(&mut Cursor::new(&self.config_store)) { + for oid_str in &oid_strings { + if let Ok(oid) = Oid::from_str(oid_str) { + oids.push(oid); + } + } + } + if oids.is_empty() { + // return default + vec![ + EMAIL_PROTECTION_OID.to_owned(), + TIMESTAMPING_OID.to_owned(), + OCSP_SIGNING_OID.to_owned(), + DOCUMENT_SIGNING_OID.to_owned(), + ] + } else { + oids + } + } + + fn get_anchors(&self) -> Vec<Vec<u8>> { + Vec::new() + } + + fn load_allowed_list(&mut self, allowed_list: &mut dyn std::io::prelude::Read) -> Result<()> { + let mut buffer = Vec::new(); + allowed_list.read_to_end(&mut buffer)?; + + if let Ok(cert_list) = load_trust_from_data(&buffer) { + for cert_der in &cert_list { + let cert_sha256 = hash_sha256(cert_der); + let cert_hash_base64 = base64::encode(&cert_sha256); + + self.allowed_cert_set.insert(cert_hash_base64); + } + } + Ok(()) + } + + fn get_allowed_list(&self) -> &std::collections::HashSet<String> { + &self.allowed_cert_set + } +} diff --git a/sdk/src/utils/hash_utils.rs b/sdk/src/utils/hash_utils.rs @@ -455,15 +455,13 @@ where } } -/// Return a multihash (Sha256) of array of bytes +/// Return a Sha256 hash of array of bytes #[allow(dead_code)] -pub fn hash256(data: &[u8]) -> String { +pub fn hash_sha256(data: &[u8]) -> Vec<u8> { let mh = Sha2_256::digest(data); let digest = mh.digest(); - let wrapped: Multihash = wrap(Code::Sha2_256, digest); - // Return Base-64 encoded hash. - encode(multibase::Base::Base64, wrapped.as_bytes()) + digest.to_vec() } pub fn hash_sha1(data: &[u8]) -> Vec<u8> { diff --git a/sdk/src/wasm/mod.rs b/sdk/src/wasm/mod.rs @@ -19,3 +19,7 @@ pub(crate) mod util; pub(crate) mod webcrypto_validator; #[cfg(target_arch = "wasm32")] pub use webcrypto_validator::validate_async; +#[cfg(target_arch = "wasm32")] +pub(crate) mod webpki_trust_handler; +#[cfg(target_arch = "wasm32")] +pub(crate) use webpki_trust_handler::WebTrustHandlerConfig; diff --git a/sdk/src/wasm/webcrypto_validator.rs b/sdk/src/wasm/webcrypto_validator.rs @@ -26,6 +26,33 @@ use crate::{ utils::hash_utils::hash_by_alg, wasm::context::WindowOrWorker, Error, Result, SigningAlg, }; +pub struct RsaHashedImportParams { + name: String, + hash: String, +} + +impl RsaHashedImportParams { + pub fn new(name: &str, hash: &str) -> Self { + RsaHashedImportParams { + 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 + } +} + pub struct EcKeyImportParams { name: String, named_curve: String, @@ -135,7 +162,34 @@ fn pss_padding_from_hash(hash: &str, salt_len: &u32) -> Result<PaddingScheme> { } } -async fn async_validate( +// Validate an Ed25519 signature for the provided data. The pkey must +// be the raw bytes representing CompressedEdwardsY. The length must 32 bytes. +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)?; + + // convert to VerifyingKey + 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, @@ -149,6 +203,33 @@ async fn async_validate( let data_array_buf = data_as_array_buffer(&data); match algo.as_ref() { + "RSASSA-PKCS1-v1_5" => { + // used for certificate validation + // Create Key + let algorithm = RsaHashedImportParams::new(&algo, &hash).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(|_err| Error::WasmKey)? + .into(); + web_sys::console::debug_2(&"CryptoKey".into(), &crypto_key); + + // Create verifier + crypto_is_verified( + &subtle_crypto, + &algorithm, + &crypto_key, + &sig_array_buf, + &data_array_buf, + ) + .await + } "RSA-PSS" => { let spki = SubjectPublicKeyInfo::try_from(pkey.as_ref()) .map_err(|err| Error::WasmRsaKeyImport(err.to_string()))?; @@ -192,7 +273,10 @@ async fn async_validate( 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.unwrap().into(); + let crypto_key: CryptoKey = JsFuture::from(promise) + .await + .map_err(|_| Error::CoseInvalidCert)? + .into(); web_sys::console::debug_2(&"CryptoKey".into(), &crypto_key); // Create verifier @@ -206,10 +290,35 @@ async fn async_validate( ) .await } + "ED25519" => { + use x509_parser::{prelude::*, public_key::PublicKey}; + + // pull out raw Ed code points + 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), } } +// This interface is called from CoseValidator. RSA validation not supported here. 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()); @@ -313,8 +422,17 @@ pub async fn validate_async(alg: SigningAlg, sig: &[u8], data: &[u8], pkey: &[u8 ) .await } - // TODO: Can we cover Ed25519? - _ => return Err(Error::UnsupportedType), + SigningAlg::Ed25519 => { + async_validate( + "ED25519".to_string(), + "SHA-512".to_string(), + 0, + pkey.to_vec(), + sig.to_vec(), + data.to_vec(), + ) + .await + } } } diff --git a/sdk/src/wasm/webpki_trust_handler.rs b/sdk/src/wasm/webpki_trust_handler.rs @@ -0,0 +1,657 @@ +// Copyright 2023 Adobe. All rights reserved. +// This file is licensed to you under the Apache License, +// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +// or the MIT license (http://opensource.org/licenses/MIT), +// at your option. + +// Unless required by applicable law or agreed to in writing, +// this software is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or +// implied. See the LICENSE-MIT and LICENSE-APACHE files for the +// specific language governing permissions and limitations under +// each license. + +use std::{ + collections::HashSet, + io::{BufRead, BufReader, Cursor, Read}, + str::FromStr, +}; + +use asn1_rs::{nom::AsBytes, Any, Class, Header, Tag}; +use x509_parser::{ + der_parser::der::{parse_der_integer, parse_der_sequence_of}, + oid_registry::Oid, + prelude::*, +}; + +use crate::{ + cose_validator::*, + error::{Error, Result}, + hash_utils::{hash_sha256, vec_compare}, + trust_handler::{ + has_allowed_oid, load_eku_configuration, load_trust_from_data, TrustHandlerConfig, + }, + utils::base64, + wasm::webcrypto_validator::async_validate, + SigningAlg, +}; + +// Struct to handle verification of trust chains using WebPki +pub(crate) struct WebTrustHandlerConfig { + pub trust_anchors: Vec<Vec<u8>>, + pub private_anchors: Vec<Vec<u8>>, + allowed_cert_set: HashSet<String>, + config_store: Vec<u8>, +} + +impl std::fmt::Debug for WebTrustHandlerConfig { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{} trust anchors, {} private anchors.", + self.trust_anchors.len(), + self.private_anchors.len() + ) + } +} + +impl WebTrustHandlerConfig { + pub fn load_default_trust(&mut self) -> Result<()> { + // load config store + let config = include_bytes!("../../tests/fixtures/certs/trust/store.cfg"); + let mut config_reader = Cursor::new(config); + self.load_configuration(&mut config_reader)?; + + // load debug/test private trust anchors + if cfg!(test) { + let pa = include_bytes!("../../tests/fixtures/certs/trust/test_cert_root_bundle.pem"); + let mut pa_reader = Cursor::new(pa); + + self.append_private_trust_data(&mut pa_reader)?; + } + + Ok(()) + } +} + +impl TrustHandlerConfig for WebTrustHandlerConfig { + fn new() -> Self { + let mut th = WebTrustHandlerConfig { + trust_anchors: Vec::new(), + private_anchors: Vec::new(), + allowed_cert_set: HashSet::new(), + config_store: Vec::new(), + }; + + if th.load_default_trust().is_err() { + th.clear(); // just use empty trust handler to fail automatically + } + + th + } + + // add trust anchors + fn load_trust_anchors_from_data(&mut self, trust_data_reader: &mut dyn Read) -> Result<()> { + let mut trust_data = Vec::new(); + trust_data_reader.read_to_end(&mut trust_data)?; + + let mut anchors = load_trust_from_data(&trust_data)?; + self.trust_anchors.append(&mut anchors); + Ok(()) + } + + // append private trust anchors + fn append_private_trust_data(&mut self, private_anchors_reader: &mut dyn Read) -> Result<()> { + let mut private_anchors_data = Vec::new(); + private_anchors_reader.read_to_end(&mut private_anchors_data)?; + + let mut anchors = load_trust_from_data(&private_anchors_data)?; + self.private_anchors.append(&mut anchors); + + Ok(()) + } + + fn clear(&mut self) { + self.trust_anchors = Vec::new(); + self.private_anchors = Vec::new(); + } + + // load EKU configuration + fn load_configuration(&mut self, config_data: &mut dyn Read) -> Result<()> { + config_data.read_to_end(&mut self.config_store)?; + Ok(()) + } + + // list off auxillary allowed EKU Oid + fn get_auxillary_ekus(&self) -> Vec<Oid> { + let mut oids = Vec::new(); + if let Ok(oid_strings) = load_eku_configuration(&mut Cursor::new(&self.config_store)) { + for oid_str in &oid_strings { + if let Ok(oid) = Oid::from_str(oid_str) { + oids.push(oid); + } + } + } + oids + } + + fn get_anchors(&self) -> Vec<Vec<u8>> { + let mut anchors = Vec::new(); + + anchors.append(&mut self.trust_anchors.clone()); + anchors.append(&mut self.private_anchors.clone()); + + anchors + } + + // add allowed list entries + fn load_allowed_list(&mut self, allowed_list: &mut dyn Read) -> Result<()> { + let mut buffer = Vec::new(); + allowed_list.read_to_end(&mut buffer)?; + + if let Ok(cert_list) = load_trust_from_data(&buffer) { + for cert_der in &cert_list { + let cert_sha256 = hash_sha256(cert_der); + let cert_hash_base64 = base64::encode(&cert_sha256); + + self.allowed_cert_set.insert(cert_hash_base64); + } + } + + // try to load the of base64 encoded encoding of the sha256 hash of the certificate DER encoding + let reader = Cursor::new(buffer); + let buf_reader = BufReader::new(reader); + + let mut inside_cert_block = false; + for l in buf_reader.lines().flatten() { + if l.contains("-----BEGIN") { + inside_cert_block = true; + } + if l.contains("-----END") { + inside_cert_block = false; + } + + // sanity check that data is base64 encoded and outside of certificate block + if !inside_cert_block && base64::decode(&l).is_ok() && !l.is_empty() { + self.allowed_cert_set.insert(l); + } + } + + Ok(()) + } + + // set of allowed cert hashes + fn get_allowed_list(&self) -> &HashSet<String> { + &self.allowed_cert_set + } +} + +fn find_allowed_eku<'a>(cert_der: &'a [u8], allowed_ekus: &'a Vec<Oid<'a>>) -> Option<&'a Oid<'a>> { + if let Ok((_rem, cert)) = X509Certificate::from_der(cert_der) { + if let Ok(Some(eku)) = cert.extended_key_usage() { + if let Some(o) = has_allowed_oid(eku.value, allowed_ekus) { + return Some(o); + } + } + } + None +} +fn cert_signing_alg(cert: &x509_parser::certificate::X509Certificate) -> Option<String> { + let cert_alg = cert.signature_algorithm.algorithm.clone(); + + let signing_alg = if cert_alg == SHA256_WITH_RSAENCRYPTION_OID { + "rsa256".to_string() + } else if cert_alg == SHA384_WITH_RSAENCRYPTION_OID { + "rsa384".to_string() + } else if cert_alg == SHA512_WITH_RSAENCRYPTION_OID { + "rsa512".to_string() + } else if cert_alg == ECDSA_WITH_SHA256_OID { + SigningAlg::Es256.to_string() + } else if cert_alg == ECDSA_WITH_SHA384_OID { + SigningAlg::Es384.to_string() + } else if cert_alg == ECDSA_WITH_SHA512_OID { + SigningAlg::Es512.to_string() + } else if cert_alg == RSASSA_PSS_OID { + if let Some(parameters) = &cert.signature_algorithm.parameters { + let seq = match parameters.as_sequence() { + Ok(s) => s, + Err(_) => return None, + }; + + let (_i, (ha_alg, mgf_ai)) = match seq.parse(|i| { + let (i, h) = Header::from_der(i)?; + if h.class() != Class::ContextSpecific || h.tag() != Tag(0) { + return Err(nom::Err::Error(asn1_rs::Error::BerValueError)); + } + + let (i, ha_alg) = AlgorithmIdentifier::from_der(i) + .map_err(|_| nom::Err::Error(asn1_rs::Error::BerValueError))?; + + let (i, h) = Header::from_der(i)?; + if h.class() != Class::ContextSpecific || h.tag() != Tag(1) { + return Err(nom::Err::Error(asn1_rs::Error::BerValueError)); + } + + let (i, mgf_ai) = AlgorithmIdentifier::from_der(i) + .map_err(|_| nom::Err::Error(asn1_rs::Error::BerValueError))?; + + // Ignore anything that follows these two parameters. + + Ok((i, (ha_alg, mgf_ai))) + }) { + Ok((ii, (h, m))) => (ii, (h, m)), + Err(_) => return None, + }; + + let mgf_ai_parameters = match mgf_ai.parameters { + Some(m) => m, + None => return None, + }; + + let mgf_ai_parameters = match mgf_ai_parameters.as_sequence() { + Ok(m) => m, + Err(_) => return None, + }; + + let (_i, mgf_ai_params_algorithm) = match Any::from_der(&mgf_ai_parameters.content) { + Ok((i, m)) => (i, m), + Err(_) => return None, + }; + + let mgf_ai_params_algorithm = match mgf_ai_params_algorithm.as_oid() { + Ok(m) => m, + Err(_) => return None, + }; + + // must be the same + if ha_alg.algorithm != mgf_ai_params_algorithm { + return None; + } + + // check for one of the mandatory types + if ha_alg.algorithm == SHA256_OID { + "ps256".to_string() + } else if ha_alg.algorithm == SHA384_OID { + "ps384".to_string() + } else if ha_alg.algorithm == SHA512_OID { + "ps512".to_string() + } else { + return None; + } + } else { + return None; + } + } else if cert_alg == ED25519_OID { + SigningAlg::Ed25519.to_string() + } else { + return None; + }; + + Some(signing_alg) +} + +async fn verify_data( + cert_der: Vec<u8>, + sig_alg: Option<String>, + sig: Vec<u8>, + data: Vec<u8>, +) -> Result<bool> { + use x509_parser::prelude::*; + + let (_, cert) = + X509Certificate::from_der(cert_der.as_bytes()).map_err(|_e| Error::CoseCertUntrusted)?; + + let certificate_public_key = cert.public_key(); + if let Some(cert_alg_string) = sig_alg { + let (algo, hash, salt_len) = match cert_alg_string.as_str() { + "rsa256" => ( + "RSASSA-PKCS1-v1_5".to_string(), + "SHA-256".to_string().to_string(), + 0, + ), + "rsa384" => ("RSASSA-PKCS1-v1_5".to_string(), "SHA-384".to_string(), 0), + "rsa512" => ("RSASSA-PKCS1-v1_5".to_string(), "SHA-512".to_string(), 0), + "es256" => ("ECDSA".to_string(), "SHA-256".to_string().to_string(), 0), + "es384" => ("ECDSA".to_string(), "SHA-384".to_string(), 0), + "es512" => ("ECDSA".to_string(), "SHA-512".to_string(), 0), + "ps256" => ("RSA-PSS".to_string(), "SHA-256".to_string(), 32), + "ps384" => ("RSA-PSS".to_string(), "SHA-384".to_string(), 48), + "ps512" => ("RSA-PSS".to_string(), "SHA-512".to_string(), 64), + "ed25519" => ("ED25519".to_string(), "SHA-512".to_string(), 0), + _ => return Err(Error::UnsupportedType), + }; + + let adjusted_sig = if cert_alg_string.starts_with("es") { + let parsed_alg_string: SigningAlg = cert_alg_string + .parse() + .map_err(|_| Error::UnknownAlgorithm)?; + match der_to_p1363(&sig, parsed_alg_string) { + Some(p1363) => p1363, + None => sig, + } + } else { + sig + }; + + async_validate( + algo, + hash, + salt_len, + certificate_public_key.raw.to_vec(), + adjusted_sig, + data, + ) + .await + } else { + return Err(Error::BadParam("unknown alg processing cert".to_string())); + } +} +// convert der signatures to P1363 format: r | s +fn der_to_p1363(data: &[u8], alg: SigningAlg) -> Option<Vec<u8>> { + // handle if this is a der sequence + if let Ok((_, bo)) = parse_der_sequence_of(parse_der_integer)(data) { + let seq = bo.as_sequence().ok()?; + + if seq.len() != 2 { + return None; + } + + let rp = seq[0].as_bigint().ok()?; + let sp = seq[1].as_bigint().ok()?; + + let mut r = rp.to_str_radix(16); + let mut s = sp.to_str_radix(16); + + let sig_len: usize = match alg { + SigningAlg::Es256 => 64, + SigningAlg::Es384 => 96, + SigningAlg::Es512 => 132, + _ => return None, + }; + + // pad or truncate as needed + let rp = if r.len() > sig_len { + // truncate + let offset = r.len() - sig_len; + &r[offset..r.len()] + } else { + // pad + while r.len() != sig_len { + r.insert(0, '0'); + } + r.as_ref() + }; + + let sp = if s.len() > sig_len { + // truncate + let offset = s.len() - sig_len; + &s[offset..s.len()] + } else { + // pad + while s.len() != sig_len { + s.insert(0, '0'); + } + s.as_ref() + }; + + if rp.len() != sig_len || rp.len() != sp.len() { + return None; + } + + // merge r and s strings + let mut new_sig = rp.to_string(); + new_sig.push_str(sp); + + // convert back from hex string to byte array + let result = (0..new_sig.len()) + .step_by(2) + .map(|i| { + u8::from_str_radix(&new_sig[i..i + 2], 16) + .map_err(|_err| crate::Error::InvalidEcdsaSignature) + }) + .collect(); + + if let Ok(p1363) = result { + Some(p1363) + } else { + None + } + } else { + Some(data.to_vec()) + } +} + +async fn check_chain_order(certs: &[Vec<u8>]) -> Result<()> { + use x509_parser::prelude::*; + + let chain_length = certs.len(); + if chain_length < 2 { + return Ok(()); + } + + for i in 1..chain_length { + let (_, current_cert) = + X509Certificate::from_der(&certs[i - 1]).map_err(|_e| Error::CoseCertUntrusted)?; + + let issuer_der = certs[i].to_vec(); + let data = current_cert.tbs_certificate.as_ref(); + let sig = current_cert.signature_value.as_ref(); + + let sig_alg = cert_signing_alg(&current_cert); + + let result = verify_data(issuer_der, sig_alg, sig.to_vec(), data.to_vec()).await; + + // keep going as long as it validate + match result { + Ok(b) => { + if !b { + return Err(Error::OtherError("cert chain order invalid".into())); + } + } + Err(e) => return Err(e), + } + } + Ok(()) +} + +async fn on_trust_list( + th: &dyn TrustHandlerConfig, + certs: &[Vec<u8>], + ee_der: &[u8], +) -> Result<bool> { + use x509_parser::prelude::*; + + // check the cert against the allowed list first + let cert_sha256 = hash_sha256(ee_der); + let cert_hash_base64 = base64::encode(&cert_sha256); + if th.get_allowed_list().contains(&cert_hash_base64) { + return Ok(true); + } + + // add ee cert if needed to the chain + let full_chain = if !certs.is_empty() && vec_compare(ee_der, &certs[0]) { + certs.to_vec() + } else { + let mut full_chain: Vec<Vec<u8>> = Vec::new(); + full_chain.push(ee_der.to_vec()); + let mut in_chain = certs.to_vec(); + full_chain.append(&mut in_chain); + full_chain + }; + + // make sure chain is in the correct order and valid + check_chain_order(&full_chain).await?; + + // build anchors and check against trust anchors, + let mut anchors: Vec<X509Certificate> = Vec::new(); + let source_anchors = th.get_anchors(); + for anchor_der in &source_anchors { + let (_, anchor) = + X509Certificate::from_der(anchor_der).map_err(|_e| Error::CoseCertUntrusted)?; + anchors.push(anchor); + } + + if anchors.is_empty() { + return Ok(false); + } + + // work back from last cert in chain against the trust anchors + for cert in certs.iter().rev() { + let (_, chain_cert) = + X509Certificate::from_der(cert).map_err(|_e| Error::CoseCertUntrusted)?; + + for anchor in &source_anchors { + let data = chain_cert.tbs_certificate.as_ref(); + let sig = chain_cert.signature_value.as_ref(); + + let sig_alg = cert_signing_alg(&chain_cert); + + let (_, anchor_cert) = + X509Certificate::from_der(anchor).map_err(|_e| Error::CoseCertUntrusted)?; + + if chain_cert.issuer() == anchor_cert.subject() { + let result = + verify_data(anchor.clone(), sig_alg, sig.to_vec(), data.to_vec()).await; + + match result { + Ok(b) => { + if b { + return Ok(true); + } + } + Err(_) => continue, + } + } + } + } + // todo: consider (path check and names restrictions) + + Ok(false) +} + +// verify certificate and trust chain +pub(crate) async fn verify_trust_async( + th: &dyn TrustHandlerConfig, + chain_der: &[Vec<u8>], + cert_der: &[u8], +) -> Result<bool> { + // check configured EKUs against end-entity cert + find_allowed_eku(cert_der, &th.get_auxillary_ekus()).ok_or(Error::CoseCertUntrusted)?; + + on_trust_list(th, chain_der, cert_der).await +} + +#[cfg(test)] +pub mod tests { + #![allow(clippy::expect_used)] + #![allow(clippy::panic)] + #![allow(clippy::unwrap_used)] + + #[cfg(target_arch = "wasm32")] + use wasm_bindgen_test::*; + + use super::*; + #[cfg_attr(not(target_arch = "wasm32"), test)] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + #[wasm_bindgen_test] + async fn test_trust_store() { + let mut th = WebTrustHandlerConfig::new(); + th.clear(); + + th.load_default_trust().unwrap(); + + // test all the certs + let ps256 = include_bytes!("../../tests/fixtures/certs/ps256.pub"); + let ps384 = include_bytes!("../../tests/fixtures/certs/ps384.pub"); + let ps512 = include_bytes!("../../tests/fixtures/certs/ps512.pub"); + let es256 = include_bytes!("../../tests/fixtures/certs/es256.pub"); + let es384 = include_bytes!("../../tests/fixtures/certs/es384.pub"); + let es512 = include_bytes!("../../tests/fixtures/certs/es512.pub"); + let ed25519 = include_bytes!("../../tests/fixtures/certs/ed25519.pub"); + + let ps256_certs = load_trust_from_data(ps256).unwrap(); + let ps384_certs = load_trust_from_data(ps384).unwrap(); + let ps512_certs = load_trust_from_data(ps512).unwrap(); + let es256_certs = load_trust_from_data(es256).unwrap(); + let es384_certs = load_trust_from_data(es384).unwrap(); + let es512_certs = load_trust_from_data(es512).unwrap(); + let ed25519_certs = load_trust_from_data(ed25519).unwrap(); + + assert!(verify_trust_async(&th, &ps256_certs[1..], &ps256_certs[0]) + .await + .unwrap()); + assert!(verify_trust_async(&th, &ps384_certs[1..], &ps384_certs[0]) + .await + .unwrap()); + assert!(verify_trust_async(&th, &ps512_certs[1..], &ps512_certs[0]) + .await + .unwrap()); + assert!(verify_trust_async(&th, &es256_certs[1..], &es256_certs[0]) + .await + .unwrap()); + + assert!(verify_trust_async(&th, &es384_certs[1..], &es384_certs[0]) + .await + .unwrap()); + assert!(verify_trust_async(&th, &es512_certs[1..], &es512_certs[0]) + .await + .unwrap()); + + assert!( + verify_trust_async(&th, &ed25519_certs[1..], &ed25519_certs[0]) + .await + .unwrap() + ); + } + + #[cfg_attr(not(target_arch = "wasm32"), test)] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + #[wasm_bindgen_test] + async fn test_broken_trust_chain() { + let mut th = WebTrustHandlerConfig::new(); + th.clear(); + + th.load_default_trust().unwrap(); + + // test all the certs + let ps256 = include_bytes!("../../tests/fixtures/certs/ps256.pub"); + let ps384 = include_bytes!("../../tests/fixtures/certs/ps384.pub"); + let ps512 = include_bytes!("../../tests/fixtures/certs/ps512.pub"); + let es256 = include_bytes!("../../tests/fixtures/certs/es256.pub"); + let es384 = include_bytes!("../../tests/fixtures/certs/es384.pub"); + let es512 = include_bytes!("../../tests/fixtures/certs/es512.pub"); + let ed25519 = include_bytes!("../../tests/fixtures/certs/ed25519.pub"); + + let ps256_certs = load_trust_from_data(ps256).unwrap(); + let ps384_certs = load_trust_from_data(ps384).unwrap(); + let ps512_certs = load_trust_from_data(ps512).unwrap(); + let es256_certs = load_trust_from_data(es256).unwrap(); + let es384_certs = load_trust_from_data(es384).unwrap(); + let es512_certs = load_trust_from_data(es512).unwrap(); + let ed25519_certs = load_trust_from_data(ed25519).unwrap(); + + assert!(!verify_trust_async(&th, &ps256_certs[2..], &ps256_certs[0]) + .await + .unwrap()); + assert!(!verify_trust_async(&th, &ps384_certs[2..], &ps384_certs[0]) + .await + .unwrap()); + assert!(!verify_trust_async(&th, &ps512_certs[2..], &ps512_certs[0]) + .await + .unwrap()); + assert!(!verify_trust_async(&th, &es256_certs[2..], &es256_certs[0]) + .await + .unwrap()); + assert!(!verify_trust_async(&th, &es384_certs[2..], &es384_certs[0]) + .await + .unwrap()); + assert!(!verify_trust_async(&th, &es512_certs[2..], &es512_certs[0]) + .await + .unwrap()); + assert!( + !verify_trust_async(&th, &ed25519_certs[2..], &ed25519_certs[0]) + .await + .unwrap() + ); + } +} diff --git a/sdk/tests/fixtures/certs/ed25519.pub b/sdk/tests/fixtures/certs/ed25519.pub @@ -27,17 +27,4 @@ B8L8nFC9GWT9sJt08wNuxJ+pMB8GA1UdIwQYMBaAFF7mVgKz9Y4kTH4Mnumakchr qU6MMAUGAytlcANBABXakz5vcdftU8Pbe8JDEcFSc+rTnopT8DXYwhtOd3Hvo7Zv v0fTuwOYImxLdmu0J1u+ULxNqK+jRO7/jSncvwA= -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ -BgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD -VQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M -WTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjA2MTAxODQ2NDFaFw0zMjA2MDcxODQ2 -NDFaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo -ZXJlMRowGAYDVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRF -U1RJTkdfT05MWTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAGPUgK9q1H3D -eKMGqLGjTXJSpsrLpe0kpxkaFMe7KUAuo2MwYTAdBgNVHQ4EFgQUXuZWArP1jiRM -fgye6ZqRyGupTowwHwYDVR0jBBgwFoAUXuZWArP1jiRMfgye6ZqRyGupTowwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwBQYDK2VwA0EA8E79g54u2fUy -dfVLPyqKmtjenOUMvVQD7waNbetLY7kvUJZCd5eaDghk30/Q1RaNjiP/2RfA/it8 -zGxQnM2hCA== ------END CERTIFICATE----- + diff --git a/sdk/tests/fixtures/certs/es256.pub b/sdk/tests/fixtures/certs/es256.pub @@ -29,18 +29,3 @@ BBQOfI1yZr+iyeUAlO+1boDitg4vazAfBgNVHSMEGDAWgBRembiG4Xgb2VcVWnUA UrYpDsuojDAKBggqhkjOPQQDAgNJADBGAiEAtdZ3+05CzFo90fWeZ4woeJcNQC4B 84Ill3YeZVvR8ZECIQDVRdha1xEDKuNTAManY0zthSosfXcvLnZui1A/y/DYeg== -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICUzCCAfmgAwIBAgIUdmkq4byvgk2FSnddHqB2yjoD68gwCgYIKoZIzj0EAwIw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMFkwEwYHKoZIzj0CAQYI -KoZIzj0DAQcDQgAEre/KpcWwGEHt+mD4xso3xotRnRx2IEsMoYwVIKI7iEJrDEye -PcvJuBywA0qiMw2yvAvGOzW/fqUTu1jABrFIk6NjMGEwHQYDVR0OBBYEFF6ZuIbh -eBvZVxVadQBStikOy6iMMB8GA1UdIwQYMBaAFF6ZuIbheBvZVxVadQBStikOy6iM -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gA -MEUCIHBC1xLwkCWSGhVXFlSnQBx9cGZivXzCbt8BuwRqPSUoAiEAteZQDk685yh9 -jgOTkp4H8oAmM1As+qlkRK2b+CHAQ3k= ------END CERTIFICATE----- diff --git a/sdk/tests/fixtures/certs/es384.pub b/sdk/tests/fixtures/certs/es384.pub @@ -32,19 +32,3 @@ A2cAMGQCMCE7nuuZLMHG+vX/QNRszFYg2sHUnFUez9flD24Az8jCUGfF0yaY0EiT +3f6o6ARgwIwT796eCDU7/lS4rQiLi+fBCt/rCktXw+LlZC2oGvB3CUfJwjOglPK euMh+KzQtk46 -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIICkTCCAhagAwIBAgIUIngKvNC/BMF3TRIafgweprIbGgAwCgYIKoZIzj0EAwMw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAEX3FzSTnCcEAP3wteNaiy4GZzZ+ABd2Y7gJpfyZf3kkCuX/I3psFq -QBRvb3/FEBaDT4VbDNlZ0WLwtw5d3PI42Zufgpxemgfjf31d8H51eU3/IfAz5AFX -y/OarhObHgVvo2MwYTAdBgNVHQ4EFgQUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wHwYD -VR0jBBgwFoAUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPOgmJbVdhDh9KlgQXqE -FzHiCt347JG4strk22MXzOgxQ0LnXStIh+viC3S1INzuBgIxAI1jiUBX/V7Gg0y6 -Y/p6a63Xp2w+ia7vlUaUBWsR3ex9NNSTPLNoDkoTCSDOE2O20w== ------END CERTIFICATE----- diff --git a/sdk/tests/fixtures/certs/es512.pub b/sdk/tests/fixtures/certs/es512.pub @@ -35,21 +35,3 @@ kyighKzu7FxvlSJZZw4w9kYGariAZska0n/qOA4v0Ad2xGVOHxNHDC/NVo2sd+kN wyQq55/Te5DQ2Y8pwWJdOgUCQgHMKMT+fk1+BdFrUlAdCSand+pkmyE+WImAVweg v33yfPLxLH6i5Vn85Q7A/zqAxYEPHko/1VYB7voh6piNCtRboA== -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIC2jCCAjygAwIBAgIUYm+LFaltpWbS9kED6RRAamOdUHowCgYIKoZIzj0EAwQw -dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx -GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO -R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw -NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT -b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG -T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIGbMBAGByqGSM49AgEG -BSuBBAAjA4GGAAQBaifSYJBkf5fgH3FWPxRdV84qwIsLd7RcIDcRJrRkan0xUYP5 -zco7R4fFGaQ9YJB8dauyqiNg00LVuPajvKmhgEMAT4eSfEhYC25F2ggXQlBIK3Q7 -mkXwJTIJSObnbw4S9Jy3W6OVKq351VpgWUcmhvGRRejW7S/D8L2tzqRW7JPI2uSj -YzBhMB0GA1UdDgQWBBS6OykommTmfYoLJuPN4OU83wjPqjAfBgNVHSMEGDAWgBS6 -OykommTmfYoLJuPN4OU83wjPqjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBhjAKBggqhkjOPQQDBAOBiwAwgYcCQV4B6uKKoCWecEDlzj2xQLFPmnBQIOzD -nyiSEcYyrCKwMV+HYS39oM+T53NvukLKUTznHwdWc9++HNaqc+IjsDl6AkIB2lXd -5+s3xf0ioU91GJ4E13o5rpAULDxVSrN34A7BlsaXYQLnSkLMqva6E7nq2JBYjkqf -iwNQm1DDcQPtPTnddOs= ------END CERTIFICATE----- diff --git a/sdk/tests/fixtures/certs/ps256.pub b/sdk/tests/fixtures/certs/ps256.pub @@ -74,40 +74,3 @@ IJg1eY45+a3AOwhuSE34alhwooH2qDEuGK7KW1W5V/02jtsbYc2upEfkMzd2AaJb oqiZiY7ceUPTraAQ3pK535dcX3XA7p9RsGztyl7jma6HO2WmO9a6rGR2xCqW5/g9 wvq03sA= -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- -\ No newline at end of file diff --git a/sdk/tests/fixtures/certs/ps384.pub b/sdk/tests/fixtures/certs/ps384.pub @@ -74,40 +74,4 @@ x+GF2ilyW4/m9H2Gsf2FZLFlGIXyGCr9bW85ajUzq/iMCnNCBka+3RQJgx7MaLf8 k/E/+nklFqHDgT4uSoKiVlD8VawHFLddzNb9F9XmmiLnAStAt59obeK5GBfGjxCx LxWpjig= -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- + diff --git a/sdk/tests/fixtures/certs/ps512.pub b/sdk/tests/fixtures/certs/ps512.pub @@ -74,40 +74,3 @@ Eqhr24aYkxCYMHAYZGN6BaUfQx3CyJxMfz7FI56eE0ipZXJuVAdCrvawfBWyxCRU zyloU9+TNxSdQOBr3UwGh0GTusoBkdnPDnk/GtcQr3HFsRh7XeLnKpV2/lJ+EJT8 c9oJ+uc= -----END CERTIFICATE----- ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- diff --git a/sdk/tests/fixtures/certs/trust/allowed_list.hash b/sdk/tests/fixtures/certs/trust/allowed_list.hash @@ -0,0 +1,7 @@ +xGWQD1EkBm0E5SSGCKJh7ltL/cZkUazBB+Eb7EWlS+M= +WxFDp77782zNAn1/JDWrioQtDnUDmzSCbtM9hNSFoNw= +aOLMzPDuBSlSjpMavQ9o7IB3I42k4EHTRNkASYorlTw= +EAorXgVs44YquxzYXgW56jcKOFpSB3Q+2YBGQWEQsI8= +VtBIk7ipEWe0NeLafQHEZFWj3YfO7qz2wspFf0fxX2g= +b7Xt2zU6gvqHILHVSkkl6qIOEosQzEs/pNPp6SDAQAE= +/L3Fsm5AeKZ99K925ZGFT62vxwwxRNcqDk4UQ0nbja0= diff --git a/sdk/tests/fixtures/certs/trust/allowed_list.pem b/sdk/tests/fixtures/certs/trust/allowed_list.pem @@ -0,0 +1,187 @@ +-----BEGIN CERTIFICATE----- +MIICSDCCAfqgAwIBAgIUb+aBTX1CsjJ1iuMJ9kRudz/7qEcwBQYDK2VwMIGMMQsw +CQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEnMCUG +A1UECgweQzJQQSBUZXN0IEludGVybWVkaWF0ZSBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRgwFgYDVQQDDA9JbnRlcm1lZGlhdGUgQ0EwHhcNMjIw +NjEwMTg0NjQxWhcNMzAwODI2MTg0NjQxWjCBgDELMAkGA1UEBhMCVVMxCzAJBgNV +BAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUxHzAdBgNVBAoMFkMyUEEgVGVzdCBT +aWduaW5nIENlcnQxGTAXBgNVBAsMEEZPUiBURVNUSU5HX09OTFkxFDASBgNVBAMM +C0MyUEEgU2lnbmVyMCowBQYDK2VwAyEAMp5+0e83nNgQhdhBW8Rshkjy90sa1A9J +IzkItcDqCuKjeDB2MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH +AwQwDgYDVR0PAQH/BAQDAgbAMB0GA1UdDgQWBBTuLrYRqW4wu6yjIK1/iW8ud7dm +kTAfBgNVHSMEGDAWgBRXTAfC/JxQvRlk/bCbdPMDbsSfqTAFBgMrZXADQQB2R6vb +I+X8CTRC54j3NTvsUj454G1/bdzbiHVgl3n+ShOAJ85FJigE7Eoav7SeXeVnNjc8 +QZ1UrJGwgBBEP84G +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIChzCCAi6gAwIBAgIUcCTmJHYF8dZfG0d1UdT6/LXtkeYwCgYIKoZIzj0EAwIw +gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl +MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV +BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe +Fw0yMjA2MTAxODQ2NDBaFw0zMDA4MjYxODQ2NDBaMIGAMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU +ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG +A1UEAwwLQzJQQSBTaWduZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQPaL6R +kAkYkKU4+IryBSYxJM3h77sFiMrbvbI8fG7w2Bbl9otNG/cch3DAw5rGAPV7NWky +l3QGuV/wt0MrAPDoo3gwdjAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsG +AQUFBwMEMA4GA1UdDwEB/wQEAwIGwDAdBgNVHQ4EFgQUFznP0y83joiNOCedQkxT +tAMyNcowHwYDVR0jBBgwFoAUDnyNcma/osnlAJTvtW6A4rYOL2swCgYIKoZIzj0E +AwIDRwAwRAIgOY/2szXjslg/MyJFZ2y7OH8giPYTsvS7UPRP9GI9NgICIDQPMKrE +LQUJEtipZ0TqvI/4mieoyRCeIiQtyuS0LACz +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIICxTCCAkugAwIBAgIUZJcmeqpOjiLn2WzjnHcPacnyR3wwCgYIKoZIzj0EAwMw +gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl +MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV +BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe +Fw0yMjA2MTAxODQ2NDBaFw0zMDA4MjYxODQ2NDBaMIGAMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU +ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG +A1UEAwwLQzJQQSBTaWduZXIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATY7kzZpbiB +azyKpJ3gb/6TZPMaNfJ+P5YeyhCynvzQtQT5l3HnJosPxUWYKTkdwqBWwe9P7QKl +KyX4PpQXVkJXYHffOGYOOKhLkJbc3JpYZzrxlZzY2FiVa1NQ/wOPXiWjeDB2MAwG +A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwQwDgYDVR0PAQH/BAQD +AgbAMB0GA1UdDgQWBBQ/UiOw/o3+8lHc5CL/J5YeG0ZBkzAfBgNVHSMEGDAWgBRR +XrpEcgTojX05PD56hENdZUn87zAKBggqhkjOPQQDAwNoADBlAjEA7xpuFvKoU9MM +jqK2NMu700X8X0n6R4a+sNlCJ06J3eZVNr9utUl7Yjzoz6VIuYw9AjBZlqn9/4Ob +Nm5hwHx0MrIiPzljsWC8Taxhi3J9Z5XkZ+aCRr/x1m40orxS92DMbAM= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIDEDCCAnGgAwIBAgIUG1w/19In+2t1z2oZeJIRV2Ad9vIwCgYIKoZIzj0EAwQw +gYwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJl +MScwJQYDVQQKDB5DMlBBIFRlc3QgSW50ZXJtZWRpYXRlIFJvb3QgQ0ExGTAXBgNV +BAsMEEZPUiBURVNUSU5HX09OTFkxGDAWBgNVBAMMD0ludGVybWVkaWF0ZSBDQTAe +Fw0yMjA2MTAxODQ2NDBaFw0zMDA4MjYxODQ2NDBaMIGAMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNvbWV3aGVyZTEfMB0GA1UECgwWQzJQQSBU +ZXN0IFNpZ25pbmcgQ2VydDEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05MWTEUMBIG +A1UEAwwLQzJQQSBTaWduZXIwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABAHbhWCA +1ywh5K74+Al75qoC2FLcqEmIJQoU4lNpy5lsPU593LNjzmVUY8VTK8bK9SLnqhZC +0sdijlqitmSOJg/VLwB9uYVTnwBmize3WEQmlEyznYJ0XdmCZYJcB4ke1aQKpHw1 +jNhHGSxEO9xn7+50v1EzEwRa0yM934mF3UxCV2c5n6N4MHYwDAYDVR0TAQH/BAIw +ADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDBDAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0O +BBYEFIvqbCtmX4qEgOBTn9Vf/mMwaL/AMB8GA1UdIwQYMBaAFE+UvBLAWvNUMwjm +Pkiv67Cg97ZPMAoGCCqGSM49BAMEA4GMADCBiAJCAQJ03SWY5LuQApoOX0CB+sTa +m0xgSs5F8nbz5ZuqW5G0i11j+6LLqr//BIFOgoUYYpOmcWlc5HTtyVAHKrz/w1Hg +AkIAgnE2pI/5YNYctyWHAIHm8dash50/Zbxmt8s/UdlS6U9rQF8+YAGkb7N5WrAw +KffLdPJia7JTK7RSBYfs7RbMn1o= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIGsDCCBGSgAwIBAgIUfj5imtzP59mXEBNbWkgFaXLfgZkwQQYJKoZIhvcNAQEK +MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEF +AKIDAgEgMIGMMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNv +bWV3aGVyZTEnMCUGA1UECgweQzJQQSBUZXN0IEludGVybWVkaWF0ZSBSb290IENB +MRkwFwYDVQQLDBBGT1IgVEVTVElOR19PTkxZMRgwFgYDVQQDDA9JbnRlcm1lZGlh +dGUgQ0EwHhcNMjIwNjEwMTg0NjI4WhcNMzAwODI2MTg0NjI4WjCBgDELMAkGA1UE +BhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUxHzAdBgNVBAoM +FkMyUEEgVGVzdCBTaWduaW5nIENlcnQxGTAXBgNVBAsMEEZPUiBURVNUSU5HX09O +TFkxFDASBgNVBAMMC0MyUEEgU2lnbmVyMIICVjBBBgkqhkiG9w0BAQowNKAPMA0G +CWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAD +ggIPADCCAgoCggIBAOtiNSWBpKkHL78khDYV2HTYkVUmTu5dgn20GiUjOjWhAyWK +5uZL+iuHWmHUOq0xqC39R+hyaMkcIAUf/XcJRK40Jh1s2kJ4+kCk7+RB1n1xeZeJ +jrKhJ7zCDhH6eFVqO9Om3phcpZyKt01yDkhfIP95GzCILuPm5lLKYI3P0FmpC8zl +5ctevgG1TXJcX8bNU6fsHmmw0rBrVXUOR+N1MOFO/h++mxIhhLW601XrgYu6lDQD +IDOc/IxwzEp8+SAzL3v6NStBEYIq2d+alUgEUAOM8EzZsungs0dovMPGcfw7COsG +4xrdmLHExRau4E1g1ANfh2QsYdraNMtS/wcpI1PG6BkqUQ4zlMoO/CI2nZ5oninb +uL9x/UJt+a6VvHA0e4bTIcJJVq3/t69mpZtNe6WqDfGU+KLZ5HJSBNSW9KyWxSAU +FuDFAMtKZRZmTBonKHSjYlYtT+/WN7n/LgFJ2EYxPeFcGGPrVqRTw38g0QA8cyFe +wHfQBZUiSKdvMRB1zmIj+9nmYsh8ganJzuPaUgsGNVKoOJZHq+Ya3ewBjwslR91k +QtEGq43PRCvx4Vf+qiXeMCzK+L1Gg0v+jt80grz+y8Ch5/EkxitaH/ei/HRJGyvD +Zu7vrV6fbWLfWysBoFStHWirQcocYDGsFm9hh7bwM+W0qvNB/hbRQ0xfrMI9AgMB +AAGjeDB2MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwQwDgYD +VR0PAQH/BAQDAgbAMB0GA1UdDgQWBBQ3KHUtnyxDJcV9ncAu37sql3aF7jAfBgNV +HSMEGDAWgBQMMoDK5ZZtTx/7+QsB1qnlDNwA4jBBBgkqhkiG9w0BAQowNKAPMA0G +CWCGSAFlAwQCAQUAoRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAQUAogMCASAD +ggIBAAmBZubOjnCXIYmg2l1pDYH+XIyp5feayZz6Nhgz6xB7CouNgvcjkYW7EaqN +RuEkAJWJC68OnjMwwe6tXWQC4ifMKbVg8aj/IRaVAqkEL/MRQ89LnL9F9AGxeugJ +ulYtpqzFOJUKCPxcXGEoPyqjY7uMdTS14JzluKUwtiQZAm4tcwh/ZdRkt69i3wRq +VxIY2TK0ncvr4N9cX1ylO6m+GxufseFSO0NwEMxjonJcvsxFwjB8eFUhE0yH3pdD +gqE2zYfv9kjYkFGngtOqbCe2ixRM5oj9qoS+aKVdOi9m/gObcJkSW9JYAJD2GHLO +yLpGWRhg4xnn1s7n2W9pWB7+txNR7aqkrUNhZQdznNVdWRGOale4uHJRSPZAetQT +oYoVAyIX1ba1L/GRo52mOOT67AJhmIVVJJFVvMvvJeQ8ktW8GlxYjG9HHbRpE0S1 +Hv7FhOg0vEAqyrKcYn5JWYGAvEr0VqUqBPz3/QZ8gbmJwXinnUku1QZbGZUIFFIS +3MDaPXMWmp2KuNMxJXHE1CfaiD7yn2plMV5QZakde3+Kfo6qv2GISK+WYhnGZAY/ +LxtEOqwVrQpDQVJ5jgR/RKPIsOobdboR/aTVjlp7OOfvLxFUvD66zOiVa96fAsfw +ltU2Cp0uWdQKSLoktmQWLYgEe3QOqvgLDeYP2ScAdm+S+lHV +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/sdk/tests/fixtures/certs/trust/store.cfg b/sdk/tests/fixtures/certs/trust/store.cfg @@ -0,0 +1,11 @@ + +//id-kp-emailProtection +1.3.6.1.5.5.7.3.4 +//id-kp-documentSigning +1.3.6.1.5.5.7.3.36 +//id-kp-timeStamping +1.3.6.1.5.5.7.3.8 +//id-kp-OCSPSigning +1.3.6.1.5.5.7.3.9 +// MS C2PA Signing +1.3.6.1.4.1.311.76.59.1.9 +\ No newline at end of file diff --git a/sdk/tests/fixtures/certs/trust/test_cert_root_bundle.pem b/sdk/tests/fixtures/certs/trust/test_cert_root_bundle.pem @@ -0,0 +1,174 @@ +-----BEGIN CERTIFICATE----- +MIICEzCCAcWgAwIBAgIUW4fUnS38162x10PCnB8qFsrQuZgwBQYDK2VwMHcxCzAJ +BgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdoZXJlMRowGAYD +VQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRFU1RJTkdfT05M +WTEQMA4GA1UEAwwHUm9vdCBDQTAeFw0yMjA2MTAxODQ2NDFaFw0zMjA2MDcxODQ2 +NDFaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTESMBAGA1UEBwwJU29tZXdo +ZXJlMRowGAYDVQQKDBFDMlBBIFRlc3QgUm9vdCBDQTEZMBcGA1UECwwQRk9SIFRF +U1RJTkdfT05MWTEQMA4GA1UEAwwHUm9vdCBDQTAqMAUGAytlcAMhAGPUgK9q1H3D +eKMGqLGjTXJSpsrLpe0kpxkaFMe7KUAuo2MwYTAdBgNVHQ4EFgQUXuZWArP1jiRM +fgye6ZqRyGupTowwHwYDVR0jBBgwFoAUXuZWArP1jiRMfgye6ZqRyGupTowwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwBQYDK2VwA0EA8E79g54u2fUy +dfVLPyqKmtjenOUMvVQD7waNbetLY7kvUJZCd5eaDghk30/Q1RaNjiP/2RfA/it8 +zGxQnM2hCA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC2jCCAjygAwIBAgIUYm+LFaltpWbS9kED6RRAamOdUHowCgYIKoZIzj0EAwQw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMIGbMBAGByqGSM49AgEG +BSuBBAAjA4GGAAQBaifSYJBkf5fgH3FWPxRdV84qwIsLd7RcIDcRJrRkan0xUYP5 +zco7R4fFGaQ9YJB8dauyqiNg00LVuPajvKmhgEMAT4eSfEhYC25F2ggXQlBIK3Q7 +mkXwJTIJSObnbw4S9Jy3W6OVKq351VpgWUcmhvGRRejW7S/D8L2tzqRW7JPI2uSj +YzBhMB0GA1UdDgQWBBS6OykommTmfYoLJuPN4OU83wjPqjAfBgNVHSMEGDAWgBS6 +OykommTmfYoLJuPN4OU83wjPqjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBhjAKBggqhkjOPQQDBAOBiwAwgYcCQV4B6uKKoCWecEDlzj2xQLFPmnBQIOzD +nyiSEcYyrCKwMV+HYS39oM+T53NvukLKUTznHwdWc9++HNaqc+IjsDl6AkIB2lXd +5+s3xf0ioU91GJ4E13o5rpAULDxVSrN34A7BlsaXYQLnSkLMqva6E7nq2JBYjkqf +iwNQm1DDcQPtPTnddOs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICkTCCAhagAwIBAgIUIngKvNC/BMF3TRIafgweprIbGgAwCgYIKoZIzj0EAwMw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEX3FzSTnCcEAP3wteNaiy4GZzZ+ABd2Y7gJpfyZf3kkCuX/I3psFq +QBRvb3/FEBaDT4VbDNlZ0WLwtw5d3PI42Zufgpxemgfjf31d8H51eU3/IfAz5AFX +y/OarhObHgVvo2MwYTAdBgNVHQ4EFgQUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wHwYD +VR0jBBgwFoAUe+FK5t6/bQGIcGY6kkeIKTX/bJ0wDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPOgmJbVdhDh9KlgQXqE +FzHiCt347JG4strk22MXzOgxQ0LnXStIh+viC3S1INzuBgIxAI1jiUBX/V7Gg0y6 +Y/p6a63Xp2w+ia7vlUaUBWsR3ex9NNSTPLNoDkoTCSDOE2O20w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICUzCCAfmgAwIBAgIUdmkq4byvgk2FSnddHqB2yjoD68gwCgYIKoZIzj0EAwIw +dzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlTb21ld2hlcmUx +GjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBGT1IgVEVTVElO +R19PTkxZMRAwDgYDVQQDDAdSb290IENBMB4XDTIyMDYxMDE4NDY0MFoXDTMyMDYw +NzE4NDY0MFowdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQHDAlT +b21ld2hlcmUxGjAYBgNVBAoMEUMyUEEgVGVzdCBSb290IENBMRkwFwYDVQQLDBBG +T1IgVEVTVElOR19PTkxZMRAwDgYDVQQDDAdSb290IENBMFkwEwYHKoZIzj0CAQYI +KoZIzj0DAQcDQgAEre/KpcWwGEHt+mD4xso3xotRnRx2IEsMoYwVIKI7iEJrDEye +PcvJuBywA0qiMw2yvAvGOzW/fqUTu1jABrFIk6NjMGEwHQYDVR0OBBYEFF6ZuIbh +eBvZVxVadQBStikOy6iMMB8GA1UdIwQYMBaAFF6ZuIbheBvZVxVadQBStikOy6iM +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gA +MEUCIHBC1xLwkCWSGhVXFlSnQBx9cGZivXzCbt8BuwRqPSUoAiEAteZQDk685yh9 +jgOTkp4H8oAmM1As+qlkRK2b+CHAQ3k= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/sdk/tests/fixtures/cloud_manifest.c2pa b/sdk/tests/fixtures/cloud_manifest.c2pa Binary files differ. diff --git a/sdk/tests/fixtures/video1.mp4 b/sdk/tests/fixtures/video1.mp4 Binary files differ. diff --git a/sdk/tests/integration.rs b/sdk/tests/integration.rs @@ -20,12 +20,15 @@ mod integration_1 { use c2pa::{ assertions::{c2pa_action, Action, Actions}, - create_signer, Ingredient, Manifest, ManifestStore, Result, Signer, SigningAlg, + create_signer, settings, Ingredient, Manifest, ManifestStore, Result, Signer, SigningAlg, }; use tempfile::tempdir; const GENERATOR: &str = "app"; + // prevent tests from polluting the results of each other because of Rust unit test concurrency + static PROTECT: std::sync::Mutex<u32> = std::sync::Mutex::new(1); // prevent tests from polluting the results of each other + fn get_temp_signer() -> Box<dyn Signer> { // sign and embed into the target file let mut signcert_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -36,9 +39,59 @@ mod integration_1 { .expect("get_signer_from_files") } + fn configure_trust( + trust_anchors: Option<String>, + allowed_list: Option<String>, + trust_config: Option<String>, + ) -> Result<()> { + let ta = r#"{"trust": { "trust_anchors": replacement_val } }"#; + let al = r#"{"trust": { "allowed_list": replacement_val } }"#; + let tc = r#"{"trust": { "trust_config": replacement_val } }"#; + + let mut enable_trust_checks = false; + if let Some(trust_list) = trust_anchors { + let replacement_val = serde_json::Value::String(trust_list).to_string(); // escape string + let setting = ta.replace("replacement_val", &replacement_val); + + c2pa::settings::load_settings_from_str(&setting, "json")?; + + enable_trust_checks = true; + } + + if let Some(allowed_list) = allowed_list { + let replacement_val = serde_json::Value::String(allowed_list).to_string(); // escape string + let setting = al.replace("replacement_val", &replacement_val); + + c2pa::settings::load_settings_from_str(&setting, "json")?; + + enable_trust_checks = true; + } + + if let Some(trust_config) = trust_config { + let replacement_val = serde_json::Value::String(trust_config).to_string(); // escape string + let setting = tc.replace("replacement_val", &replacement_val); + + c2pa::settings::load_settings_from_str(&setting, "json")?; + + enable_trust_checks = true; + } + + // enable trust checks + if enable_trust_checks { + c2pa::settings::load_settings_from_str( + r#"{"verify": { "verify_trust": true} }"#, + "json", + )?; + } + + Ok(()) + } + #[test] #[cfg(feature = "file_io")] fn test_embed_manifest() -> Result<()> { + let _protect = PROTECT.lock().unwrap(); + // set up parent and destination paths let dir = tempdir()?; let output_path = dir.path().join("test_file.jpg"); @@ -47,6 +100,17 @@ mod integration_1 { let mut ingredient_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); ingredient_path.push("tests/fixtures/libpng-test.png"); + let config = include_bytes!("../tests/fixtures/certs/trust/store.cfg"); + let priv_trust = include_bytes!("../tests/fixtures/certs/trust/test_cert_root_bundle.pem"); + + // Configure before first use so that trust settings are used for all calls. + // In production code you should check that the file is indeed UTF-8 text. + configure_trust( + Some(String::from_utf8_lossy(priv_trust).to_string()), + None, + Some(String::from_utf8_lossy(config).to_string()), + )?; + // create a new Manifest let mut manifest = Manifest::new(GENERATOR.to_owned()); @@ -107,6 +171,10 @@ mod integration_1 { #[test] #[cfg(feature = "file_io")] fn test_embed_json_manifest() -> Result<()> { + let _protect = PROTECT.lock().unwrap(); + + settings::reset_default_settings().unwrap(); + // set up parent and destination paths let dir = tempdir()?; let output_path = dir.path().join("test_file.jpg");