commit a181bde72fe37010544681bcca13055a1adbbf8c
parent 67c206e317131b0270ffdea9a510ef3883b72dc5
Author: Gavin Peacock <gpeacock@adobe.com>
Date: Thu, 2 Jun 2022 09:44:57 -0700
Rework how c2patool is configured (#28)
* doc updates, change tsa to ta
* Rework c2patool configuration
ClaimDef renamed to config.json
Now includes settings for cert files, ta_url, alg & etc
All CAI env vars removed
C2PA_PRIVATE_KEY and CPA_PUBIC_CERT can be used to pass key data without files.
* Update Readme
Diffstat:
10 files changed, 302 insertions(+), 288 deletions(-)
diff --git a/Makefile b/Makefile
@@ -42,7 +42,6 @@ c2patool-package:
cp c2patool/sample/* target/c2patool/sample
cp CHANGELOG.md target/c2patool/CHANGELOG.md
cp sdk/tests/fixtures/IMG_0003.jpg target/c2patool/image.jpg
- cp sdk/tests/fixtures/claim.json target/c2patool/claim.json
# These are for building the c2patool release bin on various platforms
build-release-win:
diff --git a/c2patool/Cargo.toml b/c2patool/Cargo.toml
@@ -9,7 +9,6 @@ rust-version = "1.58.0"
[dependencies]
anyhow = "1.0"
c2pa = { version = "0.2", features = ["file_io"] }
-dirs = "4.0"
env_logger = "0.9"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
diff --git a/c2patool/sample/config.json b/c2patool/sample/config.json
@@ -0,0 +1,57 @@
+{
+ "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/src/README.md b/c2patool/src/README.md
@@ -20,15 +20,17 @@ The -d option will output a detailed JSON report of the internal C2PA structure
## Previewing a Manifest
-If a path to a manifest def json file is given,
+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 claim.json```
+```c2patool sample/config.json```
-The manifest definition json can also be passed on the command line as string using the -c --create option
+The config json can also be passed on the command line as string using the -c --config option
-```c2patool -c '{"vendor": "myvendor", "claim_generator": "MyApplication", "assertions": [{"label": "myvendor.assertion", "data": {"name": "Jane Doe"}}]}'```
+```shell
+c2patool -c '{"assertions": [{"label": "org.contentauth.test", "data": {"name": "Jane Doe"}}]}'
+```
## Creating a new output image
@@ -39,7 +41,7 @@ If you are not changing an image and just adding C2PA data, use an existing outp
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 claim.json -o output.jpg```
+```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
@@ -57,42 +59,43 @@ These .c2pa manifest files can be read by claim tool and will generate reports.
## Setup
Before you can add a manifest, you need to create an SSL certificate
-By default, c2patool expects to find temp_key.pem and temp_key in the user's ".cai" folder.
-The location of this folder can be changed by setting the CAI_KEY_PATH environment variable.
-This expects RSA/RSA_PSS certificates and private key. It will create signatures as PS256.
-
-```set CAI_KEY_PATH="~/mykeys"```
+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 CAI_PRIVATE_KEY and CAI_PUB_CERT
-These two variable are used to set the private key and public certificates. When using these variables
-the CAI_SIGNING_ALGORITHM must also be set to one of [ ps256 | ps384 | ps512 | es256 | es384 | es512 | ed25519] and
-must be compatible with values of CAI_PRIVATE_KEY and CAI_PUB_CERT. For example to sign with es256 signatures
+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 CAI_SIGNING_ALGORITHM=es256```
-```set CAI_PRIVATE_KEY=$(cat my_es256_private_key)```
-```set CAI_PUB_CERT=$(cat my_es256_certs)```
+```set C2PA_PRIVATE_KEY=$(cat my_es256_private_key)```
+```set C2PA_PUB_CERT=$(cat my_es256_certs)```
-The both CAI_PRIVATE_KEY and CAI_PUB_CERT should be in PEM format. CAI_PUB_CERT should contain a certificate
+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 temporary files for testing you can execute the following command
+To create your own temporary files for testing you can execute the following command
-```
-mkdir -p ~/.cai ; 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 ~/.cai/temp_key.pem -out ~/.cai/temp_key.pub -sha256 ; sudo chmod 644 ~/.cai/temp_key.pem
+```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 c2patool finds the CAI_TA_URL environment variable set, c2patool will attempt to timestamp the signature using the TA service at the provided URL. The TA must be RFC3161 compliant. Example TSA setting:
+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:
-```set CAI_TA_URL=http://timestamp.digicert.com```
+```ta_url=http://timestamp.digicert.com```
-## Manifest definition file format
+## Configuration file format
-The manifest definition file is a JSON formatted file with a .json extension:
+The Configuration file is a JSON formatted file with a .json extension:
The schema for this type is as follows:
```json
@@ -104,8 +107,7 @@ The schema for this type is as follows:
"examples": [
{
"vendor": "myvendor",
- "claim_generator": "My Application",
- "title" : "My Title",
+ "claim_generator": "MyApp/0.1",
"parent": "image.jpg",
"ingredients": [],
"assertions": [
@@ -115,44 +117,71 @@ The schema for this type is as follows:
"any_tag": "whatever I want"
}
}
- ]
- }
- ],
+ ],
+ "alg": "es256",
+ "private_key": "es256_private.key",
+ "sign_cert": "es256_certs.pem",
+ "ta_url": "http://timestamp.digicert.com"
+ }
+ ],
"required": [
- "vendor",
- "claim_generator",
"assertions",
],
"properties": {
"vendor": {
"type": "string",
- "description": "typically Internet domain name (without the TLD) for the vendor (i.e. `adobe`, `nytimes`)"
+ "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"
+ "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)"
+ "description": "A human-readable string to be displayed as the tile for this Manifest (defaults to embedded file name)"
},
"credentials": {
"type": "object",
- "description": "array of W3C verifiable credentials objects defined in the c2pa assertion specification. Section 7"
+ "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)"
+ "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)"
+ "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": "object with label, and data - an object with any value as defined in the c2pa assertion specification"
+ "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/claim_def.rs b/c2patool/src/claim_def.rs
@@ -1,38 +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;
-
-/// A `ClaimDef` defines the components used to build a claim
-#[derive(Debug, Deserialize)]
-pub struct ClaimDef {
- #[serde(skip_serializing_if = "Option::is_none")]
- pub vendor: Option<String>,
- #[serde(alias = "recorder")]
- pub claim_generator: String,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub title: Option<String>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub parent: Option<PathBuf>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub credentials: Option<Vec<Value>>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub ingredients: Option<Vec<PathBuf>>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub assertions: Vec<ManifestAssertion>,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub alg: Option<String>,
-}
diff --git a/c2patool/src/config.rs b/c2patool/src/config.rs
@@ -0,0 +1,53 @@
+// 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
@@ -3,7 +3,6 @@
// 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
@@ -11,6 +10,7 @@
// 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
@@ -18,6 +18,7 @@
/// 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};
@@ -29,10 +30,10 @@ use std::{
use structopt::StructOpt;
use tempfile::tempdir;
-mod claim_def;
-use claim_def::ClaimDef;
+pub mod config;
+use config::Config;
mod signer;
-use signer::get_test_signer;
+use signer::get_signer;
// define the command line options
#[derive(Debug, StructOpt)]
@@ -48,10 +49,10 @@ struct CliArgs {
#[structopt(
short = "c",
- long = "claimdef",
- help = "claim definition passed as json string"
+ long = "config",
+ help = "Configuration passed as json string"
)]
- claim_def: Option<String>,
+ config: Option<String>,
#[structopt(
short = "d",
@@ -66,28 +67,43 @@ struct CliArgs {
}
// converts any relative paths to absolute from base_path
-fn fix_relative_path(path: &Path, base_path: &Path) -> PathBuf {
+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_claim_def(
+fn handle_config(
json: &str,
base_dir: &Path,
parent: Option<&Path>,
output_opt: Option<&Path>,
is_detailed: bool,
) -> Result<()> {
- let claim_def: ClaimDef = serde_json::from_str(json)?;
+ let config: Config = serde_json::from_str(json)?;
+
+ let base_path = match &config.base_path {
+ Some(path) => PathBuf::from(path),
+ None => PathBuf::from(base_dir),
+ };
- let mut manifest = Manifest::new(claim_def.claim_generator);
+ let signer = get_signer(&config, &base_path)?;
- if let Some(vendor) = claim_def.vendor {
+ 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) = claim_def.credentials.as_ref() {
+ if let Some(credentials) = config.credentials.as_ref() {
for credential in credentials {
manifest.add_verifiable_credential(credential)?;
}
@@ -96,10 +112,10 @@ fn handle_claim_def(
// if claim_def has a parent, set the parent asset
let parent = match parent {
Some(parent) => Some(PathBuf::from(parent)),
- None => claim_def
+ None => config
.parent
.as_deref()
- .map(|parent| fix_relative_path(parent, base_dir)),
+ .map(|parent| fix_relative_path(parent, &base_path)),
};
if let Some(parent) = parent.as_ref() {
if !parent.exists() {
@@ -110,9 +126,9 @@ fn handle_claim_def(
}
// add all the ingredients (claim def ingredients do not include the parent)
- if let Some(ingredients) = claim_def.ingredients.as_ref() {
+ if let Some(ingredients) = config.ingredients.as_ref() {
for ingredient in ingredients {
- let path = fix_relative_path(ingredient, base_dir);
+ let path = fix_relative_path(ingredient, &base_path);
if !path.exists() {
eprintln!("Ingredient file not found {:#?}", path);
exit(1);
@@ -126,7 +142,7 @@ fn handle_claim_def(
}
// add any assertions
- for assertion in claim_def.assertions {
+ for assertion in config.assertions {
manifest.add_labeled_assertion(&assertion.label, &assertion.data)?;
}
@@ -151,14 +167,17 @@ fn handle_claim_def(
}
};
- // get asset info from the output path (even it it doesn't exist yet)
- let mut asset = Ingredient::from_file_info(output);
- if let Some(t) = claim_def.title.as_ref() {
+ // 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);
};
- manifest.set_asset(asset);
- // select source from output or fallback to parent
+ // 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 => {
@@ -169,13 +188,12 @@ fn handle_claim_def(
}
};
- // embed to a temporary file and then rename or copy back to the output
- // so we never have a half written manifest
+ // 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 is used as the claim title
- let temp_path = dir.path().join(&file_name);
- let signer = get_test_signer()?;
+ // 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())
@@ -238,7 +256,7 @@ fn main() -> Result<()> {
}
env_logger::init();
- let mut claim_def = args.claim_def;
+ let mut config = args.config;
let mut base_dir = PathBuf::from(".");
if let Some(path) = args.path.clone() {
@@ -247,7 +265,6 @@ fn main() -> Result<()> {
exit(1);
}
- base_dir = PathBuf::from(&path);
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 {
@@ -255,11 +272,11 @@ fn main() -> Result<()> {
report_from_path(&path, args.detailed);
}
"json" => {
- // file paths in ClaimDef are relative to the json file
+ // file paths in Config are relative to the json file
base_dir = PathBuf::from(&path);
base_dir.pop();
- claim_def = Some(fs::read_to_string(&path)?);
+ config = Some(fs::read_to_string(&path)?);
}
_ => {
println!("Unsupported file type {}", extension);
@@ -268,8 +285,8 @@ fn main() -> Result<()> {
};
}
- if let Some(json) = claim_def {
- handle_claim_def(
+ if let Some(json) = config {
+ handle_config(
&json,
&base_dir,
args.parent.as_deref(),
diff --git a/c2patool/src/signer.rs b/c2patool/src/signer.rs
@@ -11,62 +11,77 @@
// specific language governing permissions and limitations under
// each license.
-use anyhow::Result;
+use crate::{config::Config, fix_relative_path};
+use anyhow::{Context, Result};
+/// Provides a method to read configured certs and generate a singer
+///
use c2pa::{
openssl::{EcSigner, EdSigner, RsaSigner},
signer::ConfigurableSigner,
signer::Signer,
};
-use std::{env, path::PathBuf, process::exit};
+use std::{env, path::Path, process::exit};
pub fn get_ta_url() -> Option<String> {
- //const TA_URL: &str = "http://timestamp.digicert.com";
- match std::env::var("CAI_TA_URL") {
- Ok(url) => Some(url),
- Err(_) => None,
- }
+ std::env::var("C2PA_TA_URL").ok()
}
-/// Generates a temporary signature from local keys specified by the environment
+/// 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_test_signer() -> Result<Box<dyn Signer>> {
- // Keys can be passed in separate environment variables
- if let Ok(private_key) = env::var("CAI_PRIVATE_KEY") {
- let private_key = private_key.as_bytes().to_vec();
- if let Ok(sign_cert) = env::var("CAI_PUB_CERT") {
- let sign_cert = sign_cert.as_bytes().to_vec();
- let alg = env::var("CAI_SIGNING_ALGORITHM").ok();
+pub fn get_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());
+ }
+ };
- let signer: Box<dyn Signer> = match alg {
- Some(a) => match a.to_lowercase().as_str() {
- "ps256" | "ps384" | "ps512" => Box::new(RsaSigner::from_signcert_and_pkey(
- &sign_cert,
- &private_key,
- a.to_lowercase(),
- get_ta_url(),
- )?),
- "es256" | "es384" | "es512" => Box::new(EcSigner::from_signcert_and_pkey(
- &sign_cert,
- &private_key,
- a.to_lowercase(),
- get_ta_url(),
- )?),
- "ed25519" => Box::new(EdSigner::from_signcert_and_pkey(
- &sign_cert,
- &private_key,
- a.to_lowercase(),
- get_ta_url(),
- )?),
- _ => {
- eprintln!("Unsupported CAI_SIGNING_ALGORITHM, must be one of [ ps256 | ps384 | ps512 | es256 | es384 | es512 | ed25519 ]");
- exit(2);
- }
- },
- None => {
- eprintln!("Must have CAI_SIGNING_ALGORITHM set");
- exit(1);
+ if let Some(private_key) = private_key {
+ if let Some(sign_cert) = sign_cert {
+ let signer: Box<dyn Signer> = match alg.as_str() {
+ "ps256" | "ps384" | "ps512" => Box::new(RsaSigner::from_signcert_and_pkey(
+ &sign_cert,
+ &private_key,
+ alg,
+ tsa_url,
+ )?),
+ "es256" | "es384" | "es512" => Box::new(EcSigner::from_signcert_and_pkey(
+ &sign_cert,
+ &private_key,
+ alg,
+ tsa_url,
+ )?),
+ "ed25519" => Box::new(EdSigner::from_signcert_and_pkey(
+ &sign_cert,
+ &private_key,
+ alg.to_owned(),
+ tsa_url,
+ )?),
+ _ => {
+ eprintln!("Unsupported signing algorithm, must be one of [ ps256 | ps384 | ps512 | es256 | es384 | es512 | ed25519 ]");
+ exit(2);
}
};
@@ -74,75 +89,19 @@ pub fn get_test_signer() -> Result<Box<dyn Signer>> {
}
}
- // or an environment variable can specify where to find the keys
- let key_path = match std::env::var("CAI_KEY_PATH") {
- Ok(keys_path) => PathBuf::from(keys_path),
- Err(_) => {
- // defaults to dev environment
- match env::var("CARGO_MANIFEST_DIR") {
- Ok(dir) => {
- let mut path = PathBuf::from(dir);
- path.push("..");
- path.push(".x509");
- path
- }
- Err(_) => {
- let mut dir = dirs::home_dir().expect("home_dir");
- dir.push(".cai");
- dir
- }
- }
- }
- };
- // we expect the key files to be named temp_key.pem and temp_key.pub
- let mut pem_path = key_path.clone();
- pem_path.push("temp_key.pem");
- let mut pub_path = key_path;
- pub_path.push("temp_key.pub");
- if !pem_path.is_file() || !pub_path.is_file() {
- eprintln!(
+ eprintln!(
"\n\n-----------\n\n\
- Claim creation requires key files {:?} and {:?}\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 for testing by \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\
- mkdir -p ~/.x509 ; 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 ~/.x509/temp_key.pem -out ~/.x509/temp_key.pub -sha256 ; sudo chmod 644 ~/.x509/temp_key.pem\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 should only need to do this once. \n\
- Set the environment var CAI_SIGNING_ALGORITHM=ps256 to set the signature algorithm
- The environment variable CAI_KEY_PATH can specify an alternate key folder.\n\n\
- -----------\n\n"
- ,pem_path, pub_path);
- exit(1);
- }
-
- let alg = env::var("CAI_SIGNING_ALGORITHM")
- .unwrap_or_else(|_| "ps256".to_string())
- .to_lowercase();
- let signer: Box<dyn Signer> = match alg.as_str() {
- "ps256" | "ps384" | "ps512" => Box::new(RsaSigner::from_files(
- &pub_path,
- &pem_path,
- alg,
- get_ta_url(),
- )?),
- "es256" | "es384" | "es512" => Box::new(EcSigner::from_files(
- &pub_path,
- &pem_path,
- alg,
- get_ta_url(),
- )?),
- "ed25519" => Box::new(EdSigner::from_files(
- &pub_path,
- &pem_path,
- alg,
- get_ta_url(),
- )?),
- _ => {
- eprintln!("Unsupported CAI_SIGNING_ALGORITHM, must be one of [ ps256 | ps384 | ps512 | es256 | es384 | es512 | ed25519 ]");
- exit(1);
- }
- };
-
- Ok(signer)
+ 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
@@ -68,7 +68,7 @@ mod integration {
generate_x509_temp_keys();
Command::cargo_bin("c2patool")?
- .arg(fixture_path("claim.json"))
+ .arg("sample/config.json")
.arg("-p")
.arg(fixture_path(TEST_IMAGE))
.arg("-o")
diff --git a/sdk/tests/fixtures/claim.json b/sdk/tests/fixtures/claim.json
@@ -1,61 +0,0 @@
-{
- "vendor": "myvendor",
- "claim_generator": "My Application",
- "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": "adobe.dictionary",
- "data": {
- "url": "https://cai-assertions.adobe.com/photoshop/dictionary.json"
- }
- },
- {
- "label": "c2pa.actions",
- "data": {
- "actions": [
- {
- "action": "c2pa.opened",
- "when": "2021-03-25T01:37:04.872Z"
- },
- {
- "action": "c2pa.edited",
- "parameters": {
- "name": "brightnesscontrast"
- },
- "when": "2021-03-25T01:37:22.536Z"
- }
- ],
- "metadata": {
- "dateTime": "2021-03-25T01:37:22.536Z",
- "reviewRatings": [
- {
- "code": "unknownActionsPerformed",
- "explanation": "Something untracked happened",
- "value": 4
- }
- ]
- }
- }
- },
- {
- "label": "my.assertion",
- "data": {
- "any_tag": "whatever I want"
- }
- }
- ]
-}
-\ No newline at end of file