Skip to content

Commit 25c89ed

Browse files
feat: Move COSE timestamp generation into c2pa_crypto (#803)
1 parent 66b57b5 commit 25c89ed

File tree

9 files changed

+194
-111
lines changed

9 files changed

+194
-111
lines changed

internal/crypto/src/cose/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ pub enum CoseError {
4949
#[error("error while parsing CBOR ({0})")]
5050
CborParsingError(String),
5151

52+
/// An error occurred while generating CBOR.
53+
#[error("error while generating CBOR ({0})")]
54+
CborGenerationError(String),
55+
5256
/// An error occurred while parsing a time stamp.
5357
#[error(transparent)]
5458
TimeStampError(#[from] TimeStampError),

internal/crypto/src/cose/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ pub use sign1::{cert_chain_from_sign1, parse_cose_sign1, signing_alg_from_sign1}
3434

3535
mod sigtst;
3636
pub use sigtst::{
37-
cose_countersign_data, parse_and_validate_sigtst, parse_and_validate_sigtst_async,
38-
validate_cose_tst_info, validate_cose_tst_info_async, TstToken,
37+
add_sigtst_header, add_sigtst_header_async, cose_countersign_data, parse_and_validate_sigtst,
38+
parse_and_validate_sigtst_async, validate_cose_tst_info, validate_cose_tst_info_async,
39+
TstToken,
3940
};
4041

4142
mod verifier;

internal/crypto/src/cose/sigtst.rs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414
use async_generic::async_generic;
1515
use ciborium::value::Value;
16-
use coset::{sig_structure_data, Label, ProtectedHeader, SignatureContext};
16+
use coset::{sig_structure_data, HeaderBuilder, Label, ProtectedHeader, SignatureContext};
1717
use serde::{Deserialize, Serialize};
1818

1919
use crate::{
2020
asn1::rfc3161::TstInfo,
2121
cose::CoseError,
22-
time_stamp::{verify_time_stamp, verify_time_stamp_async},
22+
time_stamp::{
23+
verify_time_stamp, verify_time_stamp_async, AsyncTimeStampProvider, TimeStampProvider,
24+
},
2325
};
2426

2527
/// Given a COSE signature, retrieve the `sigTst` header from it and validate
@@ -121,8 +123,69 @@ pub struct TstToken {
121123
pub val: Vec<u8>,
122124
}
123125

124-
#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
126+
#[derive(Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
125127
struct TstContainer {
126128
#[serde(rename = "tstTokens")]
127129
tst_tokens: Vec<TstToken>,
128130
}
131+
132+
impl TstContainer {
133+
pub fn add_token(&mut self, token: TstToken) {
134+
self.tst_tokens.push(token);
135+
}
136+
}
137+
138+
/// TO DO: Determine if this needs to be public after refactoring.
139+
///
140+
/// Given a COSE [`ProtectedHeader`] and an arbitrary block of data, use the
141+
/// provided [`TimeStampProvider`] or [`AsyncTimeStampProvider`] to request a
142+
/// timestamp for that block of data.
143+
#[async_generic(
144+
async_signature(
145+
ts_provider: &dyn AsyncTimeStampProvider,
146+
data: &[u8],
147+
p_header: &ProtectedHeader,
148+
mut header_builder: HeaderBuilder,
149+
))]
150+
pub fn add_sigtst_header(
151+
ts_provider: &dyn TimeStampProvider,
152+
data: &[u8],
153+
p_header: &ProtectedHeader,
154+
mut header_builder: HeaderBuilder,
155+
) -> Result<HeaderBuilder, CoseError> {
156+
let sd = cose_countersign_data(data, p_header);
157+
158+
let maybe_cts = if _sync {
159+
ts_provider.send_time_stamp_request(&sd)
160+
} else {
161+
ts_provider.send_time_stamp_request(&sd).await
162+
};
163+
164+
if let Some(cts) = maybe_cts {
165+
let cts = cts?;
166+
let cts = make_cose_timestamp(&cts);
167+
168+
let mut sigtst_vec: Vec<u8> = vec![];
169+
ciborium::into_writer(&cts, &mut sigtst_vec)
170+
.map_err(|e| CoseError::CborGenerationError(e.to_string()))?;
171+
172+
let sigtst_cbor: Value = ciborium::from_reader(sigtst_vec.as_slice())
173+
.map_err(|e| CoseError::CborGenerationError(e.to_string()))?;
174+
175+
header_builder = header_builder.text_value("sigTst".to_string(), sigtst_cbor);
176+
}
177+
178+
Ok(header_builder)
179+
}
180+
181+
// Wrap RFC 3161 TimeStampRsp in COSE sigTst object.
182+
fn make_cose_timestamp(ts_data: &[u8]) -> TstContainer {
183+
let token = TstToken {
184+
val: ts_data.to_vec(),
185+
};
186+
187+
let mut container = TstContainer::default();
188+
container.add_token(token);
189+
190+
container
191+
}

internal/crypto/src/time_stamp/provider.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,79 @@ pub trait TimeStampProvider {
7373
/// asynchronously.
7474
///
7575
/// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161
76-
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
77-
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
76+
#[cfg(not(target_arch = "wasm32"))]
77+
#[async_trait]
78+
pub trait AsyncTimeStampProvider: Sync {
79+
// IMPORTANT: It appears that the Sync (most platforms) vs not-Sync (WASM)
80+
// distinction can't be achieved without duplicating the trait definition 👎🏻.
81+
// Please verify that any changes made here are also made to the subsequent
82+
// definition of AsyncTimeStampProvider for WASM builds.
83+
84+
/// Return the URL for time stamp service.
85+
fn time_stamp_service_url(&self) -> Option<String> {
86+
None
87+
}
88+
89+
/// Additional request headers to pass to the time stamp service.
90+
///
91+
/// IMPORTANT: You should not include the "Content-type" header here.
92+
/// That is provided by default.
93+
fn time_stamp_request_headers(&self) -> Option<Vec<(String, String)>> {
94+
None
95+
}
96+
97+
/// Generate the request body for the HTTPS request to the time stamp
98+
/// service.
99+
fn time_stamp_request_body(&self, message: &[u8]) -> Result<Vec<u8>, TimeStampError> {
100+
default_rfc3161_message(message)
101+
}
102+
103+
/// Request a [RFC 3161] time stamp over an arbitrary data packet.
104+
///
105+
/// The default implementation will send the request to the URL
106+
/// provided by [`Self::time_stamp_service_url()`], if any.
107+
///
108+
/// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161
109+
#[allow(unused_variables)] // `message` not used on WASM
110+
async fn send_time_stamp_request(
111+
&self,
112+
message: &[u8],
113+
) -> Option<Result<Vec<u8>, TimeStampError>> {
114+
// NOTE: This is currently synchronous, but may become
115+
// async in the future.
116+
#[cfg(not(target_arch = "wasm32"))]
117+
if let Some(url) = self.time_stamp_service_url() {
118+
if let Ok(body) = self.time_stamp_request_body(message) {
119+
let headers: Option<Vec<(String, String)>> = self.time_stamp_request_headers();
120+
return Some(
121+
super::http_request::default_rfc3161_request_async(
122+
&url, headers, &body, message,
123+
)
124+
.await,
125+
);
126+
}
127+
}
128+
129+
None
130+
}
131+
}
132+
133+
/// An `AsyncTimeStampProvider` implementation can contact a [RFC 3161] time
134+
/// stamp service and generate a corresponding time stamp for a specific piece
135+
/// of data.
136+
///
137+
/// This is identical to [`TimeStampProvider`] except for performing its work
138+
/// asynchronously.
139+
///
140+
/// [RFC 3161]: https://datatracker.ietf.org/doc/html/rfc3161
141+
#[cfg(target_arch = "wasm32")]
142+
#[async_trait(?Send)]
78143
pub trait AsyncTimeStampProvider {
144+
// IMPORTANT: It appears that the Sync (most platforms) vs not-Sync (WASM)
145+
// distinction can't be achieved without duplicating the trait definition 👎🏻.
146+
// Please verify that any changes made here are also made to the previous
147+
// definition of AsyncTimeStampProvider for non-WASM builds.
148+
79149
/// Return the URL for time stamp service.
80150
fn time_stamp_service_url(&self) -> Option<String> {
81151
None

sdk/src/cose_sign.rs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717

1818
use async_generic::async_generic;
1919
use c2pa_crypto::{
20-
cose::{check_certificate_profile, CertificateTrustPolicy},
20+
cose::{
21+
add_sigtst_header, add_sigtst_header_async, check_certificate_profile,
22+
CertificateTrustPolicy,
23+
},
2124
p1363::parse_ec_der_sig,
2225
SigningAlg,
2326
};
@@ -30,13 +33,8 @@ use coset::{
3033
};
3134

3235
use crate::{
33-
claim::Claim,
34-
cose_validator::verify_cose,
35-
settings::get_settings_value,
36-
time_stamp::{
37-
cose_timestamp_countersign, cose_timestamp_countersign_async, make_cose_timestamp,
38-
},
39-
AsyncSigner, Error, Result, Signer,
36+
claim::Claim, cose_validator::verify_cose, settings::get_settings_value, AsyncSigner, Error,
37+
Result, Signer,
4038
};
4139

4240
/// Generate a COSE signature for a block of bytes which must be a valid C2PA
@@ -310,20 +308,20 @@ fn build_headers(signer: &dyn Signer, data: &[u8], alg: SigningAlg) -> Result<(H
310308
header: protected_header.clone(),
311309
};
312310

313-
let maybe_cts = if _sync {
314-
cose_timestamp_countersign(signer, data, &ph2)
315-
} else {
316-
cose_timestamp_countersign_async(signer, data, &ph2).await
317-
};
311+
let unprotected_h = HeaderBuilder::new();
318312

319-
let mut unprotected_h = if let Some(cts) = maybe_cts {
320-
let cts = cts?;
321-
let sigtst_vec = serde_cbor::to_vec(&make_cose_timestamp(&cts))?;
322-
let sigtst_cbor = serde_cbor::from_slice(&sigtst_vec)?;
323-
324-
HeaderBuilder::new().text_value("sigTst".to_string(), sigtst_cbor)
313+
let mut unprotected_h = if _sync {
314+
if let Some(tsp) = signer.time_stamp_provider() {
315+
add_sigtst_header(*tsp, data, &ph2, unprotected_h)?
316+
} else {
317+
unprotected_h
318+
}
325319
} else {
326-
HeaderBuilder::new()
320+
if let Some(tsp) = signer.async_time_stamp_provider() {
321+
add_sigtst_header_async(*tsp, data, &ph2, unprotected_h).await?
322+
} else {
323+
unprotected_h
324+
}
327325
};
328326

329327
// set the ocsp responder response if available

sdk/src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ impl From<CoseError> for Error {
354354
CoseError::UnsupportedSigningAlgorithm => Self::CoseSignatureAlgorithmNotSupported,
355355
CoseError::InvalidEcdsaSignature => Self::InvalidEcdsaSignature,
356356
CoseError::CborParsingError(_) => Self::CoseTimeStampGeneration,
357+
CoseError::CborGenerationError(_) => Self::CoseTimeStampGeneration,
357358
CoseError::TimeStampError(e) => e.into(),
358359
CoseError::CertificateProfileError(e) => e.into(),
359360
CoseError::CertificateTrustError(e) => e.into(),

sdk/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ pub(crate) mod resource_store;
170170
pub(crate) mod salt;
171171
pub(crate) mod signer;
172172
pub(crate) mod store;
173-
pub(crate) mod time_stamp;
174173

175174
pub(crate) mod utils;
176175
pub(crate) use utils::{cbor_types, hash_utils};

sdk/src/signer.rs

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

1414
use async_trait::async_trait;
15-
use c2pa_crypto::{raw_signature::RawSigner, SigningAlg};
15+
use c2pa_crypto::{
16+
raw_signature::RawSigner,
17+
time_stamp::{AsyncTimeStampProvider, TimeStampProvider},
18+
SigningAlg,
19+
};
1620

1721
use crate::{DynamicAssertion, Result};
1822

@@ -94,6 +98,13 @@ pub trait Signer {
9498
fn dynamic_assertions(&self) -> Vec<Box<dyn DynamicAssertion>> {
9599
Vec::new()
96100
}
101+
102+
/// If this struct also implements [`TimeStampProvider`], return a reference to that struct.
103+
///
104+
/// [`TimeStampProvider`]: c2pa_crypto::time_stamp::TimeStampProvider
105+
fn time_stamp_provider(&self) -> Option<Box<&dyn TimeStampProvider>> {
106+
None
107+
}
97108
}
98109

99110
/// Trait to allow loading of signing credential from external sources
@@ -207,6 +218,13 @@ pub trait AsyncSigner: Sync {
207218
fn dynamic_assertions(&self) -> Vec<Box<dyn DynamicAssertion>> {
208219
Vec::new()
209220
}
221+
222+
/// If this struct also implements [`AsyncTimeStampProvider`], return a reference to that struct.
223+
///
224+
/// [`AsyncTimeStampProvider`]: c2pa_crypto::time_stamp::AsyncTimeStampProvider
225+
fn async_time_stamp_provider(&self) -> Option<Box<&dyn AsyncTimeStampProvider>> {
226+
None
227+
}
210228
}
211229

212230
/// The `AsyncSigner` trait generates a cryptographic signature over a byte array.
@@ -279,6 +297,13 @@ pub trait AsyncSigner {
279297
fn dynamic_assertions(&self) -> Vec<Box<dyn DynamicAssertion>> {
280298
Vec::new()
281299
}
300+
301+
/// If this struct also implements [`AsyncTimeStampProvider`], return a reference to that struct.
302+
///
303+
/// [`AsyncTimeStampProvider`]: c2pa_crypto::time_stamp::AsyncTimeStampProvider
304+
fn async_time_stamp_provider<'a>(&'a self) -> Option<Box<&'a dyn AsyncTimeStampProvider>> {
305+
None
306+
}
282307
}
283308

284309
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -342,6 +367,10 @@ impl Signer for Box<dyn Signer> {
342367
fn send_timestamp_request(&self, message: &[u8]) -> Option<Result<Vec<u8>>> {
343368
(**self).send_timestamp_request(message)
344369
}
370+
371+
fn time_stamp_provider(&self) -> Option<Box<&dyn TimeStampProvider>> {
372+
(**self).time_stamp_provider()
373+
}
345374
}
346375

347376
#[cfg(not(target_arch = "wasm32"))]

0 commit comments

Comments
 (0)