Skip to main content

dig_nat/
mtls.rs

1//! mTLS layer — every peer connection is a **mutually-authenticated TLS** stream whose remote
2//! `peer_id` is verified against the one the caller asked to reach.
3//!
4//! All DIG node-to-node comms are mutual TLS: both sides present a certificate, and each derives the
5//! other's identity as `peer_id = SHA-256(TLS SubjectPublicKeyInfo DER)` (see [`crate::identity`],
6//! matching dig-gossip). dig-nat establishes the transport AND wraps it in this mTLS session, so the
7//! resulting [`crate::PeerConnection`] is always mutually authenticated with the peer_id verified.
8//!
9//! ## Verification model
10//!
11//! DIG peers use **self-signed** certificates whose *public key* IS the identity — there is no CA.
12//! So the rustls verifier here does NOT check a chain of trust; instead it:
13//!
14//! 1. captures the peer's leaf certificate,
15//! 2. derives its `peer_id` via [`crate::identity::peer_id_from_leaf_cert_der`], and
16//! 3. **pins** it: if the caller supplied an expected `peer_id`, the handshake is rejected unless it
17//!    matches; the derived id is always recorded so the caller learns exactly who it connected to.
18//!
19//! This is the standard "trust-on-first-use / key-is-identity" model for a self-authenticating P2P
20//! overlay, and it is what makes `peer_id` a meaningful authentication (not just a label).
21
22use std::sync::{Arc, Mutex};
23
24use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
25use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
26use rustls::{DigitallySignedStruct, Error as TlsError, SignatureScheme};
27
28use crate::identity::{peer_id_from_leaf_cert_der, PeerId};
29
30/// The outcome of verifying a peer's certificate: the `peer_id` it presented, captured for the
31/// caller. Shared via `Arc<Mutex<_>>` because rustls verifiers are `Sync` and run inside the
32/// handshake.
33#[derive(Debug, Default, Clone)]
34pub struct CapturedPeerId(pub Arc<Mutex<Option<PeerId>>>);
35
36impl CapturedPeerId {
37    /// The `peer_id` derived from the certificate the peer presented, if the handshake reached cert
38    /// verification.
39    pub fn get(&self) -> Option<PeerId> {
40        *self.0.lock().unwrap()
41    }
42}
43
44/// A rustls [`ServerCertVerifier`] for the DIG self-authenticating overlay.
45///
46/// It does not validate a CA chain (DIG certs are self-signed and the *key* is the identity). It
47/// derives `peer_id = SHA-256(SPKI DER)` from the presented leaf, records it into [`Self::captured`]
48/// for the caller, and — when [`Self::expected`] is set — REJECTS the handshake unless the derived
49/// id matches. Signature checks (that the peer actually holds the private key for the presented key)
50/// are delegated to ring's default crypto provider via [`Self::defaults`].
51#[derive(Debug)]
52pub struct PeerIdPinningVerifier {
53    /// The peer_id the caller wants to reach; `None` = accept any (record-only, e.g. inbound).
54    expected: Option<PeerId>,
55    /// Where the derived peer_id is written so the caller can read who connected.
56    captured: CapturedPeerId,
57    /// Supported signature schemes, from the process crypto provider.
58    defaults: Vec<SignatureScheme>,
59}
60
61impl PeerIdPinningVerifier {
62    /// Build a verifier that pins `expected` (or accepts any peer when `None`) and writes the
63    /// derived id into `captured`.
64    pub fn new(expected: Option<PeerId>, captured: CapturedPeerId) -> Self {
65        PeerIdPinningVerifier {
66            expected,
67            captured,
68            defaults: default_signature_schemes(),
69        }
70    }
71}
72
73impl ServerCertVerifier for PeerIdPinningVerifier {
74    fn verify_server_cert(
75        &self,
76        end_entity: &CertificateDer<'_>,
77        _intermediates: &[CertificateDer<'_>],
78        _server_name: &ServerName<'_>,
79        _ocsp_response: &[u8],
80        _now: UnixTime,
81    ) -> Result<ServerCertVerified, TlsError> {
82        let derived = peer_id_from_leaf_cert_der(end_entity.as_ref()).ok_or_else(|| {
83            TlsError::General("peer leaf certificate could not be parsed as X.509".to_string())
84        })?;
85        // Record who we connected to regardless of the pin outcome.
86        *self.captured.0.lock().unwrap() = Some(derived);
87        if let Some(expected) = self.expected {
88            if derived != expected {
89                return Err(TlsError::General(format!(
90                    "peer_id mismatch: expected {expected}, got {derived}"
91                )));
92            }
93        }
94        Ok(ServerCertVerified::assertion())
95    }
96
97    fn verify_tls12_signature(
98        &self,
99        message: &[u8],
100        cert: &CertificateDer<'_>,
101        dss: &DigitallySignedStruct,
102    ) -> Result<HandshakeSignatureValid, TlsError> {
103        rustls::crypto::verify_tls12_signature(
104            message,
105            cert,
106            dss,
107            &rustls::crypto::ring::default_provider().signature_verification_algorithms,
108        )
109    }
110
111    fn verify_tls13_signature(
112        &self,
113        message: &[u8],
114        cert: &CertificateDer<'_>,
115        dss: &DigitallySignedStruct,
116    ) -> Result<HandshakeSignatureValid, TlsError> {
117        rustls::crypto::verify_tls13_signature(
118            message,
119            cert,
120            dss,
121            &rustls::crypto::ring::default_provider().signature_verification_algorithms,
122        )
123    }
124
125    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
126        self.defaults.clone()
127    }
128}
129
130/// The signature schemes ring's provider supports — used for [`ServerCertVerifier::supported_verify_schemes`].
131fn default_signature_schemes() -> Vec<SignatureScheme> {
132    rustls::crypto::ring::default_provider()
133        .signature_verification_algorithms
134        .supported_schemes()
135}