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/exposed/tls.rs

//! Shared TLS material for an exposed endpoint.
//!
//! One [`TlsSettings`] (loaded from RON) drives both transports of an endpoint:
//! the gRPC server (tonic) and the HTTP/OCI gateway (hyper + rustls). rustls is
//! already in the dependency tree via reqwest, so this adds no OpenSSL surface —
//! consistent with the airgap posture.
//!
//! Client-auth semantics differ by transport on purpose:
//!   * gRPC: when `client_ca` is set, mTLS is **required** (clients are the
//!     agent / CI, which can always present a cert).
//!   * HTTP gateway: client certs are **requested but optional**, so anonymous
//!     pulls and Kubernetes `/healthz` probes still work; write requests are
//!     rejected at the application layer unless a valid identity is present.

use std::fs;
use std::sync::Arc;

use anyhow::{Context, Result};
use rustls::pki_types::pem::PemObject as _;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use rustls::ServerConfig;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TlsSettings {
    /// Path to the PEM server certificate chain.
    pub cert: String,
    /// Path to the PEM private key.
    pub key: String,
    /// Optional PEM CA bundle. When set, client certificates are trusted
    /// against it (mTLS).
    #[serde(default)]
    pub client_ca: Option<String>,

    /// **Accept a gRPC connection that presents NO client certificate**, and let
    /// the application layer decide what it may reach.
    ///
    /// Default `false`, which is what every existing config has: with
    /// `client_ca` set, the gRPC handshake REQUIRES a client certificate and a
    /// certless client never reaches an RPC at all. That is mTLS *across the
    /// board*, and it is the right answer for an estate where every client is an
    /// agent or a CI runner.
    ///
    /// It is the wrong answer for the other posture: one upstream where `crates`
    /// demands a certificate and everything else stays open. A transport that
    /// refuses the handshake cannot express "this repository, not that one" —
    /// by the time a repository NAME exists, the connection is already gone. So
    /// the requirement moves up one layer: set this, mark the repositories that
    /// demand a credential with `ron_require_auth`, and the read doors refuse
    /// exactly those.
    ///
    /// **On its own it weakens nothing.** A certificate that IS presented is
    /// still verified against `client_ca` — rustls' `allow_unauthenticated`
    /// admits an ABSENT certificate, never an untrusted one. What moves is only
    /// WHERE the refusal happens, and it still happens: a repository with
    /// `ron_require_auth` and no identity is `Unauthenticated`, and a server
    /// that declares a gated repository with no auth method at all refuses to
    /// boot rather than serving a repository nobody can ever reach.
    ///
    /// Setting it WITHOUT a `client_ca` is meaningless (there is no client
    /// verification to make optional) and holger says so at boot.
    #[serde(default)]
    pub client_auth_optional: bool,

    /// **The client certificates' revocation lists** (lane HOL-NCSC, NCSC P3): a PEM or DER
    /// CRL file, or a directory of them (`*.crl`, `*.pem`, `*.der`). With it, every client
    /// certificate a door verifies against `client_ca` is also checked against these CRLs, the
    /// whole chain: a revoked one fails the handshake (and leaves an audit row,
    /// `refusals::handshake_refused`). Each CRL must be issued and signed by a certificate in
    /// `client_ca` and carry a nextUpdate that has not passed, or the start is refused by name
    /// (`holger-tls-1`); a CRL that expires while the door runs refuses every client
    /// certificate until a fresh one is in place. The file (or directory) is read again at the
    /// next handshake after it changes: no restart. `security_profile: Critical` requires it
    /// wherever `client_ca` is set.
    #[serde(default)]
    pub crl: Option<String>,
}

impl TlsSettings {
    pub fn mtls_enabled(&self) -> bool {
        self.client_ca.is_some()
    }

    fn load_certs(path: &str) -> Result<Vec<CertificateDer<'static>>> {
        let pem = fs::read(path).with_context(|| format!("reading cert {path}"))?;
        let certs = CertificateDer::pem_slice_iter(&pem)
            .collect::<std::result::Result<Vec<_>, _>>()
            .with_context(|| format!("parsing certs in {path}"))?;
        if certs.is_empty() {
            anyhow::bail!("no certificates found in {path}");
        }
        Ok(certs)
    }

    fn load_key(path: &str) -> Result<PrivateKeyDer<'static>> {
        let pem = crate::keyfile::read_private(std::path::Path::new(path), "the TLS key")
            .with_context(|| format!("reading key {path}"))?;
        match PrivateKeyDer::from_pem_slice(&pem) {
            Ok(key) => Ok(key),
            Err(rustls::pki_types::pem::Error::NoItemsFound) => {
                Err(anyhow::anyhow!("no private key found in {path}"))
            }
            Err(e) => Err(e).with_context(|| format!("parsing key {path}")),
        }
    }

    fn provider() -> Arc<rustls::crypto::CryptoProvider> {
        Arc::new(door_provider())
    }

    /// **The client verifier of a door with `client_ca`**: webpki's, and with [`Self::crl`]
    /// one that checks revocation and follows the CRL file ([`CrlVerifier`]).
    fn client_verifier(
        &self,
        ca_path: &str,
        allow_unauthenticated: bool,
    ) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>> {
        match &self.crl {
            None => {
                let mut roots = rustls::RootCertStore::empty();
                for c in Self::load_certs(ca_path)? {
                    roots.add(c).context("adding client CA")?;
                }
                let b = rustls::server::WebPkiClientVerifier::builder_with_provider(
                    Arc::new(roots),
                    Self::provider(),
                );
                let b = if allow_unauthenticated {
                    b.allow_unauthenticated()
                } else {
                    b
                };
                b.build().context("building client verifier")
            }
            Some(crl) => Ok(Arc::new(CrlVerifier::new(
                ca_path,
                crl,
                allow_unauthenticated,
            )?)),
        }
    }
}

/// **Read and judge the CRLs at `path`** against the client CA certificates `cas`, at
/// `now` (Unix seconds). Every CRL must parse, be issued by a certificate in `cas` (its
/// subject is the CRL's issuer) and signed by its key, and carry a nextUpdate after `now`.
/// Any refusal names the file and is `holger-tls-1`.
pub fn load_crls(
    path: &str,
    cas: &[CertificateDer<'static>],
    now: i64,
) -> Result<Vec<rustls::pki_types::CertificateRevocationListDer<'static>>> {
    use rustls::pki_types::CertificateRevocationListDer;
    let refuse = |why: String| anyhow::anyhow!(holger_errcode::TLS_CRL_REFUSED.tag(why));
    let p = std::path::Path::new(path);
    let files: Vec<std::path::PathBuf> = if p.is_dir() {
        let mut f: Vec<_> = fs::read_dir(p)
            .map_err(|e| refuse(format!("the CRL directory {path} cannot be read: {e}")))?
            .flatten()
            .map(|e| e.path())
            .filter(|f| {
                f.is_file()
                    && f.extension()
                        .is_some_and(|x| x == "crl" || x == "pem" || x == "der")
            })
            .collect();
        f.sort();
        f
    } else {
        vec![p.to_path_buf()]
    };
    if files.is_empty() {
        return Err(refuse(format!(
            "the CRL directory {path} holds no *.crl, *.pem or *.der file"
        )));
    }
    let parsed_cas: Vec<x509_parser::certificate::X509Certificate<'_>> = cas
        .iter()
        .filter_map(|c| {
            x509_parser::parse_x509_certificate(c.as_ref())
                .ok()
                .map(|(_, c)| c)
        })
        .collect();
    let mut out = Vec::new();
    for f in files {
        let shown = f.display().to_string();
        let bytes =
            fs::read(&f).map_err(|e| refuse(format!("the CRL {shown} cannot be read: {e}")))?;
        let ders: Vec<CertificateRevocationListDer<'static>> =
            if bytes.windows(15).any(|w| w == b"-----BEGIN X509") {
                CertificateRevocationListDer::pem_slice_iter(&bytes)
                    .collect::<std::result::Result<Vec<_>, _>>()
                    .map_err(|e| refuse(format!("the CRL {shown} is not a CRL: {e}")))?
            } else {
                vec![CertificateRevocationListDer::from(bytes)]
            };
        if ders.is_empty() {
            return Err(refuse(format!(
                "the CRL {shown} is not a CRL: it holds no X509 CRL"
            )));
        }
        for der in ders {
            let (_, crl) = x509_parser::parse_x509_crl(der.as_ref())
                .map_err(|e| refuse(format!("the CRL {shown} is not a CRL: {e}")))?;
            let issuer = crl.issuer().to_string();
            let ca = parsed_cas
                .iter()
                .find(|c| c.subject().as_raw() == crl.issuer().as_raw())
                .ok_or_else(|| refuse(format!("the CRL {shown} is issued by {issuer:?}, which is not the client CA (`client_ca`)")))?;
            crl.verify_signature(ca.public_key()).map_err(|e| {
                refuse(format!(
                    "the CRL {shown} is not signed by the client CA {issuer:?}: {e}"
                ))
            })?;
            match crl.next_update() {
                None => {
                    return Err(refuse(format!(
                        "the CRL {shown} carries no nextUpdate, so it never says it is stale"
                    )))
                }
                Some(t) if t.timestamp() <= now => {
                    return Err(refuse(format!(
                    "the CRL {shown} expired: its nextUpdate {t} has passed; publish a fresh one"
                )))
                }
                Some(_) => {}
            }
            out.push(der);
        }
    }
    Ok(out)
}

/// **A client verifier that checks revocation and follows its CRL file** (lane HOL-NCSC).
/// It builds webpki's verifier with the CRLs ([`load_crls`], whole chain, nextUpdate
/// enforced) at the start, and again at the first handshake after the file (or directory)
/// changed. A CRL that cannot be judged at a reload refuses every client certificate (fail
/// closed) until a good one is in place, and says why in the log.
#[derive(Debug)]
struct CrlVerifier {
    ca_path: String,
    crl_path: String,
    allow_unauthenticated: bool,
    hints: Vec<rustls::DistinguishedName>,
    current: std::sync::RwLock<(String, Built)>,
}

/// A verifier built from the CRL, or why the CRL was refused.
type Built = std::result::Result<Arc<dyn rustls::server::danger::ClientCertVerifier>, String>;

impl CrlVerifier {
    fn new(ca_path: &str, crl_path: &str, allow_unauthenticated: bool) -> Result<Self> {
        let stamp = Self::stamp(crl_path);
        let (inner, hints) = Self::build(ca_path, crl_path, allow_unauthenticated)?;
        Ok(Self {
            ca_path: ca_path.into(),
            crl_path: crl_path.into(),
            allow_unauthenticated,
            hints,
            current: std::sync::RwLock::new((stamp, Ok(inner))),
        })
    }

    fn build(
        ca_path: &str,
        crl_path: &str,
        allow_unauthenticated: bool,
    ) -> Result<(
        Arc<dyn rustls::server::danger::ClientCertVerifier>,
        Vec<rustls::DistinguishedName>,
    )> {
        let cas = TlsSettings::load_certs(ca_path)?;
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map(|d| d.as_secs() as i64)
            .unwrap_or_default();
        let crls = load_crls(crl_path, &cas, now)?;
        let mut roots = rustls::RootCertStore::empty();
        for c in cas {
            roots.add(c).context("adding client CA")?;
        }
        let hints = roots.subjects();
        let b = rustls::server::WebPkiClientVerifier::builder_with_provider(
            Arc::new(roots),
            TlsSettings::provider(),
        )
        .with_crls(crls)
        .enforce_revocation_expiration();
        let b = if allow_unauthenticated {
            b.allow_unauthenticated()
        } else {
            b
        };
        Ok((b.build().context("building client verifier")?, hints))
    }

    /// What says the CRL changed: each file's name, length and modification time.
    fn stamp(path: &str) -> String {
        let one = |p: &std::path::Path| {
            fs::metadata(p)
                .map(|m| format!("{}:{}:{:?};", p.display(), m.len(), m.modified().ok()))
                .unwrap_or_default()
        };
        let p = std::path::Path::new(path);
        if p.is_dir() {
            let mut all: Vec<String> = fs::read_dir(p)
                .into_iter()
                .flatten()
                .flatten()
                .map(|e| one(&e.path()))
                .collect();
            all.sort();
            all.concat()
        } else {
            one(p)
        }
    }

    fn inner(
        &self,
    ) -> std::result::Result<Arc<dyn rustls::server::danger::ClientCertVerifier>, rustls::Error>
    {
        use crate::lock::RwLockOrRecover as _;
        let stamp = Self::stamp(&self.crl_path);
        {
            let cur = self.current.read_or_recover();
            if cur.0 == stamp {
                return cur.1.clone().map_err(rustls::Error::General);
            }
        }
        let built = match Self::build(&self.ca_path, &self.crl_path, self.allow_unauthenticated) {
            Ok((v, _)) => {
                log::info!(
                    "TLS: the client certificates' CRL {} was read again",
                    self.crl_path
                );
                Ok(v)
            }
            Err(e) => {
                let why = format!("{e:#}");
                log::error!("TLS: the client certificates' CRL {} changed and is refused, so every client certificate is refused until it is fixed: {why}", self.crl_path);
                Err(why)
            }
        };
        *self.current.write_or_recover() = (stamp, built.clone());
        built.map_err(rustls::Error::General)
    }
}

impl rustls::server::danger::ClientCertVerifier for CrlVerifier {
    fn client_auth_mandatory(&self) -> bool {
        !self.allow_unauthenticated
    }

    fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] {
        &self.hints
    }

    fn verify_client_cert(
        &self,
        end_entity: &CertificateDer<'_>,
        intermediates: &[CertificateDer<'_>],
        now: rustls::pki_types::UnixTime,
    ) -> std::result::Result<rustls::server::danger::ClientCertVerified, rustls::Error> {
        self.inner()?
            .verify_client_cert(end_entity, intermediates, now)
    }

    fn verify_tls12_signature(
        &self,
        message: &[u8],
        cert: &CertificateDer<'_>,
        dss: &rustls::DigitallySignedStruct,
    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
        self.inner()?.verify_tls12_signature(message, cert, dss)
    }

    fn verify_tls13_signature(
        &self,
        message: &[u8],
        cert: &CertificateDer<'_>,
        dss: &rustls::DigitallySignedStruct,
    ) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
        self.inner()?.verify_tls13_signature(message, cert, dss)
    }

    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
        TlsSettings::provider()
            .signature_verification_algorithms
            .supported_schemes()
    }
}

/// **The provider every door of this process is served with, and the process default**
/// (lane WG-PSK, 2026-10-30): rustls's ring provider with X25519MLKEM768 offered FIRST and the
/// classical groups after it (Skidbladnir `nordisk_pairing::pqtls`, Gunnar's front's own). A
/// recording of a handshake cannot be opened later by a quantum computer; a client that does
/// not offer the hybrid group still connects on X25519.
#[cfg(feature = "pq-tls")]
pub fn door_provider() -> rustls::crypto::CryptoProvider {
    nordisk_pairing::pqtls::provider()
}

/// [`door_provider`] of a slim shape (no `pq-tls`): ring's classical groups.
#[cfg(not(feature = "pq-tls"))]
pub fn door_provider() -> rustls::crypto::CryptoProvider {
    rustls::crypto::ring::default_provider()
}

/// Install [`door_provider`] (ring, X25519MLKEM768 first) as the process default, once.
///
/// Both `ring` and `aws-lc-rs` end up in the dependency tree (via reqwest), so
/// rustls cannot pick a default on its own and panics on first use. Pinning ring
/// makes every rustls consumer in-process (our servers, reqwest) deterministic.
pub fn ensure_crypto_provider() {
    use std::sync::Once;
    static ONCE: Once = Once::new();
    ONCE.call_once(|| {
        let _ = door_provider().install_default();
    });
}

impl TlsSettings {
    /// Build a rustls `ServerConfig` for the HTTP/OCI gateway — TLS 1.3 only.
    /// Client certs are requested but NOT required when `client_ca` is set (the
    /// public browser door): the app layer enforces identity on writes.
    pub fn rustls_gateway_config(&self) -> Result<Arc<ServerConfig>> {
        let certs = Self::load_certs(&self.cert)?;
        let key = Self::load_key(&self.key)?;

        // TLS 1.3 ONLY (stinky SB #39): every browser and package client of
        // today speaks it, and 1.2's legacy suites are the downgrade surface.
        let builder = ServerConfig::builder_with_provider(Self::provider())
            .with_protocol_versions(&[&rustls::version::TLS13])
            .context("rustls protocol versions")?;

        let mut config = match &self.client_ca {
            Some(ca_path) => {
                let verifier = self.client_verifier(ca_path, true)?;
                builder
                    .with_client_cert_verifier(verifier)
                    .with_single_cert(certs, key)
                    .context("loading server identity")?
            }
            None => builder
                .with_no_client_auth()
                .with_single_cert(certs, key)
                .context("loading server identity")?,
        };

        config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
        Ok(Arc::new(config))
    }

    /// **The gRPC door's rustls config — TLS 1.3 only** (stinky SB #39). tonic's
    /// own `ServerTlsConfig` offers TLS 1.2 too and has no knob for it, so the
    /// door terminates TLS on THIS config ([`serve_grpc_tls13`]). With
    /// `client_ca` — the box-to-box posture (a paired downstream, an agent) — a
    /// client certificate chained to it is REQUIRED in the handshake, unless the
    /// operator declared [`TlsSettings::client_auth_optional`] (then a presented
    /// one is still verified). ALPN: `h2` for gRPC, `http/1.1` for gRPC-web.
    pub fn rustls_grpc_config(&self) -> Result<Arc<ServerConfig>> {
        let certs = Self::load_certs(&self.cert)?;
        let key = Self::load_key(&self.key)?;
        let builder = ServerConfig::builder_with_provider(Self::provider())
            .with_protocol_versions(&[&rustls::version::TLS13])
            .context("rustls protocol versions")?;
        let mut config = match &self.client_ca {
            Some(ca_path) => {
                let verifier = self.client_verifier(ca_path, self.client_auth_optional)?;
                builder
                    .with_client_cert_verifier(verifier)
                    .with_single_cert(certs, key)
                    .context("loading server identity")?
            }
            None => builder
                .with_no_client_auth()
                .with_single_cert(certs, key)
                .context("loading server identity")?,
        };
        config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
        Ok(Arc::new(config))
    }

    /// Build a tonic `ServerTlsConfig` for the gRPC server. When `client_ca` is
    /// set, mTLS is required.
    pub fn tonic_config(&self) -> Result<tonic::transport::ServerTlsConfig> {
        use tonic::transport::{Certificate, Identity, ServerTlsConfig};

        let cert_pem =
            fs::read(&self.cert).with_context(|| format!("reading cert {}", self.cert))?;
        let key_pem = crate::keyfile::read_private(std::path::Path::new(&self.key), "the TLS key")
            .with_context(|| format!("reading key {}", self.key))?;
        let mut tls = ServerTlsConfig::new().identity(Identity::from_pem(cert_pem, key_pem));
        if let Some(crl) = &self.crl {
            anyhow::bail!(holger_errcode::TLS_CRL_REFUSED.tag(format!("tonic's own TLS config cannot check the CRL {crl}; this door serves on `rustls_grpc_config`")));
        }
        if let Some(ca) = &self.client_ca {
            let ca_pem = fs::read(ca).with_context(|| format!("reading client CA {ca}"))?;
            tls = tls.client_ca_root(Certificate::from_pem(ca_pem));
            // The ONE place the per-repository posture differs from the
            // across-the-board one. See [`TlsSettings::client_auth_optional`]: a
            // presented certificate is still verified against `client_ca`; only
            // its ABSENCE becomes the application layer's problem instead of the
            // handshake's.
            if self.client_auth_optional {
                tls = tls.client_auth_optional(true);
            }
        }
        Ok(tls)
    }
}

/// How long one client may take over its TLS handshake before the gRPC door
/// drops it — a slow client must not hold a connection slot open for ever.
pub const GRPC_HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);

/// **Serve gRPC (and gRPC-web) over TLS 1.3 only** (stinky SB #39) — the TLS
/// half of `nornir_service::serve_with_tls_web`, on holger's own rustls config
/// ([`TlsSettings::rustls_grpc_config`]) because tonic's offers TLS 1.2. Every
/// handshake runs on a task of its own, bounded by [`GRPC_HANDSHAKE_TIMEOUT`],
/// so a slow or refused client never stalls the accept loop; the TLS stream
/// carries its peer certificates to `Request::peer_certs` exactly as tonic's
/// own acceptor does.
pub async fn serve_grpc_tls13(
    routes: tonic::service::Routes,
    listener: tokio::net::TcpListener,
    config: Arc<ServerConfig>,
    audit: Option<Arc<dyn crate::audit::AuditLog>>,
) -> std::result::Result<(), tonic::transport::Error> {
    serve_grpc_tls13_allowing(routes, listener, config, audit, None).await
}

/// [`serve_grpc_tls13`] holding every peer to `allow` first (the admin door, HOL-NCSC).
async fn serve_grpc_tls13_allowing(
    routes: tonic::service::Routes,
    listener: tokio::net::TcpListener,
    config: Arc<ServerConfig>,
    audit: Option<Arc<dyn crate::audit::AuditLog>>,
    allow: Option<Arc<crate::listen::AllowList>>,
) -> std::result::Result<(), tonic::transport::Error> {
    let acceptor = tokio_rustls::TlsAcceptor::from(config);
    let (tx, rx) = tokio::sync::mpsc::channel::<
        std::result::Result<tokio_rustls::server::TlsStream<tokio::net::TcpStream>, std::io::Error>,
    >(64);
    // A32-14, stays a task: the gRPC TLS accept loop, for the listener's life.
    tokio::spawn(async move {
        loop {
            let (tcp, peer) = match listener.accept().await {
                // TCP_NODELAY (HOL-PERF-3): `serve_with_incoming` ignores tonic's own
                // `tcp_nodelay`, so the accept loop sets it.
                Ok(c) => {
                    let _ = c.0.set_nodelay(true);
                    c
                }
                Err(e) => {
                    log::warn!("gRPC door: accept failed: {e}");
                    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
                    continue;
                }
            };
            if let Some(a) = &allow {
                if !a.allows(peer.ip()) {
                    let row = crate::listen::AllowList::refusal(&peer);
                    match &audit {
                        Some(l) => crate::refusals::record_to(
                            l,
                            "admin listener",
                            "anonymous",
                            &peer.to_string(),
                            &row,
                        ),
                        None => crate::refusals::record(
                            "admin listener",
                            "anonymous",
                            &peer.to_string(),
                            &row,
                        ),
                    }
                    continue;
                }
            }
            let (acceptor, out, audit) = (acceptor.clone(), tx.clone(), audit.clone());
            // A32-14, stays a task: one TLS handshake per connection, bounded by its timeout.
            tokio::spawn(async move {
                match tokio::time::timeout(GRPC_HANDSHAKE_TIMEOUT, acceptor.accept(tcp)).await {
                    Ok(Ok(tls)) => {
                        let _ = out.send(Ok(tls)).await;
                    }
                    Ok(Err(e)) => {
                        log::info!("gRPC door: TLS handshake from {peer} refused: {e}");
                        crate::refusals::handshake_refused(
                            audit.as_ref(),
                            "gRPC door",
                            &peer.to_string(),
                            &e.to_string(),
                        );
                    }
                    Err(_) => {
                        log::info!("gRPC door: TLS handshake from {peer} timed out");
                        crate::refusals::handshake_refused(
                            audit.as_ref(),
                            "gRPC door",
                            &peer.to_string(),
                            "no handshake within the door's timeout",
                        );
                    }
                }
            });
            if tx.is_closed() {
                return;
            }
        }
    });
    tonic::transport::Server::builder()
        .accept_http1(true)
        .layer(tonic_web::GrpcWebLayer::new())
        .layer(crate::boundary::DomainLayer)
        .add_routes(routes)
        .serve_with_incoming(tokio_stream::wrappers::ReceiverStream::new(rx))
        .await
}

/// **The admin gRPC door** (lane HOL-NCSC): every accepted peer is held to `allow` (dropped
/// and audited, `holger-listen-4`, when outside it), then TLS 1.3 when `config` is set, as
/// [`serve_grpc_tls13`], or plain h2c with gRPC-web.
pub async fn serve_grpc_admin(
    routes: tonic::service::Routes,
    listener: tokio::net::TcpListener,
    config: Option<Arc<ServerConfig>>,
    allow: Arc<crate::listen::AllowList>,
    audit: Arc<dyn crate::audit::AuditLog>,
) -> std::result::Result<(), tonic::transport::Error> {
    if let Some(config) = config {
        return serve_grpc_tls13_allowing(routes, listener, config, Some(audit), Some(allow)).await;
    }
    let (tx, rx) = tokio::sync::mpsc::channel::<
        std::result::Result<tokio::net::TcpStream, std::io::Error>,
    >(64);
    // A32-14, stays a task: the admin door's accept loop, for the listener's life.
    tokio::spawn(async move {
        loop {
            let (tcp, peer) = match listener.accept().await {
                Ok(c) => c,
                Err(e) => {
                    log::warn!("admin gRPC door: accept failed: {e}");
                    tokio::time::sleep(std::time::Duration::from_millis(50)).await;
                    continue;
                }
            };
            if !allow.allows(peer.ip()) {
                crate::refusals::record_to(
                    &audit,
                    "admin listener",
                    "anonymous",
                    &peer.to_string(),
                    &crate::listen::AllowList::refusal(&peer),
                );
                continue;
            }
            let _ = tcp.set_nodelay(true);
            if tx.send(Ok(tcp)).await.is_err() {
                return;
            }
        }
    });
    tonic::transport::Server::builder()
        .accept_http1(true)
        .layer(tonic_web::GrpcWebLayer::new())
        .layer(crate::boundary::DomainLayer)
        .add_routes(routes)
        .serve_with_incoming(tokio_stream::wrappers::ReceiverStream::new(rx))
        .await
}

/// Extract the subject Common Name from the leaf of a DER client-cert chain.
/// Used to map an mTLS peer to an identity for [`crate::auth::validate_request`].
pub fn leaf_common_name(chain: &[CertificateDer<'_>]) -> Option<String> {
    let leaf = chain.first()?;
    let (_, cert) = x509_parser::parse_x509_certificate(leaf.as_ref()).ok()?;
    let mut cn = None;
    for attr in cert.subject().iter_common_name() {
        if let Ok(s) = attr.as_str() {
            cn = Some(s.to_string());
            break;
        }
    }
    cn
}

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

    /// One CA, a server leaf for `localhost` and a client leaf, as PEM files in `dir`.
    fn material(
        dir: &std::path::Path,
    ) -> (
        TlsSettings,
        rustls::RootCertStore,
        (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
    ) {
        let ca_key = rcgen::KeyPair::generate().unwrap();
        let mut ca_params = rcgen::CertificateParams::new(vec![]).unwrap();
        ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
        let ca = ca_params.self_signed(&ca_key).unwrap();
        let issuer = rcgen::Issuer::new(ca_params, ca_key);
        let leaf = |names: Vec<String>, cn: &str| {
            let k = rcgen::KeyPair::generate().unwrap();
            let mut p = rcgen::CertificateParams::new(names).unwrap();
            p.distinguished_name.push(rcgen::DnType::CommonName, cn);
            (p.signed_by(&k, &issuer).unwrap(), k)
        };
        let (server, server_key) = leaf(vec!["localhost".into()], "localhost");
        let (client, client_key) = leaf(vec![], "box-b");
        std::fs::write(dir.join("server.pem"), server.pem()).unwrap();
        std::fs::write(dir.join("server.key"), server_key.serialize_pem()).unwrap();
        std::fs::set_permissions(
            dir.join("server.key"),
            <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o600),
        )
        .unwrap();
        std::fs::write(dir.join("ca.pem"), ca.pem()).unwrap();
        let mut roots = rustls::RootCertStore::empty();
        roots.add(ca.der().clone()).unwrap();
        let settings = TlsSettings {
            cert: dir.join("server.pem").display().to_string(),
            key: dir.join("server.key").display().to_string(),
            client_ca: Some(dir.join("ca.pem").display().to_string()),
            client_auth_optional: false,
            crl: None,
        };
        let client_key = PrivateKeyDer::try_from(client_key.serialize_der()).unwrap();
        (settings, roots, (vec![client.der().clone()], client_key))
    }

    /// An in-memory handshake; `Ok(version)` or the error that ended it.
    fn handshake(
        server: Arc<ServerConfig>,
        client: rustls::ClientConfig,
    ) -> std::result::Result<rustls::ProtocolVersion, String> {
        let mut c =
            rustls::ClientConnection::new(Arc::new(client), "localhost".try_into().unwrap())
                .map_err(|e| e.to_string())?;
        let mut s = rustls::ServerConnection::new(server).map_err(|e| e.to_string())?;
        for _ in 0..20 {
            let mut buf = Vec::new();
            while c.wants_write() {
                c.write_tls(&mut buf).map_err(|e| e.to_string())?;
            }
            if !buf.is_empty() {
                s.read_tls(&mut &buf[..]).map_err(|e| e.to_string())?;
                s.process_new_packets().map_err(|e| e.to_string())?;
            }
            let mut back = Vec::new();
            while s.wants_write() {
                s.write_tls(&mut back).map_err(|e| e.to_string())?;
            }
            if !back.is_empty() {
                c.read_tls(&mut &back[..]).map_err(|e| e.to_string())?;
                c.process_new_packets().map_err(|e| e.to_string())?;
            }
            if !c.is_handshaking() && !s.is_handshaking() {
                return c.protocol_version().ok_or_else(|| "no version".into());
            }
        }
        Err("the handshake never finished".into())
    }

    fn client(
        versions: &[&'static rustls::SupportedProtocolVersion],
        roots: &rustls::RootCertStore,
        cert: Option<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>,
    ) -> rustls::ClientConfig {
        let b = rustls::ClientConfig::builder_with_provider(Arc::new(
            rustls::crypto::ring::default_provider(),
        ))
        .with_protocol_versions(versions)
        .unwrap()
        .with_root_certificates(roots.clone());
        match cert {
            Some((c, k)) => b.with_client_auth_cert(c, k).unwrap(),
            None => b.with_no_client_auth(),
        }
    }

    /// ★ **TLS 1.3 only, on every door** (stinky SB #39): the public HTTP door
    /// and the gRPC door refuse a client that speaks TLS 1.2 at most. The PUBLIC
    /// (browser) door still takes a client with no certificate; the gRPC door
    /// with a `client_ca` — the box-to-box posture — REQUIRES one, unless the
    /// operator declared `client_auth_optional`.
    #[test]
    fn every_door_speaks_tls_1_3_only_and_box_doors_require_a_client_certificate() {
        let dir = tempfile::tempdir().unwrap();
        let (settings, roots, client_id) = material(dir.path());
        let v12: &[&'static rustls::SupportedProtocolVersion] = &[&rustls::version::TLS12];
        let v13: &[&'static rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13];

        let web = settings.rustls_gateway_config().unwrap();
        assert!(
            handshake(web.clone(), client(v12, &roots, None)).is_err(),
            "the public door refuses TLS 1.2"
        );
        assert_eq!(
            handshake(web, client(v13, &roots, None)),
            Ok(rustls::ProtocolVersion::TLSv1_3),
            "a browser with no certificate is served over TLS 1.3"
        );

        let grpc = settings.rustls_grpc_config().unwrap();
        assert!(
            handshake(
                grpc.clone(),
                client(v12, &roots, Some(client_id.clone_key()))
            )
            .is_err(),
            "the gRPC door refuses TLS 1.2"
        );
        assert!(
            handshake(grpc.clone(), client(v13, &roots, None)).is_err(),
            "a box door with a client CA requires a client certificate"
        );
        assert_eq!(
            handshake(grpc, client(v13, &roots, Some(client_id.clone_key()))),
            Ok(rustls::ProtocolVersion::TLSv1_3)
        );
        assert_eq!(
            settings.rustls_grpc_config().unwrap().alpn_protocols,
            vec![b"h2".to_vec(), b"http/1.1".to_vec()],
            "h2 for gRPC, http/1.1 for gRPC-web"
        );

        let optional = TlsSettings {
            client_auth_optional: true,
            ..settings.clone()
        };
        assert_eq!(
            handshake(
                optional.rustls_grpc_config().unwrap(),
                client(v13, &roots, None)
            ),
            Ok(rustls::ProtocolVersion::TLSv1_3),
            "client_auth_optional admits an absent certificate"
        );
        let plain = TlsSettings {
            client_ca: None,
            ..settings
        };
        assert_eq!(
            handshake(
                plain.rustls_grpc_config().unwrap(),
                client(v13, &roots, None)
            ),
            Ok(rustls::ProtocolVersion::TLSv1_3)
        );
    }

    /// ★ **Every door offers X25519MLKEM768 FIRST, X25519 after it** (lane WG-PSK, 2026-10-30:
    /// holger.rs negotiated plain X25519 while gunnar.rs negotiates the hybrid). The HTTP/OCI
    /// door and the gRPC door take Skidbladnir's post-quantum provider (`nordisk_pairing::pqtls`,
    /// Gunnar's own): a client offering only the hybrid group is served with it, and a classical
    /// client still connects on X25519 (prefer, never require).
    #[cfg(feature = "pq-tls")]
    #[test]
    fn every_door_offers_x25519mlkem768_first_and_a_classical_client_still_connects() {
        let dir = tempfile::tempdir().unwrap();
        let (settings, roots, client_id) = material(dir.path());
        let v13: &[&'static rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13];
        let hybrid_only = || {
            let pq = nordisk_pairing::pqtls::provider();
            let provider = rustls::crypto::CryptoProvider {
                kx_groups: vec![pq.kx_groups[0]],
                ..pq
            };
            rustls::ClientConfig::builder_with_provider(Arc::new(provider))
                .with_protocol_versions(v13)
                .unwrap()
                .with_root_certificates(roots.clone())
        };
        let doors = [
            (
                "the HTTP/OCI door",
                settings.rustls_gateway_config().unwrap(),
            ),
            ("the gRPC door", settings.rustls_grpc_config().unwrap()),
        ];
        for (door, cfg) in doors {
            let names: Vec<rustls::NamedGroup> = cfg
                .crypto_provider()
                .kx_groups
                .iter()
                .map(|g| g.name())
                .collect();
            assert_eq!(
                names.get(..2),
                Some(
                    &[
                        rustls::NamedGroup::X25519MLKEM768,
                        rustls::NamedGroup::X25519
                    ][..]
                ),
                "{door}: {names:?}"
            );
            let pq = hybrid_only()
                .with_client_auth_cert(client_id.0.clone(), client_id.1.clone_key())
                .unwrap();
            assert_eq!(
                handshake_group(cfg.clone(), pq),
                Ok(rustls::NamedGroup::X25519MLKEM768),
                "{door}: a client offering only the hybrid group"
            );
            let classical = client(v13, &roots, Some(client_id.clone_key()));
            assert_eq!(
                handshake_group(cfg, classical),
                Ok(rustls::NamedGroup::X25519),
                "{door}: a classical client still connects"
            );
        }
        assert_eq!(
            door_provider().kx_groups.first().map(|g| g.name()),
            Some(rustls::NamedGroup::X25519MLKEM768),
            "the process default (tonic's door) is the same provider"
        );
    }

    /// [`handshake`], answering the key exchange group the two ends agreed on.
    #[cfg(feature = "pq-tls")]
    fn handshake_group(
        server: Arc<ServerConfig>,
        client: rustls::ClientConfig,
    ) -> std::result::Result<rustls::NamedGroup, String> {
        let mut c =
            rustls::ClientConnection::new(Arc::new(client), "localhost".try_into().unwrap())
                .map_err(|e| e.to_string())?;
        let mut s = rustls::ServerConnection::new(server).map_err(|e| e.to_string())?;
        for _ in 0..20 {
            let mut buf = Vec::new();
            while c.wants_write() {
                c.write_tls(&mut buf).map_err(|e| e.to_string())?;
            }
            if !buf.is_empty() {
                s.read_tls(&mut &buf[..]).map_err(|e| e.to_string())?;
                s.process_new_packets().map_err(|e| e.to_string())?;
            }
            let mut back = Vec::new();
            while s.wants_write() {
                s.write_tls(&mut back).map_err(|e| e.to_string())?;
            }
            if !back.is_empty() {
                c.read_tls(&mut &back[..]).map_err(|e| e.to_string())?;
                c.process_new_packets().map_err(|e| e.to_string())?;
            }
            if !c.is_handshaking() && !s.is_handshaking() {
                return c
                    .negotiated_key_exchange_group()
                    .map(|g| g.name())
                    .ok_or_else(|| "no group".into());
            }
        }
        Err("the handshake never finished".into())
    }

    /// A CA that may sign CRLs, two client leaves (serials 7 and 8) and the server leaf, as
    /// PEM files in `dir`; the settings name `crl.pem` there.
    struct CrlPki {
        settings: TlsSettings,
        roots: rustls::RootCertStore,
        issuer: rcgen::Issuer<'static, rcgen::KeyPair>,
        revoked: (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
        good: (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
    }

    fn crl_pki(dir: &std::path::Path) -> CrlPki {
        let ca_key = rcgen::KeyPair::generate().unwrap();
        let mut ca_params = rcgen::CertificateParams::new(vec![]).unwrap();
        ca_params
            .distinguished_name
            .push(rcgen::DnType::CommonName, "holger client CA");
        ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
        ca_params.key_usages = vec![
            rcgen::KeyUsagePurpose::KeyCertSign,
            rcgen::KeyUsagePurpose::CrlSign,
            rcgen::KeyUsagePurpose::DigitalSignature,
        ];
        let ca = ca_params.self_signed(&ca_key).unwrap();
        let issuer = rcgen::Issuer::new(ca_params, ca_key);
        let leaf = |names: Vec<String>, cn: &str, serial: u64| {
            let k = rcgen::KeyPair::generate().unwrap();
            let mut p = rcgen::CertificateParams::new(names).unwrap();
            p.distinguished_name.push(rcgen::DnType::CommonName, cn);
            p.serial_number = Some(rcgen::SerialNumber::from(serial));
            (p.signed_by(&k, &issuer).unwrap(), k)
        };
        let (server, server_key) = leaf(vec!["localhost".into()], "localhost", 1);
        let id = |(c, k): (rcgen::Certificate, rcgen::KeyPair)| {
            (
                vec![c.der().clone()],
                PrivateKeyDer::try_from(k.serialize_der()).unwrap(),
            )
        };
        let revoked = id(leaf(vec![], "box-b", 7));
        let good = id(leaf(vec![], "box-c", 8));
        std::fs::write(dir.join("server.pem"), server.pem()).unwrap();
        std::fs::write(dir.join("server.key"), server_key.serialize_pem()).unwrap();
        std::fs::set_permissions(
            dir.join("server.key"),
            <std::fs::Permissions as std::os::unix::fs::PermissionsExt>::from_mode(0o600),
        )
        .unwrap();
        std::fs::write(dir.join("ca.pem"), ca.pem()).unwrap();
        let mut roots = rustls::RootCertStore::empty();
        roots.add(ca.der().clone()).unwrap();
        let settings = TlsSettings {
            cert: dir.join("server.pem").display().to_string(),
            key: dir.join("server.key").display().to_string(),
            client_ca: Some(dir.join("ca.pem").display().to_string()),
            client_auth_optional: false,
            crl: Some(dir.join("crl.pem").display().to_string()),
        };
        CrlPki {
            settings,
            roots,
            issuer,
            revoked,
            good,
        }
    }

    /// A CRL by `issuer` revoking `serials`, valid until 1 January of `until`, at `path`.
    fn write_crl(
        issuer: &rcgen::Issuer<'static, rcgen::KeyPair>,
        path: &std::path::Path,
        serials: &[u64],
        until: i32,
    ) {
        let params = rcgen::CertificateRevocationListParams {
            this_update: rcgen::date_time_ymd(2020, 1, 1),
            next_update: rcgen::date_time_ymd(until, 1, 1),
            crl_number: rcgen::SerialNumber::from(u64::try_from(until).unwrap()),
            issuing_distribution_point: None,
            revoked_certs: serials
                .iter()
                .map(|s| rcgen::RevokedCertParams {
                    serial_number: rcgen::SerialNumber::from(*s),
                    revocation_time: rcgen::date_time_ymd(2021, 1, 1),
                    reason_code: Some(rcgen::RevocationReason::KeyCompromise),
                    invalidity_date: None,
                })
                .collect(),
            key_identifier_method: rcgen::KeyIdMethod::Sha256,
        };
        std::fs::write(path, params.signed_by(issuer).unwrap().pem().unwrap()).unwrap();
    }

    /// ★ HOL-NCSC (NCSC P3): **a revoked client certificate fails the handshake** at the gRPC
    /// door and the HTTP door (where a client certificate is optional, a revoked one is still
    /// refused); a certificate the CRL does not name is served.
    #[test]
    fn a_revoked_client_certificate_fails_the_handshake_at_every_door() {
        let dir = tempfile::tempdir().unwrap();
        let p = crl_pki(dir.path());
        write_crl(&p.issuer, &dir.path().join("crl.pem"), &[7], 2099);
        let v13: &[&'static rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13];
        for (door, cfg) in [
            ("the gRPC door", p.settings.rustls_grpc_config().unwrap()),
            ("the HTTP door", p.settings.rustls_gateway_config().unwrap()),
        ] {
            let e = handshake(
                cfg.clone(),
                client(v13, &p.roots, Some(p.revoked.clone_key())),
            )
            .unwrap_err();
            assert!(e.to_lowercase().contains("revoked"), "{door}: {e}");
            assert_eq!(
                handshake(cfg, client(v13, &p.roots, Some(p.good.clone_key()))),
                Ok(rustls::ProtocolVersion::TLSv1_3),
                "{door}"
            );
        }
    }

    /// ★ **A CRL that cannot be trusted is refused at the start, by name** (`holger-tls-1`):
    /// a missing file, one that is not a CRL, an expired one (its nextUpdate passed) and one
    /// the client CA did not sign. Fail-closed: no door starts on it.
    #[test]
    fn a_crl_that_cannot_be_trusted_refuses_the_start() {
        let dir = tempfile::tempdir().unwrap();
        let p = crl_pki(dir.path());
        let refused = |why: &str| {
            for e in [
                p.settings.rustls_grpc_config().err(),
                p.settings.rustls_gateway_config().err(),
            ] {
                let e = format!("{:#}", e.expect("refused"));
                assert!(
                    e.contains("[holger-tls-1]") && e.contains(why),
                    "{why}: {e}"
                );
            }
        };
        refused("crl.pem");
        std::fs::write(dir.path().join("crl.pem"), b"not a CRL").unwrap();
        refused("not a CRL");
        write_crl(&p.issuer, &dir.path().join("crl.pem"), &[7], 2021);
        refused("expired");
        let other = tempfile::tempdir().unwrap();
        let q = crl_pki(other.path());
        write_crl(&q.issuer, &dir.path().join("crl.pem"), &[7], 2099);
        refused("client CA");
    }

    /// ★ **A new CRL is read at the next handshake**, no restart: the door built before the
    /// revocation refuses the certificate once the file names it.
    #[test]
    fn a_new_crl_is_read_at_the_next_handshake() {
        let dir = tempfile::tempdir().unwrap();
        let p = crl_pki(dir.path());
        write_crl(&p.issuer, &dir.path().join("crl.pem"), &[], 2098);
        let v13: &[&'static rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13];
        let grpc = p.settings.rustls_grpc_config().unwrap();
        assert_eq!(
            handshake(
                grpc.clone(),
                client(v13, &p.roots, Some(p.revoked.clone_key()))
            ),
            Ok(rustls::ProtocolVersion::TLSv1_3),
            "not revoked yet"
        );
        write_crl(&p.issuer, &dir.path().join("crl.pem"), &[7], 2099);
        let e = handshake(grpc, client(v13, &p.roots, Some(p.revoked.clone_key()))).unwrap_err();
        assert!(e.to_lowercase().contains("revoked"), "{e}");
    }

    trait CloneKey {
        fn clone_key(&self) -> (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>);
    }
    impl CloneKey for (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>) {
        fn clone_key(&self) -> (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>) {
            (self.0.clone(), self.1.clone_key())
        }
    }

    #[cfg(feature = "testmatrix")]
    use traits::test_support::fstatus;

    #[test]
    fn extracts_common_name_from_cert() {
        let mut params = rcgen::CertificateParams::new(vec![]).unwrap();
        params
            .distinguished_name
            .push(rcgen::DnType::CommonName, "writer-bot");
        let kp = rcgen::KeyPair::generate().unwrap();
        let cert = params.self_signed(&kp).unwrap();
        let der = cert.der().clone();
        let cn = leaf_common_name(&[der]);
        assert_eq!(cn.as_deref(), Some("writer-bot"));

        #[cfg(feature = "testmatrix")]
        fstatus(
            "tls",
            "extracts_common_name_from_cert",
            cn.as_deref() == Some("writer-bot"),
            &format!("leaf CN = {cn:?}"),
        );
    }

    #[test]
    fn empty_chain_yields_no_cn() {
        let cn = leaf_common_name(&[]);
        assert_eq!(cn, None);

        #[cfg(feature = "testmatrix")]
        fstatus(
            "tls",
            "empty_chain_yields_no_cn",
            cn.is_none(),
            &format!("empty cert chain -> CN {cn:?}"),
        );
    }

    /// A self-signed leaf carrying `cn`, as DER.
    fn cert_with_cn(cn: &str) -> CertificateDer<'static> {
        let mut params = rcgen::CertificateParams::new(vec![]).unwrap();
        params
            .distinguished_name
            .push(rcgen::DnType::CommonName, cn);
        let kp = rcgen::KeyPair::generate().unwrap();
        params.self_signed(&kp).unwrap().der().clone()
    }

    /// **The identity is the LEAF's CN, never a CA's.** A client presents its own
    /// certificate first and the issuing chain behind it; reading any other
    /// element would map every client of one CA onto a single identity — one
    /// shared principal for the whole fleet, and RBAC would stop distinguishing
    /// anybody.
    #[test]
    fn leaf_common_name_reads_the_first_cert_in_the_chain() {
        let leaf = cert_with_cn("writer-bot");
        let intermediate = cert_with_cn("holger-issuing-ca");
        let root = cert_with_cn("holger-root-ca");
        let cn = leaf_common_name(&[leaf, intermediate, root]);
        assert_eq!(
            cn.as_deref(),
            Some("writer-bot"),
            "the leaf's CN is the identity"
        );

        #[cfg(feature = "testmatrix")]
        fstatus(
            "tls",
            "leaf_common_name_reads_the_first_cert_in_the_chain",
            cn.as_deref() == Some("writer-bot"),
            &format!("3-cert chain -> CN {cn:?}"),
        );
    }

    /// A certificate that names no CN, and bytes that are not a certificate at
    /// all, both yield `None` — no identity, and no panic on the TLS accept path
    /// (where the peer controls the bytes).
    #[test]
    fn a_cn_less_or_unparseable_certificate_yields_no_identity() {
        let mut params = rcgen::CertificateParams::new(vec!["holger.example".to_string()]).unwrap();
        // An EMPTY subject: the certificate identifies itself by SAN only.
        params.distinguished_name = rcgen::DistinguishedName::new();
        let kp = rcgen::KeyPair::generate().unwrap();
        let no_cn = params.self_signed(&kp).unwrap().der().clone();
        assert_eq!(leaf_common_name(&[no_cn]), None, "a SAN is not a CN");

        let garbage = CertificateDer::from(vec![0x30, 0x00, 0xde, 0xad, 0xbe, 0xef]);
        assert_eq!(
            leaf_common_name(&[garbage]),
            None,
            "unparseable DER is no identity"
        );

        #[cfg(feature = "testmatrix")]
        fstatus(
            "tls",
            "a_cn_less_or_unparseable_certificate_yields_no_identity",
            true,
            "no CN and garbage DER both map to None",
        );
    }

    /// mTLS is enabled by the presence of a client CA and by nothing else — the
    /// one switch that decides whether the gRPC transport REQUIRES a client
    /// certificate.
    #[test]
    fn mtls_is_enabled_by_the_client_ca_and_nothing_else() {
        let base = TlsSettings {
            cert: "/etc/holger/server.pem".into(),
            key: "/etc/holger/server.key".into(),
            client_ca: None,
            client_auth_optional: false,
            crl: None,
        };
        assert!(!base.mtls_enabled(), "no client CA ⇒ no mTLS");
        let mtls = TlsSettings {
            client_ca: Some("/etc/holger/ca.pem".into()),
            ..base.clone()
        };
        assert!(mtls.mtls_enabled());
    }

    /// A TLS block pointing at files that are not there fails LOUDLY on both
    /// transports, naming the file — a server must never start with a silently
    /// empty identity and serve cleartext or an anonymous handshake instead.
    #[test]
    fn missing_or_unreadable_tls_material_is_an_error_not_a_silent_downgrade() {
        let dir = tempfile::tempdir().unwrap();
        let missing = dir.path().join("nope.pem");
        let s = TlsSettings {
            cert: missing.display().to_string(),
            key: missing.display().to_string(),
            client_ca: None,
            client_auth_optional: false,
            crl: None,
        };
        let e = s
            .rustls_gateway_config()
            .expect_err("a missing cert must fail the gateway");
        assert!(e.to_string().contains("reading cert"), "{e:#}");
        let e = s
            .tonic_config()
            .expect_err("a missing cert must fail the gRPC transport");
        assert!(e.to_string().contains("reading cert"), "{e:#}");

        // A file that exists but holds no certificate is refused just as loudly,
        // rather than yielding an empty chain.
        let empty = dir.path().join("empty.pem");
        std::fs::write(&empty, b"# nothing here\n").unwrap();
        let s = TlsSettings {
            cert: empty.display().to_string(),
            key: empty.display().to_string(),
            client_ca: None,
            client_auth_optional: false,
            crl: None,
        };
        let e = s
            .rustls_gateway_config()
            .expect_err("an empty PEM must fail");
        assert!(e.to_string().contains("no certificates found"), "{e:#}");
    }
}