nautalid 0.1.0

Scratch container substrate — TLS 1.3 HTTP/2+3 kernel, LID/AetherDB, optional filter bus (GPL-3.0-or-later).
//! Inbound mTLS client certificate exposed to Axum handlers.

use std::sync::Arc;

use axum::extract::FromRequestParts;
use axum::http::request::Parts;
use rustls::pki_types::CertificateDer;
use sha2::{Digest, Sha256};

/// Client certificate chain presented during inbound mTLS (may be empty).
#[derive(Clone, Debug)]
pub struct PeerClientCert(pub Arc<Vec<CertificateDer<'static>>>);

impl PeerClientCert {
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// SHA-256 fingerprint (lowercase hex) of the leaf certificate DER.
    #[must_use]
    pub fn leaf_sha256_hex(&self) -> Option<String> {
        let leaf = self.0.first()?;
        Some(hex::encode(Sha256::digest(leaf.as_ref())))
    }
}

impl<S> FromRequestParts<S> for PeerClientCert
where
    S: Send + Sync,
{
    type Rejection = ();

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        parts
            .extensions
            .get::<Self>()
            .cloned()
            .ok_or(())
    }
}

/// Extract peer certificates from a rustls server connection.
#[must_use]
pub fn from_rustls_server(
    conn: &rustls::ServerConnection,
) -> PeerClientCert {
    let certs = conn
        .peer_certificates()
        .map(|chain| {
            chain
                .iter()
                .map(|c| c.clone().into_owned())
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();
    PeerClientCert(Arc::new(certs))
}

/// Extract peer certificates from a QUIC connection.
#[must_use]
pub fn from_quinn_connection(connection: &quinn::Connection) -> PeerClientCert {
    let Some(identity) = connection.peer_identity() else {
        return PeerClientCert(Arc::new(Vec::new()));
    };
    let Some(certs) = identity.downcast_ref::<Vec<CertificateDer<'_>>>() else {
        return PeerClientCert(Arc::new(Vec::new()));
    };
    PeerClientCert(Arc::new(certs.clone()))
}