Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

provenance/examples/sign_slsa.rs

//! Sign a SLSA Provenance v1 statement about one file as a DSSE envelope, the way
//! Holger's provenance scanner reads it (the 1.0 demo's online side).
//!
//! ```text
//! cargo run -p holger-provenance --example sign_slsa -- \
//!     <ed25519-pkcs8.der> <keyid> <builder-id> <artifact> > <artifact>.intoto.jsonl
//! cargo run -p holger-provenance --example sign_slsa -- --new-key <ed25519-pkcs8.der>
//! ```
//!
//! `--new-key` writes a fresh Ed25519 PKCS#8 key and prints the RON `keys:` entry
//! for its public half. The private key never leaves the file it was written to.

use ring::signature::{Ed25519KeyPair, KeyPair as _};
use sha2::{Digest, Sha256};

fn b64(b: &[u8]) -> String {
    holger_provenance::crypto::b64_encode(b)
}

fn ron_entry(keyid: &str, pair: &Ed25519KeyPair) -> String {
    let mut spki = hex::decode("302a300506032b6570032100").unwrap();
    spki.extend_from_slice(pair.public_key().as_ref());
    format!(
        "(id: \"{keyid}\", alg: ed25519, public_key: \"{}\")",
        b64(&spki)
    )
}

fn main() -> anyhow::Result<()> {
    let args: Vec<String> = std::env::args().skip(1).collect();
    if args.first().map(String::as_str) == Some("--new-key") {
        let path = args
            .get(1)
            .ok_or_else(|| anyhow::anyhow!("--new-key <path>"))?;
        let pkcs8 = Ed25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new())
            .map_err(|e| anyhow::anyhow!("{e}"))?;
        std::fs::write(path, pkcs8.as_ref())?;
        let pair =
            Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).map_err(|e| anyhow::anyhow!("{e}"))?;
        println!(
            "{}",
            ron_entry(
                args.get(2).map(String::as_str).unwrap_or("demo-release"),
                &pair
            )
        );
        return Ok(());
    }
    let [key, keyid, builder, artifact] = args.as_slice() else {
        anyhow::bail!("usage: sign_slsa <ed25519-pkcs8.der> <keyid> <builder-id> <artifact>");
    };
    let pair = Ed25519KeyPair::from_pkcs8(&std::fs::read(key)?)
        .map_err(|e| anyhow::anyhow!("the key: {e}"))?;
    let bytes = std::fs::read(artifact)?;
    let name = std::path::Path::new(artifact)
        .file_name()
        .unwrap()
        .to_string_lossy()
        .into_owned();
    let statement = serde_json::to_vec(&serde_json::json!({
        "_type": "https://in-toto.io/Statement/v1",
        "subject": [{"name": name, "digest": {"sha256": hex::encode(Sha256::digest(&bytes))}}],
        "predicateType": "https://slsa.dev/provenance/v1",
        "predicate": {
            "buildDefinition": {
                "buildType": "https://holger.rs/demo-build/v1",
                "externalParameters": {"artifact": name},
                "resolvedDependencies": []
            },
            "runDetails": {"builder": {"id": builder}, "metadata": {"invocationId": format!("sign_slsa {}", chrono::Utc::now().to_rfc3339())}}
        }
    }))?;
    let sig = pair.sign(&znippy_common::sign::dsse_pae(
        holger_provenance::statement::PAYLOAD_TYPE_INTOTO,
        &statement,
    ));
    println!(
        "{}",
        serde_json::to_string(&serde_json::json!({
            "payloadType": holger_provenance::statement::PAYLOAD_TYPE_INTOTO,
            "payload": b64(&statement),
            "signatures": [{"keyid": keyid, "sig": b64(sig.as_ref())}],
        }))?
    );
    eprintln!("signed {name} for {}", ron_entry(keyid, &pair));
    Ok(())
}