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::cert_binding::{evaluate, verify_binding_from_leaf_cert, BindingPolicy};
29use crate::identity::{peer_id_from_leaf_cert_der, PeerId};
30
31/// The outcome of verifying a peer's certificate: the `peer_id` it presented, captured for the
32/// caller. Shared via `Arc<Mutex<_>>` because rustls verifiers are `Sync` and run inside the
33/// handshake.
34#[derive(Debug, Default, Clone)]
35pub struct CapturedPeerId(pub Arc<Mutex<Option<PeerId>>>);
36
37impl CapturedPeerId {
38 /// The `peer_id` derived from the certificate the peer presented, if the handshake reached cert
39 /// verification.
40 pub fn get(&self) -> Option<PeerId> {
41 *self.0.lock().unwrap()
42 }
43}
44
45/// The peer's verified BLS G1 identity pubkey, captured from the cert binding (#1204) when the
46/// handshake carried a valid one. `None` means the peer presented no valid binding (a legacy peer
47/// under [`BindingPolicy::Opportunistic`], or binding verification was [`BindingPolicy::Off`]). The
48/// sealing layer (S2) seals to this key so a misdelivery cannot be opened by the wrong node.
49#[derive(Debug, Default, Clone)]
50pub struct CapturedBlsPub(pub Arc<Mutex<Option<[u8; 48]>>>);
51
52impl CapturedBlsPub {
53 /// The verified BLS G1 pubkey the peer's `peer_id` is bound to, if a valid binding was presented.
54 pub fn get(&self) -> Option<[u8; 48]> {
55 *self.0.lock().unwrap()
56 }
57}
58
59/// A rustls [`ServerCertVerifier`] for the DIG self-authenticating overlay.
60///
61/// It does not validate a CA chain (DIG certs are self-signed and the *key* is the identity). It
62/// derives `peer_id = SHA-256(SPKI DER)` from the presented leaf, records it into [`Self::captured`]
63/// for the caller, and — when [`Self::expected`] is set — REJECTS the handshake unless the derived
64/// id matches. Signature checks (that the peer actually holds the private key for the presented key)
65/// are delegated to ring's default crypto provider via [`Self::defaults`].
66#[derive(Debug)]
67pub struct PeerIdPinningVerifier {
68 /// The peer_id the caller wants to reach; `None` = accept any (record-only, e.g. inbound).
69 expected: Option<PeerId>,
70 /// Where the derived peer_id is written so the caller can read who connected.
71 captured: CapturedPeerId,
72 /// The BLS peer_id-binding stance (#1204). [`BindingPolicy::Off`] = do not verify the binding.
73 binding_policy: BindingPolicy,
74 /// Where the verified peer BLS pubkey is written (when a valid binding was presented).
75 captured_bls: CapturedBlsPub,
76 /// Supported signature schemes, from the process crypto provider.
77 defaults: Vec<SignatureScheme>,
78}
79
80impl PeerIdPinningVerifier {
81 /// Build a verifier that pins `expected` (or accepts any peer when `None`) and writes the
82 /// derived id into `captured`. The BLS cert binding is NOT verified ([`BindingPolicy::Off`]) —
83 /// use [`PeerIdPinningVerifier::with_binding`] to enable + capture it.
84 pub fn new(expected: Option<PeerId>, captured: CapturedPeerId) -> Self {
85 PeerIdPinningVerifier {
86 expected,
87 captured,
88 binding_policy: BindingPolicy::Off,
89 captured_bls: CapturedBlsPub::default(),
90 defaults: default_signature_schemes(),
91 }
92 }
93
94 /// Enable BLS cert-binding verification (#1204) under `policy`, writing the verified peer BLS
95 /// pubkey into `captured_bls`. Under [`BindingPolicy::Required`] a missing/invalid binding
96 /// REJECTS the handshake (fail-closed, anti-downgrade); under [`BindingPolicy::Opportunistic`] a
97 /// present-but-invalid binding is rejected while an absent one is tolerated (legacy peers).
98 pub fn with_binding(mut self, policy: BindingPolicy, captured_bls: CapturedBlsPub) -> Self {
99 self.binding_policy = policy;
100 self.captured_bls = captured_bls;
101 self
102 }
103}
104
105impl ServerCertVerifier for PeerIdPinningVerifier {
106 fn verify_server_cert(
107 &self,
108 end_entity: &CertificateDer<'_>,
109 _intermediates: &[CertificateDer<'_>],
110 _server_name: &ServerName<'_>,
111 _ocsp_response: &[u8],
112 _now: UnixTime,
113 ) -> Result<ServerCertVerified, TlsError> {
114 let derived = peer_id_from_leaf_cert_der(end_entity.as_ref()).ok_or_else(|| {
115 TlsError::General("peer leaf certificate could not be parsed as X.509".to_string())
116 })?;
117 // Record who we connected to regardless of the pin outcome.
118 *self.captured.0.lock().unwrap() = Some(derived);
119 if let Some(expected) = self.expected {
120 if derived != expected {
121 return Err(TlsError::General(format!(
122 "peer_id mismatch: expected {expected}, got {derived}"
123 )));
124 }
125 }
126
127 // Verify the BLS peer_id↔pubkey binding (#1204) per the configured policy. `Off` short-circuits
128 // (no crypto); otherwise a present-but-invalid binding always rejects, and `Required` also
129 // rejects an absent binding (fail-closed / anti-downgrade). A verified binding's pubkey is
130 // captured for the sealing layer to seal to.
131 if self.binding_policy != BindingPolicy::Off {
132 let outcome = verify_binding_from_leaf_cert(end_entity.as_ref());
133 match evaluate(&outcome, self.binding_policy) {
134 Ok(bls_pub) => *self.captured_bls.0.lock().unwrap() = bls_pub,
135 Err(reason) => {
136 return Err(TlsError::General(format!(
137 "peer {derived} rejected by cert BLS binding policy: {reason}"
138 )))
139 }
140 }
141 }
142
143 Ok(ServerCertVerified::assertion())
144 }
145
146 fn verify_tls12_signature(
147 &self,
148 message: &[u8],
149 cert: &CertificateDer<'_>,
150 dss: &DigitallySignedStruct,
151 ) -> Result<HandshakeSignatureValid, TlsError> {
152 rustls::crypto::verify_tls12_signature(
153 message,
154 cert,
155 dss,
156 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
157 )
158 }
159
160 fn verify_tls13_signature(
161 &self,
162 message: &[u8],
163 cert: &CertificateDer<'_>,
164 dss: &DigitallySignedStruct,
165 ) -> Result<HandshakeSignatureValid, TlsError> {
166 rustls::crypto::verify_tls13_signature(
167 message,
168 cert,
169 dss,
170 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
171 )
172 }
173
174 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
175 self.defaults.clone()
176 }
177}
178
179/// The signature schemes ring's provider supports — used for [`ServerCertVerifier::supported_verify_schemes`].
180fn default_signature_schemes() -> Vec<SignatureScheme> {
181 rustls::crypto::ring::default_provider()
182 .signature_verification_algorithms
183 .supported_schemes()
184}