Skip to content

Commit 66b57b5

Browse files
feat: Move COSE signature verification into c2pa_crypto (#801)
1 parent 1ca698d commit 66b57b5

File tree

7 files changed

+187
-291
lines changed

7 files changed

+187
-291
lines changed

internal/crypto/src/cose/error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use thiserror::Error;
1515

1616
use crate::{
1717
cose::{CertificateProfileError, CertificateTrustError},
18+
raw_signature::RawSignatureValidationError,
1819
time_stamp::TimeStampError,
1920
};
2021

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

44+
/// Could not parse ECDSA signature.
45+
#[error("could not parse ECDSA signature")]
46+
InvalidEcdsaSignature,
47+
4348
/// An error occurred while parsing CBOR.
4449
#[error("error while parsing CBOR ({0})")]
4550
CborParsingError(String),
@@ -57,6 +62,10 @@ pub enum CoseError {
5762
#[error(transparent)]
5863
CertificateTrustError(#[from] CertificateTrustError),
5964

65+
/// An error was occurred when interpreting the underlying raw signature.
66+
#[error(transparent)]
67+
RawSignatureValidationError(#[from] RawSignatureValidationError),
68+
6069
/// An unexpected internal error occured while requesting the time stamp
6170
/// response.
6271
#[error("internal error ({0})")]

internal/crypto/src/cose/verifier.rs

Lines changed: 131 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,32 @@
1111
// specific language governing permissions and limitations under
1212
// each license.
1313

14+
use std::io::Cursor;
15+
16+
use asn1_rs::FromDer;
1417
use async_generic::async_generic;
1518
use c2pa_status_tracker::{
1619
log_item,
1720
validation_codes::{
18-
SIGNING_CREDENTIAL_TRUSTED, SIGNING_CREDENTIAL_UNTRUSTED, TIMESTAMP_MISMATCH,
19-
TIMESTAMP_OUTSIDE_VALIDITY,
21+
ALGORITHM_UNSUPPORTED, SIGNING_CREDENTIAL_INVALID, SIGNING_CREDENTIAL_TRUSTED,
22+
SIGNING_CREDENTIAL_UNTRUSTED, TIMESTAMP_MISMATCH, TIMESTAMP_OUTSIDE_VALIDITY,
2023
},
2124
StatusTracker,
2225
};
2326
use coset::CoseSign1;
27+
use x509_parser::prelude::X509Certificate;
2428

2529
use crate::{
2630
asn1::rfc3161::TstInfo,
2731
cose::{
28-
cert_chain_from_sign1, check_certificate_profile, CertificateTrustError,
32+
cert_chain_from_sign1, check_certificate_profile, parse_cose_sign1, signing_alg_from_sign1,
33+
validate_cose_tst_info, validate_cose_tst_info_async, CertificateTrustError,
2934
CertificateTrustPolicy, CoseError,
3035
},
36+
p1363::parse_ec_der_sig,
37+
raw_signature::{async_validator_for_signing_alg, validator_for_signing_alg},
3138
time_stamp::TimeStampError,
39+
SigningAlg, ValidationInfo,
3240
};
3341

3442
/// A `Verifier` reads a COSE signature and reports on its validity.
@@ -52,6 +60,109 @@ pub enum Verifier<'a> {
5260
}
5361

5462
impl Verifier<'_> {
63+
/// Verify a COSE signature according to the configured policies.
64+
#[async_generic]
65+
pub fn verify_signature(
66+
&self,
67+
cose_sign1: &[u8],
68+
data: &[u8],
69+
additional_data: &[u8],
70+
validation_log: &mut impl StatusTracker,
71+
) -> Result<ValidationInfo, CoseError> {
72+
let mut sign1 = parse_cose_sign1(cose_sign1, data, validation_log)?;
73+
74+
let Ok(alg) = signing_alg_from_sign1(&sign1) else {
75+
log_item!(
76+
"Cose_Sign1",
77+
"unsupported or missing Cose algorithm",
78+
"verify_cose"
79+
)
80+
.validation_status(ALGORITHM_UNSUPPORTED)
81+
.failure_no_throw(validation_log, CoseError::UnsupportedSigningAlgorithm);
82+
83+
return Err(CoseError::UnsupportedSigningAlgorithm);
84+
};
85+
86+
let tst_info_res = if _sync {
87+
validate_cose_tst_info(&sign1, data)
88+
} else {
89+
validate_cose_tst_info_async(&sign1, data).await
90+
};
91+
92+
if _sync {
93+
self.verify_profile(&sign1, &tst_info_res, validation_log)?;
94+
self.verify_trust(&sign1, &tst_info_res, validation_log)?;
95+
} else {
96+
self.verify_profile_async(&sign1, &tst_info_res, validation_log)
97+
.await?;
98+
self.verify_trust_async(&sign1, &tst_info_res, validation_log)
99+
.await?;
100+
}
101+
102+
match alg {
103+
SigningAlg::Es256 | SigningAlg::Es384 | SigningAlg::Es512 => {
104+
if parse_ec_der_sig(&sign1.signature).is_ok() {
105+
// Should have been in P1363 format, not DER.
106+
log_item!("Cose_Sign1", "unsupported signature format", "verify_cose")
107+
.validation_status(SIGNING_CREDENTIAL_INVALID)
108+
.failure_no_throw(validation_log, CoseError::InvalidEcdsaSignature);
109+
110+
// validation_log.log(log_item, CoseError::InvalidEcdsaSignature)?;
111+
return Err(CoseError::InvalidEcdsaSignature);
112+
}
113+
}
114+
_ => (),
115+
}
116+
117+
// Reconstruct payload and additional data as it should have been at time of
118+
// signing.
119+
sign1.payload = Some(data.to_vec());
120+
let tbs = sign1.tbs_data(additional_data);
121+
122+
let certs = cert_chain_from_sign1(&sign1)?;
123+
let end_entity_cert_der = &certs[0];
124+
125+
let (_rem, sign_cert) = X509Certificate::from_der(end_entity_cert_der)
126+
.map_err(|_| CoseError::CborParsingError("invalid X509 certificate".to_string()))?;
127+
let pk = sign_cert.public_key();
128+
let pk_der = pk.raw;
129+
130+
if _sync {
131+
let Some(validator) = validator_for_signing_alg(alg) else {
132+
return Err(CoseError::UnsupportedSigningAlgorithm);
133+
};
134+
135+
validator.validate(&sign1.signature, &tbs, pk_der)?;
136+
} else {
137+
let Some(validator) = async_validator_for_signing_alg(alg) else {
138+
return Err(CoseError::UnsupportedSigningAlgorithm);
139+
};
140+
141+
validator
142+
.validate_async(&sign1.signature, &tbs, pk_der)
143+
.await?;
144+
}
145+
146+
let subject = sign_cert
147+
.subject()
148+
.iter_organization()
149+
.map(|attr| attr.as_str())
150+
.last()
151+
.ok_or(CoseError::MissingSigningCertificateChain)?
152+
.map(|attr| attr.to_string())
153+
.map_err(|_| CoseError::MissingSigningCertificateChain)?;
154+
155+
Ok(ValidationInfo {
156+
alg: Some(alg),
157+
date: tst_info_res.map(|t| t.gen_time.into()).ok(),
158+
cert_serial_number: Some(sign_cert.serial.clone()),
159+
issuer_org: Some(subject),
160+
validated: true,
161+
cert_chain: dump_cert_chain(&certs)?,
162+
revocation_status: Some(true),
163+
})
164+
}
165+
55166
/// Verify certificate profile if so configured.
56167
///
57168
/// TO DO: This might not need to be public after refactoring.
@@ -194,3 +305,20 @@ impl Verifier<'_> {
194305
}
195306
}
196307
}
308+
309+
fn dump_cert_chain(certs: &[Vec<u8>]) -> Result<Vec<u8>, CoseError> {
310+
let mut out_buf: Vec<u8> = Vec::new();
311+
let mut writer = Cursor::new(out_buf);
312+
313+
for der_bytes in certs {
314+
let c = x509_certificate::X509Certificate::from_der(der_bytes)
315+
.map_err(|_e| CoseError::CborParsingError("invalid X509 certificate".to_string()))?;
316+
317+
c.write_pem(&mut writer).map_err(|_| {
318+
CoseError::InternalError("I/O error constructing cert_chain dump".to_string())
319+
})?;
320+
}
321+
322+
out_buf = writer.into_inner();
323+
Ok(out_buf)
324+
}

internal/crypto/src/validation_info.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,22 @@ use crate::SigningAlg;
2323
pub struct ValidationInfo {
2424
/// Algorithm used to validate the signature
2525
pub alg: Option<SigningAlg>,
26+
2627
/// Date the signature was created
2728
pub date: Option<DateTime<Utc>>,
29+
2830
/// Certificate serial number
2931
pub cert_serial_number: Option<BigUint>,
32+
3033
/// Certificate issuer organization
3134
pub issuer_org: Option<String>,
35+
3236
/// Signature validity
3337
pub validated: bool,
38+
3439
/// Certificate chain used to validate the signature
3540
pub cert_chain: Vec<u8>,
41+
3642
/// Signature revocation status
3743
pub revocation_status: Option<bool>,
3844
}

sdk/src/claim.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,8 +1074,15 @@ impl Claim {
10741074
let sign1 = parse_cose_sign1(&sig, &data, validation_log)?;
10751075
check_ocsp_status_async(&sign1, &data, ctp, validation_log).await?;
10761076

1077-
let verified =
1078-
verify_cose_async(sig, data, additional_bytes, cert_check, ctp, validation_log).await;
1077+
let verified = verify_cose_async(
1078+
&sig,
1079+
&data,
1080+
&additional_bytes,
1081+
cert_check,
1082+
ctp,
1083+
validation_log,
1084+
)
1085+
.await;
10791086

10801087
Claim::verify_internal(claim, asset_data, is_provenance, verified, validation_log)
10811088
}

0 commit comments

Comments
 (0)