Skip to content

Commit f798992

Browse files
authored
feat: fetch OCSP wasm (#1402)
Make ocsp_fetch available as async. Add ocsp_fetch for Wasm. as with fetching the manifest, the browser through wasm-bindgen only supports async HTTP GET. WASI supports synchronous and asynchronous fetching. Remove Actix as a dependency. Actix for our purposes is redundant with tokio, which is used for most async tests. The c2pa_test_async macro can be used for these tests. I can break this part up into another PR if that's easier.
1 parent 7960414 commit f798992

File tree

9 files changed

+461
-239
lines changed

9 files changed

+461
-239
lines changed

sdk/src/builder.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,10 @@ mod tests {
16081608
crypto::raw_signature::SigningAlg,
16091609
hash_stream_by_alg,
16101610
settings::Settings,
1611-
utils::{test::write_jpeg_placeholder_stream, test_signer::test_signer},
1611+
utils::{
1612+
test::write_jpeg_placeholder_stream,
1613+
test_signer::{async_test_signer, test_signer},
1614+
},
16121615
validation_results::ValidationState,
16131616
HashedUri, Reader,
16141617
};
@@ -2903,6 +2906,53 @@ mod tests {
29032906
assert_eq!(parent.assertions().len(), 1);
29042907
}
29052908

2909+
#[c2pa_test_async]
2910+
async fn test_redaction_async() {
2911+
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml")).unwrap();
2912+
2913+
// the label of the assertion we are going to redact
2914+
const ASSERTION_LABEL: &str = "stds.schema-org.CreativeWork";
2915+
2916+
let mut input = Cursor::new(TEST_IMAGE);
2917+
2918+
let parent = Reader::from_stream_async("image/jpeg", &mut input)
2919+
.await
2920+
.expect("from_stream");
2921+
let parent_manifest_label = parent.active_label().unwrap();
2922+
// Create a redacted uri for the assertion we are going to redact.
2923+
let redacted_uri =
2924+
crate::jumbf::labels::to_assertion_uri(parent_manifest_label, ASSERTION_LABEL);
2925+
2926+
let mut builder = Builder::edit();
2927+
builder.definition.redactions = Some(vec![redacted_uri.clone()]);
2928+
2929+
let redacted_action = crate::assertions::Action::new("c2pa.redacted")
2930+
.set_reason("testing".to_owned())
2931+
.set_parameter("redacted".to_owned(), redacted_uri.clone())
2932+
.unwrap();
2933+
2934+
builder.add_action(redacted_action).unwrap();
2935+
2936+
let signer = async_test_signer(SigningAlg::Ps256);
2937+
// Embed a manifest using the signer.
2938+
let mut output = Cursor::new(Vec::new());
2939+
builder
2940+
.sign_async(signer.as_ref(), "image/jpeg", &mut input, &mut output)
2941+
.await
2942+
.expect("builder sign");
2943+
2944+
output.set_position(0);
2945+
2946+
let reader = Reader::from_stream_async("image/jpeg", &mut output)
2947+
.await
2948+
.expect("from_bytes");
2949+
//println!("{reader}");
2950+
let m = reader.active_manifest().unwrap();
2951+
assert_eq!(m.ingredients().len(), 1);
2952+
let parent = reader.get_manifest(parent_manifest_label).unwrap();
2953+
assert_eq!(parent.assertions().len(), 1);
2954+
}
2955+
29062956
#[test]
29072957
fn test_supported_mime_types() {
29082958
let mime_types = Builder::supported_mime_types();

sdk/src/cose_sign.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ impl AsyncTimeStampProvider for AsyncSignerWrapper<'_> {
274274
mod tests {
275275
#![allow(clippy::unwrap_used)]
276276

277-
// Only used for test with file_io
278277
use c2pa_macros::c2pa_test_async;
279278
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
280279
use wasm_bindgen_test::wasm_bindgen_test;

sdk/src/crypto/cose/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ mod error;
3232
pub use error::CoseError;
3333

3434
mod ocsp;
35-
pub(crate) use ocsp::fetch_and_check_ocsp_response;
3635
pub use ocsp::{check_ocsp_status, check_ocsp_status_async, get_ocsp_der, OcspFetchPolicy};
36+
pub(crate) use ocsp::{fetch_and_check_ocsp_response, fetch_and_check_ocsp_response_async};
3737

3838
mod sign;
3939
pub use sign::{sign, sign_async, sign_v2_embedded, sign_v2_embedded_async, CosePayload};

sdk/src/crypto/cose/ocsp.rs

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,15 @@
1212
// each license.
1313

1414
use async_generic::async_generic;
15+
use chrono::{DateTime, Utc};
1516
use ciborium::value::Value;
1617
use coset::{CoseSign1, Label};
17-
#[cfg(not(target_arch = "wasm32"))]
18-
use {
19-
crate::crypto::cose::cert_chain_from_sign1,
20-
chrono::{DateTime, Utc},
21-
};
2218

2319
use crate::{
2420
crypto::{
2521
asn1::rfc3161::TstInfo,
2622
cose::{
27-
check_end_entity_certificate_profile, validate_cose_tst_info,
23+
cert_chain_from_sign1, check_end_entity_certificate_profile, validate_cose_tst_info,
2824
validate_cose_tst_info_async, CertificateTrustError, CertificateTrustPolicy, CoseError,
2925
},
3026
ocsp::OcspResponse,
@@ -101,7 +97,12 @@ pub fn check_ocsp_status(
10197

10298
None => match fetch_policy {
10399
OcspFetchPolicy::FetchAllowed => {
104-
fetch_and_check_ocsp_response(sign1, data, ctp, tst_info, validation_log)
100+
if _sync {
101+
fetch_and_check_ocsp_response(sign1, data, ctp, tst_info, validation_log)
102+
} else {
103+
fetch_and_check_ocsp_response_async(sign1, data, ctp, tst_info, validation_log)
104+
.await
105+
}
105106
}
106107
OcspFetchPolicy::DoNotFetch => {
107108
if let Some(ocsp_response_ders) = ocsp_responses {
@@ -276,54 +277,53 @@ fn check_stapled_ocsp_response(
276277
}
277278

278279
/// Fetches and validates an OCSP response for the given COSE signature.
279-
// TO DO: Add async version of this?
280+
#[async_generic()]
281+
#[allow(unreachable_code)] // wasm-bindgen will immediately return error for synchronous use.
282+
#[allow(unused_variables)]
280283
pub(crate) fn fetch_and_check_ocsp_response(
281284
sign1: &CoseSign1,
282285
data: &[u8],
283286
ctp: &CertificateTrustPolicy,
284287
_tst_info: Option<&TstInfo>,
285288
validation_log: &mut StatusTracker,
286289
) -> Result<OcspResponse, CoseError> {
287-
#[cfg(target_arch = "wasm32")]
288-
{
289-
let _ = (sign1, data, ctp, validation_log);
290-
Ok(OcspResponse::default())
291-
}
290+
let certs = cert_chain_from_sign1(sign1)?;
292291

293-
#[cfg(not(target_arch = "wasm32"))]
294-
{
295-
let certs = cert_chain_from_sign1(sign1)?;
296-
297-
let Some(ocsp_der) = crate::crypto::ocsp::fetch_ocsp_response(&certs) else {
298-
return Ok(OcspResponse::default());
299-
};
292+
let ocsp_der: Vec<u8> = if _sync {
293+
match crate::crypto::ocsp::fetch_ocsp_response(&certs) {
294+
Some(der) => der,
295+
None => return Ok(OcspResponse::default()),
296+
}
297+
} else {
298+
match crate::crypto::ocsp::fetch_ocsp_response_async(&certs).await {
299+
Some(der) => der,
300+
None => return Ok(OcspResponse::default()),
301+
}
302+
};
300303

301-
let ocsp_response_der = ocsp_der;
304+
let ocsp_response_der = ocsp_der;
302305

303-
let signing_time: Option<DateTime<Utc>> =
304-
validate_cose_tst_info(sign1, data, ctp, validation_log)
305-
.ok()
306-
.map(|tst_info| tst_info.gen_time.clone().into());
306+
let signing_time: Option<DateTime<Utc>> =
307+
validate_cose_tst_info(sign1, data, ctp, validation_log)
308+
.ok()
309+
.map(|tst_info| tst_info.gen_time.clone().into());
307310

308-
// Check the OCSP response, but only if it is well-formed.
309-
// Revocation errors are reported in the validation log.
310-
let Ok(ocsp_data) =
311-
OcspResponse::from_der_checked(&ocsp_response_der, signing_time, validation_log)
312-
else {
313-
// TO REVIEW: This is how the old code worked, but is it correct to ignore a
314-
// malformed OCSP response?
315-
return Ok(OcspResponse::default());
311+
// Check the OCSP response, but only if it is well-formed.
312+
// Revocation errors are reported in the validation log.
313+
let ocsp_data =
314+
match OcspResponse::from_der_checked(&ocsp_response_der, signing_time, validation_log) {
315+
Ok(data) => data,
316+
Err(_) => return Ok(OcspResponse::default()),
316317
};
317318

318-
// If we get a valid response validate the certs.
319-
if ocsp_data.revoked_at.is_none() {
320-
if let Some(ocsp_certs) = &ocsp_data.ocsp_certs {
321-
check_end_entity_certificate_profile(&ocsp_certs[0], ctp, validation_log, None)?;
322-
}
319+
// If we get a valid response validate the certs.
320+
if ocsp_data.revoked_at.is_none() {
321+
if let Some(ocsp_certs) = &ocsp_data.ocsp_certs {
322+
check_end_entity_certificate_profile(&ocsp_certs[0], ctp, validation_log, None)?;
323323
}
324-
325-
Ok(ocsp_data)
326324
}
325+
326+
Ok(ocsp_data)
327327
}
328328

329329
/// Returns the DER-encoded OCSP response from the "rVals" unprotected header in a COSE_Sign1 message.

0 commit comments

Comments
 (0)