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: Move COSE signature verification into c2pa_crypto #801

Merged
merged 6 commits into from
Dec 26, 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: 9 additions & 0 deletions internal/crypto/src/cose/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use thiserror::Error;

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

Expand All @@ -40,6 +41,10 @@ pub enum CoseError {
#[error("the certificate was signed using an unsupported signature algorithm")]
UnsupportedSigningAlgorithm,

/// Could not parse ECDSA signature.
#[error("could not parse ECDSA signature")]
InvalidEcdsaSignature,

/// An error occurred while parsing CBOR.
#[error("error while parsing CBOR ({0})")]
CborParsingError(String),
Expand All @@ -57,6 +62,10 @@ pub enum CoseError {
#[error(transparent)]
CertificateTrustError(#[from] CertificateTrustError),

/// An error was occurred when interpreting the underlying raw signature.
#[error(transparent)]
RawSignatureValidationError(#[from] RawSignatureValidationError),

/// An unexpected internal error occured while requesting the time stamp
/// response.
#[error("internal error ({0})")]
Expand Down
134 changes: 131 additions & 3 deletions internal/crypto/src/cose/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,32 @@
// specific language governing permissions and limitations under
// each license.

use std::io::Cursor;

use asn1_rs::FromDer;
use async_generic::async_generic;
use c2pa_status_tracker::{
log_item,
validation_codes::{
SIGNING_CREDENTIAL_TRUSTED, SIGNING_CREDENTIAL_UNTRUSTED, TIMESTAMP_MISMATCH,
TIMESTAMP_OUTSIDE_VALIDITY,
ALGORITHM_UNSUPPORTED, SIGNING_CREDENTIAL_INVALID, SIGNING_CREDENTIAL_TRUSTED,
SIGNING_CREDENTIAL_UNTRUSTED, TIMESTAMP_MISMATCH, TIMESTAMP_OUTSIDE_VALIDITY,
},
StatusTracker,
};
use coset::CoseSign1;
use x509_parser::prelude::X509Certificate;

use crate::{
asn1::rfc3161::TstInfo,
cose::{
cert_chain_from_sign1, check_certificate_profile, CertificateTrustError,
cert_chain_from_sign1, check_certificate_profile, parse_cose_sign1, signing_alg_from_sign1,
validate_cose_tst_info, validate_cose_tst_info_async, CertificateTrustError,
CertificateTrustPolicy, CoseError,
},
p1363::parse_ec_der_sig,
raw_signature::{async_validator_for_signing_alg, validator_for_signing_alg},
time_stamp::TimeStampError,
SigningAlg, ValidationInfo,
};

/// A `Verifier` reads a COSE signature and reports on its validity.
Expand All @@ -52,6 +60,109 @@
}

impl Verifier<'_> {
/// Verify a COSE signature according to the configured policies.
#[async_generic]
pub fn verify_signature(
&self,
cose_sign1: &[u8],
data: &[u8],
additional_data: &[u8],
validation_log: &mut impl StatusTracker,
) -> Result<ValidationInfo, CoseError> {
let mut sign1 = parse_cose_sign1(cose_sign1, data, validation_log)?;

let Ok(alg) = signing_alg_from_sign1(&sign1) else {
log_item!(
"Cose_Sign1",
"unsupported or missing Cose algorithm",
"verify_cose"
)
.validation_status(ALGORITHM_UNSUPPORTED)
.failure_no_throw(validation_log, CoseError::UnsupportedSigningAlgorithm);

return Err(CoseError::UnsupportedSigningAlgorithm);

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L75-L83

Added lines #L75 - L83 were not covered by tests
};

let tst_info_res = if _sync {

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L86

Added line #L86 was not covered by tests
validate_cose_tst_info(&sign1, data)
} else {
validate_cose_tst_info_async(&sign1, data).await
};

if _sync {
self.verify_profile(&sign1, &tst_info_res, validation_log)?;
self.verify_trust(&sign1, &tst_info_res, validation_log)?;
} else {
self.verify_profile_async(&sign1, &tst_info_res, validation_log)
.await?;
self.verify_trust_async(&sign1, &tst_info_res, validation_log)
.await?;

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L96-L99

Added lines #L96 - L99 were not covered by tests
}

match alg {
SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => {
if parse_ec_der_sig(&sign1.signature).is_ok() {
// Should have been in P1363 format, not DER.
log_item!("Cose_Sign1", "unsupported signature format", "verify_cose")
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CoseError::InvalidEcdsaSignature);

// validation_log.log(log_item, CoseError::InvalidEcdsaSignature)?;
return Err(CoseError::InvalidEcdsaSignature);

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L106-L111

Added lines #L106 - L111 were not covered by tests
}
}
_ => (),
}

// Reconstruct payload and additional data as it should have been at time of
// signing.
sign1.payload = Some(data.to_vec());
let tbs = sign1.tbs_data(additional_data);

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

let (_rem, sign_cert) = X509Certificate::from_der(end_entity_cert_der)
.map_err(|_| CoseError::CborParsingError("invalid X509 certificate".to_string()))?;
let pk = sign_cert.public_key();
let pk_der = pk.raw;

if _sync {
let Some(validator) = validator_for_signing_alg(alg) else {
return Err(CoseError::UnsupportedSigningAlgorithm);

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L132

Added line #L132 was not covered by tests
};

validator.validate(&sign1.signature, &tbs, pk_der)?;
} else {
let Some(validator) = async_validator_for_signing_alg(alg) else {
return Err(CoseError::UnsupportedSigningAlgorithm);

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L137-L138

Added lines #L137 - L138 were not covered by tests
};

validator
.validate_async(&sign1.signature, &tbs, pk_der)
.await?;

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L141-L143

Added lines #L141 - L143 were not covered by tests
}

let subject = sign_cert
.subject()
.iter_organization()
.map(|attr| attr.as_str())
.last()
.ok_or(CoseError::MissingSigningCertificateChain)?
.map(|attr| attr.to_string())
.map_err(|_| CoseError::MissingSigningCertificateChain)?;

Ok(ValidationInfo {
alg: Some(alg),
date: tst_info_res.map(|t| t.gen_time.into()).ok(),
cert_serial_number: Some(sign_cert.serial.clone()),
issuer_org: Some(subject),
validated: true,
cert_chain: dump_cert_chain(&certs)?,
revocation_status: Some(true),
})
}

/// Verify certificate profile if so configured.
///
/// TO DO: This might not need to be public after refactoring.
Expand Down Expand Up @@ -194,3 +305,20 @@
}
}
}

fn dump_cert_chain(certs: &[Vec<u8>]) -> Result<Vec<u8>, CoseError> {
let mut out_buf: Vec<u8> = Vec::new();
let mut writer = Cursor::new(out_buf);

for der_bytes in certs {
let c = x509_certificate::X509Certificate::from_der(der_bytes)
.map_err(|_e| CoseError::CborParsingError("invalid X509 certificate".to_string()))?;

c.write_pem(&mut writer).map_err(|_| {
CoseError::InternalError("I/O error constructing cert_chain dump".to_string())

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

View check run for this annotation

Codecov / codecov/patch

internal/crypto/src/cose/verifier.rs#L318

Added line #L318 was not covered by tests
})?;
}

out_buf = writer.into_inner();
Ok(out_buf)
}
6 changes: 6 additions & 0 deletions internal/crypto/src/validation_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,22 @@ use crate::SigningAlg;
pub struct ValidationInfo {
/// Algorithm used to validate the signature
pub alg: Option<SigningAlg>,

/// Date the signature was created
pub date: Option<DateTime<Utc>>,

/// Certificate serial number
pub cert_serial_number: Option<BigUint>,

/// Certificate issuer organization
pub issuer_org: Option<String>,

/// Signature validity
pub validated: bool,

/// Certificate chain used to validate the signature
pub cert_chain: Vec<u8>,

/// Signature revocation status
pub revocation_status: Option<bool>,
}
11 changes: 9 additions & 2 deletions sdk/src/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1074,8 +1074,15 @@ impl Claim {
let sign1 = parse_cose_sign1(&sig, &data, validation_log)?;
check_ocsp_status_async(&sign1, &data, ctp, validation_log).await?;

let verified =
verify_cose_async(sig, data, additional_bytes, cert_check, ctp, validation_log).await;
let verified = verify_cose_async(
&sig,
&data,
&additional_bytes,
cert_check,
ctp,
validation_log,
)
.await;

Claim::verify_internal(claim, asset_data, is_provenance, verified, validation_log)
}
Expand Down
Loading
Loading