Skip to content

Commit 5b13bb2

Browse files
feat: Consolidate implementations of cert_chain_from_sign1 in c2pa_crypto (#796)
1 parent fed4478 commit 5b13bb2

File tree

4 files changed

+95
-194
lines changed

4 files changed

+95
-194
lines changed

internal/crypto/src/cose/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ mod ocsp;
3030
pub use ocsp::{check_ocsp_status, check_ocsp_status_async, OcspFetchPolicy};
3131

3232
mod sign1;
33-
pub use sign1::{parse_cose_sign1, signing_alg_from_sign1};
33+
pub use sign1::{cert_chain_from_sign1, parse_cose_sign1, signing_alg_from_sign1};
3434

3535
mod sigtst;
3636
pub use sigtst::{

internal/crypto/src/cose/ocsp.rs

Lines changed: 3 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ fn fetch_and_check_ocsp_response(
124124

125125
#[cfg(not(target_arch = "wasm32"))]
126126
{
127-
let certs = get_cert_chain(sign1)?;
127+
use crate::cose::cert_chain_from_sign1;
128+
129+
let certs = cert_chain_from_sign1(sign1)?;
128130

129131
let Some(ocsp_der) = crate::ocsp::fetch_ocsp_response(&certs) else {
130132
return Ok(OcspResponse::default());
@@ -186,85 +188,3 @@ fn get_ocsp_der(sign1: &coset::CoseSign1) -> Option<Vec<u8>> {
186188
}
187189
})
188190
}
189-
190-
// TO DO: See if this gets more widely used in crate.
191-
#[cfg(not(target_arch = "wasm32"))]
192-
fn get_cert_chain(sign1: &coset::CoseSign1) -> Result<Vec<Vec<u8>>, CoseError> {
193-
use coset::iana::{self, EnumI64};
194-
195-
// Check the protected header first.
196-
let Some(value) = sign1
197-
.protected
198-
.header
199-
.rest
200-
.iter()
201-
.find_map(|x: &(Label, Value)| {
202-
if x.0 == Label::Text("x5chain".to_string())
203-
|| x.0 == Label::Int(iana::HeaderParameter::X5Chain.to_i64())
204-
{
205-
Some(x.1.clone())
206-
} else {
207-
None
208-
}
209-
})
210-
else {
211-
// Not there: Also try unprotected header. (This was permitted in older versions
212-
// of C2PA.)
213-
return get_unprotected_header_certs(sign1);
214-
};
215-
216-
// Certs may be in protected or unprotected header, but not both.
217-
if get_unprotected_header_certs(sign1).is_ok() {
218-
return Err(CoseError::MultipleSigningCertificateChains);
219-
}
220-
221-
cert_chain_from_cbor_value(value)
222-
}
223-
224-
#[cfg(not(target_arch = "wasm32"))]
225-
fn get_unprotected_header_certs(sign1: &coset::CoseSign1) -> Result<Vec<Vec<u8>>, CoseError> {
226-
let Some(value) = sign1
227-
.unprotected
228-
.rest
229-
.iter()
230-
.find_map(|x: &(Label, Value)| {
231-
if x.0 == Label::Text("x5chain".to_string()) {
232-
Some(x.1.clone())
233-
} else {
234-
None
235-
}
236-
})
237-
else {
238-
return Err(CoseError::MissingSigningCertificateChain);
239-
};
240-
241-
cert_chain_from_cbor_value(value)
242-
}
243-
244-
#[cfg(not(target_arch = "wasm32"))]
245-
fn cert_chain_from_cbor_value(value: Value) -> Result<Vec<Vec<u8>>, CoseError> {
246-
match value {
247-
Value::Array(cert_chain) => {
248-
let certs: Vec<Vec<u8>> = cert_chain
249-
.iter()
250-
.filter_map(|c| {
251-
if let Value::Bytes(der_bytes) = c {
252-
Some(der_bytes.clone())
253-
} else {
254-
None
255-
}
256-
})
257-
.collect();
258-
259-
if certs.is_empty() {
260-
Err(CoseError::MissingSigningCertificateChain)
261-
} else {
262-
Ok(certs)
263-
}
264-
}
265-
266-
Value::Bytes(ref der_bytes) => Ok(vec![der_bytes.clone()]),
267-
268-
_ => Err(CoseError::MissingSigningCertificateChain),
269-
}
270-
}

internal/crypto/src/cose/sign1.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
// each license.
1313

1414
use c2pa_status_tracker::{log_item, validation_codes::CLAIM_SIGNATURE_MISMATCH, StatusTracker};
15-
use coset::{iana::Algorithm, CoseSign1, RegisteredLabelWithPrivate, TaggedCborSerializable};
15+
use ciborium::value::Value;
16+
use coset::{
17+
iana::{self, Algorithm, EnumI64},
18+
CoseSign1, Label, RegisteredLabelWithPrivate, TaggedCborSerializable,
19+
};
1620

1721
use crate::{cose::CoseError, SigningAlg};
1822

@@ -78,3 +82,80 @@ pub fn signing_alg_from_sign1(sign1: &coset::CoseSign1) -> Result<SigningAlg, Co
7882
_ => Err(CoseError::UnsupportedSigningAlgorithm),
7983
}
8084
}
85+
86+
/// TEMPORARILY PUBLIC while refactoring.
87+
pub fn cert_chain_from_sign1(sign1: &coset::CoseSign1) -> Result<Vec<Vec<u8>>, CoseError> {
88+
// Check the protected header first.
89+
let Some(value) = sign1
90+
.protected
91+
.header
92+
.rest
93+
.iter()
94+
.find_map(|x: &(Label, Value)| {
95+
if x.0 == Label::Text("x5chain".to_string())
96+
|| x.0 == Label::Int(iana::HeaderParameter::X5Chain.to_i64())
97+
{
98+
Some(x.1.clone())
99+
} else {
100+
None
101+
}
102+
})
103+
else {
104+
// Not there: Also try unprotected header. (This was permitted in older versions
105+
// of C2PA.)
106+
return get_unprotected_header_certs(sign1);
107+
};
108+
109+
// Certs may be in protected or unprotected header, but not both.
110+
if get_unprotected_header_certs(sign1).is_ok() {
111+
return Err(CoseError::MultipleSigningCertificateChains);
112+
}
113+
114+
cert_chain_from_cbor_value(value)
115+
}
116+
117+
fn get_unprotected_header_certs(sign1: &coset::CoseSign1) -> Result<Vec<Vec<u8>>, CoseError> {
118+
let Some(value) = sign1
119+
.unprotected
120+
.rest
121+
.iter()
122+
.find_map(|x: &(Label, Value)| {
123+
if x.0 == Label::Text("x5chain".to_string()) {
124+
Some(x.1.clone())
125+
} else {
126+
None
127+
}
128+
})
129+
else {
130+
return Err(CoseError::MissingSigningCertificateChain);
131+
};
132+
133+
cert_chain_from_cbor_value(value)
134+
}
135+
136+
fn cert_chain_from_cbor_value(value: Value) -> Result<Vec<Vec<u8>>, CoseError> {
137+
match value {
138+
Value::Array(cert_chain) => {
139+
let certs: Vec<Vec<u8>> = cert_chain
140+
.iter()
141+
.filter_map(|c| {
142+
if let Value::Bytes(der_bytes) = c {
143+
Some(der_bytes.clone())
144+
} else {
145+
None
146+
}
147+
})
148+
.collect();
149+
150+
if certs.is_empty() {
151+
Err(CoseError::MissingSigningCertificateChain)
152+
} else {
153+
Ok(certs)
154+
}
155+
}
156+
157+
Value::Bytes(ref der_bytes) => Ok(vec![der_bytes.clone()]),
158+
159+
_ => Err(CoseError::MissingSigningCertificateChain),
160+
}
161+
}

sdk/src/cose_validator.rs

Lines changed: 9 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use async_generic::async_generic;
1717
use c2pa_crypto::{
1818
asn1::rfc3161::TstInfo,
1919
cose::{
20-
check_certificate_profile, parse_cose_sign1, signing_alg_from_sign1,
20+
cert_chain_from_sign1, check_certificate_profile, parse_cose_sign1, signing_alg_from_sign1,
2121
validate_cose_tst_info, validate_cose_tst_info_async, CertificateTrustError,
2222
CertificateTrustPolicy, CoseError, OcspFetchPolicy,
2323
},
@@ -28,11 +28,7 @@ use c2pa_crypto::{
2828
SigningAlg, ValidationInfo,
2929
};
3030
use c2pa_status_tracker::{log_item, validation_codes::*, StatusTracker};
31-
use ciborium::value::Value;
32-
use coset::{
33-
iana::{self, EnumI64},
34-
sig_structure_data, Label,
35-
};
31+
use coset::sig_structure_data;
3632
use x509_parser::{num_bigint::BigUint, prelude::*};
3733

3834
use crate::{
@@ -136,108 +132,10 @@ fn check_trust(
136132

137133
fn get_sign_cert(sign1: &coset::CoseSign1) -> Result<Vec<u8>> {
138134
// element 0 is the signing cert
139-
let certs = get_sign_certs(sign1)?;
135+
let certs = cert_chain_from_sign1(sign1)?;
140136
Ok(certs[0].clone())
141137
}
142138

143-
fn get_unprotected_header_certs(sign1: &coset::CoseSign1) -> Result<Vec<Vec<u8>>> {
144-
if let Some(der) = sign1
145-
.unprotected
146-
.rest
147-
.iter()
148-
.find_map(|x: &(Label, Value)| {
149-
if x.0 == Label::Text("x5chain".to_string()) {
150-
Some(x.1.clone())
151-
} else {
152-
None
153-
}
154-
})
155-
{
156-
let mut certs: Vec<Vec<u8>> = Vec::new();
157-
158-
match der {
159-
Value::Array(cert_chain) => {
160-
// handle array of certs
161-
for c in cert_chain {
162-
if let Value::Bytes(der_bytes) = c {
163-
certs.push(der_bytes.clone());
164-
}
165-
}
166-
167-
if certs.is_empty() {
168-
Err(Error::CoseMissingKey)
169-
} else {
170-
Ok(certs)
171-
}
172-
}
173-
Value::Bytes(ref der_bytes) => {
174-
// handle single cert case
175-
certs.push(der_bytes.clone());
176-
Ok(certs)
177-
}
178-
_ => Err(Error::CoseX5ChainMissing),
179-
}
180-
} else {
181-
Err(Error::CoseX5ChainMissing)
182-
}
183-
}
184-
// get the public key der
185-
fn get_sign_certs(sign1: &coset::CoseSign1) -> Result<Vec<Vec<u8>>> {
186-
// check for protected header int, then protected header x5chain,
187-
// then the legacy unprotected x5chain to get the public key der
188-
189-
// check the protected header
190-
if let Some(der) = sign1
191-
.protected
192-
.header
193-
.rest
194-
.iter()
195-
.find_map(|x: &(Label, Value)| {
196-
if x.0 == Label::Text("x5chain".to_string())
197-
|| x.0 == Label::Int(iana::HeaderParameter::X5Chain.to_i64())
198-
{
199-
Some(x.1.clone())
200-
} else {
201-
None
202-
}
203-
})
204-
{
205-
// make sure there are no certs in the legacy unprotected header, certs
206-
// are only allowing in protect OR unprotected header
207-
if get_unprotected_header_certs(sign1).is_ok() {
208-
return Err(Error::CoseVerifier);
209-
}
210-
211-
let mut certs: Vec<Vec<u8>> = Vec::new();
212-
213-
match der {
214-
Value::Array(cert_chain) => {
215-
// handle array of certs
216-
for c in cert_chain {
217-
if let Value::Bytes(der_bytes) = c {
218-
certs.push(der_bytes.clone());
219-
}
220-
}
221-
222-
if certs.is_empty() {
223-
return Err(Error::CoseX5ChainMissing);
224-
} else {
225-
return Ok(certs);
226-
}
227-
}
228-
Value::Bytes(ref der_bytes) => {
229-
// handle single cert case
230-
certs.push(der_bytes.clone());
231-
return Ok(certs);
232-
}
233-
_ => return Err(Error::CoseX5ChainMissing),
234-
}
235-
}
236-
237-
// check the unprotected header if necessary
238-
get_unprotected_header_certs(sign1)
239-
}
240-
241139
// internal util function to dump the cert chain in PEM format
242140
fn dump_cert_chain(certs: &[Vec<u8>]) -> Result<Vec<u8>> {
243141
let mut out_buf: Vec<u8> = Vec::new();
@@ -350,7 +248,7 @@ pub(crate) async fn verify_cose_async(
350248
let mut result = ValidationInfo::default();
351249

352250
// get the cert chain
353-
let certs = get_sign_certs(&sign1)?;
251+
let certs = cert_chain_from_sign1(&sign1)?;
354252

355253
// get the public key der
356254
let der_bytes = &certs[0];
@@ -455,7 +353,7 @@ pub(crate) async fn verify_cose_async(
455353
result.date = tst_info_res.ok().map(|t| gt_to_datetime(t.gen_time));
456354

457355
// return cert chain
458-
result.cert_chain = dump_cert_chain(&get_sign_certs(&sign1)?)?;
356+
result.cert_chain = dump_cert_chain(&cert_chain_from_sign1(&sign1)?)?;
459357
}
460358

461359
Ok(result)
@@ -500,7 +398,7 @@ pub(crate) fn get_signing_info(
500398
};
501399

502400
let certs = match sign1 {
503-
Ok(s) => match get_sign_certs(&s) {
401+
Ok(s) => match cert_chain_from_sign1(&s) {
504402
Ok(c) => dump_cert_chain(&c).unwrap_or_default(),
505403
Err(_) => Vec::new(),
506404
},
@@ -556,7 +454,7 @@ pub(crate) fn verify_cose(
556454
let mut result = ValidationInfo::default();
557455

558456
// get the cert chain
559-
let certs = get_sign_certs(&sign1)?;
457+
let certs = cert_chain_from_sign1(&sign1)?;
560458

561459
// get the public key der
562460
let der_bytes = &certs[0];
@@ -726,6 +624,8 @@ fn gt_to_datetime(
726624
pub mod tests {
727625
use c2pa_crypto::SigningAlg;
728626
use c2pa_status_tracker::DetailedStatusTracker;
627+
use ciborium::Value;
628+
use coset::Label;
729629
use sha2::digest::generic_array::sequence::Shorten;
730630
use x509_parser::{certificate::X509Certificate, pem::Pem};
731631

0 commit comments

Comments
 (0)