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}