Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/contentauth/c2pa-rs into as…
Browse files Browse the repository at this point in the history
…ync_sign

* 'main' of https://github.com/contentauth/c2pa-rs:
  Adds an add_validation_status method to Ingredient (#68)
  Prepare 0.8.1 release
  (IGNORE) Disallow overlapping publish runs (#78)
  Use rsa crate for RSA-PSS verification in Wasm (#77)
  Prepare 0.8.0 release
  Add a new API to provide access to COSE signing logic for external signers (#75)
  (MINOR) Move crate-level functions for creating signers to new public `create_signer` mod (#72)
  Prepare 0.7.2 release
  Fix broken documentation build (#74)
  Prepare 0.7.1 release
  Configure docs.rs to include feature-gated items (#73)
  Update XMP Toolkit to 0.5.0 (#71)

# Conflicts:
#	sdk/Cargo.toml
#	sdk/src/cose_sign.rs
  • Loading branch information
mauricefisher64 committed Jul 15, 2022
2 parents 2f5dbeb + f23822c commit dd3ee52
Show file tree
Hide file tree
Showing 30 changed files with 446 additions and 396 deletions.
28 changes: 16 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,27 +146,31 @@ jobs:
command: fmt
args: --all -- --check

doc_format:
name: Doc format
docs_rs:
name: Preflight docs.rs build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Install Rust toolchain
- name: Install nightly Rust toolchain
# Nightly is used here because the docs.rs build
# uses nightly and we use doc_cfg features that are
# not in stable Rust as of this writing (Rust 1.62).
uses: actions-rs/toolchain@v1
with:
toolchain: stable
toolchain: nightly
override: true

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v1

- name: Run cargo docs
uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps
# This is intended to mimic the docs.rs build
# environment. The goal is to fail PR validation
# if the subsequent release would result in a failed
# documentation build on docs.rs.
run: cargo +nightly doc --all-features
env:
RUSTDOCFLAGS: --cfg docsrs
DOCS_RS: 1

cargo-deny:
name: License / vulnerability audit
Expand All @@ -179,7 +183,7 @@ jobs:
- advisories
- bans licenses sources

# Prevent sudden announcement of a new advisory from failing ci:
# Prevent sudden announcement of a new advisory from failing CI:
continue-on-error: ${{ matrix.checks == 'advisories' }}

steps:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:

jobs:
publish:
concurrency: publish-mutex
runs-on: ubuntu-latest
steps:

Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ This project adheres to [Semantic Versioning](https://semver.org), except that

Do not manually edit this file. It will be automatically updated when a new release is published.

## 0.8.1
_15 July 2022_

* Use rsa crate for RSA-PSS verification in Wasm ([#77](https://github.com/contentauth/c2pa-rs/pull/77))

## 0.8.0
_15 July 2022_

* Add a new API to provide access to COSE signing logic for external signers ([#75](https://github.com/contentauth/c2pa-rs/pull/75))
* (MINOR) Move crate-level functions for creating signers to new public `create_signer` mod ([#72](https://github.com/contentauth/c2pa-rs/pull/72))

## 0.7.2
_14 July 2022_

* Fix broken documentation build ([#74](https://github.com/contentauth/c2pa-rs/pull/74))

## 0.7.1
_14 July 2022_

* Configure docs.rs to include feature-gated items ([#73](https://github.com/contentauth/c2pa-rs/pull/73))
* Update XMP Toolkit to 0.5.0 ([#71](https://github.com/contentauth/c2pa-rs/pull/71))
* Refactor code to limit memory usage and remove data copies during hash generation ([#67](https://github.com/contentauth/c2pa-rs/pull/67))

## 0.7.0
_29 June 2022_

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Add this to your `Cargo.toml`:

```toml
[dependencies]
c2pa = "0.7.0"
c2pa = "0.8.1"
```

## Crate features
Expand All @@ -52,7 +52,7 @@ c2pa = "0.7.0"
* `bmff` enables handling of BMFF file formats. Currently only MP4, M4A, and MOV are enabled for writing.
* `file_io` enables manifest generation, signing via OpenSSL, and embedding manifests in various file formats.
* `serialize_thumbnails` includes binary thumbnail data in the [Serde](https://serde.rs/) serialization output.
* `xmp_write` enables updating XMP on embed with the `dcterms:provenance` field (requires [xmp_toolkit](https://crates.io/crates/xmp_toolkit)).
* `xmp_write` enables updating XMP on embed with the `dcterms:provenance` field. (Requires [xmp_toolkit](https://crates.io/crates/xmp_toolkit).)

## Rust version requirements

Expand Down
2 changes: 1 addition & 1 deletion make_test_images/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "make_test_images"
version = "0.7.0"
version = "0.8.1"
authors = ["Gavin Peacock <gpeacock@adobe.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"
Expand Down
6 changes: 2 additions & 4 deletions make_test_images/src/make_test_images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
// each license.

//! Constructs a set of test images using a configuration script
//!
use c2pa::{
assertions::{c2pa_action, Action, Actions, CreativeWork, SchemaDotOrgPerson},
get_signer_from_files, jumbf_io, Error, Ingredient, IngredientOptions, Manifest, ManifestStore,
Signer,
create_signer, jumbf_io, Error, Ingredient, IngredientOptions, Manifest, ManifestStore, Signer,
};

use anyhow::{Context, Result};
Expand All @@ -38,7 +36,7 @@ fn get_signer_with_alg(alg: &str) -> c2pa::Result<Box<dyn Signer>> {
signcert_path.push(format!("../sdk/tests/fixtures/certs/{}.pub", alg));
let mut pkey_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
pkey_path.push(format!("../sdk/tests/fixtures/certs/{}.pem", alg));
get_signer_from_files(signcert_path, pkey_path, alg, None)
create_signer::from_files(signcert_path, pkey_path, alg, None)
}

/// Defines an operation for creating a test image
Expand Down
29 changes: 18 additions & 11 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "c2pa"
version = "0.7.0"
version = "0.8.1"
description = "Rust SDK for C2PA (Coalition for Content Provenance and Authenticity) implementors"
authors = ["Maurice Fisher <mfisher@adobe.com>", "Gavin Peacock <gpeacock@adobe.com>", "Eric Scouten <scouten@adobe.com>", "Leonard Rosenthol <lrosenth@adobe.com>", "Dave Kozma <dkozma@adobe.com>"]
license = "MIT OR Apache-2.0"
Expand All @@ -12,6 +12,10 @@ edition = "2018"
rust-version = "1.58.0"
exclude = ["tests/fixtures"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
async_signer = ["async-trait", "file_io"]
bmff = [] # Work in progress support for BMFF-based containers
Expand Down Expand Up @@ -55,7 +59,7 @@ serde_cbor = "0.11.1"
serde_derive = "1.0.127"
serde_json = "1.0.66"
serde-transcode = "1.1.1"
sha2 = "0.9.5"
sha2 = "0.10.2"
tempfile = "3.1.0"
thiserror = ">= 1.0.20, < 1.0.32"
time = ">= 0.2.23"
Expand All @@ -70,25 +74,28 @@ url = "2.2.2"
ureq = "2.4.0"
instant = "0.1.0"
openssl = { version = "0.10.31", features = ["vendored"], optional = true }
xmp_toolkit = { version = "0.3.4", optional = true }
xmp_toolkit = { version = "0.5.1", optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
console_log = { version = "0.2", features = ["color"] }
getrandom = { version = "0.2.2", features = ["js"] }
getrandom = { version = "0.2.7", features = ["js"] }
# We need to use the `inaccurate` flag here to ensure usage of the JavaScript Date API
# to handle certificate timestamp checking correctly.
instant = { version = "0.1.0", features = ["wasm-bindgen", "inaccurate"] }
js-sys = "0.3.54"
serde-wasm-bindgen = "0.4.1"
wasm-bindgen = "0.2.77"
wasm-bindgen-futures = "0.4.27"
web-sys = { version = "0.3.54", features = ["Crypto", "SubtleCrypto", "CryptoKey", "Window", "WorkerGlobalScope"] }
instant = { version = "0.1.12", features = ["wasm-bindgen", "inaccurate"] }
js-sys = "0.3.58"
rand = "0.8.5"
rsa = "0.6.1"
serde-wasm-bindgen = "0.4.3"
spki = "0.6.0"
wasm-bindgen = "0.2.81"
wasm-bindgen-futures = "0.4.31"
web-sys = { version = "0.3.58", features = ["Crypto", "SubtleCrypto", "CryptoKey", "Window", "WorkerGlobalScope"] }

[dev-dependencies]
anyhow = "1.0.40"

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3.0"
wasm-bindgen-test = "0.3.31"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
actix = "0.11.0"
4 changes: 2 additions & 2 deletions sdk/examples/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use anyhow::Result;

use c2pa::{
assertions::{c2pa_action, labels, Action, Actions, CreativeWork, SchemaDotOrgPerson},
get_signer_from_files, Ingredient, Manifest, ManifestStore,
create_signer, Ingredient, Manifest, ManifestStore,
};
use std::path::PathBuf;

Expand Down Expand Up @@ -111,7 +111,7 @@ pub fn main() -> Result<()> {
// sign and embed into the target file
let signcert_path = "../sdk/tests/fixtures/certs.ps256.pem";
let pkey_path = "../sdk/tests/fixtures/certs.ps256.pub";
let signer = get_signer_from_files(signcert_path, pkey_path, "ps256", None)?;
let signer = create_signer::from_files(signcert_path, pkey_path, "ps256", None)?;

manifest.embed(&source, &dest, &*signer)?;

Expand Down
94 changes: 72 additions & 22 deletions sdk/src/cose_sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@
// 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, Header, HeaderBuilder, Label, TaggedCborSerializable,
};

use crate::{
claim::Claim,
cose_validator::verify_cose,
status_tracker::OneShotStatusTracker,
time_stamp::{cose_timestamp_countersign, make_cose_timestamp},
Error, Result, Signer,
};
fn build_unprotected_header(
data: &[u8],
alg: &str,
Expand All @@ -36,23 +44,6 @@ fn build_unprotected_header(
"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(),
Expand All @@ -68,7 +59,6 @@ fn build_unprotected_header(
_ => return Err(Error::UnsupportedType),
};

// Get the public CAs for the Signer
let sc_der_array_or_bytes = match certs.len() {
1 => Value::Bytes(certs[0].clone()), // single cert
_ => {
Expand Down Expand Up @@ -109,8 +99,43 @@ fn build_unprotected_header(
Ok((alg_id, unprotected_header))
}

/// 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>> {
/// 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.
Expand Down Expand Up @@ -300,3 +325,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);
}
}
12 changes: 6 additions & 6 deletions sdk/src/cose_validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1013,9 +1013,9 @@ pub mod tests {
fn test_verify_cose_good() {
let validator = get_validator("ps256").unwrap();

let sig_bytes = include_bytes!("../tests/fixtures/sig.data");
let data_bytes = include_bytes!("../tests/fixtures/data.data");
let key_bytes = include_bytes!("../tests/fixtures/key.data");
let sig_bytes = include_bytes!("../tests/fixtures/sig_ps256.data");
let data_bytes = include_bytes!("../tests/fixtures/data_ps256.data");
let key_bytes = include_bytes!("../tests/fixtures/key_ps256.data");

assert!(validator
.validate(sig_bytes, data_bytes, key_bytes)
Expand Down Expand Up @@ -1050,9 +1050,9 @@ pub mod tests {
fn test_verify_cose_bad() {
let validator = get_validator("ps256").unwrap();

let sig_bytes = include_bytes!("../tests/fixtures/sig.data");
let data_bytes = include_bytes!("../tests/fixtures/data.data");
let key_bytes = include_bytes!("../tests/fixtures/key.data");
let sig_bytes = include_bytes!("../tests/fixtures/sig_ps256.data");
let data_bytes = include_bytes!("../tests/fixtures/data_ps256.data");
let key_bytes = include_bytes!("../tests/fixtures/key_ps256.data");

let mut bad_bytes = data_bytes.to_vec();
bad_bytes[0] = b'c';
Expand Down
Loading

0 comments on commit dd3ee52

Please sign in to comment.