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

nordisk/dwarves

crates/dwarves-slsa/tests/release_key_attestation.rs

//! The release key's YubiKey attestation (`keys/release-signing-key.attestation.pem`,
//! the same files as `slsa/` in holger and gunnar): the attestation certificate chains to
//! the YubiKey's PIV attestation CA ("Yubico PIV Attestation B 1"), and the key it attests
//! is the built-in release key. A different key does not match the attestation.

use std::path::{Path, PathBuf};
use std::process::Command;

use dwarves_slsa::dsse;
use dwarves_slsa::release_key;

fn key_file(name: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("keys")
        .join(name)
}

/// `openssl x509 <flag> -noout -in <cert>`.
fn x509(cert: &Path, flag: &str) -> String {
    let out = Command::new("openssl")
        .args(["x509", flag, "-noout", "-in"])
        .arg(cert)
        .output()
        .expect("openssl is needed for this test");
    assert!(out.status.success(), "openssl x509 {flag} failed");
    String::from_utf8(out.stdout).unwrap()
}

/// The SPKI DER of the (P-256) public key in an X.509 certificate (PEM).
fn attested_spki(cert: &Path) -> Vec<u8> {
    dsse::spki_from_pem(&x509(cert, "-pubkey")).unwrap()
}

#[test]
fn the_attestation_chains_to_the_yubikey_attestation_ca() {
    let out = Command::new("openssl")
        .args(["verify", "-partial_chain", "-CAfile"])
        .arg(key_file("yubikey-piv-attestation-ca.pem"))
        .arg(key_file("release-signing-key.attestation.pem"))
        .output()
        .expect("openssl is needed for this test");
    let text = String::from_utf8_lossy(&out.stdout);
    assert!(
        out.status.success() && text.trim_end().ends_with(": OK"),
        "{text}"
    );

    // The leaf attests slot 9c; the intermediate is the device's own attestation
    // certificate (slot f9), issued by Yubico's PIV attestation CA.
    let leaf = key_file("release-signing-key.attestation.pem");
    let ca = key_file("yubikey-piv-attestation-ca.pem");
    assert!(x509(&leaf, "-subject").contains("YubiKey PIV Attestation 9c"));
    assert!(x509(&leaf, "-issuer").contains("YubiKey PIV Attestation"));
    assert!(x509(&ca, "-issuer").contains("Yubico PIV Attestation B 1"));
}

#[test]
fn the_attested_key_is_the_release_key() {
    let attested = attested_spki(&key_file("release-signing-key.attestation.pem"));
    assert_eq!(attested, release_key::spki().unwrap());
    assert_eq!(dsse::keyid(&attested), release_key::RELEASE_KEY_ID);
}

#[test]
fn a_different_key_does_not_match_the_attestation() {
    let attested = attested_spki(&key_file("release-signing-key.attestation.pem"));
    let other =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/openssl/release-test.pub");
    let other = dsse::spki_from_pem(&std::fs::read_to_string(other).unwrap()).unwrap();
    assert_ne!(attested, other);
    assert_ne!(dsse::keyid(&other), release_key::RELEASE_KEY_ID);

    // The CA is not the release key either: only the attested leaf is.
    let ca = x509(&key_file("yubikey-piv-attestation-ca.pem"), "-pubkey");
    assert_ne!(ca.trim(), release_key::RELEASE_KEY_PEM.trim());
}

#[test]
fn a_certificate_not_from_the_yubikey_ca_is_refused() {
    let d = tempfile::tempdir().unwrap();
    let key = d.path().join("k.pem");
    let cert = d.path().join("c.pem");
    assert!(Command::new("openssl")
        .args([
            "req",
            "-x509",
            "-newkey",
            "ec",
            "-pkeyopt",
            "ec_paramgen_curve:prime256v1",
            "-nodes",
            "-days",
            "1",
            "-subj",
            "/CN=not-a-yubikey",
            "-keyout"
        ])
        .arg(&key)
        .arg("-out")
        .arg(&cert)
        .output()
        .unwrap()
        .status
        .success());
    let out = Command::new("openssl")
        .args(["verify", "-partial_chain", "-CAfile"])
        .arg(key_file("yubikey-piv-attestation-ca.pem"))
        .arg(&cert)
        .output()
        .unwrap();
    assert!(!out.status.success());
    assert_ne!(attested_spki(&cert), release_key::spki().unwrap());
}