haematite-cli 0.21.0

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
use std::io::Write;

use base64::Engine;

use super::*;

const SEED: &[u8; 32] = b"lys-cose-conformance-test-seed01";
const MILLIS: u64 = 1_700_000_000_000;
/// The issuer's golden attestation for this seed, payload and time, as
/// pinned by Cambium's seat MCP conformance test.
const GOLDEN: &str = "d2845850a301270378276170706c69636174696f6e2f766e642e6c79732e6174746573746174696f6e2e76322b63626f72045820214e41cc8475f5bc9af68dbf33fa56e8adf5144a4a81134268f4ae379e103bfda0582ea20158209404f8b8cec8ad98a88b106d9345d518c273f012c1306b8af5103e865997191e021b0000018bcfe568005840ba6eae66b3a04f25e6116e7278ae8b0e2174a2964bb0917ae53ab2ef326c22a706e90cae6886b20ecd086fee4b515de046d450f90de81f9aa46a9b34bd7f1a09";

fn pem() -> String {
    let der = [PKCS8_ED25519_PREFIX.as_slice(), SEED].concat();
    let body = base64::engine::general_purpose::STANDARD.encode(der);
    format!("-----BEGIN PRIVATE KEY-----\n{body}\n-----END PRIVATE KEY-----\n")
}

#[test]
fn attestation_matches_the_issuer_golden_bytes() {
    let key = SigningKey::from_bytes(SEED);
    let cose = attest(&key, b"lys attestation conformance payload\n", MILLIS);
    assert_eq!(hex(&cose), GOLDEN);
}

#[test]
fn a_pem_file_and_a_seat_file_read_the_same_key() -> Result<(), Box<dyn std::error::Error>> {
    let mut plain = tempfile::NamedTempFile::new()?;
    plain.write_all(pem().as_bytes())?;
    let mut seat = tempfile::NamedTempFile::new()?;
    seat.write_all(
        serde_json::json!({ "privateKeyPem": pem() })
            .to_string()
            .as_bytes(),
    )?;
    let from_pem = read_key(plain.path()).map_err(|refusal| refusal.message)?;
    let from_seat = read_key(seat.path()).map_err(|refusal| refusal.message)?;
    assert_eq!(from_pem.to_bytes(), *SEED);
    assert_eq!(from_seat.to_bytes(), *SEED);
    Ok(())
}

#[test]
fn a_key_that_is_not_ed25519_pkcs8_is_refused_without_its_bytes()
-> Result<(), Box<dyn std::error::Error>> {
    let mut file = tempfile::NamedTempFile::new()?;
    file.write_all(b"-----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----\n")?;
    let Err(refusal) = read_key(file.path()) else {
        return Err("a malformed key was accepted".into());
    };
    assert_eq!(refusal.code, "mcp_lys_key_unreadable");
    assert!(!refusal.message.contains("AAAA"));
    Ok(())
}

#[test]
fn the_header_names_agent_time_nonce_and_a_verifiable_attestation() -> Result<(), String> {
    let key = SigningKey::from_bytes(SEED);
    let header = signature(
        &key,
        "agent-under-test",
        "/api/agents/agent-under-test/pass",
        b"{}",
        MILLIS,
    )
    .map_err(|refusal| refusal.message)?;
    let words: Vec<&str> = header.split(' ').collect();
    assert_eq!(words.len(), 4);
    assert_eq!(words[0], "agent-under-test");
    assert_eq!(words[1], MILLIS.to_string());
    assert_eq!(words[2].len(), NONCE_BYTES * 2);
    let payload = [
        REQUEST_DOMAIN,
        "POST",
        "/api/agents/agent-under-test/pass",
        &hex(&Sha256::digest(b"{}")),
        &MILLIS.to_string(),
        words[2],
    ]
    .join("\n");
    assert_eq!(words[3], hex(&attest(&key, payload.as_bytes(), MILLIS)));
    Ok(())
}