use std::{
env,
path::{Path, PathBuf},
};
use anyhow::{Context, Result};
use c2pa::{create_signer, BoxedSigner, SigningAlg};
use serde::Deserialize;
const DEFAULT_CERTS: &[u8] = include_bytes!("../sample/es256_certs.pem");
const DEFAULT_KEY: &[u8] = include_bytes!("../sample/es256_private.key");
pub fn get_ta_url() -> Option<String> {
std::env::var("C2PA_TA_URL").ok()
}
pub fn output_signer_info(settings_path: &std::path::Path) -> anyhow::Result<()> {
use std::io::Write;
let (cert_pem, alg, tsa_url) =
if let Ok(settings) = c2pa::settings::Settings::new().with_file(settings_path) {
match settings.signer {
Some(c2pa::settings::signer::SignerSettings::Local {
alg,
sign_cert,
tsa_url,
..
}) => (sign_cert, alg, tsa_url),
_ => (
String::from_utf8_lossy(DEFAULT_CERTS).into_owned(),
SigningAlg::Es256,
None,
),
}
} else {
(
String::from_utf8_lossy(DEFAULT_CERTS).into_owned(),
SigningAlg::Es256,
None,
)
};
let info = crate::SignerInfo {
alg,
sign_cert: cert_pem,
tsa_url,
reserve_size: None,
};
let json = serde_json::to_string(&info).context("serializing signer info")?;
std::io::stdout()
.write_all(json.as_bytes())
.context("writing signer info to stdout")?;
Ok(())
}
pub fn sign_from_stdin() -> anyhow::Result<()> {
use std::io::{Read, Write};
let mut data = Vec::new();
std::io::stdin()
.read_to_end(&mut data)
.context("reading bytes from stdin")?;
let signer = create_signer::from_keys(DEFAULT_CERTS, DEFAULT_KEY, SigningAlg::Es256, None)
.context("creating test signer")?;
let sig = c2pa::Signer::sign(signer.as_ref(), &data).context("signing")?;
std::io::stdout()
.write_all(&sig)
.context("writing signature to stdout")?;
Ok(())
}
#[derive(Debug, Default, Deserialize)]
pub struct SignConfig {
pub alg: Option<String>,
pub private_key: Option<PathBuf>,
pub sign_cert: Option<PathBuf>,
pub ta_url: Option<String>,
}
impl SignConfig {
pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).context("reading manifest configuration")
}
pub fn set_base_path<P: AsRef<Path>>(&mut self, base: P) -> &Self {
if let Some(path) = self.private_key.as_ref() {
if !path.is_absolute() {
self.private_key = Some(base.as_ref().join(path));
}
}
if let Some(path) = self.sign_cert.as_ref() {
if !path.is_absolute() {
self.sign_cert = Some(base.as_ref().join(path));
}
}
self
}
pub fn signer(&self) -> Result<BoxedSigner> {
let alg = self.alg.as_deref().unwrap_or("es256").to_lowercase();
let alg: SigningAlg = alg.parse().map_err(|_| c2pa::Error::UnsupportedType)?;
let tsa_url = self.ta_url.clone().or_else(get_ta_url);
let mut private_key = None;
let mut sign_cert = None;
if let Some(path) = self.private_key.as_deref() {
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) = self.sign_cert.as_deref() {
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 = create_signer::from_keys(&sign_cert, &private_key, alg, tsa_url)
.context("Invalid certification data")?;
return Ok(signer);
}
}
eprintln!(
"\n\n-----------\n\n\
Note: Using default private key and signing certificate. This is only valid for development.\n\
A permanent key and cert should be provided in the manifest definition or in the environment variables.\n");
let signer = create_signer::from_keys(DEFAULT_CERTS, DEFAULT_KEY, alg, tsa_url)
.context("Invalid certification data")?;
Ok(signer)
}
}
#[cfg(test)]
pub mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
const CONFIG: &str = r#"{
"alg": "es256",
"private_key": "es256_private.key",
"sign_cert": "es256_certs.pem",
"ta_url": "http://timestamp.digicert.com"
}"#;
#[test]
fn test_sign_config() {
let mut sign_config = SignConfig::from_json(CONFIG).expect("from_json");
sign_config.set_base_path("sample");
let signer = sign_config.signer().expect("get signer");
assert_eq!(signer.alg(), SigningAlg::Es256);
}
#[test]
fn test_sign_default() {
let sign_config = SignConfig::default();
let signer = sign_config.signer().expect("get signer");
assert_eq!(signer.alg(), SigningAlg::Es256);
}
#[test]
fn test_sign_from() {
let sign_config = SignConfig::default();
let signer = sign_config.signer().expect("get signer");
assert_eq!(signer.alg(), SigningAlg::Es256);
}
}