Skip to content

Commit

Permalink
feat: Introduce c2pa_crypto::Verifier::verify_trust (#798)
Browse files Browse the repository at this point in the history
  • Loading branch information
scouten-adobe authored Dec 24, 2024
1 parent f1f356b commit be23bdc
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 110 deletions.
9 changes: 8 additions & 1 deletion internal/crypto/src/cose/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@

use thiserror::Error;

use crate::{cose::CertificateProfileError, time_stamp::TimeStampError};
use crate::{
cose::{CertificateProfileError, CertificateTrustError},
time_stamp::TimeStampError,
};

/// Describes errors that can occur when processing or generating [COSE]
/// signatures.
Expand Down Expand Up @@ -50,6 +53,10 @@ pub enum CoseError {
#[error(transparent)]
CertificateProfileError(#[from] CertificateProfileError),

/// The signing certificate(s) did not match the required trust policy.
#[error(transparent)]
CertificateTrustError(#[from] CertificateTrustError),

/// An unexpected internal error occured while requesting the time stamp
/// response.
#[error("internal error ({0})")]
Expand Down
4 changes: 2 additions & 2 deletions internal/crypto/src/cose/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,5 @@ pub use sigtst::{
validate_cose_tst_info, validate_cose_tst_info_async, TstToken,
};

mod verify;
pub use verify::Verifier;
mod verifier;
pub use verifier::Verifier;
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@
use async_generic::async_generic;
use c2pa_status_tracker::{
log_item,
validation_codes::{TIMESTAMP_MISMATCH, TIMESTAMP_OUTSIDE_VALIDITY},
validation_codes::{
SIGNING_CREDENTIAL_TRUSTED, SIGNING_CREDENTIAL_UNTRUSTED, TIMESTAMP_MISMATCH,
TIMESTAMP_OUTSIDE_VALIDITY,
},
StatusTracker,
};
use coset::CoseSign1;

use crate::{
asn1::rfc3161::TstInfo,
cose::{cert_chain_from_sign1, check_certificate_profile, CertificateTrustPolicy, CoseError},
cose::{
cert_chain_from_sign1, check_certificate_profile, CertificateTrustError,
CertificateTrustPolicy, CoseError,
},
time_stamp::TimeStampError,
};

Expand Down Expand Up @@ -117,4 +123,74 @@ impl Verifier<'_> {
}
}
}

/// Verify certificate profile if so configured.
///
/// TO DO: This might not need to be public after refactoring.
#[async_generic]
pub fn verify_trust(
&self,
sign1: &CoseSign1,
tst_info_res: &Result<TstInfo, CoseError>,
validation_log: &mut impl StatusTracker,
) -> Result<(), CoseError> {
// IMPORTANT: This function assumes that verify_profile has already been called.

let ctp = match self {
Self::VerifyTrustPolicy(ctp) => *ctp,

Self::VerifyCertificateProfileOnly(_ctp) => {
return Ok(());
}

Self::IgnoreProfileAndTrustPolicy => {
return Ok(());
}
};

let certs = cert_chain_from_sign1(sign1)?;
let end_entity_cert_der = &certs[0];
let chain_der = &certs[1..];

let signing_time_epoch = tst_info_res.as_ref().ok().map(|tst_info| {
let dt: chrono::DateTime<chrono::Utc> = tst_info.gen_time.clone().into();
dt.timestamp()
});

let verify_result = if _sync {
ctp.check_certificate_trust(chain_der, end_entity_cert_der, signing_time_epoch)
} else {
ctp.check_certificate_trust_async(chain_der, end_entity_cert_der, signing_time_epoch)
.await
};

match verify_result {
Ok(()) => {
log_item!("Cose_Sign1", "signing certificate trusted", "verify_cose")
.validation_status(SIGNING_CREDENTIAL_TRUSTED)
.success(validation_log);

Ok(())
}

Err(CertificateTrustError::CertificateNotTrusted) => {
log_item!("Cose_Sign1", "signing certificate untrusted", "verify_cose")
.validation_status(SIGNING_CREDENTIAL_UNTRUSTED)
.failure_no_throw(validation_log, CertificateTrustError::CertificateNotTrusted);

Err(CertificateTrustError::CertificateNotTrusted.into())
}

Err(e) => {
log_item!("Cose_Sign1", "signing certificate untrusted", "verify_cose")
.validation_status(SIGNING_CREDENTIAL_UNTRUSTED)
.failure_no_throw(validation_log, &e);

// TO REVIEW: Mixed message: Are we using CoseCertUntrusted in log or &e from
// above? validation_log.log(log_item,
// Error::CoseCertUntrusted)?;
Err(e.into())
}
}
}
}
108 changes: 5 additions & 103 deletions sdk/src/cose_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ use std::io::Cursor;

use async_generic::async_generic;
use c2pa_crypto::{
asn1::rfc3161::TstInfo,
cose::{
cert_chain_from_sign1, parse_cose_sign1, signing_alg_from_sign1, validate_cose_tst_info,
validate_cose_tst_info_async, CertificateTrustError, CertificateTrustPolicy,
OcspFetchPolicy, Verifier,
validate_cose_tst_info_async, CertificateTrustPolicy, OcspFetchPolicy, Verifier,
},
ocsp::OcspResponse,
p1363::parse_ec_der_sig,
Expand Down Expand Up @@ -68,65 +66,6 @@ pub(crate) fn check_ocsp_status(
}
}

#[async_generic(async_signature(
ctp: &CertificateTrustPolicy,
chain_der: &[Vec<u8>],
cert_der: &[u8],
signing_time_epoch: Option<i64>,
validation_log: &mut impl StatusTracker
))]
#[allow(unused)]
fn check_trust(
ctp: &CertificateTrustPolicy,
chain_der: &[Vec<u8>],
cert_der: &[u8],
signing_time_epoch: Option<i64>,
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),
}

let verify_result = if _sync {
ctp.check_certificate_trust(chain_der, cert_der, signing_time_epoch)
} else {
ctp.check_certificate_trust_async(chain_der, cert_der, signing_time_epoch)
.await
};

match verify_result {
Ok(()) => {
log_item!("Cose_Sign1", "signing certificate trusted", "verify_cose")
.validation_status(SIGNING_CREDENTIAL_TRUSTED)
.success(validation_log);

Ok(())
}
Err(CertificateTrustError::CertificateNotTrusted) => {
log_item!("Cose_Sign1", "signing certificate untrusted", "verify_cose")
.validation_status(SIGNING_CREDENTIAL_UNTRUSTED)
.failure_no_throw(validation_log, Error::CoseCertUntrusted);

Err(Error::CoseCertUntrusted)
}
Err(e) => {
log_item!("Cose_Sign1", "signing certificate untrusted", "verify_cose")
.validation_status(SIGNING_CREDENTIAL_UNTRUSTED)
.failure_no_throw(validation_log, &e);

// TO REVIEW: Mixed message: Are we using CoseCertUntrusted in log or &e from above?
// validation_log.log(log_item, Error::CoseCertUntrusted)?;
Err(e.into())
}
}
}

// ---- TEMPORARY MARKER: Above this line will not move to c2pa-crypto

fn get_sign_cert(sign1: &coset::CoseSign1) -> Result<Vec<u8>> {
Expand Down Expand Up @@ -207,11 +146,6 @@ fn extract_serial_from_cert(cert: &X509Certificate) -> BigUint {
cert.serial.clone()
}

fn tst_info_to_timestamp(tst_info: &TstInfo) -> i64 {
let dt: chrono::DateTime<chrono::Utc> = tst_info.gen_time.clone().into();
dt.timestamp()
}

/// Asynchronously validate a COSE_SIGN1 byte vector and verify against expected data
/// cose_bytes - byte array containing the raw COSE_SIGN1 data
/// data: data that was used to create the cose_bytes, these must match
Expand Down Expand Up @@ -267,31 +201,11 @@ pub(crate) async fn verify_cose_async(
.verify_profile_async(&sign1, &tst_info_res, validation_log)
.await?;

// verify cert matches requested algorithm
if cert_check {
// is the certificate trusted
#[cfg(target_arch = "wasm32")]
check_trust_async(
ctp,
&certs[1..],
der_bytes,
tst_info_res.as_ref().ok().map(tst_info_to_timestamp),
validation_log,
)
// TO REVIEW: Do we need the async case on non-WASM platforms?
verifier
.verify_trust_async(&sign1, &tst_info_res, validation_log)
.await?;

#[cfg(not(target_arch = "wasm32"))]
check_trust(
ctp,
&certs[1..],
der_bytes,
tst_info_res.as_ref().ok().map(tst_info_to_timestamp),
validation_log,
)?;

// todo: check TSA certs against trust list
}

// check signature format
if let Err(_e) = check_sig(&sign1.signature, alg) {
log_item!("Cose_Sign1", "unsupported signature format", "verify_cose")
Expand Down Expand Up @@ -449,19 +363,7 @@ pub(crate) fn verify_cose(
};

verifier.verify_profile(&sign1, &tst_info_res, validation_log)?;

if cert_check {
// is the certificate trusted
check_trust(
ctp,
&certs[1..],
der_bytes,
tst_info_res.as_ref().ok().map(tst_info_to_timestamp),
validation_log,
)?;

// todo: check TSA certs against trust list
}
verifier.verify_trust(&sign1, &tst_info_res, validation_log)?;

// check signature format
if let Err(e) = check_sig(&sign1.signature, alg) {
Expand Down
1 change: 1 addition & 0 deletions sdk/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ impl From<CoseError> for Error {
CoseError::CborParsingError(_) => Self::CoseTimeStampGeneration,
CoseError::TimeStampError(e) => e.into(),
CoseError::CertificateProfileError(e) => e.into(),
CoseError::CertificateTrustError(e) => e.into(),
CoseError::InternalError(e) => Self::InternalError(e),
}
}
Expand Down
6 changes: 4 additions & 2 deletions sdk/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3595,7 +3595,7 @@ pub mod tests {

use std::io::Write;

use c2pa_crypto::{time_stamp::TimeStampError, SigningAlg};
use c2pa_crypto::SigningAlg;
use c2pa_status_tracker::StatusTracker;
use memchr::memmem;
use serde::Serialize;
Expand Down Expand Up @@ -4895,7 +4895,9 @@ pub mod tests {
// replace the title that is inside the claim data - should cause signature to not match
let report = patch_and_report("C.jpg", b"C.jpg", b"X.jpg");
assert!(!report.logged_items().is_empty());
assert!(report.has_error(TimeStampError::InvalidData));
assert!(report.has_error(Error::TimeStampError(
c2pa_crypto::time_stamp::TimeStampError::InvalidData
)));
assert!(report.has_status(validation_status::TIMESTAMP_MISMATCH));
}

Expand Down

0 comments on commit be23bdc

Please sign in to comment.