diff --git a/cawg_identity/src/claim_aggregation/ica_signature_verifier.rs b/cawg_identity/src/claim_aggregation/ica_signature_verifier.rs index 7b4fc2ff0..6379406e2 100644 --- a/cawg_identity/src/claim_aggregation/ica_signature_verifier.rs +++ b/cawg_identity/src/claim_aggregation/ica_signature_verifier.rs @@ -12,6 +12,7 @@ // each license. use async_trait::async_trait; +use c2pa_status_tracker::{log_item, StatusTracker}; use coset::{CoseSign1, RegisteredLabelWithPrivate, TaggedCborSerializable}; use crate::{ @@ -45,13 +46,34 @@ impl SignatureVerifier for IcaSignatureVerifier { async fn check_signature( &self, - _signer_payload: &SignerPayload, + signer_payload: &SignerPayload, signature: &[u8], + status_tracker: &mut StatusTracker, ) -> Result> { + if signer_payload.sig_type != super::CAWG_ICA_SIG_TYPE { + // TO DO: Where would we get assertion label? + log_item!( + "NEED TO FIND LABEL".to_owned(), + "unsupported signature type", + "X509SignatureVerifier::check_signature" + ) + .validation_status("cawg.identity.sig_type.unknown") + .failure_no_throw( + status_tracker, + ValidationError::::UnknownSignatureType( + signer_payload.sig_type.clone(), + ), + ); + + return Err(ValidationError::UnknownSignatureType( + signer_payload.sig_type.clone(), + )); + } + // The signature should be a `CoseSign1` object. let sign1 = CoseSign1::from_tagged_slice(signature)?; - // Identify the signature + // Identify the signature. let _ssi_alg = if let Some(ref alg) = sign1.protected.header.alg { match alg { // TEMPORARY: Require EdDSA algorithm. diff --git a/cawg_identity/src/claim_aggregation/mod.rs b/cawg_identity/src/claim_aggregation/mod.rs index 9fa0ceafb..cb419d217 100644 --- a/cawg_identity/src/claim_aggregation/mod.rs +++ b/cawg_identity/src/claim_aggregation/mod.rs @@ -29,3 +29,5 @@ mod ica_validation_error; pub use ica_validation_error::IcaValidationError; pub(crate) mod w3c_vc; + +const CAWG_ICA_SIG_TYPE: &str = "cawg.identity_claims_aggregation"; diff --git a/cawg_identity/src/identity_assertion/assertion.rs b/cawg_identity/src/identity_assertion/assertion.rs index ae9302eec..7dc60fc33 100644 --- a/cawg_identity/src/identity_assertion/assertion.rs +++ b/cawg_identity/src/identity_assertion/assertion.rs @@ -17,6 +17,7 @@ use std::{ }; use c2pa::{Manifest, Reader}; +use c2pa_status_tracker::{log_item, StatusTracker}; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; @@ -63,14 +64,31 @@ impl IdentityAssertion { /// Iterator returns a [`Result`] because each assertion may fail to parse. /// /// Aside from CBOR parsing, no further validation is performed. - pub fn from_manifest( - manifest: &Manifest, - ) -> impl Iterator> + use<'_> { + pub fn from_manifest<'a>( + manifest: &'a Manifest, + status_tracker: &'a mut StatusTracker, + ) -> impl Iterator> + use<'a> { manifest .assertions() .iter() .filter(|a| a.label().starts_with("cawg.identity")) - .map(|a| a.to_assertion()) + .map(|a| (a.label().to_owned(), a.to_assertion())) + .inspect(|(label, r)| { + if let Err(err) = r { + // TO DO: a.label() is probably wrong (not a full JUMBF URI) + log_item!( + label.clone(), + "invalid CBOR", + "IdentityAssertion::from_manifest" + ) + .validation_status("cawg.identity.cbor.invalid") + .failure_no_throw( + status_tracker, + c2pa::Error::AssertionSpecificError(err.to_string()), + ); + } + }) + .map(move |(_label, r)| r) } /// Create a summary report from this `IdentityAssertion`. @@ -83,17 +101,20 @@ impl IdentityAssertion { pub async fn to_summary( &self, manifest: &Manifest, + status_tracker: &mut StatusTracker, verifier: &SV, ) -> impl Serialize where ::Output: 'static, { - self.to_summary_impl(manifest, verifier).await + self.to_summary_impl(manifest, status_tracker, verifier) + .await } pub(crate) async fn to_summary_impl( &self, manifest: &Manifest, + status_tracker: &mut StatusTracker, verifier: &SV, ) -> IdentityAssertionReport< <::Output as ToCredentialSummary>::CredentialSummary, @@ -101,7 +122,7 @@ impl IdentityAssertion { where ::Output: 'static, { - match self.validate(manifest, verifier).await { + match self.validate(manifest, status_tracker, verifier).await { Ok(named_actor) => { let summary = named_actor.to_summary(); @@ -120,13 +141,15 @@ impl IdentityAssertion { /// Summarize all of the identity assertions found for a [`Manifest`]. pub async fn summarize_all( manifest: &Manifest, + status_tracker: &mut StatusTracker, verifier: &SV, ) -> impl Serialize { - Self::summarize_all_impl(manifest, verifier).await + Self::summarize_all_impl(manifest, status_tracker, verifier).await } pub(crate) async fn summarize_all_impl( manifest: &Manifest, + status_tracker: &mut StatusTracker, verifier: &SV, ) -> IdentityAssertionsForManifest< <::Output as ToCredentialSummary>::CredentialSummary, @@ -139,9 +162,16 @@ impl IdentityAssertion { >, > = vec![]; - for assertion in Self::from_manifest(manifest) { + let assertion_results: Vec> = + Self::from_manifest(manifest, status_tracker).collect(); + + for assertion in assertion_results { let report = match assertion { - Ok(assertion) => assertion.to_summary_impl(manifest, verifier).await, + Ok(assertion) => { + assertion + .to_summary_impl(manifest, status_tracker, verifier) + .await + } Err(_) => { todo!("Handle assertion failed to parse case"); } @@ -163,6 +193,7 @@ impl IdentityAssertion { #[cfg(feature = "v1_api")] pub async fn summarize_manifest_store( store: &c2pa::ManifestStore, + status_tracker: &mut StatusTracker, verifier: &SV, ) -> impl Serialize { // NOTE: We can't write this using .map(...).collect() because there are async @@ -175,7 +206,7 @@ impl IdentityAssertion { > = BTreeMap::new(); for (id, manifest) in store.manifests() { - let report = Self::summarize_all_impl(manifest, verifier).await; + let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await; reports.insert(id.clone(), report); } @@ -189,6 +220,7 @@ impl IdentityAssertion { /// Summarize all of the identity assertions found for a [`Reader`]. pub async fn summarize_from_reader( reader: &Reader, + status_tracker: &mut StatusTracker, verifier: &SV, ) -> impl Serialize { // NOTE: We can't write this using .map(...).collect() because there are async @@ -201,7 +233,7 @@ impl IdentityAssertion { > = BTreeMap::new(); for (id, manifest) in reader.manifests() { - let report = Self::summarize_all_impl(manifest, verifier).await; + let report = Self::summarize_all_impl(manifest, status_tracker, verifier).await; reports.insert(id.clone(), report); } @@ -222,20 +254,37 @@ impl IdentityAssertion { pub async fn validate( &self, manifest: &Manifest, + status_tracker: &mut StatusTracker, verifier: &SV, ) -> Result> { - self.check_padding()?; + self.check_padding(status_tracker)?; - self.signer_payload.check_against_manifest(manifest)?; + self.signer_payload + .check_against_manifest(manifest, status_tracker)?; verifier - .check_signature(&self.signer_payload, &self.signature) + .check_signature(&self.signer_payload, &self.signature, status_tracker) .await } - fn check_padding(&self) -> Result<(), ValidationError> { + fn check_padding( + &self, + status_tracker: &mut StatusTracker, + ) -> Result<(), ValidationError> { if !self.pad1.iter().all(|b| *b == 0) { - return Err(ValidationError::InvalidPadding); + // TO DO: Where would we get assertion label? + log_item!( + "NEED TO FIND LABEL".to_owned(), + "invalid value in pad fields", + "SignerPayload::check_padding" + ) + .validation_status("cawg.identity.pad.invalid") + .failure(status_tracker, ValidationError::::InvalidPadding)?; + + // We'll only get to this line if `pad1` is invalid and the status tracker is + // configured to continue through recoverable errors. In that case, we want to + // avoid logging a second "invalid padding" warning if `pad2` is also invalid. + return Ok(()); } if let Some(pad2) = self.pad2.as_ref() { diff --git a/cawg_identity/src/identity_assertion/signature_verifier.rs b/cawg_identity/src/identity_assertion/signature_verifier.rs index bac866da6..c2d5f281a 100644 --- a/cawg_identity/src/identity_assertion/signature_verifier.rs +++ b/cawg_identity/src/identity_assertion/signature_verifier.rs @@ -12,6 +12,7 @@ // each license. use async_trait::async_trait; +use c2pa_status_tracker::StatusTracker; use serde::Serialize; use crate::{SignerPayload, ValidationError}; @@ -36,7 +37,7 @@ pub trait SignatureVerifier: Sync { /// included in the `SignatureError` variant of [`ValidationError`]. /// /// [`ValidationError`]: crate::ValidationError - type Error; + type Error: std::fmt::Debug; /// Verify the signature, returning an instance of [`Output`] if the /// signature is valid. @@ -46,6 +47,7 @@ pub trait SignatureVerifier: Sync { &self, signer_payload: &SignerPayload, signature: &[u8], + status_tracker: &mut StatusTracker, ) -> Result>; } @@ -79,6 +81,7 @@ pub trait SignatureVerifier { &self, signer_payload: &SignerPayload, signature: &[u8], + status_tracker: &mut StatusTracker, ) -> Result>; } diff --git a/cawg_identity/src/identity_assertion/signer_payload.rs b/cawg_identity/src/identity_assertion/signer_payload.rs index b93db4c58..859ac66a5 100644 --- a/cawg_identity/src/identity_assertion/signer_payload.rs +++ b/cawg_identity/src/identity_assertion/signer_payload.rs @@ -14,6 +14,7 @@ use std::{collections::HashSet, fmt::Debug, sync::LazyLock}; use c2pa::{HashedUri, Manifest}; +use c2pa_status_tracker::{log_item, StatusTracker}; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -41,9 +42,10 @@ pub struct SignerPayload { } impl SignerPayload { - pub(super) fn check_against_manifest( + pub(super) fn check_against_manifest( &self, manifest: &Manifest, + status_tracker: &mut StatusTracker, ) -> Result<(), ValidationError> { // All assertions mentioned in referenced_assertions also need to be referenced // in the claim. @@ -81,9 +83,17 @@ impl SignerPayload { // )); // } } else { - return Err(ValidationError::AssertionNotInClaim( - ref_assertion.url().to_owned(), - )); + // TO DO: Where would we get assertion label? + log_item!( + "NEED TO FIND LABEL".to_owned(), + "referenced assertion not in claim", + "SignerPayload::check_against_manifest" + ) + .validation_status("cawg.identity.assertion.mismatch") + .failure( + status_tracker, + ValidationError::::AssertionNotInClaim(ref_assertion.url().to_owned()), + )?; } } @@ -101,7 +111,14 @@ impl SignerPayload { false } }) { - return Err(ValidationError::NoHardBindingAssertion); + // TO DO: Where would we get assertion label? + log_item!( + "NEED TO FIND LABEL".to_owned(), + "no hard binding assertion", + "SignerPayload::check_against_manifest" + ) + .validation_status("cawg.identity.hard_binding_missing") + .failure(status_tracker, ValidationError::::NoHardBindingAssertion)?; } // Make sure no assertion references are duplicated. @@ -110,8 +127,19 @@ impl SignerPayload { for label in &ref_assertion_labels { let label = label.clone(); if labels.contains(&label) { - return Err(ValidationError::DuplicateAssertionReference(label)); + // TO DO: Where would we get assertion label? + log_item!( + "NEED TO FIND LABEL".to_owned(), + "multiple references to same assertion", + "SignerPayload::check_against_manifest" + ) + .validation_status("cawg.identity.assertion.duplicate") + .failure( + status_tracker, + ValidationError::::DuplicateAssertionReference(label.clone()), + )?; } + labels.insert(label); } diff --git a/cawg_identity/src/tests/builder/simple_case.rs b/cawg_identity/src/tests/builder/simple_case.rs index f982adc33..f7e4f205b 100644 --- a/cawg_identity/src/tests/builder/simple_case.rs +++ b/cawg_identity/src/tests/builder/simple_case.rs @@ -14,6 +14,7 @@ use std::io::{Cursor, Seek}; use c2pa::{Builder, Reader, SigningAlg}; +use c2pa_status_tracker::StatusTracker; #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use wasm_bindgen_test::wasm_bindgen_test; @@ -72,17 +73,17 @@ async fn simple_case() { assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.active_manifest().unwrap(); - let mut ia_iter = IdentityAssertion::from_manifest(manifest); + let mut st = StatusTracker::default(); + let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st); // Should find exactly one identity assertion. let ia = ia_iter.next().unwrap().unwrap(); - dbg!(&ia); - assert!(ia_iter.next().is_none()); + drop(ia_iter); // And that identity assertion should be valid for this manifest. let nsv = NaiveSignatureVerifier {}; - let naive_credential = ia.validate(manifest, &nsv).await.unwrap(); + let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap(); let nc_summary = naive_credential.to_summary(); let nc_json = serde_json::to_string(&nc_summary).unwrap(); @@ -127,17 +128,17 @@ async fn simple_case_async() { assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.active_manifest().unwrap(); - let mut ia_iter = IdentityAssertion::from_manifest(manifest); + let mut st = StatusTracker::default(); + let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st); // Should find exactly one identity assertion. let ia = ia_iter.next().unwrap().unwrap(); - dbg!(&ia); - assert!(ia_iter.next().is_none()); + drop(ia_iter); // And that identity assertion should be valid for this manifest. let nsv = NaiveSignatureVerifier {}; - let naive_credential = ia.validate(manifest, &nsv).await.unwrap(); + let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap(); let nc_summary = naive_credential.to_summary(); let nc_json = serde_json::to_string(&nc_summary).unwrap(); diff --git a/cawg_identity/src/tests/claim_aggregation/interop.rs b/cawg_identity/src/tests/claim_aggregation/interop.rs index 171b0d526..c4a6516c1 100644 --- a/cawg_identity/src/tests/claim_aggregation/interop.rs +++ b/cawg_identity/src/tests/claim_aggregation/interop.rs @@ -14,6 +14,7 @@ use std::{io::Cursor, str::FromStr}; use c2pa::{HashedUri, Reader}; +use c2pa_status_tracker::StatusTracker; use chrono::{DateTime, FixedOffset}; use iref::UriBuf; use non_empty_string::NonEmptyString; @@ -41,15 +42,17 @@ async fn adobe_connected_identities() { assert_eq!(reader.validation_status(), None); let manifest = reader.active_manifest().unwrap(); - let mut ia_iter = IdentityAssertion::from_manifest(manifest); + let mut st = StatusTracker::default(); + let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st); // Should find exactly one identity assertion. let ia = ia_iter.next().unwrap().unwrap(); assert!(ia_iter.next().is_none()); + drop(ia_iter); // And that identity assertion should be valid for this manifest. let isv = IcaSignatureVerifier {}; - let ica = ia.validate(manifest, &isv).await.unwrap(); + let ica = ia.validate(manifest, &mut st, &isv).await.unwrap(); // There should be exactly one verified identity. let ica_vc = ica.credential_subjects.first(); @@ -87,7 +90,8 @@ async fn adobe_connected_identities() { ); // Check the summary report for the entire manifest store. - let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &isv).await; + let mut st = StatusTracker::default(); + let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &mut st, &isv).await; let ia_json = serde_json::to_string(&ia_summary).unwrap(); assert_eq!( @@ -96,7 +100,8 @@ async fn adobe_connected_identities() { ); // Check the summary report for this manifest. - let ia_summary = IdentityAssertion::summarize_all(manifest, &isv).await; + let mut st = StatusTracker::default(); + let ia_summary = IdentityAssertion::summarize_all(manifest, &mut st, &isv).await; let ia_json = serde_json::to_string(&ia_summary).unwrap(); assert_eq!( @@ -121,8 +126,9 @@ async fn ims_multiple_manifests() { assert_eq!(reader.validation_status(), None); // Check the summary report for the entire manifest store. + let mut st = StatusTracker::default(); let isv = IcaSignatureVerifier {}; - let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &isv).await; + let ia_summary = IdentityAssertion::summarize_from_reader(&reader, &mut st, &isv).await; let ia_json = serde_json::to_string(&ia_summary).unwrap(); assert_eq!( diff --git a/cawg_identity/src/tests/examples/x509_signing.rs b/cawg_identity/src/tests/examples/x509_signing.rs index 04b20a1a6..efffde147 100644 --- a/cawg_identity/src/tests/examples/x509_signing.rs +++ b/cawg_identity/src/tests/examples/x509_signing.rs @@ -19,6 +19,7 @@ use std::io::{Cursor, Seek}; use c2pa::{Builder, Reader, SigningAlg}; use c2pa_crypto::raw_signature; +use c2pa_status_tracker::StatusTracker; use crate::{ builder::{AsyncIdentityAssertionBuilder, AsyncIdentityAssertionSigner}, @@ -85,13 +86,18 @@ async fn x509_signing() { assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.active_manifest().unwrap(); - let mut ia_iter = IdentityAssertion::from_manifest(manifest); + let mut st = StatusTracker::default(); + let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st); let ia = ia_iter.next().unwrap().unwrap(); assert!(ia_iter.next().is_none()); + drop(ia_iter); let x509_verifier = X509SignatureVerifier {}; - let sig_info = ia.validate(manifest, &x509_verifier).await.unwrap(); + let sig_info = ia + .validate(manifest, &mut st, &x509_verifier) + .await + .unwrap(); let cert_info = &sig_info.cert_info; assert_eq!(cert_info.alg.unwrap(), SigningAlg::Ed25519); diff --git a/cawg_identity/src/tests/fixtures/naive_credential_holder.rs b/cawg_identity/src/tests/fixtures/naive_credential_holder.rs index d37e89e84..bd4ba50af 100644 --- a/cawg_identity/src/tests/fixtures/naive_credential_holder.rs +++ b/cawg_identity/src/tests/fixtures/naive_credential_holder.rs @@ -23,6 +23,7 @@ use std::fmt::Debug; use async_trait::async_trait; +use c2pa_status_tracker::StatusTracker; use serde::Serialize; use crate::{ @@ -86,6 +87,7 @@ impl SignatureVerifier for NaiveSignatureVerifier { &self, signer_payload: &SignerPayload, signature: &[u8], + _status_tracker: &mut StatusTracker, ) -> Result> { let mut signer_payload_cbor: Vec = vec![]; ciborium::into_writer(signer_payload, &mut signer_payload_cbor) diff --git a/cawg_identity/src/tests/fixtures/validation_method/duplicate_assertion_reference.jpg b/cawg_identity/src/tests/fixtures/validation_method/duplicate_assertion_reference.jpg new file mode 100644 index 000000000..9452280d5 Binary files /dev/null and b/cawg_identity/src/tests/fixtures/validation_method/duplicate_assertion_reference.jpg differ diff --git a/cawg_identity/src/tests/fixtures/validation_method/extra_assertion_claim_v1.jpg b/cawg_identity/src/tests/fixtures/validation_method/extra_assertion_claim_v1.jpg new file mode 100644 index 000000000..55b199e65 Binary files /dev/null and b/cawg_identity/src/tests/fixtures/validation_method/extra_assertion_claim_v1.jpg differ diff --git a/cawg_identity/src/tests/examples/x509_signing.jpg b/cawg_identity/src/tests/fixtures/validation_method/extra_field.jpg similarity index 99% rename from cawg_identity/src/tests/examples/x509_signing.jpg rename to cawg_identity/src/tests/fixtures/validation_method/extra_field.jpg index 2de3d0599..ac39240e2 100644 Binary files a/cawg_identity/src/tests/examples/x509_signing.jpg and b/cawg_identity/src/tests/fixtures/validation_method/extra_field.jpg differ diff --git a/cawg_identity/src/tests/fixtures/validation_method/invalid_sig_type.jpg b/cawg_identity/src/tests/fixtures/validation_method/invalid_sig_type.jpg new file mode 100644 index 000000000..dc7bdc51e Binary files /dev/null and b/cawg_identity/src/tests/fixtures/validation_method/invalid_sig_type.jpg differ diff --git a/cawg_identity/src/tests/fixtures/validation_method/malformed_cbor.jpg b/cawg_identity/src/tests/fixtures/validation_method/malformed_cbor.jpg new file mode 100644 index 000000000..a897f431d Binary files /dev/null and b/cawg_identity/src/tests/fixtures/validation_method/malformed_cbor.jpg differ diff --git a/cawg_identity/src/tests/fixtures/validation_method/no_hard_binding.jpg b/cawg_identity/src/tests/fixtures/validation_method/no_hard_binding.jpg new file mode 100644 index 000000000..4a844606b Binary files /dev/null and b/cawg_identity/src/tests/fixtures/validation_method/no_hard_binding.jpg differ diff --git a/cawg_identity/src/tests/fixtures/validation_method/pad1_invalid.jpg b/cawg_identity/src/tests/fixtures/validation_method/pad1_invalid.jpg new file mode 100644 index 000000000..6afefe62e Binary files /dev/null and b/cawg_identity/src/tests/fixtures/validation_method/pad1_invalid.jpg differ diff --git a/cawg_identity/src/tests/identity_assertion/mod.rs b/cawg_identity/src/tests/identity_assertion/mod.rs index f7cceb843..33022fb08 100644 --- a/cawg_identity/src/tests/identity_assertion/mod.rs +++ b/cawg_identity/src/tests/identity_assertion/mod.rs @@ -12,3 +12,4 @@ // each license. mod signer_payload; +mod validation_method; diff --git a/cawg_identity/src/tests/identity_assertion/validation_method.rs b/cawg_identity/src/tests/identity_assertion/validation_method.rs new file mode 100644 index 000000000..7330fc4b7 --- /dev/null +++ b/cawg_identity/src/tests/identity_assertion/validation_method.rs @@ -0,0 +1,633 @@ +// Copyright 2025 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. + +//! This test suite checks the enforcement of generic identity assertion +//! validation as described in [§7.1, Validation method]. +//! +//! IMPORTANT: The CAWG SDK does not currently support the optional fields named +//! * `expected_partial_claim` +//! * `expected_claim_generator` +//! * `expected_countersigners` +//! +//! [§7.1, Validation method]: https://cawg.io/identity/1.1-draft/#_validation_method + +use std::io::Cursor; + +use c2pa::Reader; +use c2pa_crypto::raw_signature::SigningAlg; +use c2pa_status_tracker::{LogKind, StatusTracker}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::wasm_bindgen_test; + +use crate::{x509::X509SignatureVerifier, IdentityAssertion}; + +/// An identity assertion MUST contain a valid CBOR data structure that contains +/// the required fields as documented in the identity rule in [Section 5.2, +/// “CBOR schema”]. The `cawg.identity.cbor.invalid` error code SHALL be used to +/// report assertions that do not follow this rule. +/// +/// [Section 5.2, “CBOR schema”]: https://cawg.io/identity/1.1-draft/#_cbor_schema +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +async fn malformed_cbor() { + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_method/malformed_cbor.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find malformed CBOR error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + let ia_err = ia_results[0].as_ref().unwrap_err(); + assert_eq!(ia_err.to_string(), "could not decode assertion cawg.identity (version (no version), content type application/json): missing field `signer_payload`"); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "cawg.identity"); + assert_eq!(log.description, "invalid CBOR"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.cbor.invalid" + ); +} + +/// A validator SHALL NOT consider any extra fields not documented in the +/// `identity` rule during the validation process. +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +async fn extra_fields() { + // The test asset `extra_field.jpg` was written using a temporarily modified + // version of this SDK that generated an `other_stuff` string value at the top + // level of the identity assertion CBOR. + + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_method/extra_field.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find malformed CBOR error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + let ia = ia_results[0].as_ref().unwrap(); + dbg!(ia); + + let sp = &ia.signer_payload; + assert_eq!(sp.referenced_assertions.len(), 1); + + assert_eq!( + sp.referenced_assertions[0].url(), + "self#jumbf=c2pa.assertions/c2pa.hash.data".to_owned() + ); + + assert_eq!(sp.sig_type, "cawg.x509.cose".to_owned()); + + // TEMPORARY: Should report success code. + assert_eq!(status_tracker.logged_items().len(), 0); + + // let log = &status_tracker.logged_items()[0]; + // assert_eq!(log.kind, LogKind::Failure); + // assert_eq!(log.label, "cawg.identity"); + // assert_eq!(log.description, "invalid CBOR"); + // assert_eq!( + // log.validation_status.as_ref().unwrap().as_ref(), + // "cawg.identity.cbor.invalid" + // ); +} + +/// For each entry in `signer_payload.referenced_assertions`, the validator MUST +/// verify that the same entry exists in either the `created_assertions` or +/// `gathered_assertions` entry of the C2PA claim. (For version 1 claims, the +/// entry must appear in the `assertions` entry.) The +/// `cawg.identity.assertion.mismatch` error code SHALL be used to report +/// violations of this rule. +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +async fn assertion_not_in_claim_v1() { + // The test asset `extra_assertion_claim_v1.jpg` was written using a temporarily + // modified version of this SDK that incorrectly added an extra hashed URI to + // `referenced_assertions` that is not present in the claim. + + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_method/extra_assertion_claim_v1.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find extra assertion error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + // This condition is parseable, but incorrect. There should be a validation + // status log for this failure. + let ia = ia_results[0].as_ref().unwrap(); + + let sp = &ia.signer_payload; + assert_eq!(sp.referenced_assertions.len(), 2); + + assert_eq!( + sp.referenced_assertions[0].url(), + "self#jumbf=c2pa.assertions/c2pa.hash.data".to_owned() + ); + + assert_eq!( + sp.referenced_assertions[1].url(), + "self#jumbf=c2pa/urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4/c2pa.assertions/testing.bogus.assertion".to_owned() + ); + + assert_eq!(sp.sig_type, "cawg.x509.cose".to_owned()); + + let x509_verifier = X509SignatureVerifier {}; + let sig_info = ia + .validate( + reader.active_manifest().unwrap(), + &mut status_tracker, + &x509_verifier, + ) + .await + .unwrap(); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "NEED TO FIND LABEL"); // !!! + assert_eq!(log.description, "referenced assertion not in claim"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.assertion.mismatch" + ); + + let cert_info = &sig_info.cert_info; + assert_eq!(cert_info.alg.unwrap(), SigningAlg::Ed25519); + assert_eq!( + cert_info.issuer_org.as_ref().unwrap(), + "C2PA Test Signing Cert" + ); +} + +/// For each entry in `signer_payload.referenced_assertions`, the validator MUST +/// verify that the same entry exists in either the `created_assertions` or +/// `gathered_assertions` entry of the C2PA claim. (For version 1 claims, the +/// entry must appear in the `assertions` entry.) The +/// `cawg.identity.assertion.mismatch` error code SHALL be used to report +/// violations of this rule. +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +#[ignore] +async fn assertion_not_in_claim_v2() { + todo!("Generate a suitable V2 asset with an extra assertion"); +} + +/// The validator SHOULD verify that no entry in +/// `signer_payload.referenced_assertions` is duplicated. The +/// `cawg.identity.assertion.duplicate` error code SHALL be used to report +/// violations of this rule. +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +async fn duplicate_assertion_reference() { + // The test asset `duplicate_assertion_reference.jpg` was written using a + // temporarily modified version of this SDK that incorrectly added a + // duplicate hashed URI to `referenced_assertions`. + + let format = "image/jpeg"; + let test_image = + include_bytes!("../fixtures/validation_method/duplicate_assertion_reference.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find extra assertion error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + // This condition is parseable, but incorrect. There should be a validation + // status log for this failure. + let ia = ia_results[0].as_ref().unwrap(); + + let sp = &ia.signer_payload; + assert_eq!(sp.referenced_assertions.len(), 2); + + assert_eq!( + sp.referenced_assertions[0].url(), + "self#jumbf=c2pa.assertions/c2pa.hash.data".to_owned() + ); + + assert_eq!( + sp.referenced_assertions[1].url(), + "self#jumbf=c2pa.assertions/c2pa.hash.data".to_owned() + ); + + assert_eq!(sp.sig_type, "cawg.x509.cose".to_owned()); + + let x509_verifier = X509SignatureVerifier {}; + let sig_info = ia + .validate( + reader.active_manifest().unwrap(), + &mut status_tracker, + &x509_verifier, + ) + .await + .unwrap(); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "NEED TO FIND LABEL"); // !!! + assert_eq!(log.description, "multiple references to same assertion"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.assertion.duplicate" + ); + + let cert_info = &sig_info.cert_info; + assert_eq!(cert_info.alg.unwrap(), SigningAlg::Ed25519); + assert_eq!( + cert_info.issuer_org.as_ref().unwrap(), + "C2PA Test Signing Cert" + ); +} + +/// The validator MUST ensure that `signer_payload.referenced_assertions` +/// contains at least one _hard binding_ assertion as described in [Section 9.2, +/// “Hard bindings”] of the C2PA technical specification. The +/// `cawg.identity.hard_binding_missing` error code SHALL be used to report a +/// missing hard binding assertion. +/// +/// [Section 9.2, “Hard bindings”]: https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html#_hard_bindings +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +async fn no_hard_binding() { + // The test asset `duplicate_assertion.jpg` was written using a temporarily + // modified version of this SDK that incorrectly added a duplicate hashed URI to + // `referenced_assertions`. + + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_method/no_hard_binding.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find extra assertion error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + // This condition is parseable, but incorrect. There should be a validation + // status log for this failure. + let ia = ia_results[0].as_ref().unwrap(); + + let sp = &ia.signer_payload; + assert!(sp.referenced_assertions.is_empty()); + assert_eq!(sp.sig_type, "cawg.x509.cose".to_owned()); + + let x509_verifier = X509SignatureVerifier {}; + let sig_info = ia + .validate( + reader.active_manifest().unwrap(), + &mut status_tracker, + &x509_verifier, + ) + .await + .unwrap(); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "NEED TO FIND LABEL"); // !!! + assert_eq!(log.description, "no hard binding assertion"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.hard_binding_missing" + ); + + let cert_info = &sig_info.cert_info; + assert_eq!(cert_info.alg.unwrap(), SigningAlg::Ed25519); + assert_eq!( + cert_info.issuer_org.as_ref().unwrap(), + "C2PA Test Signing Cert" + ); +} + +/// The validator MUST maintain a list of valid `signer_payload.sig_type` values +/// and corresponding code paths for the `signature` values that it is prepared +/// to accept. Validators SHOULD be prepared to accept all signature types +/// described in [Section 8, “Credentials, signatures, and validation methods”]. +/// The `cawg.identity.sig_type.unknown` error code SHALL be used to report +/// assertions that contain unrecognized `signer_payload.sig_type` values. +/// +/// [Section 8, “Credentials, signatures, and validation methods”]: https://cawg.io/identity/1.1-draft/#_credentials_signatures_and_validation_methods +/// +/// This test is repeated for each implementation of [`SignatureVerifier`] +/// because the implementation of that type is the one that reports the error. +mod invalid_sig_type { + use std::io::Cursor; + + use c2pa::Reader; + use c2pa_status_tracker::{LogKind, StatusTracker}; + + use crate::{ + claim_aggregation::IcaSignatureVerifier, x509::X509SignatureVerifier, IdentityAssertion, + }; + + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test + )] + #[cfg_attr(target_os = "wasi", wstd::test)] + async fn x509_signature_verifier() { + // The test asset `invalid_sig_type.jpg` was written using a temporarily + // modified version of this SDK that added a proof-of-concept signature type + // that's not intended for general consumption. The validator in this test case + // is not configured to read that signature type. + + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_method/invalid_sig_type.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find extra assertion error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + // This condition is parseable, but incorrect. There should be a validation + // status log for this failure. + let ia = ia_results[0].as_ref().unwrap(); + + let sp = &ia.signer_payload; + assert_eq!(sp.referenced_assertions.len(), 1); + + assert_eq!( + sp.referenced_assertions[0].url(), + "self#jumbf=c2pa.assertions/c2pa.hash.data".to_owned() + ); + + assert_eq!(sp.sig_type, "INVALID.identity.naive_credential".to_owned()); + + // Intentionally not using NaiveSignatureVerifier here. + let x509_verifier = X509SignatureVerifier {}; + let err = ia + .validate( + reader.active_manifest().unwrap(), + &mut status_tracker, + &x509_verifier, + ) + .await + .unwrap_err(); + + // Comparing via strings since CoseError doesn't impl PartialEq. :-( + assert_eq!( + err.to_string(), + "unable to parse a signature of type \"INVALID.identity.naive_credential\"" + ); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "NEED TO FIND LABEL"); // !!! + assert_eq!(log.description, "unsupported signature type"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.sig_type.unknown" + ); + } + + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + #[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test + )] + #[cfg_attr(target_os = "wasi", wstd::test)] + async fn ica_verifier() { + // The test asset `invalid_sig_type.jpg` was written using a temporarily + // modified version of this SDK that added a proof-of-concept signature type + // that's not intended for general consumption. The validator in this test case + // is not configured to read that signature type. + + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_method/invalid_sig_type.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find extra assertion error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + // This condition is parseable, but incorrect. There should be a validation + // status log for this failure. + let ia = ia_results[0].as_ref().unwrap(); + + let sp = &ia.signer_payload; + assert_eq!(sp.referenced_assertions.len(), 1); + + assert_eq!( + sp.referenced_assertions[0].url(), + "self#jumbf=c2pa.assertions/c2pa.hash.data".to_owned() + ); + + assert_eq!(sp.sig_type, "INVALID.identity.naive_credential".to_owned()); + + // Intentionally not using NaiveSignatureVerifier here. + let ica_verifier = IcaSignatureVerifier {}; + let err = ia + .validate( + reader.active_manifest().unwrap(), + &mut status_tracker, + &ica_verifier, + ) + .await + .unwrap_err(); + + // Comparing via strings since CoseError doesn't impl PartialEq. :-( + assert_eq!( + err.to_string(), + "unable to parse a signature of type \"INVALID.identity.naive_credential\"" + ); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "NEED TO FIND LABEL"); // !!! + assert_eq!(log.description, "unsupported signature type"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.sig_type.unknown" + ); + } +} + +/// The `pad1` and `pad2` fields of an identity assertion MUST contain only +/// zero-value (`0x00`) bytes. The `cawg.identity.pad.invalid` error code SHALL +/// be used to report assertions that contain other values in these fields. +#[cfg_attr(not(target_arch = "wasm32"), tokio::test)] +#[cfg_attr( + all(target_arch = "wasm32", not(target_os = "wasi")), + wasm_bindgen_test +)] +#[cfg_attr(target_os = "wasi", wstd::test)] +async fn pad1_invalid() { + // The test asset `pad1_invalid.jpg` was written using a temporarily + // modified version of this SDK that incorrectly placed a non-zero value in the + // `pad1` field. + + let format = "image/jpeg"; + let test_image = include_bytes!("../fixtures/validation_method/pad1_invalid.jpg"); + + let mut test_image = Cursor::new(test_image); + + // Initial read with default `Reader` should pass without issues. + let reader = Reader::from_stream(format, &mut test_image).unwrap(); + assert_eq!(reader.validation_status(), None); + + // Re-parse with identity assertion code should find invalid pad error. + let mut status_tracker = StatusTracker::default(); + + let active_manifest = reader.active_manifest().unwrap(); + let ia_results: Vec> = + IdentityAssertion::from_manifest(active_manifest, &mut status_tracker).collect(); + + assert_eq!(ia_results.len(), 1); + + // This condition is parseable, but incorrect. There should be a validation + // status log for this failure. + let ia = ia_results[0].as_ref().unwrap(); + + let sp = &ia.signer_payload; + assert_eq!(sp.referenced_assertions.len(), 1); + + assert_eq!( + sp.referenced_assertions[0].url(), + "self#jumbf=c2pa.assertions/c2pa.hash.data".to_owned() + ); + + assert_eq!(sp.sig_type, "cawg.x509.cose".to_owned()); + + let x509_verifier = X509SignatureVerifier {}; + let sig_info = ia + .validate( + reader.active_manifest().unwrap(), + &mut status_tracker, + &x509_verifier, + ) + .await + .unwrap(); + + assert_eq!(status_tracker.logged_items().len(), 1); + + let log = &status_tracker.logged_items()[0]; + assert_eq!(log.kind, LogKind::Failure); + assert_eq!(log.label, "NEED TO FIND LABEL"); // !!! + assert_eq!(log.description, "invalid value in pad fields"); + assert_eq!( + log.validation_status.as_ref().unwrap().as_ref(), + "cawg.identity.pad.invalid" + ); + + let cert_info = &sig_info.cert_info; + assert_eq!(cert_info.alg.unwrap(), SigningAlg::Ed25519); + assert_eq!( + cert_info.issuer_org.as_ref().unwrap(), + "C2PA Test Signing Cert" + ); +} diff --git a/cawg_identity/src/tests/x509.rs b/cawg_identity/src/tests/x509.rs index fd1203d68..6749c1467 100644 --- a/cawg_identity/src/tests/x509.rs +++ b/cawg_identity/src/tests/x509.rs @@ -15,6 +15,7 @@ use std::io::{Cursor, Seek}; use c2pa::{Builder, Reader, SigningAlg}; use c2pa_crypto::raw_signature; +use c2pa_status_tracker::StatusTracker; #[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] use wasm_bindgen_test::wasm_bindgen_test; @@ -77,15 +78,20 @@ async fn simple_case() { assert_eq!(manifest_store.validation_status(), None); let manifest = manifest_store.active_manifest().unwrap(); - let mut ia_iter = IdentityAssertion::from_manifest(manifest); + let mut st = StatusTracker::default(); + let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st); // Should find exactly one identity assertion. let ia = ia_iter.next().unwrap().unwrap(); assert!(ia_iter.next().is_none()); + drop(ia_iter); // And that identity assertion should be valid for this manifest. let x509_verifier = X509SignatureVerifier {}; - let sig_info = ia.validate(manifest, &x509_verifier).await.unwrap(); + let sig_info = ia + .validate(manifest, &mut st, &x509_verifier) + .await + .unwrap(); let cert_info = &sig_info.cert_info; assert_eq!(cert_info.alg.unwrap(), SigningAlg::Ed25519); diff --git a/cawg_identity/src/x509/x509_signature_verifier.rs b/cawg_identity/src/x509/x509_signature_verifier.rs index 2ce2cf4e1..b788e7286 100644 --- a/cawg_identity/src/x509/x509_signature_verifier.rs +++ b/cawg_identity/src/x509/x509_signature_verifier.rs @@ -16,7 +16,7 @@ use c2pa_crypto::{ cose::{parse_cose_sign1, CertificateInfo, CoseError, Verifier}, raw_signature::RawSignatureValidationError, }; -use c2pa_status_tracker::StatusTracker; +use c2pa_status_tracker::{log_item, StatusTracker}; use coset::CoseSign1; use serde::Serialize; @@ -43,8 +43,21 @@ impl SignatureVerifier for X509SignatureVerifier { &self, signer_payload: &SignerPayload, signature: &[u8], + status_tracker: &mut StatusTracker, ) -> Result> { if signer_payload.sig_type != super::CAWG_X509_SIG_TYPE { + // TO DO: Where would we get assertion label? + log_item!( + "NEED TO FIND LABEL".to_owned(), + "unsupported signature type", + "X509SignatureVerifier::check_signature" + ) + .validation_status("cawg.identity.sig_type.unknown") + .failure_no_throw( + status_tracker, + ValidationError::::UnknownSignatureType(signer_payload.sig_type.clone()), + ); + return Err(ValidationError::UnknownSignatureType( signer_payload.sig_type.clone(), )); @@ -82,6 +95,7 @@ impl SignatureVerifier for X509SignatureVerifier { /// Contains information the X.509 certificate chain and the COSE signature that /// was used to generate this identity assertion signature. +#[derive(Debug)] pub struct X509SignatureInfo { /// Parsed COSE signature. pub cose_sign1: CoseSign1, diff --git a/sdk/src/error.rs b/sdk/src/error.rs index 49d4892ff..eccaa49d8 100644 --- a/sdk/src/error.rs +++ b/sdk/src/error.rs @@ -46,6 +46,9 @@ pub enum Error { #[error("could not find the assertion to redact")] AssertionRedactionNotFound, + #[error("assertion-specific error: {0}")] + AssertionSpecificError(String), + #[error("bad parameter: {0}")] BadParam(String),