diff --git a/Cargo.lock b/Cargo.lock index f824b7013..5b58a767d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -823,9 +823,13 @@ dependencies = [ name = "cawg-identity" version = "0.1.1" dependencies = [ + "async-trait", + "c2pa-crypto", + "ciborium", "hex-literal", "serde", "serde_bytes", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-test", ] diff --git a/cawg_identity/Cargo.toml b/cawg_identity/Cargo.toml index de7db0004..c3d686195 100644 --- a/cawg_identity/Cargo.toml +++ b/cawg_identity/Cargo.toml @@ -25,9 +25,13 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +async-trait = "0.1.78" +c2pa-crypto = { path = "../internal/crypto", version = "0.2.0" } +ciborium = "0.2.2" hex-literal = "0.4.1" serde = { version = "1.0.197", features = ["derive"] } serde_bytes = "0.11.14" +thiserror = "1.0.61" [target.'cfg(target_arch = "wasm32")'.dependencies] wasm-bindgen = "0.2.95" diff --git a/cawg_identity/src/builder/credential_holder.rs b/cawg_identity/src/builder/credential_holder.rs new file mode 100644 index 000000000..8c69a7ea3 --- /dev/null +++ b/cawg_identity/src/builder/credential_holder.rs @@ -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::{builder::IdentityBuilderError, 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://cawg.io/identity/1.1-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) -> Result, IdentityBuilderError>; +} diff --git a/cawg_identity/src/builder/error.rs b/cawg_identity/src/builder/error.rs new file mode 100644 index 000000000..c40288b2b --- /dev/null +++ b/cawg_identity/src/builder/error.rs @@ -0,0 +1,44 @@ +// Copyright 2025 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 std::fmt::Debug; + +use c2pa_crypto::raw_signature::RawSignerError; +use thiserror::Error; + +/// Describes errors that can occur when building a CAWG identity assertion. +#[derive(Debug, Error)] +pub enum IdentityBuilderError { + /// The box size provided for the signature is too small. + #[error("the signature box is too small")] + BoxSizeTooSmall, + + /// An error occurred while generating CBOR. + #[error("error while generating CBOR ({0})")] + CborGenerationError(String), + + /// An error occurred when generating the underlying raw signature. + #[error(transparent)] + RawSignerError(#[from] RawSignerError), + + /// An unexpected internal error occured while requesting the time stamp + /// response. + #[error("internal error ({0})")] + InternalError(String), +} + +impl From> for IdentityBuilderError { + fn from(err: ciborium::ser::Error) -> Self { + Self::CborGenerationError(err.to_string()) + } +} diff --git a/cawg_identity/src/builder/mod.rs b/cawg_identity/src/builder/mod.rs new file mode 100644 index 000000000..4cfe2c7bf --- /dev/null +++ b/cawg_identity/src/builder/mod.rs @@ -0,0 +1,21 @@ +// 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. + +pub(crate) mod credential_holder; +pub use credential_holder::CredentialHolder; + +mod error; +pub use error::IdentityBuilderError; diff --git a/cawg_identity/src/lib.rs b/cawg_identity/src/lib.rs index fad44a640..9131d32c4 100644 --- a/cawg_identity/src/lib.rs +++ b/cawg_identity/src/lib.rs @@ -18,6 +18,8 @@ #![deny(warnings)] #![doc = include_str!("../README.md")] +pub mod builder; + mod identity_assertion; pub use identity_assertion::signer_payload::{HashedUri, SignerPayload}; diff --git a/cawg_identity/src/tests/builder/error.rs b/cawg_identity/src/tests/builder/error.rs new file mode 100644 index 000000000..6b345a6f9 --- /dev/null +++ b/cawg_identity/src/tests/builder/error.rs @@ -0,0 +1,25 @@ +// Copyright 2025 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::IdentityBuilderError; + +#[test] +fn impl_from_ciborium_err() { + let ciborium_err: ciborium::ser::Error = ciborium::ser::Error::Value("foo".to_string()); + let builder_err: IdentityBuilderError = ciborium_err.into(); + + assert_eq!( + builder_err.to_string(), + "error while generating CBOR (Value(\"foo\"))" + ); +} diff --git a/cawg_identity/src/tests/builder/mod.rs b/cawg_identity/src/tests/builder/mod.rs new file mode 100644 index 000000000..796328d55 --- /dev/null +++ b/cawg_identity/src/tests/builder/mod.rs @@ -0,0 +1,14 @@ +// Copyright 2025 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. + +mod error; diff --git a/cawg_identity/src/tests/fixtures/mod.rs b/cawg_identity/src/tests/fixtures/mod.rs new file mode 100644 index 000000000..0c8f76572 --- /dev/null +++ b/cawg_identity/src/tests/fixtures/mod.rs @@ -0,0 +1,16 @@ +// 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. + +mod naive_credential_holder; +#[allow(unused)] +pub(crate) use naive_credential_holder::NaiveCredentialHolder; diff --git a/cawg_identity/src/tests/fixtures/naive_credential_holder.rs b/cawg_identity/src/tests/fixtures/naive_credential_holder.rs new file mode 100644 index 000000000..5add49e88 --- /dev/null +++ b/cawg_identity/src/tests/fixtures/naive_credential_holder.rs @@ -0,0 +1,50 @@ +// 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. + +//! Naive implementation of credential-handling traits for +//! proof-of-concept/testing purposes. +//! +//! The "signature" in this example is simply the CBOR encoding +//! of the `signer_payload` struct. This is really intended to test +//! the signature mechanism, not to be a meaningful signature itself. +//! +//! Not suitable for production use. + +use async_trait::async_trait; + +use crate::{ + builder::{CredentialHolder, IdentityBuilderError}, + SignerPayload, +}; + +pub(crate) struct NaiveCredentialHolder {} + +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +impl CredentialHolder for NaiveCredentialHolder { + fn sig_type(&self) -> &'static str { + "INVALID.identity.naive_credential" + } + + fn reserve_size(&self) -> usize { + 1000 + } + + async fn sign(&self, signer_payload: &SignerPayload) -> Result, IdentityBuilderError> { + // Naive implementation simply serializes SignerPayload + // in CBOR format and calls it a "signature." + let mut result: Vec = vec![]; + ciborium::into_writer(signer_payload, &mut result)?; + Ok(result) + } +} diff --git a/cawg_identity/src/tests/mod.rs b/cawg_identity/src/tests/mod.rs index ad1e07c0f..91a2ad982 100644 --- a/cawg_identity/src/tests/mod.rs +++ b/cawg_identity/src/tests/mod.rs @@ -18,6 +18,8 @@ #![allow(clippy::panic)] #![allow(clippy::unwrap_used)] +mod builder; +pub(crate) mod fixtures; mod identity_assertion; mod internal;