Skip to main content

Reference files

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

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
    let cert_content = "-----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-----";

    // Extract the base64-encoded certificate content
    let cert_data = cert_content
        .lines()
        .filter(|line| !line.contains("BEGIN CERTIFICATE") && !line.contains("END CERTIFICATE"))
        .collect::<Vec<&str>>()
        .join("");

    info!("Extracted certificate data length: {} chars", cert_data.len());

    // Decode the base64 certificate data
    let cert_binary = BASE64_STANDARD.decode(cert_data)?;
    info!("Decoded certificate length: {} bytes", cert_binary.len());

    // For this implementation, we're just using the single certificate from the PEM file
    let certs = vec![cert_binary];

    info!("Certificate chain loaded with {} certificates", certs.len());
    for (i, cert) in certs.iter().enumerate() {
        info!("Certificate {} length: {} bytes", i, cert.len());
    }

    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
}

Cargo.toml

[package]
name = "tool"
version = "0.1.0"
edition = "2021"

[dependencies]
base64 = "0.21"
c2pa = "0.43.0"   # adjust to match the version you already use
reqwest = { version = "0.11", features = ["blocking", "rustls-tls", "native-tls", "json"] }   
sha2 = "0.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
log = "0.4"
env_logger = "0.10"

[[bin]]
name = "remote-signer"
path = "src/main.rs"