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

nordisk/holger

server/lib/src/evidence_key.rs

//! **The evidence public-key door** (lane HOL-OCI-EVIDENCE, owner 2026-10-31).
//!
//! Holger signs its evidence (the scan verdict, the image SBOM, the approvals) with the
//! key of `attestation.signer_cert` ([`crate::attest::AttestSigner`]), and serves that
//! evidence to registry clients as OCI referrers and `.att` layers ([`crate::referrers`]).
//! A customer verifies it with `cosign verify-attestation --key <url>`; this module is
//! the `<url>`: a public, read-only door with the public half of the SAME certificate,
//! in the shapes verifiers read.
//!
//! | Path | Body | Content type |
//! |---|---|---|
//! | `GET /-/evidence-key`, `/-/evidence-key.pem` | the SubjectPublicKeyInfo as PEM (`PUBLIC KEY`), what `cosign --key` takes | `application/x-pem-file` |
//! | `GET /-/evidence-key.jwks` | a JWK Set (RFC 7517) with the one key: `kid` the DSSE keyid Holger writes (hex SHA-256 of the certificate), `x5c` the certificate, `x5t#S256` | `application/jwk-set+json` |
//! | `GET /-/evidence-key.crt` | the certificate (PEM) | `application/x-pem-file` |
//!
//! Open on every box, a read-gated one too: a public key is public, and a verifier that
//! must sign in to fetch the key it verifies with is a verifier that never runs. A Holger
//! that signs no evidence answers 404 and names `attestation.signer_cert`.

use base64::Engine as _;
use serde_json::{json, Value};
use sha2::Digest as _;

/// The door's paths.
pub const PEM_PATH: &str = "/-/evidence-key";
pub const PEM_PATH_EXT: &str = "/-/evidence-key.pem";
pub const JWKS_PATH: &str = "/-/evidence-key.jwks";
pub const CERT_PATH: &str = "/-/evidence-key.crt";

/// Is `path` one of this door's?
pub fn is_door(path: &str) -> bool {
    matches!(path, PEM_PATH | PEM_PATH_EXT | JWKS_PATH | CERT_PATH)
}

/// What a Holger with no evidence signer answers.
pub const NO_SIGNER: &str =
    "this Holger signs no evidence: its config names no `attestation.signer_cert` \
(with `signer_key`), so there is no evidence key to serve";

/// Holger's evidence key, in every shape the door serves. Built once, at boot, from the
/// certificate the signer was loaded with.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EvidenceKey {
    /// `-----BEGIN PUBLIC KEY-----` (SubjectPublicKeyInfo).
    pub public_pem: String,
    /// `-----BEGIN CERTIFICATE-----`.
    pub cert_pem: String,
    /// The JWK Set, serialized.
    pub jwks: String,
    /// The DSSE keyid Holger writes into every envelope: hex SHA-256 of the certificate.
    pub keyid: String,
}

fn pem(label: &str, der: &[u8]) -> String {
    let body = base64::engine::general_purpose::STANDARD.encode(der);
    let mut out = format!("-----BEGIN {label}-----\n");
    for line in body.as_bytes().chunks(64) {
        out.push_str(std::str::from_utf8(line).unwrap_or_default());
        out.push('\n');
    }
    out.push_str(&format!("-----END {label}-----\n"));
    out
}

fn b64url(b: &[u8]) -> String {
    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b)
}

const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";
const OID_P256: &str = "1.2.840.10045.3.1.7";
const OID_ED25519: &str = "1.3.101.112";

impl EvidenceKey {
    /// The key of a DER certificate (an evidence signer is P-256 or Ed25519).
    pub fn from_cert_der(der: &[u8]) -> Result<Self, String> {
        let (_, cert) = x509_parser::parse_x509_certificate(der)
            .map_err(|e| format!("the evidence signer's certificate does not parse: {e}"))?;
        let spki = cert.public_key();
        let alg = spki.algorithm.algorithm.to_id_string();
        let key = spki.subject_public_key.data.as_ref();
        let thumb = sha2::Sha256::digest(der);
        let keyid = hex::encode(thumb);
        let mut jwk = match alg.as_str() {
            OID_EC_PUBLIC_KEY => {
                let curve = spki
                    .algorithm
                    .parameters
                    .as_ref()
                    .and_then(|p| p.as_oid().ok())
                    .map(|o| o.to_id_string());
                if curve.as_deref() != Some(OID_P256) {
                    return Err(format!(
                        "the evidence key is an EC key on {curve:?}, not P-256"
                    ));
                }
                if key.len() != 65 || key[0] != 4 {
                    return Err("the evidence key's P-256 point is not uncompressed".into());
                }
                json!({ "kty": "EC", "crv": "P-256", "alg": "ES256", "x": b64url(&key[1..33]), "y": b64url(&key[33..]) })
            }
            OID_ED25519 => {
                if key.len() != 32 {
                    return Err("the evidence key's Ed25519 key is not 32 bytes".into());
                }
                json!({ "kty": "OKP", "crv": "Ed25519", "alg": "EdDSA", "x": b64url(key) })
            }
            other => {
                return Err(format!(
                    "the evidence key's algorithm {other} is neither P-256 nor Ed25519"
                ))
            }
        };
        jwk["kid"] = Value::String(keyid.clone());
        jwk["use"] = Value::String("sig".into());
        jwk["x5c"] = json!([base64::engine::general_purpose::STANDARD.encode(der)]);
        jwk["x5t#S256"] = Value::String(b64url(&thumb));
        Ok(Self {
            public_pem: pem("PUBLIC KEY", spki.raw),
            cert_pem: pem("CERTIFICATE", der),
            jwks: json!({ "keys": [jwk] }).to_string(),
            keyid,
        })
    }

    /// The body and content type of one door path, `None` for a path that is not the door's.
    pub fn answer(&self, path: &str) -> Option<(&'static str, String)> {
        match path {
            PEM_PATH | PEM_PATH_EXT => Some(("application/x-pem-file", self.public_pem.clone())),
            JWKS_PATH => Some(("application/jwk-set+json", self.jwks.clone())),
            CERT_PATH => Some(("application/x-pem-file", self.cert_pem.clone())),
            _ => None,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_p256_certificate_gives_its_key_in_every_shape() {
        let k = rcgen::KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256).unwrap();
        let cert = rcgen::CertificateParams::new(vec![])
            .unwrap()
            .self_signed(&k)
            .unwrap();
        let e = EvidenceKey::from_cert_der(cert.der()).unwrap();
        assert_eq!(e.public_pem.trim(), k.public_key_pem().trim());
        assert_eq!(e.keyid, hex::encode(sha2::Sha256::digest(cert.der())));
        let v: Value = serde_json::from_str(&e.jwks).unwrap();
        assert_eq!(v["keys"][0]["crv"], "P-256");
        assert!(is_door("/-/evidence-key.jwks") && !is_door("/-/evidence-keys"));
    }

    #[test]
    fn a_certificate_that_is_not_one_says_so() {
        assert!(EvidenceKey::from_cert_der(b"not a certificate")
            .unwrap_err()
            .contains("does not parse"));
    }
}