Skip to content

Commit f1f356b

Browse files
feat: Introduce c2pa_crypto::cose::Verifier (#797)
1 parent 5b13bb2 commit f1f356b

File tree

4 files changed

+151
-81
lines changed

4 files changed

+151
-81
lines changed

internal/crypto/src/cose/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,6 @@ pub use sigtst::{
3737
cose_countersign_data, parse_and_validate_sigtst, parse_and_validate_sigtst_async,
3838
validate_cose_tst_info, validate_cose_tst_info_async, TstToken,
3939
};
40+
41+
mod verify;
42+
pub use verify::Verifier;

internal/crypto/src/cose/verify.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2022 Adobe. All rights reserved.
2+
// This file is licensed to you under the Apache License,
3+
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
4+
// or the MIT license (http://opensource.org/licenses/MIT),
5+
// at your option.
6+
7+
// Unless required by applicable law or agreed to in writing,
8+
// this software is distributed on an "AS IS" BASIS, WITHOUT
9+
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
10+
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
11+
// specific language governing permissions and limitations under
12+
// each license.
13+
14+
use async_generic::async_generic;
15+
use c2pa_status_tracker::{
16+
log_item,
17+
validation_codes::{TIMESTAMP_MISMATCH, TIMESTAMP_OUTSIDE_VALIDITY},
18+
StatusTracker,
19+
};
20+
use coset::CoseSign1;
21+
22+
use crate::{
23+
asn1::rfc3161::TstInfo,
24+
cose::{cert_chain_from_sign1, check_certificate_profile, CertificateTrustPolicy, CoseError},
25+
time_stamp::TimeStampError,
26+
};
27+
28+
/// A `Verifier` reads a COSE signature and reports on its validity.
29+
///
30+
/// It can provide different levels of verification depending on the enum value
31+
/// chosen.
32+
#[derive(Debug)]
33+
pub enum Verifier<'a> {
34+
/// Use a [`CertificateTrustPolicy`] to validate the signing certificate's
35+
/// profile against C2PA requirements _and_ validate the certificate's
36+
/// membership against a trust configuration.
37+
VerifyTrustPolicy(&'a CertificateTrustPolicy),
38+
39+
/// Validate the certificate's membership against a trust configuration, but
40+
/// do not against any trust list. The [`CertificateTrustPolicy`] is used to
41+
/// enforce EKU (Extended Key Usage) policy only.
42+
VerifyCertificateProfileOnly(&'a CertificateTrustPolicy),
43+
44+
/// Ignore both trust configuration and trust lists.
45+
IgnoreProfileAndTrustPolicy,
46+
}
47+
48+
impl Verifier<'_> {
49+
/// Verify certificate profile if so configured.
50+
///
51+
/// TO DO: This might not need to be public after refactoring.
52+
#[async_generic]
53+
pub fn verify_profile(
54+
&self,
55+
sign1: &CoseSign1,
56+
tst_info_res: &Result<TstInfo, CoseError>,
57+
validation_log: &mut impl StatusTracker,
58+
) -> Result<(), CoseError> {
59+
let ctp = match self {
60+
Self::VerifyTrustPolicy(ctp) => *ctp,
61+
Self::VerifyCertificateProfileOnly(ctp) => *ctp,
62+
Self::IgnoreProfileAndTrustPolicy => {
63+
return Ok(());
64+
}
65+
};
66+
67+
let certs = cert_chain_from_sign1(sign1)?;
68+
let end_entity_cert_der = &certs[0];
69+
70+
match tst_info_res {
71+
Ok(tst_info) => Ok(check_certificate_profile(
72+
end_entity_cert_der,
73+
ctp,
74+
validation_log,
75+
Some(tst_info),
76+
)?),
77+
78+
Err(CoseError::NoTimeStampToken) => Ok(check_certificate_profile(
79+
end_entity_cert_der,
80+
ctp,
81+
validation_log,
82+
None,
83+
)?),
84+
85+
Err(CoseError::TimeStampError(TimeStampError::InvalidData)) => {
86+
log_item!(
87+
"Cose_Sign1",
88+
"timestamp did not match signed data",
89+
"verify_cose"
90+
)
91+
.validation_status(TIMESTAMP_MISMATCH)
92+
.failure_no_throw(validation_log, TimeStampError::InvalidData);
93+
94+
Err(TimeStampError::InvalidData.into())
95+
}
96+
97+
Err(CoseError::TimeStampError(TimeStampError::ExpiredCertificate)) => {
98+
log_item!(
99+
"Cose_Sign1",
100+
"timestamp certificate outside of validity",
101+
"verify_cose"
102+
)
103+
.validation_status(TIMESTAMP_OUTSIDE_VALIDITY)
104+
.failure_no_throw(validation_log, TimeStampError::ExpiredCertificate);
105+
106+
Err(TimeStampError::ExpiredCertificate.into())
107+
}
108+
109+
Err(e) => {
110+
log_item!("Cose_Sign1", "error parsing timestamp", "verify_cose")
111+
.failure_no_throw(validation_log, e);
112+
113+
// Frustratingly, we can't clone CoseError. The likely cases are already handled
114+
// above, so we'll call this an internal error.
115+
116+
Err(CoseError::InternalError(e.to_string()))
117+
}
118+
}
119+
}
120+
}

sdk/src/cose_validator.rs

Lines changed: 26 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,13 @@ use async_generic::async_generic;
1717
use c2pa_crypto::{
1818
asn1::rfc3161::TstInfo,
1919
cose::{
20-
cert_chain_from_sign1, check_certificate_profile, parse_cose_sign1, signing_alg_from_sign1,
21-
validate_cose_tst_info, validate_cose_tst_info_async, CertificateTrustError,
22-
CertificateTrustPolicy, CoseError, OcspFetchPolicy,
20+
cert_chain_from_sign1, parse_cose_sign1, signing_alg_from_sign1, validate_cose_tst_info,
21+
validate_cose_tst_info_async, CertificateTrustError, CertificateTrustPolicy,
22+
OcspFetchPolicy, Verifier,
2323
},
2424
ocsp::OcspResponse,
2525
p1363::parse_ec_der_sig,
2626
raw_signature::{validator_for_signing_alg, RawSignatureValidator},
27-
time_stamp::TimeStampError,
2827
SigningAlg, ValidationInfo,
2928
};
3029
use c2pa_status_tracker::{log_item, validation_codes::*, StatusTracker};
@@ -255,42 +254,21 @@ pub(crate) async fn verify_cose_async(
255254

256255
let tst_info_res = validate_cose_tst_info_async(&sign1, &data).await;
257256

258-
// verify cert matches requested algorithm
259-
if cert_check {
260-
// verify certs
261-
match &tst_info_res {
262-
Ok(tst_info) => {
263-
check_certificate_profile(der_bytes, ctp, validation_log, Some(tst_info))?
264-
}
265-
266-
Err(CoseError::NoTimeStampToken) => {
267-
check_certificate_profile(der_bytes, ctp, validation_log, None)?
268-
}
269-
270-
Err(CoseError::TimeStampError(TimeStampError::InvalidData)) => {
271-
log_item!(
272-
"Cose_Sign1",
273-
"timestamp message imprint did not match",
274-
"verify_cose"
275-
)
276-
.validation_status(TIMESTAMP_MISMATCH)
277-
.failure(validation_log, Error::CoseTimeStampMismatch)?;
278-
}
279-
280-
Err(CoseError::TimeStampError(TimeStampError::ExpiredCertificate)) => {
281-
log_item!("Cose_Sign1", "timestamp outside of validity", "verify_cose")
282-
.validation_status(TIMESTAMP_OUTSIDE_VALIDITY)
283-
.failure(validation_log, Error::CoseTimeStampValidity)?;
284-
}
285-
286-
_ => {
287-
log_item!("Cose_Sign1", "error parsing timestamp", "verify_cose")
288-
.failure_no_throw(validation_log, Error::CoseInvalidTimeStamp);
289-
290-
return Err(Error::CoseInvalidTimeStamp);
291-
}
257+
let verifier = if cert_check {
258+
match get_settings_value::<bool>("verify.verify_trust") {
259+
Ok(true) => Verifier::VerifyTrustPolicy(ctp),
260+
_ => Verifier::VerifyCertificateProfileOnly(ctp),
292261
}
262+
} else {
263+
Verifier::IgnoreProfileAndTrustPolicy
264+
};
293265

266+
verifier
267+
.verify_profile_async(&sign1, &tst_info_res, validation_log)
268+
.await?;
269+
270+
// verify cert matches requested algorithm
271+
if cert_check {
294272
// is the certificate trusted
295273
#[cfg(target_arch = "wasm32")]
296274
check_trust_async(
@@ -461,49 +439,18 @@ pub(crate) fn verify_cose(
461439

462440
let tst_info_res = validate_cose_tst_info(&sign1, data);
463441

464-
if cert_check {
465-
// verify certs
466-
match &tst_info_res {
467-
Ok(tst_info) => {
468-
check_certificate_profile(der_bytes, ctp, validation_log, Some(tst_info))?
469-
}
470-
471-
Err(CoseError::NoTimeStampToken) => {
472-
check_certificate_profile(der_bytes, ctp, validation_log, None)?
473-
}
474-
475-
Err(CoseError::TimeStampError(TimeStampError::InvalidData)) => {
476-
log_item!(
477-
"Cose_Sign1",
478-
"timestamp did not match signed data",
479-
"verify_cose"
480-
)
481-
.validation_status(TIMESTAMP_MISMATCH)
482-
.failure_no_throw(validation_log, Error::CoseTimeStampMismatch);
483-
484-
return Err(Error::CoseTimeStampMismatch);
485-
}
486-
487-
Err(CoseError::TimeStampError(TimeStampError::ExpiredCertificate)) => {
488-
log_item!(
489-
"Cose_Sign1",
490-
"timestamp certificate outside of validity",
491-
"verify_cose"
492-
)
493-
.validation_status(TIMESTAMP_OUTSIDE_VALIDITY)
494-
.failure_no_throw(validation_log, Error::CoseTimeStampValidity);
495-
496-
return Err(Error::CoseTimeStampValidity);
497-
}
498-
499-
_ => {
500-
log_item!("Cose_Sign1", "error parsing timestamp", "verify_cose")
501-
.failure_no_throw(validation_log, Error::CoseInvalidTimeStamp);
502-
503-
return Err(Error::CoseInvalidTimeStamp);
504-
}
442+
let verifier = if cert_check {
443+
match get_settings_value::<bool>("verify.verify_trust") {
444+
Ok(true) => Verifier::VerifyTrustPolicy(ctp),
445+
_ => Verifier::VerifyCertificateProfileOnly(ctp),
505446
}
447+
} else {
448+
Verifier::IgnoreProfileAndTrustPolicy
449+
};
506450

451+
verifier.verify_profile(&sign1, &tst_info_res, validation_log)?;
452+
453+
if cert_check {
507454
// is the certificate trusted
508455
check_trust(
509456
ctp,

sdk/src/store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3595,7 +3595,7 @@ pub mod tests {
35953595

35963596
use std::io::Write;
35973597

3598-
use c2pa_crypto::SigningAlg;
3598+
use c2pa_crypto::{time_stamp::TimeStampError, SigningAlg};
35993599
use c2pa_status_tracker::StatusTracker;
36003600
use memchr::memmem;
36013601
use serde::Serialize;
@@ -4895,7 +4895,7 @@ pub mod tests {
48954895
// replace the title that is inside the claim data - should cause signature to not match
48964896
let report = patch_and_report("C.jpg", b"C.jpg", b"X.jpg");
48974897
assert!(!report.logged_items().is_empty());
4898-
assert!(report.has_error(Error::CoseTimeStampMismatch));
4898+
assert!(report.has_error(TimeStampError::InvalidData));
48994899
assert!(report.has_status(validation_status::TIMESTAMP_MISMATCH));
49004900
}
49014901

0 commit comments

Comments
 (0)