use std::error::Error;
use std::ffi::CStr;
use std::fs::File;
use std::io::Cursor;
use std::io::Read;
use std::os::raw::c_char;
use std::slice;
use base64::{Engine, engine::general_purpose::STANDARD as BASE64_STANDARD};
use c2pa::{ManifestStore, Signer, SigningAlg, assertions::Exif};
use env_logger;use log::{debug, error, info};
use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
const SAD_API_URL: &str = "https://clientauth.demo.one.digicert.com/documentmanager/csc/v1/credentials/authorize";
const SIGNING_API_URL: &str = "https://clientauth.demo.one.digicert.com/documentmanager/csc/v1/signatures/signHash";
const CLIENT_CERT_PATH: &str = "/Users/shantanu.soni/Documents/tool/Certificate_pkcs12.p12";
const CLIENT_CERT_PASS: &str = "_IRX_7WZgHMG";
#[derive(Serialize)]
struct SADRequest {
credentialID: String,
numSignatures: u8,
hash: Vec<String>,
PIN: String,
}
#[derive(Deserialize)]
struct SADResponse {
pub expiresIn: u64,
SAD: String,
}
#[derive(Serialize)]
struct SignRequest {
credentialID: String,
SAD: String,
hash: Vec<String>,
signAlgo: String,
signAlgoParams: String,
}
#[derive(Deserialize)]
struct SignResponse {
signatures: Vec<String>,
}
struct RemoteSigner {
signature: Vec<u8>,
certificates: Vec<Vec<u8>>,
}
impl Signer for RemoteSigner {
fn sign(&self, _data: &[u8]) -> c2pa::Result<Vec<u8>> {
Ok(self.signature.clone())
}
fn alg(&self) -> SigningAlg {
SigningAlg::Ps256
}
fn certs(&self) -> c2pa::Result<Vec<Vec<u8>>> {
// Return the entire certificate chain
Ok(self.certificates.clone())
}
fn reserve_size(&self) -> usize {
// Calculate total size needed for signature and certificates
let signature_size = self.signature.len();
let certs_size: usize = self.certificates.iter().map(|cert| cert.len()).sum();
// Add some padding for COSE structure overhead
let total_size = signature_size + certs_size + 1024;
info!("Reserve size calculation:");
info!(" Signature size: {} bytes", signature_size);
info!(" Certificates total size: {} bytes", certs_size);
info!(" Total reserve size: {} bytes", total_size);
total_size
}
}
#[unsafe(no_mangle)]
// verify an asset with the given mime type from a byte array of length
pub extern "C" fn verify_bytes(format: *const c_char, bytes: *const u8, length: usize) {
// convert C pointers into Rust
let format = unsafe { CStr::from_ptr(format).to_string_lossy().into_owned() };
let bytes: &[u8] = unsafe { slice::from_raw_parts(bytes, length as usize) };
// Verify the manifests
match ManifestStore::from_bytes(&format, bytes, true) {
Ok(manifest_store) => println!("{}", manifest_store),
Err(e) => eprintln!("Error {:?}", e),
}
}
fn build_client_with_pkcs12() -> Result<Client, Box<dyn Error>> {
use reqwest::tls::Identity;
use std::fs::File;
use std::io::Read;
let mut buf = Vec::new();
File::open(CLIENT_CERT_PATH)?.read_to_end(&mut buf)?;
let pkcs12 = Identity::from_pkcs12_der(&buf, CLIENT_CERT_PASS)?;
let client = Client::builder()
.identity(pkcs12)
.danger_accept_invalid_certs(true)
.build()?;
Ok(client)
}
pub fn fetch_sad(hash: &str) -> Result<String, Box<dyn Error>> {
let client = build_client_with_pkcs12()?;
println!("Fetching sad hash {}", hash);
let mut headers = HeaderMap::new();
headers.insert("Content-Type", HeaderValue::from_static("application/json"));
let request_body = SADRequest {
credentialID: "basic_np-14-08-2025-11-01-44-165".to_string(),
numSignatures: 1,
hash: vec![hash.to_string()],
PIN: "sha123".to_string(),
};
//My code below
info!("Sending headers: {:?}", headers);
let json_body = serde_json::to_string(&request_body)?;
info!("Sending JSON body: {}", json_body);
//My code above
info!("Fetching SAD from {}", SAD_API_URL);
debug!(
"SAD Request Body: {:?}",
serde_json::to_string(&request_body)?
);
let response: SADResponse = client
.post(SAD_API_URL)
.headers(headers)
.json(&request_body)
.send()?
.error_for_status()?
.json()?;
info!("Received SAD: {}", response.SAD);
Ok(response.SAD)
}
pub fn sign_via_api(hash: &str, sad: &str) -> Result<String, Box<dyn Error>> {
let client = build_client_with_pkcs12()?;
let mut headers = HeaderMap::new();
headers.insert("Content-Type", HeaderValue::from_static("application/json"));
let request_body = SignRequest {
credentialID: "basic_np-14-08-2025-11-01-44-165".to_string(),
SAD: sad.to_string(),
hash: vec![hash.to_string()],
signAlgo: "1.2.840.113549.1.1.10".to_string(),
signAlgoParams: "MDSgDzANBglghkgBZQMEAgEFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgEFAKIDAgEg"
.to_string(),
};
info!("Sending signing request to {}", SIGNING_API_URL);
debug!(
"Sign Request Body: {:?}",
serde_json::to_string(&request_body)?
);
let response: SignResponse = client
.post(SIGNING_API_URL)
.headers(headers)
.json(&request_body)
.send()?
.error_for_status()?
.json()?;
info!("Received signature response: {:?}", response.signatures);
Ok(response.signatures.first().cloned().ok_or_else(|| {
Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"Empty signature response",
))
})?)
}
fn read_certificate_chain() -> Result<Vec<Vec<u8>>, Box<dyn Error>> {
info!("Using embedded certificate");
// Use the certificate content directly as a string
// kept the same content you provided, but parsed into separate PEM blocks
let cert_content = r#"-----BEGIN CERTIFICATE----- MIIFADCCA+igAwIBAgIUH/zI6vvQ8/X0PkQlQKGIBxQzeqEwDQYJKoZIhvcNAQEL BQAwgaQxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRVdGFoMRkwFwYDVQQHExBTYXJh Z290YSBTcHJpbmdzMQ4wDAYDVQQREwU4NDA0NTEiMCAGA1UECRMZMjczOCBTLiBT YW5kYWx3b29kIENpcmNsZTEdMBsGA1UEChMUV2luIHRoZSBjdXN0b21lciBMTEMx GDAWBgNVBAMTD0RUTSBRQSBDMlBBIElDQTAeFw0yNTA4MTQxMTAxNDRaFw0yODA4 MTQxMTAxNDRaMH4xLTArBgNVBAUTJGIyOTUwOGJlLTY1NmUtNDdkZS1iNDRmLWM5 ZjBhNGYwMzlkMTELMAkGA1UEBhMCSU4xDTALBgNVBAQTBFNvbmkxFTATBgNVBCoT DFNoYW50YW51IFJhajEaMBgGA1UEAxMRU2hhbnRhbnUgUmFqIFNvbmkwggEiMA0G CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7oAfqHNz5GV5W4OqpjeZ0+gLXz0KQ VM7UCvggYCzCRQjd7bMK78LBS/Z81+wta154o9VdQefdsF+3hN+2jNUAcfhgSPHM KrI7QM5x5hVErV1BcFiThXTQkRT6yJAH952q/LEfN/qS7qxDaMMozzAobQoE/uUk Z2gZISqtU55+kYYaBY+rpSZe3PBnkwAXjRbuDykrxHZMSqWF4XDOZiPyMCB9uzZF t8UzchI28qlu638bLn/nAUkoUcfZSP9BnSILZfRTiurBgm1bPh/gBH92kVy9SIdQ 3UnDcEE31v9lU8SNpGZQ3KuBgXt67kkBhraiU5ymsJAITNAJxqOUvQG3AgMBAAGj ggFNMIIBSTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQMCYIItFcs0o5kZ0bX08PF l1wbvTAfBgNVHSMEGDAWgBSF/Nmqm1Qo/J//ntUOvBkqam6uGTAOBgNVHQ8BAf8E BAMCBeAwIgYDVR0lAQH/BBgwFgYKKwYBBAGCNwoDDAYIKwYBBQUHAwQwgYAGCCsG AQUFBwEBBHQwcjAtBggrBgEFBQcwAYYhaHR0cDovL29jc3AuZGVtby5vbmUuZGln aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kZW1vLm9uZS5k aWdpY2VydC5jb20vRFRNUUFDMlBBSUNBLmNydDBCBgNVHR8EOzA5MDegNaAzhjFo dHRwOi8vY3JsLmRlbW8ub25lLmRpZ2ljZXJ0LmNvbS9EVE1RQUMyUEFJQ0EuY3Js MA0GCSqGSIb3DQEBCwUAA4IBAQC2RhbKI2CzOwjxICZHHVsJxN5xp0/YsjeKiFai diKGi8MenVoF45uZCP6CWfBlcv3weE9HTP1xwQMYAKa1q0A3XpWN97vW7IaaiB8x 8YBpKPYxV2jtEpqkrXWYxk1bisIYQQh7iI1s1EwhlzJOtd0fh/YjCUywfwjt3uj9 HsB+GSaC5f5rYZG9QcdxN4BiH2MVEbWLJyleBlQnCBRkRsfflZbOq08W1620RmWq B/d72HgjxZS1m5O8MqodWA4GIhKpzRSsDGkqMKKCKaeV0wagIKzim3yr/qpBAey1 L9XJRt6OnTM/SLlSGVT5+Od9oHu7N7QagwKYdWxF3HgKSBNL -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- MIIFCzCCA/OgAwIBAgIUA4M1bwtczOw4md/NE2Mdwgy6IR4wDQYJKoZIhvcNAQEL BQAwgaUxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRVdGFoMRkwFwYDVQQHExBTYXJh Z290YSBTcHJpbmdzMQ4wDAYDVQQREwU4NDA0NTEiMCAGA1UECRMZMjczOCBTLiBT YW5kYWx3b29kIENpcmNsZTEdMBsGA1UEChMUV2luIHRoZSBjdXN0b21lciBMTEMx GTAXBgNVBAMTEERUTSBRQSBDMlBBIFJvb3QwIBcNMjUwODE0MTAxMjIwWhgPMjA1 NTA4MTQxMDExMTRaMIGkMQswCQYDVQQGEwJVUzENMAsGA1UECBMEVXRhaDEZMBcG A1UEBxMQU2FyYWdvdGEgU3ByaW5nczEOMAwGA1UEERMFODQwNDUxIjAgBgNVBAkT GTI3MzggUy4gU2FuZGFsd29vZCBDaXJjbGUxHTAbBgNVBAoTFFdpbiB0aGUgY3Vz dG9tZXIgTExDMRgwFgYDVQQDEw9EVE0gUUEgQzJQQSBJQ0EwggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDbrzlf8IbeC13EhsyoKliOTbUHbJGxOvdojouk f6oBoEl04kSei9gn5zC4BkEKkUO/A0e82oB4rJCti9kXzGjs1xL68xg0xzI9ADxm GO7lqpQO/Jxi8knakz+smGshkOYH6SFNNZCXLqDMo8rTnTx79KevlfpgFTJLFGGT jFf3Pqwu6Ooxf5He8933NsOPNWf3iYUEzc3sdCf8Cg1NypkcuYRe2MGGf25JF7H/ gH08DnnqZkjitWpsypnja8iAx8swhpQaSAEL1k1yRnPo8D08JJJsf8+Q7ab2rIoj L1CCqSFdXe6wdGDTi4jn1keGQEWE7FVZz44O0ue7m8gH4Oq1AgMBAAGjggEuMIIB KjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSF/Nmqm1Qo/J//ntUOvBkqam6u GTAfBgNVHSMEGDAWgBTw2aAelyuNkKYlgtjxi/puSZqsVDAOBgNVHQ8BAf8EBAMC AYYwgYEGCCsGAQUFBwEBBHUwczAtBggrBgEFBQcwAYYhaHR0cDovL29jc3AuZGVt by5vbmUuZGlnaWNlcnQuY29tMEIGCCsGAQUFBzAChjZodHRwOi8vY2FjZXJ0cy5k ZW1vLm9uZS5kaWdpY2VydC5jb20vRFRNUUFDMlBBUm9vdC5jcnQwQwYDVR0fBDww OjA4oDagNIYyaHR0cDovL2NybC5kZW1vLm9uZS5kaWdpY2VydC5jb20vRFRNUUFD MlBBUm9vdC5jcmwwDQYJKoZIhvcNAQELBQADggEBAG/sCkrGHThC8B1jPO0PzaSi BA1BOwCXyukkva+lmOEOCgzOkbUr0SC9/pRV4JCpIBAFnnyoJpENmRc3KgBBvLI2 o8ovo19eK1m/1DLI1XQxSCwJ5DzKj7ffF4EZ539WvCspB3pn6bkmH5CuFtrzOKpi +WDvf7z4rS3EGZxVnaS0vLbdsTXdsg/DUtfNt7Ja15cL2NMFOVuUPKl9y0WOi9jA T8PvG60j4CH2Faoaqr1ggb3ncn64j5kFpIPkISzL1QXLWL0finS05kFqsOaCSQNA Zob5Nwm5jlcEeLhm/5fesRSfVOMIHSFnlW/gTKrON+ZDDZ7BlQdyFo2sWY6m6I8= -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- MIIEHjCCAwagAwIBAgIUWRwXzvDaUnXDtxtcP7uh0HJvfCQwDQYJKoZIhvcNAQEL BQAwgaUxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRVdGFoMRkwFwYDVQQHExBTYXJh Z290YSBTcHJpbmdzMQ4wDAYDVQQREwU4NDA0NTEiMCAGA1UECRMZMjczOCBTLiBT YW5kYWx3b29kIENpcmNsZTEdMBsGA1UEChMUV2luIHRoZSBjdXN0b21lciBMTEMx GTAXBgNVBAMTEERUTSBRQSBDMlBBIFJvb3QwIBcNMjUwODE0MTAxMTM4WhgPMjA1 NTA4MTQxMDExMTRaMIGlMQswCQYDVQQGEwJVUzENMAsGA1UECBMEVXRhaDEZMBcG A1UEBxMQU2FyYWdvdGEgU3ByaW5nczEOMAwGA1UEERMFODQwNDUxIjAgBgNVBAkT GTI3MzggUy4gU2FuZGFsd29vZCBDaXJjbGUxHTAbBgNVBAoTFFdpbiB0aGUgY3Vz dG9tZXIgTExDMRkwFwYDVQQDExBEVE0gUUEgQzJQQSBSb290MIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qaFgu/1tH1N8binitY6CktingTMfcPUPmy9 OSxJ/NxutpQvzXFbP+u+PasRDCBrPYN6bWObA6b5rwSsBmJLvBXwLR50QKgLqm52 TTCpqP+2GjbtgX1+L6fK/+FY6/WKmcmQaBTwvCU+1kUXI9kolIzprjyO6DMu716c MhF+eIGoz4w2AuksA/qJPzIElmOXUgn/9xiAP2yxOz/ijEqFxKMioYqYlyHAAY87 elcx8sXbSzUiV8c/1gSUvIfIygixiMNdpw0kOuDupp/jkx4ISaX7HQ2l+Ufa2fED 8RAhIaljQR6lfVPGoYVJOG3J530tYoShErPySC7zhmUjG4QiDwIDAQABo0IwQDAP BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTw2aAelyuNkKYlgtjxi/puSZqsVDAO BgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBACtH1D/io8EpBvqiuR4e zsyiJRJvcO4J2n0/Yh0siz2UimMuISjsr0F6lq5Tp7dJpLWG4TfCGrJcPGJSumBJ NhFXPXtaU6klLoA+vW3mjkVrKqF+Qo8tE3jHagQEnLg9N0f340K7u2IVYLuKyWgE XpLcq/VDXlBltVTxmyjAyM7gJqDbUQ2OF8hUjAL/udljyv9EYleuSBdxVpikJvOF bFKtHVpTVLirqURk3FNhmmoICIb/mQbAIf9fOY53KtIQBbKfM6JnYSdhpDRE80Yl AMmzsqxfhChSbPSvf7sEu1T4AoLsnrD6oyLEXuPZWej/phw0tGd9detUoxi23PZU oIY= -----END CERTIFICATE-----"#;
// Split the string into blocks by the BEGIN marker, skip the leading non-cert part
let mut certs: Vec<Vec<u8>> = Vec::new();
for part in cert_content.split("-----BEGIN CERTIFICATE-----").skip(1) {
// for each part, cut at END marker and take the base64 body
if let Some((body, _rest)) = part.split_once("-----END CERTIFICATE-----") {
// remove whitespace and newlines from the base64 body
let b64 = body
.chars()
.filter(|c| !c.is_whitespace())
.collect::<String>();
if !b64.is_empty() {
let cert_binary = BASE64_STANDARD.decode(b64)?;
certs.push(cert_binary);
}
}
}
info!("Certificate chain loaded with {} certificates", certs.len());
for (i, cert) in certs.iter().enumerate() {
info!("Certificate {} length: {} bytes", i, cert.len());
}
// If no certificates were found, return an error (keeps behavior explicit)
if certs.is_empty() {
Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"No certificates found in embedded PEM",
)))
} else {
Ok(certs)
}
}
//#[cfg(feature = "sign")]
#[unsafe(no_mangle)]
pub extern "C" fn sign_bytes(
format: *const c_char,
source_bytes: *const u8,
length: usize,
dest_bytes: *mut u8,
dest_size: usize,
) -> i64 {
// Initialize logging
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
// convert C pointers into Rust
let format = unsafe { CStr::from_ptr(format).to_string_lossy().into_owned() };
let source_bytes: &[u8] = unsafe { slice::from_raw_parts(source_bytes, length as usize) };
let dest_bytes: &mut [u8] =
unsafe { slice::from_raw_parts_mut(dest_bytes, dest_size as usize) };
// Create manifest
let mut manifest = c2pa::Manifest::new("my_app".to_owned());
manifest.set_title("EmbedStream");
let exif = Exif::from_json_str(
r#"{
"@context" : {
"exif": "http://ns.adobe.com/exif/1.0/"
},
"exif:GPSVersionID": "2.2.0.0",
"exif:GPSLatitude": "39,21.102N",
"exif:GPSLongitude": "74,26.5737W",
"exif:GPSAltitudeRef": 0,
"exif:GPSAltitude": "100963/29890",
"exif:GPSTimeStamp": "2019-09-22T18:22:57Z"
}"#,
)
.unwrap();
manifest.add_assertion(&exif).unwrap();
// Get manifest bytes and compute hash
let manifest_bytes = serde_json::to_vec(&manifest).unwrap();
let mut hasher = Sha256::new();
hasher.update(&manifest_bytes);
let hashed_data = hasher.finalize();
let base64_hash = BASE64_STANDARD.encode(hashed_data);
// Get SAD and signature
let sad = match fetch_sad(&base64_hash) {
Ok(sad) => sad,
Err(e) => {
error!("Failed to fetch SAD: {}", e);
return -1;
}
};
let signed_signature = match sign_via_api(&base64_hash, &sad) {
Ok(signature) => signature,
Err(e) => {
error!("Failed to get signature: {}", e);
return -1;
}
};
// Decode the signed signature
let decoded_signature = match BASE64_STANDARD.decode(&signed_signature) {
Ok(sig) => sig,
Err(e) => {
error!("Failed to decode signature: {}", e);
return -1;
}
};
// Read certificate chain from file
let cert_chain = match read_certificate_chain() {
Ok(chain) => {
info!("Certificate chain loaded with {} certificates", chain.len());
for (i, cert) in chain.iter().enumerate() {
info!("Certificate {} length: {} bytes", i, cert.len());
}
chain
}
Err(e) => {
error!("Failed to read certificate chain: {}", e);
return -1;
}
};
println!("Certificate chain: {:?}", cert_chain);
// Create a custom signer that uses the decoded signature and certificate chain
let signer = RemoteSigner {
signature: decoded_signature.clone(),
certificates: cert_chain,
};
info!("Signature length: {} bytes", decoded_signature.len());
// Convert buffer to cursor
let mut stream = Cursor::new(source_bytes.to_vec());
// Embed the manifest
let _manifest_bytes = match manifest.embed_stream(&format, &mut stream, &signer) {
Ok(manifest) => manifest,
Err(e) => {
error!("Embed Stream Error: {}", e);
return -1;
}
};
// Get the updated asset
let bytes = stream.into_inner();
// Copy the signed asset into the output buffer
if dest_size >= bytes.len() {
dest_bytes[..bytes.len()].clone_from_slice(&bytes);
} else {
error!("dest_size too small, {} bytes required", bytes.len());
return -1;
}
// Verify the manifests
match ManifestStore::from_bytes(&format, &bytes, true) {
Ok(manifest_store) => println!("{}", manifest_store),
Err(e) => {
error!("Manifest from bytes Error {:?}", e);
return -1;
}
};
bytes.len() as i64}
use std::io::{self, Read, Write};
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
use log::{error, info};
use env_logger;
mod lib; // if your existing code is in lib.rs
fn main() {
env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
// 1. Read claim bytes from stdin
let mut stdin = io::stdin();
let mut claim_bytes = Vec::new();
if let Err(e) = stdin.read_to_end(&mut claim_bytes) {
eprintln!("Failed to read claim bytes from stdin: {}", e);
std::process::exit(1);
}
info!("Read {} claim bytes from stdin", claim_bytes.len());
// 2. Compute hash of claim bytes
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(&claim_bytes);
let hash = hasher.finalize();
let base64_hash = BASE64_STANDARD.encode(hash);
// 3. Fetch SAD
let sad = match lib::fetch_sad(&base64_hash) {
Ok(sad) => sad,
Err(e) => {
error!("Failed to fetch SAD: {}", e);
std::process::exit(1);
}
};
// 4. Ask remote API to sign
let signed_signature = match lib::sign_via_api(&base64_hash, &sad) {
Ok(signature) => signature,
Err(e) => {
error!("Failed to sign via API: {}", e);
std::process::exit(1);
}
};
// 5. Decode base64 signature (if needed)
let decoded_signature = match BASE64_STANDARD.decode(&signed_signature) {
Ok(sig) => sig,
Err(e) => {
error!("Failed to decode base64 signature: {}", e);
std::process::exit(1);
}
};
// 6. Write the signature bytes to stdout
let mut stdout = io::stdout();
if let Err(e) = stdout.write_all(&decoded_signature) {
eprintln!("Failed to write signature to stdout: {}", e);
std::process::exit(1);
}
}