c2pa-rs

A fork of https://github.com/contentauth/c2pa-rs/
git clone git://archive.git.mtrnord.blog/mtrnords-photography-manager/c2pa-rs.git
Log | Files | Refs | README

commit 2a599549064e846afdadd159636f28c404977c7f
parent c59dc83893f764f62e16ed6d109c2f94d49d2857
Author: Eric Scouten <scouten@adobe.com>
Date:   Fri, 15 Jul 2022 08:31:51 -0700

Add a new API to provide access to COSE signing logic for external signers (#75)


Diffstat:
Msdk/src/cose_sign.rs | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msdk/src/lib.rs | 4+++-
2 files changed, 77 insertions(+), 23 deletions(-)

diff --git a/sdk/src/cose_sign.rs b/sdk/src/cose_sign.rs @@ -11,14 +11,58 @@ // specific language governing permissions and limitations under // each license. -use crate::time_stamp::{cose_timestamp_countersign, make_cose_timestamp}; -use crate::{Error, Result, Signer}; // enable when TimeStamp Authority is ready +//! Provides access to COSE signature generation. + +#![deny(missing_docs)] use ciborium::value::Value; use coset::{iana, CoseSign1, CoseSign1Builder, HeaderBuilder, Label, TaggedCborSerializable}; -/// Returns signed Cose_Sign1 bytes for "data". The Cose_Sign1 will be signed with the algorithm from `Signer`. -pub fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Vec<u8>> { +use crate::{ + claim::Claim, + cose_validator::verify_cose, + status_tracker::OneShotStatusTracker, + time_stamp::{cose_timestamp_countersign, make_cose_timestamp}, + Error, Result, Signer, +}; + +/// Generate a COSE signature for a block of bytes which must be a valid C2PA +/// claim structure. +/// +/// Should only be used when the underlying signature mechanism is detached +/// from the generation of the C2PA manifest (and thus the claim embedded in it). +/// +/// ## Actions taken +/// +/// 1. Verifies that the data supplied is a valid C2PA claim. The function will +/// respond with [`Error::ClaimDecoding`] if not. +/// 2. Signs the data using the provided [`Signer`] instance. Will ensure that +/// the signature is padded to match `box_size`, which should be the number of +/// bytes reserved for the `c2pa.signature` JUMBF box in this claim's manifest. +/// (If `box_size` is too small for the generated signature, this function +/// will respond with an error.) +/// 3. Verifies that the signature is valid COSE. Will respond with an error +/// if unable to validate. +pub fn sign_claim(claim_bytes: &[u8], signer: &dyn Signer, box_size: usize) -> Result<Vec<u8>> { + // must be a valid Claim + let label = "dummy_label"; + let _claim = Claim::from_data(label, claim_bytes)?; + + // generate and verify a CoseSign1 representation of the data + cose_sign(signer, claim_bytes, box_size).and_then(|sig| { + // Sanity check: Ensure that this signature is valid. + + let mut cose_log = OneShotStatusTracker::new(); + match verify_cose(&sig, claim_bytes, b"", false, &mut cose_log) { + Ok(_) => Ok(sig), + Err(err) => Err(err), + } + }) +} + +/// Returns signed Cose_Sign1 bytes for `data`. +/// The Cose_Sign1 will be signed with the algorithm from [`Signer`]. +pub(crate) fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Vec<u8>> { // 13.2.1. X.509 Certificates // // X.509 Certificates are stored in a header named x5chain draft-ietf-cose-x509. @@ -53,23 +97,6 @@ pub fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Ve "ps512" => HeaderBuilder::new() .algorithm(iana::Algorithm::PS512) .build(), - /* No longer supported by C2PA - "rs256" => { - HeaderBuilder::new() - .algorithm(iana::Algorithm::RS256) - .build() - } - "rs384" => { - HeaderBuilder::new() - .algorithm(iana::Algorithm::RS384) - .build() - } - "rs512" => { - HeaderBuilder::new() - .algorithm(iana::Algorithm::RS512) - .build() - } - */ "es256" => HeaderBuilder::new() .algorithm(iana::Algorithm::ES256) .build(), @@ -85,7 +112,7 @@ pub fn cose_sign(signer: &dyn Signer, data: &[u8], box_size: usize) -> Result<Ve _ => return Err(Error::UnsupportedType), }; - // Get the public CAs for the Signer + // Get the public CAs for the Signer. let certs = signer.certs()?; let sc_der_array_or_bytes = match certs.len() { 1 => Value::Bytes(certs[0].clone()), // single cert @@ -219,3 +246,28 @@ fn pad_cose_sig(sign1: &mut CoseSign1, end_size: usize) -> Result<Vec<u8>> { )); pad_cose_sig(sign1, end_size) } + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + + use super::sign_claim; + + use crate::{claim::Claim, utils::test::temp_signer}; + + #[test] + fn test_sign_claim() { + let mut claim = Claim::new("extern_sign_test", Some("contentauth")); + claim.build().unwrap(); + + let claim_bytes = claim.data().unwrap(); + + let box_size = 10000; + + let signer = temp_signer(); + + let cose_sign1 = sign_claim(&claim_bytes, &signer, box_size).unwrap(); + + assert_eq!(cose_sign1.len(), box_size); + } +} diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs @@ -115,8 +115,10 @@ pub(crate) mod assertion; pub(crate) mod asset_handlers; pub(crate) mod asset_io; pub(crate) mod claim; + #[cfg(feature = "file_io")] -pub(crate) mod cose_sign; +pub mod cose_sign; + #[cfg(all(feature = "xmp_write", feature = "file_io"))] pub(crate) mod embedded_xmp; pub(crate) mod hashed_uri;