Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
6 changes: 1 addition & 5 deletions sdk/examples/cawg_identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ mod cawg {
crypto::raw_signature,
identity::{
builder::{AsyncIdentityAssertionBuilder, AsyncIdentityAssertionSigner},
validator::CawgValidator,
x509::AsyncX509CredentialHolder,
},
AsyncSigner, Builder, Reader, SigningAlg,
Expand Down Expand Up @@ -125,10 +124,7 @@ mod cawg {

builder.sign_file_async(&signer, source, &dest).await?;

let mut reader = Reader::from_file(dest)?;

reader.post_validate_async(&CawgValidator {}).await?;

let reader = Reader::from_file_with_cawg_async(dest).await?;
println!("{reader}");
Ok(())
}
Expand Down
26 changes: 18 additions & 8 deletions sdk/src/identity/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ mod tests {
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::wasm_bindgen_test;

use super::*;
use crate::{Reader, ValidationState};

const CONNECTED_IDENTITIES_VALID: &[u8] =
Expand All @@ -78,9 +77,13 @@ mod tests {
crate::settings::set_settings_value("verify.verify_trust", false).unwrap();

let mut stream = Cursor::new(CONNECTED_IDENTITIES_VALID);
let mut reader = Reader::from_stream("image/jpeg", &mut stream).unwrap();
reader.post_validate_async(&CawgValidator {}).await.unwrap();

let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream)
.await
.unwrap();

//println!("validation results: {}", reader);

assert_eq!(
reader
.validation_results()
Expand All @@ -100,9 +103,13 @@ mod tests {
crate::settings::set_settings_value("verify.verify_trust", false).unwrap();

let mut stream = Cursor::new(MULTIPLE_IDENTITIES_VALID);
let mut reader = Reader::from_stream("image/jpeg", &mut stream).unwrap();
reader.post_validate_async(&CawgValidator {}).await.unwrap();

let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream)
.await
.unwrap();

println!("validation results: {reader}");

assert_eq!(
reader
.validation_results()
Expand All @@ -116,10 +123,13 @@ mod tests {
}

#[c2pa_test_async]
async fn test_post_validate_with_hard_binding_missing() {
async fn test_cawg_validate_with_hard_binding_missing() {
let mut stream = Cursor::new(NO_HARD_BINDING);
let mut reader = Reader::from_stream("image/jpeg", &mut stream).unwrap();
reader.post_validate_async(&CawgValidator {}).await.unwrap();

let reader = Reader::from_stream_with_cawg_async("image/jpeg", &mut stream)
.await
.unwrap();

assert_eq!(
reader
.validation_results()
Expand Down
23 changes: 21 additions & 2 deletions sdk/src/identity/x509/x509_credential_holder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ mod tests {
x509::{X509CredentialHolder, X509SignatureVerifier},
IdentityAssertion,
},
status_tracker::StatusTracker,
status_tracker::{LogKind, StatusTracker},
Builder, Reader, SigningAlg,
};

Expand Down Expand Up @@ -134,9 +134,28 @@ mod tests {
// Read back the Manifest that was generated.
dest.rewind().unwrap();

let manifest_store = Reader::from_stream(format, &mut dest).unwrap();
let manifest_store = Reader::from_stream_with_cawg_async(format, &mut dest)
.await
.unwrap();

assert_eq!(manifest_store.validation_status(), None);

let validation_results = manifest_store.validation_results().unwrap();
let active_manifest_results = validation_results.active_manifest().unwrap();
let active_manifest_success_codes = active_manifest_results.success();

let mut ia_success_codes = active_manifest_success_codes.iter().filter(|s| {
s.url()
.map(|url| url.ends_with("cawg.identity"))
.unwrap_or(false)
&& !s.code().starts_with("assertion.")
});

let ia_success = ia_success_codes.next().unwrap();
assert_eq!(ia_success.code(), "signingCredential.trusted");
assert!(ia_success.url().unwrap().ends_with("cawg.identity"));
assert_eq!(ia_success.kind(), &LogKind::Success);

let manifest = manifest_store.active_manifest().unwrap();
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);
Expand Down
175 changes: 171 additions & 4 deletions sdk/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use crate::{
crypto::base64,
dynamic_assertion::PartialClaim,
error::{Error, Result},
identity::validator::CawgValidator,
jumbf::labels::{manifest_label_from_uri, to_absolute_uri, to_relative_uri},
jumbf_io,
manifest::StoreOptions,
Expand Down Expand Up @@ -108,13 +109,17 @@ type ValidationFn =

impl Reader {
/// Create a manifest store [`Reader`] from a stream. A Reader is used to validate C2PA data from an asset.
///
/// # Arguments
/// * `format` - The format of the stream. MIME type or extension that maps to a MIME type.
/// * `stream` - The stream to read from. Must implement the Read and Seek traits. (NOTE: Explain Send trait, required for both sync & async?).
///
/// # Returns
/// A [`Reader`] for the manifest store.
///
/// # Errors
/// Returns an [`Error`] when the manifest data cannot be read. If there's no error upon reading, you must still check validation status to ensure that the manifest data is validated. That is, even if there are no errors, the data still might not be valid.
///
/// # Example
/// This example reads from a memory buffer and prints out the JSON manifest data.
/// ```no_run
Expand All @@ -125,11 +130,35 @@ impl Reader {
/// let reader = Reader::from_stream("image/jpeg", stream).unwrap();
/// println!("{}", reader.json());
/// ```
#[async_generic()]
///
/// # Note
/// This function does not validate [CAWG identity] assertions that may be
/// contained within any C2PA Manifests. If an async call is feasible, use
/// [from_stream_with_cawg_async()]; if not, you can construct an async runtime
/// on your own and perform the CAWG validation separately as shown in the
/// following example:
///
/// ```no_run
/// use std::io::Cursor;
///
/// use c2pa::{identity::validator::CawgValidator, Reader};
/// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg"));
/// let mut reader = Reader::from_stream("image/jpeg", stream).unwrap();
/// let runtime = tokio::runtime::Runtime::new().unwrap();
/// runtime
/// .block_on(reader.post_validate_async(&CawgValidator {}))
/// .unwrap();
/// println!("{}", reader.json());
/// ```
///
/// [CAWG identity]: https://cawg.io/identity
/// [from_stream_with_cawg_async()]: Self::from_stream_with_cawg_async
#[async_generic]
#[cfg(not(target_arch = "wasm32"))]
pub fn from_stream(format: &str, mut stream: impl Read + Seek + Send) -> Result<Reader> {
let verify = get_settings_value::<bool>("verify.verify_after_reading")?; // defaults to true
let mut validation_log = StatusTracker::default();

let store = if _sync {
Store::from_stream(format, &mut stream, verify, &mut validation_log)
} else {
Expand All @@ -139,7 +168,7 @@ impl Reader {
Self::from_store(store, &validation_log)
}

#[async_generic()]
#[async_generic]
#[cfg(target_arch = "wasm32")]
pub fn from_stream(format: &str, mut stream: impl Read + Seek) -> Result<Reader> {
let verify = get_settings_value::<bool>("verify.verify_after_reading")?; // defaults to true
Expand All @@ -154,23 +183,109 @@ impl Reader {
Self::from_store(store, &validation_log)
}

/// Create a manifest store [`Reader`] from a stream. A `Reader` is used to
/// validate C2PA data from an asset. This variation also validates
/// [CAWG identity] assertions within the C2PA data.
///
/// # Arguments
/// * `format` - The format of the stream. MIME type or extension that maps to a MIME type.
/// * `stream` - The stream to read from. Must implement the Read and Seek traits. (NOTE: Explain Send trait, required for both sync & async?).
///
/// # Returns
/// A [`Reader`] for the manifest store.
///
/// # Errors
/// Returns an [`Error`] when the manifest data cannot be read. If there's no error upon reading, you must still check validation status to ensure that the manifest data is validated. That is, even if there are no errors, the data still might not be valid.
///
/// # Example
/// This example reads from a memory buffer and prints out the JSON manifest data.
/// ```ignore
/// # async fn main() {
/// use std::io::Cursor;
///
/// use c2pa::{identity::validator::CawgValidator, Reader};
///
/// let mut stream = Cursor::new(include_bytes!("../tests/fixtures/CA.jpg"));
/// let reader = Reader::from_stream_with_cawg_async("image/jpeg", stream)
/// .await
/// .unwrap();
/// println!("{}", reader.json());
/// # }
/// ```
///
/// [CAWG identity]: https://cawg.io/identity
/// [from_stream_with_cawg_async()]: Self::from_stream_with_cawg_async
#[cfg(not(target_arch = "wasm32"))]
pub async fn from_stream_with_cawg_async(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TO DO: Delete this and fold back into the from_stream_async call. Add a note (depending on outcome) that says not supported on sync.

Copy link
Collaborator Author

@scouten-adobe scouten-adobe Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TO DO: If CAWG identity assertion is encountered, do the asynchronous blocking call in the sync implementation (borrow from the C bindings). Gavin may add a setting to disable if the blocking behavior is undesirable.

format: &str,
stream: impl Read + Seek + Send,
) -> Result<Reader> {
let mut reader = Self::from_stream_async(format, stream).await?;

if get_settings_value::<bool>("verify.verify_after_reading")? {
reader.post_validate_async(&CawgValidator {}).await?;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without verify.verify_after_reading, this will behave the same as Reader::from_stream_async?

Unless I'm mistaken, that feels a bit unintuitive to me. I'd think that calling from_stream_with_cawg_async would be "enough" from an end-user perspective to opt in to CAWG validation.


Ok(reader)
}

#[cfg(target_arch = "wasm32")]
pub async fn from_stream_with_cawg_async(
format: &str,
stream: impl Read + Seek,
) -> Result<Reader> {
let mut reader = Self::from_stream_async(format, stream).await?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementations in
#[cfg(target_arch = "wasm32")]
pub async fn from_stream_with_cawg_async

and

#[cfg(not(target_arch = "wasm32"))]
pub async fn from_stream_with_cawg_async

are the same. Is there no way to reuse instead of duplicating? No trick to bypass the differences in requesteds traits on the stream ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish … I wish …

Sadly, there is not such a trick. (I've looked.)


if get_settings_value::<bool>("verify.verify_after_reading")? {
reader.post_validate_async(&CawgValidator {}).await?;
}

Ok(reader)
}

#[cfg(feature = "file_io")]
/// Create a manifest store [`Reader`] from a file.
/// If the `fetch_remote_manifests` feature is enabled, and the asset refers to a remote manifest, the function fetches a remote manifest.
///
/// NOTE: If the file does not have a manifest store, the function will check for a sidecar manifest with the same base file name and a .c2pa extension.
///
/// # Arguments
/// * `path` - The path to the file.
///
/// # Returns
/// A [`Reader`] for the manifest store.
///
/// # Errors
/// Returns an [`Error`] when the manifest data cannot be read from the specified file. If there's no error upon reading, you must still check validation status to ensure that the manifest data is validated. That is, even if there are no errors, the data still might not be valid.
///
/// # Example
/// This example
///
/// ```no_run
/// use c2pa::Reader;
/// let reader = Reader::from_file("path/to/file.jpg").unwrap();
/// ```
#[async_generic()]
///
/// # Note
/// This function does not validate [CAWG identity] assertions that may be
/// contained within any C2PA Manifests. If an async call is feasible, use
/// [from_file_with_cawg_async()]; if not, you can construct an async runtime
/// on your own and perform the CAWG validation separately as shown in the
/// following example:
///
/// ```ignore
/// use c2pa::{identity::validator::CawgValidator, Reader};
///
/// let mut reader = Reader::from_file("path/to/file.jpg").unwrap();
/// let runtime = tokio::runtime::Runtime::new().unwrap();
/// runtime
/// .block_on(reader.post_validate_async(&CawgValidator {}))
/// .unwrap();
/// println!("{}", reader.json());
/// ```
///
/// [CAWG identity]: https://cawg.io/identity
/// [from_file_with_cawg_async()]: Self::from_file_with_cawg_async
#[async_generic]
pub fn from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Reader> {
let path = path.as_ref();
let format = crate::format_from_path(path).ok_or(crate::Error::UnsupportedType)?;
Expand Down Expand Up @@ -204,6 +319,58 @@ impl Reader {
}
}

#[cfg(feature = "file_io")]
/// Create a manifest store [`Reader`] from a file.
/// If the `fetch_remote_manifests` feature is enabled, and the asset refers to a remote manifest, the function fetches a remote manifest.
///
/// NOTE: If the file does not have a manifest store, the function will check for a sidecar manifest with the same base file name and a .c2pa extension.
///
/// # Arguments
/// * `path` - The path to the file.
///
/// # Returns
/// A [`Reader`] for the manifest store.
///
/// # Errors
/// Returns an [`Error`] when the manifest data cannot be read from the specified file. If there's no error upon reading, you must still check validation status to ensure that the manifest data is validated. That is, even if there are no errors, the data still might not be valid.
///
/// # Example
///
/// ```no_run
/// use c2pa::Reader;
/// let reader = Reader::from_file("path/to/file.jpg").unwrap();
/// ```
///
/// # Note
/// This function does not validate [CAWG identity] assertions that may be
/// contained within any C2PA Manifests. If an async call is feasible, use
/// [from_file_with_cawg_async()]; if not, you can construct an async runtime
/// on your own and perform the CAWG validation separately as shown in the
/// following example:
///
/// ```ignore
/// use c2pa::{identity::validator::CawgValidator, Reader};
///
/// let mut reader = Reader::from_file("path/to/file.jpg").unwrap();
/// let runtime = tokio::runtime::Runtime::new().unwrap();
/// runtime
/// .block_on(reader.post_validate_async(&CawgValidator {}))
/// .unwrap();
/// println!("{}", reader.json());
/// ```
///
/// [CAWG identity]: https://cawg.io/identity
/// [from_file_with_cawg_async()]: Self::from_file_with_cawg_async
pub async fn from_file_with_cawg_async<P: AsRef<std::path::Path>>(path: P) -> Result<Reader> {
let mut reader = Self::from_file_async(path).await?;

if get_settings_value::<bool>("verify.verify_after_reading")? {
reader.post_validate_async(&CawgValidator {}).await?;
}

Ok(reader)
}

/// Create a manifest store [`Reader`] from a JSON string.
/// # Arguments
/// * `json` - A JSON string containing a manifest store definition.
Expand Down
Loading