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

nordisk/Skidbladnir

crates/installer/plugin-door-wire/tests/register_over_mtls.rs

//! **The whole client, against a door over real mutual TLS.**
//!
//! A fake plugin door (the installer CA, a server certificate named
//! `bootstrap.installer`, the client's CN read off the handshake) that keeps
//! what it was sent, the way vm1's does: `GET /register/<id>` answers what it
//! holds, `POST` is taken only from the box it names as primary. What is
//! proven is the rule every product server lives by — **register once, again
//! only when something changed, and never from the wrong box** — through the
//! same `register_if_changed` gunnar-server and holger-server call.

use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use nordisk_pairing::mtls::{self, InstallerCa, Issued};
use plugin_door_wire::{digest, register_if_changed, Done, Held, Me, Plugin, Registered, SelfRegister};
use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};

const DAY: Duration = Duration::from_secs(24 * 3600);

#[derive(Default)]
struct Door {
    held: Option<Held>,
    posts: usize,
}

fn boxed(ca: &InstallerCa, name: &str) -> Issued {
    let (csr, key) = mtls::box_request().expect("csr");
    Issued { cert_pem: ca.sign_box(&csr, name, DAY).expect("sign"), key_pem: key }
}

/// One request per connection, like the real door (`Connection: close`).
async fn serve(ca: InstallerCa, door_cert: Issued, primary: &'static str, state: Arc<Mutex<Door>>) -> SocketAddr {
    let cfg = mtls::server_config(&ca.cert_pem, &door_cert).expect("server config");
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("bind");
    let at = listener.local_addr().unwrap();
    tokio::spawn(async move {
        loop {
            let Ok((tcp, _)) = listener.accept().await else { return };
            let acceptor = tokio_rustls::TlsAcceptor::from(cfg.clone());
            let state = state.clone();
            tokio::spawn(async move {
                let Ok(mut tls) = acceptor.accept(tcp).await else { return };
                let cn = mtls::peer_cn(tls.get_ref().1).unwrap_or_default();
                let mut buf = Vec::new();
                let mut chunk = [0u8; 65536];
                // Read the head, then Content-Length bytes of body.
                let (head, body) = loop {
                    let n = tls.read(&mut chunk).await.unwrap_or(0);
                    if n == 0 {
                        return;
                    }
                    buf.extend_from_slice(&chunk[..n]);
                    if let Some(i) = buf.windows(4).position(|w| w == b"\r\n\r\n") {
                        let head = String::from_utf8_lossy(&buf[..i]).to_string();
                        let len: usize = head
                            .lines()
                            .find_map(|l| l.strip_prefix("Content-Length: "))
                            .and_then(|v| v.trim().parse().ok())
                            .unwrap_or(0);
                        while buf.len() < i + 4 + len {
                            let n = tls.read(&mut chunk).await.unwrap_or(0);
                            if n == 0 {
                                break;
                            }
                            buf.extend_from_slice(&chunk[..n]);
                        }
                        break (head, buf[i + 4..].to_vec());
                    }
                };
                let mut words = head.split_whitespace();
                let (method, path) = (words.next().unwrap_or(""), words.next().unwrap_or(""));
                let id = path.strip_prefix("/register/").unwrap_or("").to_string();
                let (status, json) = {
                    let mut d = state.lock().unwrap();
                    match method {
                        _ if cn != primary && cn != mtls::INSTALLER_CN => (403, serde_json::to_string(&Registered::refused("self-register-not-primary", format!("`{cn}` is not the primary"))).unwrap()),
                        "GET" => (200, serde_json::to_string(&d.held.clone().unwrap_or(Held { id: id.clone(), ..Held::default() })).unwrap()),
                        "POST" => {
                            let req: SelfRegister = serde_json::from_slice(&body).expect("a registration");
                            use base64::Engine as _;
                            let module = base64::engine::general_purpose::STANDARD.decode(&req.module).unwrap();
                            let outcome = if d.held.is_some() { "REPLACED" } else { "ADDED" };
                            d.posts += 1;
                            d.held = Some(Held { id, digest: Some(digest(&module)), ticket_key: req.ticket_key, registrar: Some(cn.clone()), monetize_pubkey: Some("ab".repeat(32)) });
                            (200, serde_json::to_string(&Registered { outcome: outcome.into(), line: format!("{outcome} by {cn}"), monetize_pubkey: Some("ab".repeat(32)) }).unwrap())
                        }
                        _ => (405, "{}".into()),
                    }
                };
                let answer = format!("HTTP/1.1 {status} X\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{json}", json.len());
                let _ = tls.write_all(answer.as_bytes()).await;
                let _ = tls.shutdown().await;
            });
        }
    });
    at
}

#[tokio::test(flavor = "current_thread")]
async fn once_then_nothing_then_replaced_and_never_from_the_twin() {
    let ca = InstallerCa::mint("door.test", DAY).expect("ca");
    let state = Arc::new(Mutex::new(Door::default()));
    let door = serve(ca.clone(), boxed(&ca, "vm1"), "vm2", state.clone()).await;

    let primary = boxed(&ca, "vm2");
    let me = Me { name: "vm2", identity: &primary, ca_pem: &ca.cert_pem, ticket_key: None };
    let v1 = b"\0asm\x01\0\0\0 version one".to_vec();
    let v2 = b"\0asm\x01\0\0\0 version two".to_vec();

    // First time: sent, ADDED, and the front's monetize key comes back.
    let done = register_if_changed(door, &me, "holger", Plugin { module: &v1, signature: b"sig" }).await.expect("first");
    assert!(matches!(&done, Done::Sent { status: 200, answer } if answer.outcome == "ADDED"), "{done:?}");
    assert_eq!(done.monetize_pubkey(), Some("ab".repeat(32).as_str()));

    // Every later boot at the same version: nothing is sent.
    let done = register_if_changed(door, &me, "holger", Plugin { module: &v1, signature: b"sig" }).await.expect("again");
    assert!(matches!(done, Done::Unchanged(_)), "{done:?}");
    assert_eq!(state.lock().unwrap().posts, 1, "the same module from the same box is never sent twice");

    // A new version: REPLACED.
    let done = register_if_changed(door, &me, "holger", Plugin { module: &v2, signature: b"sig" }).await.expect("new version");
    assert!(matches!(&done, Done::Sent { answer, .. } if answer.outcome == "REPLACED"), "{done:?}");
    assert_eq!(state.lock().unwrap().held.as_ref().and_then(|h| h.digest.clone()), Some(digest(&v2)));

    // The twin, with a certificate from the same CA: refused, nothing written.
    let twin = boxed(&ca, "vm3");
    let as_twin = Me { name: "vm3", identity: &twin, ca_pem: &ca.cert_pem, ticket_key: None };
    let err = register_if_changed(door, &as_twin, "holger", Plugin { module: &v1, signature: b"sig" }).await.expect_err("the twin");
    assert!(err.contains("403"), "{err}");
    assert_eq!(state.lock().unwrap().posts, 2);

    // A certificate from ANOTHER CA does not get through the handshake at all.
    let stranger_ca = InstallerCa::mint("elsewhere", DAY).unwrap();
    let stranger = boxed(&stranger_ca, "vm2");
    let as_stranger = Me { name: "vm2", identity: &stranger, ca_pem: &ca.cert_pem, ticket_key: None };
    assert!(register_if_changed(door, &as_stranger, "holger", Plugin { module: &v1, signature: b"sig" }).await.is_err());
    assert_eq!(state.lock().unwrap().posts, 2);
}

/// A door that serves the CA the client pins but does NOT trust the client's
/// certificate (another CA): under TLS 1.3 the client's handshake completes and
/// the refusal arrives as an alert on its FIRST READ. MEASURED on the o-ops and
/// release-3 mocks 2026-09-29: main's self-registration said only
/// `no HTTP answer: ""` — the read's error was thrown away, so the real reason
/// (the alert) was never said.
#[tokio::test]
async fn a_door_that_refuses_the_client_certificate_is_named_not_an_empty_answer() {
    let ca = InstallerCa::mint("door.test", DAY).expect("ca");
    let other = InstallerCa::mint("other.test", DAY).expect("other ca");
    let door_cert = boxed(&ca, "vm1");
    // The door presents `ca`'s certificate, and takes clients of `other` only.
    let cfg = mtls::server_config(&other.cert_pem, &door_cert).expect("server config");
    let cfg = {
        let mut c = (*cfg).clone();
        c.cert_resolver = mtls::server_config(&ca.cert_pem, &door_cert).expect("door cert").cert_resolver.clone();
        Arc::new(c)
    };
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.expect("bind");
    let door = listener.local_addr().unwrap();
    tokio::spawn(async move {
        while let Ok((tcp, _)) = listener.accept().await {
            let acceptor = tokio_rustls::TlsAcceptor::from(cfg.clone());
            tokio::spawn(async move {
                let _ = acceptor.accept(tcp).await;
            });
        }
    });
    let primary = boxed(&ca, "vm2");
    let me = Me { name: "vm2", identity: &primary, ca_pem: &ca.cert_pem, ticket_key: None };
    let v1 = b"\0asm\x01\0\0\0 version one".to_vec();
    let e = register_if_changed(door, &me, "gunnar", Plugin { module: &v1, signature: b"sig" }).await.expect_err("a refused certificate is no answer");
    assert!(e.contains("no HTTP answer"), "{e}");
    assert!(!e.ends_with("no HTTP answer: \"\""), "the read's own error must be said, not an empty answer: {e}");
    assert!(e.to_ascii_lowercase().contains("alert") || e.to_ascii_lowercase().contains("certificate"), "the TLS alert names the refused certificate: {e}");
}