Skip to content

Commit

Permalink
feat: Add support for DynamicAssertions in JSON format (#924)
Browse files Browse the repository at this point in the history
* feat: Add support for DynamicAssertions in JSON format
Adds DynamicAssertionContent type.
  • Loading branch information
gpeacock authored Feb 15, 2025
1 parent a6f5d46 commit 89ae3c0
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 31 deletions.
18 changes: 9 additions & 9 deletions cawg_identity/src/builder/identity_assertion_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// each license.

use async_trait::async_trait;
use c2pa::{AsyncDynamicAssertion, DynamicAssertion, PreliminaryClaim};
use c2pa::{AsyncDynamicAssertion, DynamicAssertion, DynamicAssertionContent, PreliminaryClaim};
use serde_bytes::ByteBuf;

use super::{CredentialHolder, IdentityBuilderError};
Expand Down Expand Up @@ -50,8 +50,8 @@ impl DynamicAssertion for IdentityAssertionBuilder {
"cawg.identity".to_string()
}

fn reserve_size(&self) -> usize {
self.credential_holder.reserve_size()
fn reserve_size(&self) -> c2pa::Result<usize> {
Ok(self.credential_holder.reserve_size())
// TO DO: Credential holder will state reserve size for signature.
// Add additional size for CBOR wrapper outside signature.
}
Expand All @@ -61,7 +61,7 @@ impl DynamicAssertion for IdentityAssertionBuilder {
_label: &str,
size: Option<usize>,
claim: &PreliminaryClaim,
) -> c2pa::Result<Vec<u8>> {
) -> c2pa::Result<DynamicAssertionContent> {
// TO DO: Better filter for referenced assertions.
// For now, just require hard binding.

Expand Down Expand Up @@ -120,8 +120,8 @@ impl AsyncDynamicAssertion for AsyncIdentityAssertionBuilder {
"cawg.identity".to_string()
}

fn reserve_size(&self) -> usize {
self.credential_holder.reserve_size()
fn reserve_size(&self) -> c2pa::Result<usize> {
Ok(self.credential_holder.reserve_size())
// TO DO: Credential holder will state reserve size for signature.
// Add additional size for CBOR wrapper outside signature.
}
Expand All @@ -131,7 +131,7 @@ impl AsyncDynamicAssertion for AsyncIdentityAssertionBuilder {
_label: &str,
size: Option<usize>,
claim: &PreliminaryClaim,
) -> c2pa::Result<Vec<u8>> {
) -> c2pa::Result<DynamicAssertionContent> {
// TO DO: Better filter for referenced assertions.
// For now, just require hard binding.

Expand All @@ -158,7 +158,7 @@ fn finalize_identity_assertion(
signer_payload: SignerPayload,
size: Option<usize>,
signature_result: Result<Vec<u8>, IdentityBuilderError>,
) -> c2pa::Result<Vec<u8>> {
) -> c2pa::Result<DynamicAssertionContent> {
// TO DO: Think through how errors map into c2pa::Error.
let signature = signature_result.map_err(|e| c2pa::Error::BadParam(e.to_string()))?;

Expand Down Expand Up @@ -202,5 +202,5 @@ fn finalize_identity_assertion(
assert_eq!(assertion_size, assertion_cbor.len());
}

Ok(assertion_cbor)
Ok(DynamicAssertionContent::Cbor(assertion_cbor))
}
24 changes: 18 additions & 6 deletions sdk/src/dynamic_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ use async_trait::async_trait;

use crate::{hashed_uri::HashedUri, Result};

/// The type of content that can be returned by a [`DynamicAssertion`] content call.
pub enum DynamicAssertionContent {
/// The assertion is a CBOR-encoded binary blob.
Cbor(Vec<u8>),

/// The assertion is a JSON-encoded string.
Json(String),

/// The assertion is a binary blob with a content type.
Binary(String, Vec<u8>),
}

/// A `DynamicAssertion` is an assertion that has the ability to adjust
/// its content based on other assertions within the overall [`Manifest`].
///
Expand All @@ -41,7 +53,7 @@ pub trait DynamicAssertion {
/// assertion).
///
/// [`Builder`]: crate::Builder
fn reserve_size(&self) -> usize;
fn reserve_size(&self) -> Result<usize>;

/// Return the final assertion content.
///
Expand All @@ -61,7 +73,7 @@ pub trait DynamicAssertion {
label: &str,
size: Option<usize>,
claim: &PreliminaryClaim,
) -> Result<Vec<u8>>;
) -> Result<DynamicAssertionContent>;
}

/// An `AsyncDynamicAssertion` is an assertion that has the ability
Expand Down Expand Up @@ -89,7 +101,7 @@ pub trait AsyncDynamicAssertion: Sync {
/// assertion).
///
/// [`Builder`]: crate::Builder
fn reserve_size(&self) -> usize;
fn reserve_size(&self) -> Result<usize>;

/// Return the final assertion content.
///
Expand All @@ -109,7 +121,7 @@ pub trait AsyncDynamicAssertion: Sync {
label: &str,
size: Option<usize>,
claim: &PreliminaryClaim,
) -> Result<Vec<u8>>;
) -> Result<DynamicAssertionContent>;
}

/// An `AsyncDynamicAssertion` is an assertion that has the ability
Expand Down Expand Up @@ -137,7 +149,7 @@ pub trait AsyncDynamicAssertion {
/// assertion).
///
/// [`Builder`]: crate::Builder
fn reserve_size(&self) -> usize;
fn reserve_size(&self) -> Result<usize>;

/// Return the final assertion content.
///
Expand All @@ -157,7 +169,7 @@ pub trait AsyncDynamicAssertion {
label: &str,
size: Option<usize>,
claim: &PreliminaryClaim,
) -> Result<Vec<u8>>;
) -> Result<DynamicAssertionContent>;
}

/// Describes information from the preliminary C2PA Claim that may
Expand Down
4 changes: 3 additions & 1 deletion sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ pub use builder::{Builder, ManifestDefinition};
pub use c2pa_crypto::raw_signature::SigningAlg;
pub use callback_signer::{CallbackFunc, CallbackSigner};
pub use claim_generator_info::ClaimGeneratorInfo;
pub use dynamic_assertion::{AsyncDynamicAssertion, DynamicAssertion, PreliminaryClaim};
pub use dynamic_assertion::{
AsyncDynamicAssertion, DynamicAssertion, DynamicAssertionContent, PreliminaryClaim,
};
pub use error::{Error, Result};
pub use external_manifest::ManifestPatchCallback;
pub use hash_utils::{hash_stream_by_alg, HashRange};
Expand Down
44 changes: 29 additions & 15 deletions sdk/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ use crate::{
assertions::{
labels::{self, CLAIM},
BmffHash, DataBox, DataHash, DataMap, ExclusionsMap, Ingredient, Relationship, SubsetMap,
UserCbor,
User, UserCbor,
},
asset_io::{
CAIRead, CAIReadWrite, HashBlockObjectType, HashObjectPositions, RemoteRefEmbedType,
Expand All @@ -55,7 +55,9 @@ use crate::{
},
cose_sign::{cose_sign, cose_sign_async},
cose_validator::{verify_cose, verify_cose_async},
dynamic_assertion::{AsyncDynamicAssertion, DynamicAssertion, PreliminaryClaim},
dynamic_assertion::{
AsyncDynamicAssertion, DynamicAssertion, DynamicAssertionContent, PreliminaryClaim,
},
error::{Error, Result},
external_manifest::ManifestPatchCallback,
hash_utils::{hash_by_alg, vec_compare, verify_by_alg},
Expand Down Expand Up @@ -2042,7 +2044,7 @@ impl Store {
// Two passes since we are accessing two fields in self.
let mut assertions = Vec::new();
for da in dyn_assertions.iter() {
let reserve_size = da.reserve_size();
let reserve_size = da.reserve_size()?;
let data1 = serde_cbor::ser::to_vec_packed(&vec![0; reserve_size])?;
let cbor_delta = data1.len() - reserve_size;
let da_data = serde_cbor::ser::to_vec_packed(&vec![0; reserve_size - cbor_delta])?;
Expand Down Expand Up @@ -2080,16 +2082,24 @@ impl Store {
let label = crate::jumbf::labels::assertion_label_from_uri(&uri.url())
.ok_or(Error::BadParam("write_dynamic_assertions".to_string()))?;

let da_size = da.reserve_size();
let da_size = da.reserve_size()?;
let da_data = if _sync {
da.content(&label, Some(da_size), preliminary_claim)?
} else {
da.content(&label, Some(da_size), preliminary_claim).await?
};

// TO DO: Add new assertion to preliminary_claim
// todo: support for non-CBOR asssertions?
final_assertions.push(UserCbor::new(&label, da_data).to_assertion()?);
match da_data {
DynamicAssertionContent::Cbor(data) => {
final_assertions.push(UserCbor::new(&label, data).to_assertion()?);
}
DynamicAssertionContent::Json(data) => {
final_assertions.push(User::new(&label, &data).to_assertion()?);
}
DynamicAssertionContent::Binary(format, data) => {
todo!("Binary dynamic assertions not yet supported");
}
}
}

let pc = self.provenance_claim_mut().ok_or(Error::ClaimEncoding)?;
Expand Down Expand Up @@ -6137,19 +6147,19 @@ pub mod tests {
"com.mycompany.myassertion".to_string()
}

fn reserve_size(&self) -> usize {
fn reserve_size(&self) -> Result<usize> {
let assertion = TestAssertion {
my_tag: "some value I will replace".to_string(),
};
serde_cbor::to_vec(&assertion).unwrap().len()
Ok(serde_cbor::to_vec(&assertion)?.len())
}

fn content(
&self,
_label: &str,
_size: Option<usize>,
claim: &PreliminaryClaim,
) -> Result<Vec<u8>> {
) -> Result<DynamicAssertionContent> {
assert!(claim
.assertions()
.inspect(|a| {
Expand All @@ -6161,7 +6171,9 @@ pub mod tests {
my_tag: "some value I will replace".to_string(),
};

Ok(serde_cbor::to_vec(&assertion).unwrap())
Ok(DynamicAssertionContent::Cbor(
serde_cbor::to_vec(&assertion).unwrap(),
))
}
}

Expand Down Expand Up @@ -6268,19 +6280,19 @@ pub mod tests {
"com.mycompany.myassertion".to_string()
}

fn reserve_size(&self) -> usize {
fn reserve_size(&self) -> Result<usize> {
let assertion = TestAssertion {
my_tag: "some value I will replace".to_string(),
};
serde_cbor::to_vec(&assertion).unwrap().len()
Ok(serde_cbor::to_vec(&assertion)?.len())
}

async fn content(
&self,
_label: &str,
_size: Option<usize>,
claim: &PreliminaryClaim,
) -> Result<Vec<u8>> {
) -> Result<DynamicAssertionContent> {
assert!(claim
.assertions()
.inspect(|a| {
Expand All @@ -6292,7 +6304,9 @@ pub mod tests {
my_tag: "some value I will replace".to_string(),
};

Ok(serde_cbor::to_vec(&assertion).unwrap())
Ok(DynamicAssertionContent::Cbor(
serde_cbor::to_vec(&assertion).unwrap(),
))
}
}

Expand Down
Loading

0 comments on commit 89ae3c0

Please sign in to comment.