Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add SDK for CAWG identity assertion #644

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
369 changes: 369 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

40 changes: 39 additions & 1 deletion cawg_identity/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "cawg-identity"
version = "0.1.1"
description = "Implementation of CAWG identity assertion specification"
description = "Rust SDK for CAWG (Creator Assertions Working Group) identity assertion"
authors = [
"Eric Scouten <[email protected]>",
]
Expand All @@ -21,3 +21,41 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
async-trait = "0.1.78"
base64 = "0.22.1"
c2pa = { path = "../sdk", version = "0.40.0" }
c2pa-crypto = { path = "../internal/crypto", version = "0.2.0" }
chrono = { version = "0.4.38", features = ["serde"] }
ciborium = "0.2.2"
coset = "0.3.8"
ed25519-dalek = { version = "2.1.1", features = ["rand_core"] }
hex-literal = "0.4.1"
iref = { version = "3.2.2", features = ["serde"] }
jumbf = "0.4.0"
multibase = "0.9.1"
nonempty-collections = { version = "0.2.9", features = ["serde"] }
non-empty-string = { version = "=0.2.4", features = ["serde"] }
rand = "0.8.5"
regex = "1.11"
reqwest = { version = "0.12.8", default-features = false, features = ["rustls-tls"] }
serde = { version = "1.0.197", features = ["derive"] }
serde_bytes = "0.11.14"
serde_json = "1.0.117"
static-iref = "3.0"
thiserror = "1.0.61"
zeroize = { version = "1.8", features = ["zeroize_derive"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2.95"

[dev-dependencies]
serde = { version = "1.0.197", features = ["derive"] }
tempfile = "3.10.1"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
c2pa = { path = "../sdk", version = "0.40.0", features = ["file_io", "openssl_sign", "v1_api"] }
httpmock = "0.7.0"
tokio = { version = "1.40", features = ["macros"] }

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.31"
33 changes: 32 additions & 1 deletion cawg_identity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,35 @@

[![CI](https://github.com/contentauth/c2pa-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/contentauth/c2pa-rs/actions/workflows/ci.yml) [![Latest Version](https://img.shields.io/crates/v/cawg-identity.svg)](https://crates.io/crates/cawg-identity) [![docs.rs](https://img.shields.io/docsrs/cawg-identity)](https://docs.rs/cawg-identity/) [![codecov](https://codecov.io/gh/contentauth/c2pa-rs/branch/main/graph/badge.svg?token=YVHWI19EGN)](https://codecov.io/gh/contentauth/c2pa-rs)

This is a placeholder crate and not yet intended to be used directly. This will become an implementation of the core of the [Creator Assertions Working Group identity assertion draft specification](https://creator-assertions.github.io/identity/).
Implementation of the core of the [Creator Assertions Working Group identity assertion draft specification](https://creator-assertions.github.io/identity/).

## Contributions and feedback

We welcome contributions to this project. For information on contributing, providing feedback, and about ongoing work, see [Contributing](./CONTRIBUTING.md).

## Known limitations

This is very early days for this crate. Many things are subject to change at this point.

In particular, there is no support (yet) for multiple identity assertions in a single manifest.

## Requirements

The toolkit requires **Rust version 1.76.0** or newer. When a newer version of Rust becomes required, a new minor (1.x.0) version of this crate will be released.

### Supported platforms

The toolkit has been tested on the following operating systems:

* Windows
* Only the MSVC build chain is supported on Windows. We would welcome a PR to enable GNU build chain support on Windows.

* MacOS (Intel and Apple silicon)

* Ubuntu Linux on x86 and ARM v8 (aarch64)

## License

The `cawg-identity` crate is distributed under the terms of both the MIT license and the Apache License (Version 2.0).

Some components and dependent crates are licensed under different terms; please check the license terms for each crate and component for details.
51 changes: 51 additions & 0 deletions cawg_identity/src/builder/credential_holder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use async_trait::async_trait;

use crate::SignerPayload;

/// An implementation of `CredentialHolder` is able to generate a signature over
/// the [`SignerPayload`] data structure on behalf of a credential holder.
///
/// Implementations of this trait will specialize based on the kind of
/// credential as specified in [§8. Credentials, signatures, and validation
/// methods] from the CAWG Identity Assertion specification.
///
/// [§8. Credentials, signatures, and validation methods]: https://creator-assertions.github.io/identity/1.0-draft/#_credentials_signatures_and_validation_methods
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait CredentialHolder {
/// Returns the designated `sig_type` value for this kind of credential.
fn sig_type(&self) -> &'static str;

/// Returns the maximum expected size in bytes of the `signature`
/// field for the identity assertion which will be subsequently
/// returned by the [`sign`] function. Signing will fail if the
/// subsequent signature is larger than this number of bytes.
///
/// [`sign`]: Self::sign
fn reserve_size(&self) -> usize;

/// Signs the [`SignerPayload`] data structure on behalf of the credential
/// holder.
///
/// If successful, returns the exact binary content to be placed in
/// the `signature` field for this identity assertion.
///
/// The signature MUST NOT be larger than the size previously stated
/// by the [`reserve_size`] function.
///
/// [`reserve_size`]: Self::reserve_size
async fn sign(&self, signer_payload: &SignerPayload) -> c2pa::Result<Vec<u8>>;
}
34 changes: 34 additions & 0 deletions cawg_identity/src/builder/identity_assertion_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

use crate::builder::CredentialHolder;

/// An `IdentityAssertionBuilder` gathers together the necessary components
/// for an identity assertion. When added to a [`ManifestBuilder`],
/// it ensures that the proper data is added to the final C2PA Manifest.
///
/// [`ManifestBuilder`]: crate::builder::ManifestBuilder
pub struct IdentityAssertionBuilder {
pub(crate) credential_holder: Box<dyn CredentialHolder>,
// referenced_assertions: Vec<MumbleSomething>,
}

impl IdentityAssertionBuilder {
/// Create an `IdentityAssertionBuilder` for the given
/// `CredentialHolder` instance.
pub fn for_credential_holder<CH: CredentialHolder + 'static>(credential_holder: CH) -> Self {
Self {
credential_holder: Box::new(credential_holder),
}
}
}
106 changes: 106 additions & 0 deletions cawg_identity/src/builder/manifest_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

#![allow(deprecated)] // TEMPORARY while building

use c2pa::{CAIRead, CAIReadWrite, Manifest, ManifestPatchCallback, Signer};

use crate::{builder::IdentityAssertionBuilder, internal, IdentityAssertion};

/// TO DO: Docs
#[derive(Default)]
pub struct ManifestBuilder {
identity_assertions: Vec<IdentityAssertion>,
patched_manifest_store: Option<Vec<u8>>,
}

impl ManifestBuilder {
/// Adds an identity assertion to the builder.
pub fn add_assertion(&mut self, identity_assertion: IdentityAssertionBuilder) {
self.identity_assertions
.push(IdentityAssertion::from_builder(identity_assertion));
}

/// This function wraps all the C2PA SDK calls in the (currently)
/// correct sequence. This is likely to change as the C2PA SDK
/// evolves.
pub async fn build(
mut self,
mut manifest: Manifest,
_format: &str,
input_stream: &mut dyn CAIRead,
output_stream: &mut dyn CAIReadWrite,
signer: &dyn Signer,
) -> c2pa::Result<()> {
for ia in self.identity_assertions.iter() {
manifest.add_cbor_assertion("cawg.identity", ia)?;
}

let (placed_manifest, _active_manifest_label) =
manifest.get_placed_manifest(signer.reserve_size(), "jpg", input_stream)?;

let updated_manifest = self
.rewrite_placed_manifest(&placed_manifest)
.await
.ok_or(c2pa::Error::ClaimEncoding)?;

self.patched_manifest_store = Some(updated_manifest);

input_stream.rewind()?;

Manifest::embed_placed_manifest(
&placed_manifest,
"jpg",
input_stream,
output_stream,
signer,
&[Box::new(self)],
)
.map(|_| ())
}

async fn rewrite_placed_manifest(&mut self, manifest_store: &[u8]) -> Option<Vec<u8>> {
let mut updated_ms = manifest_store.to_vec();

let ms = internal::c2pa_parser::ManifestStore::from_slice(manifest_store)?;
let m = ms.active_manifest()?;

let claim = m.claim()?;
let ast = m.assertion_store()?;

for ia in self.identity_assertions.iter_mut() {
// TO DO: Support for multiple identity assertions.

let assertion = ast.find_by_label("cawg.identity")?;
let assertion_dbox = assertion.data_box()?;

let assertion_offset = assertion_dbox.offset_within_superbox(&ms.sbox)?;
let assertion_size = assertion_dbox.data.len();

updated_ms = ia
.update_with_signature(updated_ms, assertion_offset, assertion_size, &claim)
.await?;
}

Some(updated_ms)
}
}

impl ManifestPatchCallback for ManifestBuilder {
fn patch_manifest(&self, _manifest_store: &[u8]) -> c2pa::Result<Vec<u8>> {
match self.patched_manifest_store.as_ref() {
Some(ms) => Ok(ms.clone()),
None => Err(c2pa::Error::ClaimEncoding),

Check warning on line 103 in cawg_identity/src/builder/manifest_builder.rs

View check run for this annotation

Codecov / codecov/patch

cawg_identity/src/builder/manifest_builder.rs#L103

Added line #L103 was not covered by tests
}
}
}
28 changes: 28 additions & 0 deletions cawg_identity/src/builder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 Adobe. All rights reserved.
// This file is licensed to you under the Apache License,
// Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
// or the MIT license (http://opensource.org/licenses/MIT),
// at your option.

// Unless required by applicable law or agreed to in writing,
// this software is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR REPRESENTATIONS OF ANY KIND, either express or
// implied. See the LICENSE-MIT and LICENSE-APACHE files for the
// specific language governing permissions and limitations under
// each license.

//! This module contains the APIs you will use to build a
//! C2PA manifest that contains one or more CAWG identity assertions.
//!
//! This code must be used instead of the APIs in [`c2pa::Manifest`]
//! to ensure that the identity assertion properly references the
//! finalized hard binding assertion.

pub(crate) mod credential_holder;
pub use credential_holder::CredentialHolder;

pub(crate) mod identity_assertion_builder;
pub use identity_assertion_builder::IdentityAssertionBuilder;

mod manifest_builder;
pub use manifest_builder::ManifestBuilder;
Loading
Loading