use std::collections::HashSet;
use async_trait::async_trait;
use serde_bytes::ByteBuf;
use super::{CredentialHolder, IdentityBuilderError};
use crate::{
dynamic_assertion::{
AsyncDynamicAssertion, DynamicAssertion, DynamicAssertionContent, PartialClaim,
},
identity::{builder::AsyncCredentialHolder, IdentityAssertion, SignerPayload},
};
pub struct IdentityAssertionBuilder {
credential_holder: Box<dyn CredentialHolder + Sync + Send>,
referenced_assertions: HashSet<String>,
roles: Vec<String>,
}
impl IdentityAssertionBuilder {
pub fn for_credential_holder<CH: CredentialHolder + 'static + Send + Sync>(
credential_holder: CH,
) -> Self {
Self {
credential_holder: Box::new(credential_holder),
referenced_assertions: HashSet::new(),
roles: vec![],
}
}
pub fn add_referenced_assertions(&mut self, labels: &[&str]) {
for label in labels {
self.referenced_assertions.insert(label.to_string());
}
}
pub fn add_roles(&mut self, roles: &[&str]) {
for role in roles {
self.roles.push(role.to_string());
}
}
}
impl DynamicAssertion for IdentityAssertionBuilder {
fn label(&self) -> String {
"cawg.identity".to_string()
}
fn reserve_size(&self) -> crate::Result<usize> {
Ok(self.credential_holder.reserve_size())
}
fn content(
&self,
_label: &str,
size: Option<usize>,
claim: &PartialClaim,
) -> crate::Result<DynamicAssertionContent> {
let referenced_assertions = claim
.assertions()
.filter(|a| {
if a.url().contains("c2pa.assertions/c2pa.hash.") {
return true;
}
let label = if let Some((_, label)) = a.url().rsplit_once('/') {
label.to_string()
} else {
a.url()
};
self.referenced_assertions.contains(&label)
})
.cloned()
.collect();
let signer_payload = SignerPayload {
referenced_assertions,
sig_type: self.credential_holder.sig_type().to_owned(),
roles: self.roles.clone(),
};
let signature_result = self.credential_holder.sign(&signer_payload);
finalize_identity_assertion(signer_payload, size, signature_result)
}
}
pub struct AsyncIdentityAssertionBuilder {
#[cfg(not(target_arch = "wasm32"))]
credential_holder: Box<dyn AsyncCredentialHolder + Sync + Send>,
#[cfg(target_arch = "wasm32")]
credential_holder: Box<dyn AsyncCredentialHolder>,
referenced_assertions: HashSet<String>,
roles: Vec<String>,
}
#[cfg(target_arch = "wasm32")]
unsafe impl Send for AsyncIdentityAssertionBuilder {}
impl AsyncIdentityAssertionBuilder {
pub fn for_credential_holder<CH: AsyncCredentialHolder + 'static>(
credential_holder: CH,
) -> Self {
Self {
credential_holder: Box::new(credential_holder),
referenced_assertions: HashSet::new(),
roles: vec![],
}
}
pub fn add_referenced_assertions(&mut self, labels: &[&str]) {
for label in labels {
self.referenced_assertions.insert(label.to_string());
}
}
pub fn add_roles(&mut self, roles: &[&str]) {
for role in roles {
self.roles.push(role.to_string());
}
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl AsyncDynamicAssertion for AsyncIdentityAssertionBuilder {
fn label(&self) -> String {
"cawg.identity".to_string()
}
fn reserve_size(&self) -> crate::Result<usize> {
Ok(self.credential_holder.reserve_size())
}
async fn content(
&self,
_label: &str,
size: Option<usize>,
claim: &PartialClaim,
) -> crate::Result<DynamicAssertionContent> {
let referenced_assertions = claim
.assertions()
.filter(|a| {
if a.url().contains("c2pa.assertions/c2pa.hash.") {
return true;
}
let label = if let Some((_, label)) = a.url().rsplit_once('/') {
label.to_string()
} else {
a.url()
};
self.referenced_assertions.contains(&label)
})
.cloned()
.collect();
let signer_payload = SignerPayload {
referenced_assertions,
sig_type: self.credential_holder.sig_type().to_owned(),
roles: self.roles.clone(),
};
let signature_result = self.credential_holder.sign(&signer_payload).await;
finalize_identity_assertion(signer_payload, size, signature_result)
}
}
fn finalize_identity_assertion(
signer_payload: SignerPayload,
size: Option<usize>,
signature_result: Result<Vec<u8>, IdentityBuilderError>,
) -> crate::Result<DynamicAssertionContent> {
let signature = signature_result.map_err(|e| crate::Error::BadParam(e.to_string()))?;
let mut ia = IdentityAssertion {
signer_payload,
signature,
pad1: vec![],
pad2: None,
label: None,
};
let mut assertion_cbor: Vec<u8> = vec![];
c2pa_cbor::to_writer(&mut assertion_cbor, &ia)
.map_err(|e| crate::Error::BadParam(e.to_string()))?;
if let Some(assertion_size) = size {
if assertion_cbor.len() > assertion_size {
return Err(crate::Error::BadParam(format!("Serialized assertion is {len} bytes, which exceeds the planned size of {assertion_size} bytes", len = assertion_cbor.len())));
}
ia.pad1 = vec![0u8; assertion_size - assertion_cbor.len() - 15];
assertion_cbor.clear();
c2pa_cbor::to_writer(&mut assertion_cbor, &ia)
.map_err(|e| crate::Error::BadParam(e.to_string()))?;
ia.pad2 = Some(ByteBuf::from(vec![
0u8;
assertion_size - assertion_cbor.len() - 6
]));
assertion_cbor.clear();
c2pa_cbor::to_writer(&mut assertion_cbor, &ia)
.map_err(|e| crate::Error::BadParam(e.to_string()))?;
assert_eq!(assertion_size, assertion_cbor.len());
}
Ok(DynamicAssertionContent::Cbor(assertion_cbor))
}
#[cfg(test)]
mod tests {
#![allow(clippy::panic)]
#![allow(clippy::unwrap_used)]
use std::io::{Cursor, Seek};
use c2pa_macros::c2pa_test_async;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::wasm_bindgen_test;
use crate::{
identity::{
builder::{
AsyncIdentityAssertionBuilder, AsyncIdentityAssertionSigner,
IdentityAssertionBuilder, IdentityAssertionSigner,
},
tests::fixtures::{
manifest_json, parent_json, NaiveAsyncCredentialHolder, NaiveCredentialHolder,
NaiveSignatureVerifier,
},
IdentityAssertion, ToCredentialSummary,
},
status_tracker::StatusTracker,
Builder, Reader, SigningAlg,
};
const TEST_IMAGE: &[u8] = include_bytes!("../../../tests/fixtures/CA.jpg");
const TEST_THUMBNAIL: &[u8] = include_bytes!("../../../tests/fixtures/thumbnail.jpg");
#[c2pa_test_async]
async fn simple_case() {
let format = "image/jpeg";
let mut source = Cursor::new(TEST_IMAGE);
let mut dest = Cursor::new(Vec::new());
let mut builder = Builder::default().with_definition(manifest_json()).unwrap();
builder
.add_ingredient_from_stream(parent_json(), format, &mut source)
.unwrap();
builder
.add_resource("thumbnail.jpg", Cursor::new(TEST_THUMBNAIL))
.unwrap();
let mut signer = IdentityAssertionSigner::from_test_credentials(SigningAlg::Ps256);
let nch = NaiveCredentialHolder {};
let iab = IdentityAssertionBuilder::for_credential_holder(nch);
signer.add_identity_assertion(iab);
builder
.sign(&signer, format, &mut source, &mut dest)
.unwrap();
dest.rewind().unwrap();
let manifest_store = Reader::default().with_stream(format, &mut dest).unwrap();
assert_eq!(manifest_store.validation_status(), None);
let manifest = manifest_store.active_manifest().unwrap();
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);
let ia = ia_iter.next().unwrap().unwrap();
assert!(ia_iter.next().is_none());
drop(ia_iter);
let label = ia.label.as_ref().unwrap();
assert!(label.ends_with("cawg.identity"));
assert!(label.contains("/c2pa.assertions/"));
let nsv = NaiveSignatureVerifier {};
let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap();
let nc_summary = naive_credential.to_summary();
let nc_json = serde_json::to_string(&nc_summary).unwrap();
assert_eq!(nc_json, "{}");
}
#[c2pa_test_async]
async fn simple_case_async() {
let format = "image/jpeg";
let mut source = Cursor::new(TEST_IMAGE);
let mut dest = Cursor::new(Vec::new());
let mut builder = Builder::default().with_definition(manifest_json()).unwrap();
builder
.add_ingredient_from_stream_async(parent_json(), format, &mut source)
.await
.unwrap();
builder
.add_resource("thumbnail.jpg", Cursor::new(TEST_THUMBNAIL))
.unwrap();
let mut signer = AsyncIdentityAssertionSigner::from_test_credentials(SigningAlg::Ps256);
let nch = NaiveAsyncCredentialHolder {};
let iab = AsyncIdentityAssertionBuilder::for_credential_holder(nch);
signer.add_identity_assertion(iab);
builder
.sign_async(&signer, format, &mut source, &mut dest)
.await
.unwrap();
dest.rewind().unwrap();
let manifest_store = Reader::default().with_stream(format, &mut dest).unwrap();
assert_eq!(manifest_store.validation_status(), None);
let manifest = manifest_store.active_manifest().unwrap();
let mut st = StatusTracker::default();
let mut ia_iter = IdentityAssertion::from_manifest(manifest, &mut st);
let ia = ia_iter.next().unwrap().unwrap();
assert!(ia_iter.next().is_none());
drop(ia_iter);
let nsv = NaiveSignatureVerifier {};
let naive_credential = ia.validate(manifest, &mut st, &nsv).await.unwrap();
let nc_summary = naive_credential.to_summary();
let nc_json = serde_json::to_string(&nc_summary).unwrap();
assert_eq!(nc_json, "{}");
}
}