Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce c2pa_crypto::Verifier::verify_trust #798

Merged
merged 3 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
}
}
}

/// 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()
});

Check warning on line 158 in internal/crypto/src/cose/verifier.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L156-L158

Added lines #L156 - L158 were not covered by tests

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

Check warning on line 164 in internal/crypto/src/cose/verifier.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L163-L164

Added lines #L163 - L164 were not covered by tests
};

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())

Check warning on line 192 in internal/crypto/src/cose/verifier.rs

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L184-L192

Added lines #L184 - L192 were not covered by tests
}
}
}
}
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
Loading