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 6f91da0c3add42d0d77494bc57701dbd9e6aa4e9
parent 9da7a0785e5b0e0bd0a66f592ddf69ab25c55f30
Author: Eric Scouten <scouten@adobe.com>
Date:   Wed,  8 Jun 2022 14:44:20 -0700

Remove c2patool source now that it's in its own repo (#35)

c2patool now lives in its own repo (https://github.com/contentauth/c2patool); remove it from here.

Co-authored-by: Gavin Peacock <gpeacock@adobe.com>
Diffstat:
M.github/dependabot.yml | 5-----
M.github/workflows/publish.yml | 6------
MCargo.toml | 2+-
MMakefile | 43-------------------------------------------
MREADME.md | 4++--
Dc2patool/Cargo.toml | 22----------------------
Dc2patool/sample/config.json | 58----------------------------------------------------------
Dc2patool/sample/es256_certs.pem | 39---------------------------------------
Dc2patool/sample/es256_private.key | 5-----
Dc2patool/src/README.md | 189-------------------------------------------------------------------------------
Dc2patool/src/config.rs | 53-----------------------------------------------------
Dc2patool/src/main.rs | 300-------------------------------------------------------------------------------
Dc2patool/src/signer.rs | 81-------------------------------------------------------------------------------
Dc2patool/tests/integration.rs | 123-------------------------------------------------------------------------------
14 files changed, 3 insertions(+), 927 deletions(-)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml @@ -9,11 +9,6 @@ updates: interval: "daily" - package-ecosystem: "cargo" - directory: "c2patool" - schedule: - interval: "daily" - - - package-ecosystem: "cargo" directory: "make_test_images" schedule: interval: "daily" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml @@ -98,9 +98,3 @@ jobs: cargo publish --token $CRATES_IO_SECRET -p c2pa env: CRATES_IO_SECRET: ${{ secrets.CRATES_IO_SECRET }} - - - name: Publish c2patool crate - run: | - cargo publish --token $CRATES_IO_SECRET -p c2patool - env: - CRATES_IO_SECRET: ${{ secrets.CRATES_IO_SECRET }} diff --git a/Cargo.toml b/Cargo.toml @@ -1,2 +1,2 @@ [workspace] -members = ["sdk", "c2patool", "make_test_images"] +members = ["sdk", "make_test_images"] diff --git a/Makefile b/Makefile @@ -32,49 +32,6 @@ test-wasm: # Run this before pushing a PR to pre-validate test: check-format check-docs clippy test-local test-wasm -# Creates a folder wtih c2patool bin, samples and readme -c2patool-package: - rm -rf target/c2patool* - mkdir -p target/c2patool - mkdir -p target/c2patool/sample - cp target/release/c2patool target/c2patool/c2patool - cp c2patool/src/README.md target/c2patool/README.md - cp c2patool/sample/* target/c2patool/sample - cp CHANGELOG.md target/c2patool/CHANGELOG.md - cp sdk/tests/fixtures/IMG_0003.jpg target/c2patool/image.jpg - -# These are for building the c2patool release bin on various platforms -build-release-win: - cargo build --release - -build-release-mac-arm: - rustup target add aarch64-apple-darwin - MACOSX_DEPLOYMENT_TARGET=11.1 cargo build --target=aarch64-apple-darwin --release - -build-release-mac-x86: - rustup target add x86_64-apple-darwin - MACOSX_DEPLOYMENT_TARGET=10.15 cargo build --target=x86_64-apple-darwin --release - -build-release-mac-universal: build-release-mac-arm build-release-mac-x86 - lipo -create -output target/release/c2patool target/aarch64-apple-darwin/release/c2patool target/x86_64-apple-darwin/release/c2patool - -build-release-linux: - cargo build --release - -# Builds and packages a zip for c2patool for each platform -ifeq ($(PLATFORM), mac) -c2patool-release: build-release-mac-universal c2patool-package - cd target && zip -r c2patool_mac.zip c2patool && cd .. -endif -ifeq ($(PLATFORM), win) -c2patool-release: build-release-win c2patool-package - cd target && tar.exe -a -c -f c2patool_win.zip c2patool && cd .. -endif -ifeq ($(PLATFORM), linux) -c2patool-release: build-release-linux c2patool-package - cd target && tar -czvf c2patool_linux.tar.gz c2patool && cd .. -endif - # Builds and views documentation doc: cargo doc --no-deps --open diff --git a/README.md b/README.md @@ -1,8 +1,8 @@ -# C2PA Rust SDK and Command-Line Tool +# C2PA Rust SDK The **[Coalition for Content Provenance and Authenticity](https://c2pa.org)** (C2PA) addresses the prevalence of misleading information online through the development of technical standards for certifying the source and history (or provenance) of media content. C2PA is a Joint Development Foundation project, formed through an alliance between Adobe, Arm, Intel, Microsoft and Truepic. -This Rust library and command-line tool for creating and inspecting C2PA data structures are created by Adobe and other contributors as part of our work on the [Content Authenticity Initiative](https://contentauthenticity.org). +This Rust library for creating and inspecting C2PA data structures are created by Adobe and other contributors as part of our work on the [Content Authenticity Initiative](https://contentauthenticity.org). ## Key Features diff --git a/c2patool/Cargo.toml b/c2patool/Cargo.toml @@ -1,22 +0,0 @@ -[package] -name = "c2patool" -version = "0.2.0" -authors = ["Gavin Peacock <gpeacock@adobe.com>", "Maurice Fisher <mfisher@adobe.com>"] -license = "MIT OR Apache-2.0" -edition = "2018" -rust-version = "1.58.0" - -[dependencies] -anyhow = "1.0" -c2pa = { path = "../sdk", features = ["file_io"] } -env_logger = "0.9" -log = "0.4" -serde = { version = "1.0", features = ["derive"] } -serde_derive = "1.0" -serde_json = "1.0" -structopt = "0.3" -tempfile = "3.3" - -[dev-dependencies] -assert_cmd = "2.0" -predicates = "2.1" diff --git a/c2patool/sample/config.json b/c2patool/sample/config.json @@ -1,57 +0,0 @@ -{ - "alg": "es256", - "private_key": "es256_private.key", - "sign_cert": "es256_certs.pem", - "ta": "http://timestamp.digicert.com", - - "vendor": "myvendor", - "claim_generator": "TestApp", - "title": "My Title", - "assertions": [ - { - "label": "stds.schema-org.CreativeWork", - "data": { - "@context": "https://schema.org", - "@type": "CreativeWork", - "author": [ - { - "@type": "Person", - "name": "Joe Bloggs" - } - ], - "url": "https://contentauthenticity.org/" - } - }, - { - "label": "c2pa.actions", - "data": { - "actions": [ - { - "action": "c2pa.opened" - }, - { - "action": "c2pa.edited", - "parameters": { - "name": "brightnesscontrast" - } - } - ], - "metadata": { - "reviewRatings": [ - { - "code": "c2pa.unknown", - "explanation": "Something untracked happened", - "value": 4 - } - ] - } - } - }, - { - "label": "my.assertion", - "data": { - "any_tag": "whatever I want" - } - } - ] -} -\ No newline at end of file diff --git a/c2patool/sample/es256_certs.pem b/c2patool/sample/es256_certs.pem @@ -1,39 +0,0 @@ -Bag Attributes - localKeyID: 21 9D 38 2E 7C 25 38 78 94 3F CA DA 5A A7 BC BA 3F 7F 24 21 -subject=/O=Media Publisher Company/CN=Bob -issuer=/O=Media Publisher Company/CN=Media Publisher Company Intermediate CA ------BEGIN CERTIFICATE----- -MIICQDCCAaGgAwIBAgIUXsGqKw4Bw9PJBv1BcL3SLGiyCT4wCgYIKoZIzj0EAwIw -VDEgMB4GA1UECgwXTWVkaWEgUHVibGlzaGVyIENvbXBhbnkxMDAuBgNVBAMMJ01l -ZGlhIFB1Ymxpc2hlciBDb21wYW55IEludGVybWVkaWF0ZSBDQTAeFw0yMjA0MDQx -NDE1NDhaFw0yMzA0MDQxNDE1NDhaMDAxIDAeBgNVBAoMF01lZGlhIFB1Ymxpc2hl -ciBDb21wYW55MQwwCgYDVQQDDANCb2IwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC -AAQv+8qHEYohEJwVLKQIqJX6se0E+CKk89bwRYRRfCsmHeZuFLdBcJ082I67mpSM -zzFN1eSX82Mdphb7+t/uIKPko3UwczAOBgNVHQ8BAf8EBAMCBsAwFgYDVR0lAQH/ -BAwwCgYIKwYBBQUHAwQwCQYDVR0TBAIwADAdBgNVHQ4EFgQUTqgwjgSAknNN8T+e -bNXm+mbtlfIwHwYDVR0jBBgwFoAUXF8IatqTvlwmGnVmM2L6+v1IQMcwCgYIKoZI -zj0EAwIDgYwAMIGIAkIAkZ0LAaJ209QLyiSn/hIMfbBReg+d61gX8U+9OqBWYiD2 -i6u59mJrKdwCuj8po8jh7ntkcXHc1v+3ztHWCHCI9R0CQgErPKUhrxei5mbKU0Xx -NUsTBB6oHMZccZCn1FS0R7YaCFume2mscC1rGGNXqu/Skgsq6FPkFHJqyFTZhtcW -pJWKaw== ------END CERTIFICATE----- -Bag Attributes: <No Attributes> -subject=/O=Media Publisher Company/CN=Media Publisher Company Intermediate CA -issuer=/CN=Media Provenance Intermediate CA 1 ------BEGIN CERTIFICATE----- -MIICbTCCAc+gAwIBAgIUA7qQpsd9jsBL7dahNfBx+ftJ5VQwCgYIKoZIzj0EAwQw -LTErMCkGA1UEAwwiTWVkaWEgUHJvdmVuYW5jZSBJbnRlcm1lZGlhdGUgQ0EgMTAe -Fw0yMjA0MDQxNDE1MDRaFw0zMjAzMzExNDE1MDRaMFQxIDAeBgNVBAoMF01lZGlh -IFB1Ymxpc2hlciBDb21wYW55MTAwLgYDVQQDDCdNZWRpYSBQdWJsaXNoZXIgQ29t -cGFueSBJbnRlcm1lZGlhdGUgQ0EwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABABo -g4jSfIvYPwpADEOiQjWOSD5KXTJl9k/gz0vVpE1D3gdn5TK2UiuEiKiiZvND45pi -U/TW0jVs6Rfns7mBTKEAygFGbcjEVqMZTfXcWYIi2AvLARe/HCeVMO3x5g4AmDmr -CgshTHWNwrit6u/ae9YdOv5QdqBLKW6NRdvv4jvpZpK/QKNjMGEwHQYDVR0OBBYE -FFxfCGrak75cJhp1ZjNi+vr9SEDHMB8GA1UdIwQYMBaAFP59tM4KPiaHG81hkWz1 -1CoFfPYiMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49 -BAMEA4GLADCBhwJCAeYlt/gDEOc0Y5NFLTcf3tOrwYR+m4Gn+bbgrdKznU2ygm0P -PqKHjCT+bMZmO7NJHHcwRg245AeKp1tCJ/nM/3+FAkFMQq5CaD6l7meYE+8wFtlD -vXlpFWg3ryuroc/DDBFMyrTZKWCz3wl5bLfx4GoWvdsrFBdClrhTO2srJmmQKH6t -QA== ------END CERTIFICATE----- - diff --git a/c2patool/sample/es256_private.key b/c2patool/sample/es256_private.key @@ -1,5 +0,0 @@ ------BEGIN EC PRIVATE KEY----- -MHcCAQEEIL2yf62xPPFBKbDcBEKU5HIickmG2DEDcZt0lD3N4aOLoAoGCCqGSM49 -AwEHoUQDQgAEL/vKhxGKIRCcFSykCKiV+rHtBPgipPPW8EWEUXwrJh3mbhS3QXCd -PNiOu5qUjM8xTdXkl/NjHaYW+/rf7iCj5A== ------END EC PRIVATE KEY----- diff --git a/c2patool/src/README.md b/c2patool/src/README.md @@ -1,189 +0,0 @@ -# c2paTool - -Command line tool for displaying and adding C2PA manifests -A file path to a JPEG or a claim definition JSON file must be provided -If a JPEG path is given, this will generate a summary report of any manifests in that file -If a manifest definition JSON file is specified, the manifest will be created and displayed in a JSON report - -## Displaying Manifest data - -Invoking the tool with a path to an image file will output a JSON report of the Manifests in the file -File formats supported are jpeg and png. - -```c2patool image.jpg``` - -## Displaying detailed Manifest data - -The -d option will output a detailed JSON report of the internal C2PA structure - -```c2patool image.jpg -d``` - -## Previewing a Manifest - -If a path to a json config file is given, -the tool will generate a new manifest using the values given in definition -this will display the results but not save anything unless an output (-o) is specified - -```c2patool sample/config.json``` - -The config json can also be passed on the command line as string using the -c --config option - -```shell -c2patool -c '{"assertions": [{"label": "org.contentauth.test", "data": {"name": "Jane Doe"}}]}' -``` - -## Creating a new output image - -A file path for creating an output file with any added claim data -If the output file already exists, any C2PA data in that file will be replaced and the image maintained -If the output file doesn't exist, a parent file must be available for a source image -If you are not changing an image and just adding C2PA data, use an existing output file and no parent -If you have edited an image and want to add C2PA data to it, pass the original as the parent -and put the edited file at the output location to have the C2PA data added. - -```c2patool sample/config.json -o output.jpg``` -## Overriding the parent file - -When using a json file, the parent file can be specified by passing -p or --parent with the path to the file -This allows adding the same manifest data to different source images - -## Working with .c2pa manifest files - -If the extension of the output file is '.c2pa' a standalone manifest store will be written - -```c2patool claim_image.jpg -o manifest.c2pa``` - -These .c2pa manifest files can be read by claim tool and will generate reports. - -```c2patool manifest.c2pa``` -## Setup - -Before you can add a manifest, you need to create an SSL certificate -You can specify the path to the cert files in the configuration fields -``` -private_key -sign_cert -``` -If you are using a signing algorithm other than the default ps256, you will need to specify it in -```alg``` -Which can be set to one of [ ps256 | ps384 | ps512 | es256 | es384 | es512 | ed25519] and -must be compatible with values of private key and sign cert. - -The key and cert can also be placed in the environment variables C2PA_PRIVATE_KEY and C2PA_PUB_CERT -These two variable are used to set the private key and public certificates. For example to sign with es256 signatures -using the content of a private key file and certificate file: - -```set C2PA_PRIVATE_KEY=$(cat my_es256_private_key)``` -```set C2PA_PUB_CERT=$(cat my_es256_certs)``` - -The both private key and sign cert should be in PEM format. The sign cert should contain a certificate -chain PEMs starting for the end-entity certificate used to sign the claim ending with intermediate certificate -before the root CA certificate. See ```sample`` folder for example certificates. - -To create your own temporary files for testing you can execute the following command - -```shell -sudo openssl req -new -newkey rsa:4096 -sigopt rsa_padding_mode:pss -days 180 -extensions v3_ca -addext "keyUsage = digitalSignature" -addext "extendedKeyUsage = emailProtection" -nodes -x509 -keyout private.key -out certs.pem -sha256 -``` - -Note you may have need to update your openssl version if the above command does not work. - -c2patool can also timestamp the signature data that is embedded. This is useful for validating an asset when the embedded -certificates have expired. If the config has a ta_url set, c2patool will attempt to timestamp the signature using the TA service at the provided URL. The TA must be RFC3161 compliant. Example TA setting: - -```ta_url=http://timestamp.digicert.com``` - -## Configuration file format - -The Configuration file is a JSON formatted file with a .json extension: - -The schema for this type is as follows: -```json -{ - "$schema": "http://json-schema.org/draft-07/schema", - "$id": "http://ns.adobe.com/cai/claim-definition/v1", - "type": "object", - "description": "Definition format for claim created with c2patool", - "examples": [ - { - "vendor": "myvendor", - "claim_generator": "MyApp/0.1", - "parent": "image.jpg", - "ingredients": [], - "assertions": [ - { - "label": "my.assertion", - "data": { - "any_tag": "whatever I want" - } - } - ], - "alg": "es256", - "private_key": "es256_private.key", - "sign_cert": "es256_certs.pem", - "ta_url": "http://timestamp.digicert.com" - } - ], - "required": [ - "assertions", - ], - "properties": { - "vendor": { - "type": "string", - "description": "Typically an Internet domain name (without the TLD) for the vendor (i.e. `adobe`, `nytimes`)" - }, - "claim_generator": { - "type": "string", - "description": "A UserAgent string that will let a user know what software/hardware/system produced this Manifest - names should not contain spaces (defaults to c2patool)" - }, - "title": { - "type": "string", - "description": "A human-readable string to be displayed as the tile for this Manifest (defaults to embedded file name)" - }, - "credentials": { - "type": "object", - "description": "An array of W3C verifiable credentials objects defined in the c2pa assertion specification. Section 7" - }, - "parent": { - "type": "string", - "format": "Local file system path", - "description": "A file path to the source image that was modified by this Manifest (if any)" - }, - "Ingredients": { - "type": "array of string", - "format": "Array of local file system paths", - "description": "File paths to images that were used to modify the image referenced by this Manifest (if any)" - }, - "assertions": { - "type": "object", - "description": "Objects with label, and data - standard c2pa labels must match values as defined in the c2pa assertion specification" - }, - "alg": { - "type": "string", - "format": "Local file system path", - "description": "Signing algorithm: one of [ ps256 | ps384 | ps512 | es256 | es384 | es512 | ed25519]" - }, - "ta_url": { - "type": "string", - "format": "http URL", - "description": "A URL to an RFC3161 compliant Time Stamp Authority" - }, - "private_key": { - "type": "string", - "format": "Local file system path", - "description": "File path to a private key file" - }, - "sign_cert": { - "type": "string", - "format": "Local file system path", - "description": "File path to signing cert file" - }, - "base_path": { - "type": "string", - "format": "Local file system path", - "description": "File path to a folder to use as the base for relative paths in config" - }, - }, - "additionalProperties": false -} -``` diff --git a/c2patool/src/config.rs b/c2patool/src/config.rs @@ -1,53 +0,0 @@ -// Copyright 2022 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 c2pa::ManifestAssertion; - -use serde::Deserialize; -use serde_json::Value; -use std::path::PathBuf; - -/// Defines the components used to build a claim to embed in a manifest -#[derive(Debug, Deserialize)] -pub struct Config { - /// Optional prefix added to the generated Manifest Label - /// - /// This is typically Internet domain name for the vendor (i.e. `adobe`) - pub vendor: Option<String>, - /// A User Agent formatted string identifying the software/hardware/system produced this claim - /// - /// Spaces are not allowed in names, versions can be specified with product/1.0 syntax - pub claim_generator: Option<String>, - /// Optional title to use for this claim, defaults to the output file name - pub title: Option<String>, - /// Optional parent ingredient file path - pub parent: Option<PathBuf>, - /// A List of verified credentials - pub credentials: Option<Vec<Value>>, - /// A list of non-parent ingredients to include - pub ingredients: Option<Vec<PathBuf>>, - /// A list of [ManifestAssertion] to add to this created manifest - pub assertions: Vec<ManifestAssertion>, - /// An optional base path to use for any relative paths in defined in this structure - pub base_path: Option<PathBuf>, - /// Signing algorithm to use - must match the associated certs - /// - /// Must be one of [ ps256 | ps384 | ps512 | es256 | es384 | es512 | ed25519 ] - /// Defaults to ps256 - pub alg: Option<String>, - /// A path to a file containing the private key required for signing - pub private_key: Option<PathBuf>, - /// A path to a file containing the signing cert required for signing - pub sign_cert: Option<PathBuf>, - /// A Url to a Time Authority to use when signing the manifest - pub ta_url: Option<String>, -} diff --git a/c2patool/src/main.rs b/c2patool/src/main.rs @@ -1,300 +0,0 @@ -// Copyright 2022 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. - -#![doc = include_str!("README.md")] -/// Tool to display and create C2PA manifests -/// -/// A file path to a jpeg must be provided -/// If only the path is given, this will generate a summary report of any claims in that file -/// If a claim def json file is specified, the claim will be added to any existing claims -/// If the claim def includes an asset_path, the claims in that file will be used instead -/// -/// -use anyhow::Result; -use c2pa::{Error, Ingredient, Manifest, ManifestStore, ManifestStoreReport}; - -use std::{ - fs, - path::{Path, PathBuf}, - process::exit, -}; -use structopt::StructOpt; -use tempfile::tempdir; - -pub mod config; -use config::Config; -mod signer; -use signer::get_c2pa_signer; - -// define the command line options -#[derive(Debug, StructOpt)] -#[structopt(author = "Adobe", about = "Tool for displaying and creating C2PA manifests",setting = structopt::clap::AppSettings::ColoredHelp)] -struct CliArgs { - #[structopt(parse(from_os_str))] - #[structopt(short = "o", long = "output", help = "path to output file")] - output: Option<std::path::PathBuf>, - - #[structopt(parse(from_os_str))] - #[structopt(short = "p", long = "parent", help = "path to parent file")] - parent: Option<std::path::PathBuf>, - - #[structopt( - short = "c", - long = "config", - help = "Configuration passed as json string" - )] - config: Option<String>, - - #[structopt( - short = "d", - long = "detailed", - help = "display detailed internal manifest data" - )] - detailed: bool, - - /// The path to the file to read (jpg or json for adding claims) - #[structopt(parse(from_os_str))] - path: Option<std::path::PathBuf>, -} - -// converts any relative paths to absolute from base_path -pub fn fix_relative_path(path: &Path, base_path: &Path) -> PathBuf { - if path.is_absolute() { - return PathBuf::from(path); - } - let mut p = PathBuf::from(base_path); - p.push(path); - p -} - -fn handle_config( - json: &str, - base_dir: &Path, - parent: Option<&Path>, - output_opt: Option<&Path>, - is_detailed: bool, -) -> Result<()> { - let config: Config = serde_json::from_str(json)?; - - // if the config has a base path, use it for relative paths in the config - // otherwise set the base path to the location of the config file - let base_path = match &config.base_path { - Some(path) => PathBuf::from(path), - None => PathBuf::from(base_dir), - }; - - let signer = get_c2pa_signer(&config, &base_path)?; - - let claim_generator = match config.claim_generator { - Some(claim_generator) => claim_generator, - None => format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")), - }; - - let mut manifest = Manifest::new(claim_generator); - - if let Some(vendor) = config.vendor { - manifest.set_vendor(vendor); - } - - if let Some(credentials) = config.credentials.as_ref() { - for credential in credentials { - manifest.add_verifiable_credential(credential)?; - } - } - - // if claim_def has a parent, set the parent asset - let parent = match parent { - Some(parent) => Some(PathBuf::from(parent)), - None => config - .parent - .as_deref() - .map(|parent| fix_relative_path(parent, &base_path)), - }; - if let Some(parent) = parent.as_ref() { - if !parent.exists() { - eprintln!("Parent file not found {:#?}", parent); - exit(1); - } - manifest.set_parent(Ingredient::from_file(parent)?)?; - } - - // add all the ingredients (claim def ingredients do not include the parent) - if let Some(ingredients) = config.ingredients.as_ref() { - for ingredient in ingredients { - let path = fix_relative_path(ingredient, &base_path); - if !path.exists() { - eprintln!("Ingredient file not found {:#?}", path); - exit(1); - } - let ingredient = Ingredient::from_file(&path).unwrap_or_else(|e| { - eprintln!("error loading ingredient {:?} {:?}", &path, e); - exit(1); - }); - manifest.add_ingredient(ingredient); - } - } - - // add any assertions - for assertion in config.assertions { - manifest.add_labeled_assertion(assertion.label(), &assertion.value()?)?; - } - - // if we have an output option, then we must have a source image to add a claim to - // we need to determine the source file and copy it a temporary location where we will update it - // once successfully written we can copy the temp back to the output location, possibly overwriting - // The source can be an existing file at the output_path, or the parent file if we have one. - if let Some(output) = output_opt { - let file_name = match output.file_name().and_then(|s| s.to_str()) { - Some(name) => name, - None => { - eprintln!("Missing or invalid filename on output"); - exit(1); - } - }; - // check for valid extension and do special extension handling - let _extension = match output.extension().and_then(|s| s.to_str()) { - Some(ext) => ext, - None => { - eprintln!("Missing or invalid extension on output"); - exit(1); - } - }; - - // Predefine the manifest asset if we need to set a title - // Todo: find a better way to set the title - if let Some(t) = config.title.as_ref() { - let mut asset = Ingredient::from_file_info(output); - asset.set_title(t.to_owned()); - manifest.set_asset(asset); - }; - - // The source path points to the image we want to sign. - // If a file already exists at the output location, we will treat that as the source - // Otherwise, since this tool does no image editing, we can treat the parent file as the source. - let source_path = match output.exists() { - true => output, - false => { - parent.as_deref().filter(|p| p.exists()).or_else(||{ - eprintln!("A valid parent path or existing output file is required for claim embedding"); - exit(1); - }).unwrap() - } - }; - - // Embed to a temporary file and then rename or copy back to the output. - // This way we never have a half written manifest if something fails. - let dir = tempdir()?; - - // temp file_name must match output file name, it may be used as the claim title - let temp_path = dir.path().join(&file_name); - - manifest - .embed(source_path, &temp_path, signer.as_ref()) - .unwrap_or_else(|e| { - eprintln!("error embedding manifest: {:?}", e); - exit(1); - }); - - // embed completed successfully, now rename to the target path - std::fs::rename(&temp_path, &output) - // if rename fails, try to copy in case we are on different volumes - .or_else(|_| std::fs::copy(&temp_path, &output).and(Ok(()))) - .map_err(Error::IoError)?; - - // print a report on the output file - report_from_path(&output, is_detailed); - - Ok(()) - } else { - if is_detailed { - eprintln!("detailed report not supported for preview") - } else { - println!("{}", ManifestStore::from_manifest(&manifest)?); - } - Ok(()) - } -} - -// prints the requested kind of report or exits with error -fn report_from_path<P: AsRef<Path>>(path: &P, is_detailed: bool) { - let report = match is_detailed { - true => ManifestStoreReport::from_file(path).map(|r| r.to_string()), - false => ManifestStore::from_file(path).map(|r| r.to_string()), - }; - match report { - Ok(report) => { - println!("{}", report); - } - Err(Error::JumbfNotFound) | Err(Error::LogStop) => { - println!("No claim found"); - exit(1) - } - Err(Error::PrereleaseError) => { - eprintln!("Prerelease claim found"); - exit(1) - } - Err(e) => { - println!("Error Loading {:?} {:?}", &path.as_ref(), e); - exit(1); - } - } -} - -fn main() -> Result<()> { - let args = CliArgs::from_args(); - - // set RUST_LOG=debug to get detailed debug logging - if std::env::var("RUST_LOG").is_err() { - std::env::set_var("RUST_LOG", "error"); - } - env_logger::init(); - - let mut config = args.config; - let mut base_dir = PathBuf::from("."); - - if let Some(path) = args.path.clone() { - if !path.exists() { - println!("File not found {:?}", path); - exit(1); - } - - let extension = path.extension().and_then(|p| p.to_str()).unwrap_or(""); - // path can be a jpeg source file or a json working claim description - match extension { - "jpg" | "jpeg" | "png" | "c2pa" => { - report_from_path(&path, args.detailed); - } - "json" => { - // file paths in Config are relative to the json file - base_dir = PathBuf::from(&path); - base_dir.pop(); - - config = Some(fs::read_to_string(&path)?); - } - _ => { - println!("Unsupported file type {}", extension); - exit(1); - } - }; - } - - if let Some(json) = config { - handle_config( - &json, - &base_dir, - args.parent.as_deref(), - args.output.as_deref(), - args.detailed, - )?; - } - Ok(()) -} diff --git a/c2patool/src/signer.rs b/c2patool/src/signer.rs @@ -1,81 +0,0 @@ -// Copyright 2022 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. - -/// Provides a method to read configured certs and generate a singer -/// -use crate::{config::Config, fix_relative_path}; - -use anyhow::{Context, Result}; -use c2pa::{get_signer, Signer}; - -use std::{env, path::Path, process::exit}; - -pub fn get_ta_url() -> Option<String> { - std::env::var("C2PA_TA_URL").ok() -} - -/// Generates a signature from local keys specified by the environment -/// keys can be directly in environment variables -/// or in a folder referenced by CAI_KEY_PATH -/// also supports default dev environment keys -pub fn get_c2pa_signer(config: &Config, base_path: &Path) -> Result<Box<dyn Signer>> { - let alg = config.alg.as_deref().unwrap_or("ps256").to_lowercase(); - let tsa_url = config.ta_url.clone().or_else(get_ta_url); - - let mut private_key = None; - let mut sign_cert = None; - - if let Some(path) = config.private_key.as_deref() { - let path = fix_relative_path(path, base_path); - private_key = - Some(std::fs::read(&path).context(format!("Reading private key: {:?}", &path))?); - } - if private_key.is_none() { - if let Ok(key) = env::var("C2PA_PRIVATE_KEY") { - private_key = Some(key.as_bytes().to_vec()); - } - }; - - if let Some(path) = config.sign_cert.as_deref() { - let path = fix_relative_path(path, base_path); - sign_cert = Some(std::fs::read(&path).context(format!("Reading sign cert: {:?}", &path))?); - } - if sign_cert.is_none() { - if let Ok(cert) = env::var("C2PA_SIGN_CERT") { - sign_cert = Some(cert.as_bytes().to_vec()); - } - }; - - if let Some(private_key) = private_key { - if let Some(sign_cert) = sign_cert { - let signer = get_signer(&sign_cert, &private_key, &alg, tsa_url)?; - return Ok(signer); - } - } - - eprintln!( - "\n\n-----------\n\n\ - Claim creation requires a private key and signing certificate \n\ - Set the config file fields, private_key and sign_cert to paths to the required files. - \n\ - You can generate a throwaway RSAPSS SSH private key and cert for testing on macos by \n\ - pasting the following line into a terminal and hitting enter\n\ - openssl req -new -newkey rsa:4096 -sigopt rsa_padding_mode:pss -days 180 -extensions v3_ca -addext \"keyUsage = digitalSignature\" -addext \"extendedKeyUsage = emailProtection\" -nodes -x509 -keyout private.key -out certs.pem -sha256\n\ - \n\ - You then need to reference those files in config.private_key and config.sign_cert\n\ - \n\ - The private key can alternatively be passed in the environment var C2PA_PRIVATE_KEY - The signing cert can alternatively be passed in the environment var C2PA_SIGN_CERT - -----------\n\n"); - exit(1); -} diff --git a/c2patool/tests/integration.rs b/c2patool/tests/integration.rs @@ -1,123 +0,0 @@ -// Copyright 2022 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. - -// isolate from wasm by wrapping in module -#[cfg(not(target_arch = "wasm32"))] -mod integration { - use assert_cmd::prelude::*; // Add methods on commands - use predicates::prelude::*; - use std::path::PathBuf; - use std::process::Command; - - const TEST_IMAGE: &str = "earth_apollo17.jpg"; - //const TEST_IMAGE: &str = "libpng-test.png"; // save for png testing - //const TEST_IMAGE_WITH_MANIFEST: &str = "C.jpg"; // save for manifest tests - - fn fixture_path(name: &str) -> PathBuf { - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("../sdk/tests/fixtures"); - path.push(name); - std::fs::canonicalize(path).expect("canonicalize") - } - - fn temp_path(name: &str) -> PathBuf { - let mut path = PathBuf::from(env!("CARGO_TARGET_TMPDIR")); - std::fs::create_dir_all(&path).ok(); - path.push(name); - path - } - - #[test] - fn tool_not_found() -> Result<(), Box<dyn std::error::Error>> { - let mut cmd = Command::cargo_bin("c2patool")?; - cmd.arg("test/file/not.found"); - cmd.assert() - .failure() - .stdout(predicate::str::contains("File not found")); - Ok(()) - } - - #[test] - fn tool_version_check() { - // ensure c2patool version matches the toolkit version - assert_eq!(c2pa::VERSION, env!("CARGO_PKG_VERSION")); - } - - #[test] - fn tool_jpeg_no_report() -> Result<(), Box<dyn std::error::Error>> { - let mut cmd = Command::cargo_bin("c2patool")?; - cmd.arg(fixture_path(TEST_IMAGE)); - cmd.assert() - .failure() - .stdout(predicate::str::contains("No claim found")); - Ok(()) - } - - #[test] - fn tool_embed_jpeg_report() -> Result<(), Box<dyn std::error::Error>> { - generate_x509_temp_keys(); - - Command::cargo_bin("c2patool")? - .arg("sample/config.json") - .arg("-p") - .arg(fixture_path(TEST_IMAGE)) - .arg("-o") - .arg(temp_path("out.jpg")) - .assert() - .success() // should this be a failure? - .stdout(predicate::str::contains("My Title")); - Ok(()) - } - - /* remove this until the c2patool supports .c2pa write again - #[test] - fn tool_manifest_report() -> Result<(), Box<dyn std::error::Error>> { - generate_x509_temp_keys(); - - // first export a c2pa file - Command::cargo_bin("c2patool")? - .arg(fixture_path(TEST_IMAGE_WITH_MANIFEST)) - .arg("-o") - .arg(temp_path("manifest.c2pa")) - .assert() - .success() - .stdout(predicate::str::contains("C2PA Testing")); - // then read it back in - Command::cargo_bin("c2patool")? - .arg(temp_path("manifest.c2pa")) - .assert() - .success() - .stdout(predicate::str::contains("C2PA Testing")); - Ok(()) - } - */ - - fn generate_x509_temp_keys() { - let mut x509_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); - x509_path.pop(); - x509_path.push(".x509"); - - std::fs::create_dir_all(&x509_path).expect("Can't create .x509 dir in repo"); - - // Test for existence of x509_path.temp_key.pub and .pem. - - let priv_key_path = x509_path.join("temp_key.pem"); - let sign_cert_path = x509_path.join("temp_key.pub"); - - if !(priv_key_path.exists() && sign_cert_path.exists()) { - // Creating the signer (which we don't use) has the side effect of - // creating temporary private key and signing certificate. - c2pa::get_temp_signer(&x509_path); - } - } -}