Skip to main content

Reference files

C2paLibrary.java

package org.example;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

public interface C2paLibrary extends Library {
    // JNA will map "c2pa_rs" to libc2pa_rs.so / libc2pa_rs.dylib / c2pa_rs.dll
    C2paLibrary INSTANCE = Native.load("c2pa_rs", C2paLibrary.class);

    static C2paLibrary getInstance() {
        return INSTANCE;
    }

    // The native functions - match signatures in Rust
    long sign_bytes(String format, byte[] source_bytes, long length, byte[] dest_bytes, long dest_size);
    void verify_bytes(String format, byte[] bytes, long length);
}

C2paTest.java

package org.example;

import java.io.*;
import java.nio.file.*;

public class C2paTest {
    public static void main(String[] args) throws IOException {
        System.setProperty("jna.library.path", "lib");
        byte[] image = Files.readAllBytes(Paths.get("images/A.jpg"));
        byte[] dest = new byte[image.length + 1024 * 200]; // large buffer

        long result = C2paLibrary.getInstance().sign_bytes("jpeg", image, image.length, dest, dest.length);

        if (result > 0) {
            System.out.println("Signing successful. Output size: " + result);
            Files.write(Paths.get("with_claim.jpg"), java.util.Arrays.copyOf(dest, (int) result));

            // Optional: verify
            C2paLibrary.getInstance().verify_bytes("jpeg", dest, (int) result);
        } else {
            System.err.println("Signing failed.");
        }
    }
}

Cargo.toml

[package]
name = "c2pa_rs"
version = "0.1.0"
edition = "2024"

[lib]
name = "c2pa_rs"
crate-type = ["cdylib","staticlib"]

[dependencies]
c2pa = { version = "0.18.1", default-features = false, features = ["sign"] }
reqwest = { version = "0.12.20", features = ["blocking", "json", "native-tls"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
base64 = "0.22.1"
log = "0.4"
env_logger = "0.11.8"
sha2 = "0.10"
uuid = { version = "1.12.0", features = ["v4"] }
once_cell = "1.19"

[profile.release]
strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
lto = "thin" # Link time optimization.

lib.rs

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}

main.rs

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);    
    }
}