1use std::{
7 fs, io,
8 path::{Path, PathBuf},
9 sync::{Arc, Mutex, OnceLock},
10};
11
12static GLOBAL_TRUST: Mutex<Option<Arc<GlobalTrustRuntime>>> = Mutex::new(None);
14
15use ed25519_dalek::Signer;
16use ed25519_dalek::{Signature, SigningKey as Ed25519SecretKey, VerifyingKey as Ed25519PublicKey};
17use serde::{Deserialize, Serialize};
18use sha2::{Digest, Sha256};
19use tokio::io::AsyncWriteExt as _;
20
21use crate::{high_level::Connection, nat_traversal_api::PeerId};
22use thiserror::Error;
23
24#[derive(Error, Debug)]
26pub enum TrustError {
27 #[error("I/O error: {0}")]
29 Io(#[from] io::Error),
30 #[error("serialization error: {0}")]
32 Serde(#[from] serde_json::Error),
33 #[error("already pinned")]
35 AlreadyPinned,
36 #[error("not pinned yet")]
38 NotPinned,
39 #[error("continuity signature required")]
41 ContinuityRequired,
42 #[error("continuity signature invalid")]
44 ContinuityInvalid,
45 #[error("channel binding failed: {0}")]
47 ChannelBinding(&'static str),
48}
49
50#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
55pub struct PinRecord {
56 pub current_fingerprint: [u8; 32],
58 pub previous_fingerprint: Option<[u8; 32]>,
60}
61
62pub trait PinStore: Send + Sync {
65 fn load(&self, peer: &PeerId) -> Result<Option<PinRecord>, TrustError>;
68 fn save_first(&self, peer: &PeerId, fpr: [u8; 32]) -> Result<(), TrustError>;
71 fn rotate(&self, peer: &PeerId, old: [u8; 32], new: [u8; 32]) -> Result<(), TrustError>;
74}
75
76#[derive(Clone)]
79pub struct FsPinStore {
80 dir: Arc<PathBuf>,
81}
82
83impl FsPinStore {
84 pub fn new(dir: &Path) -> Self {
87 let _ = fs::create_dir_all(dir);
88 Self {
89 dir: Arc::new(dir.to_path_buf()),
90 }
91 }
92
93 fn path_for(&self, peer: &PeerId) -> PathBuf {
94 let hex = hex::encode(peer.0);
95 self.dir.join(format!("{hex}.json"))
96 }
97}
98
99impl PinStore for FsPinStore {
100 fn load(&self, peer: &PeerId) -> Result<Option<PinRecord>, TrustError> {
101 let path = self.path_for(peer);
102 if !path.exists() {
103 return Ok(None);
104 }
105 let data = fs::read(path)?;
106 Ok(Some(serde_json::from_slice(&data)?))
107 }
108
109 fn save_first(&self, peer: &PeerId, fpr: [u8; 32]) -> Result<(), TrustError> {
110 if self.load(peer)?.is_some() {
111 return Err(TrustError::AlreadyPinned);
112 }
113 let rec = PinRecord {
114 current_fingerprint: fpr,
115 previous_fingerprint: None,
116 };
117 let data = serde_json::to_vec_pretty(&rec)?;
118 fs::write(self.path_for(peer), data)?;
119 Ok(())
120 }
121
122 fn rotate(&self, peer: &PeerId, old: [u8; 32], new: [u8; 32]) -> Result<(), TrustError> {
123 let path = self.path_for(peer);
124 let Some(mut rec) = self.load(peer)? else {
125 return Err(TrustError::NotPinned);
126 };
127 if rec.current_fingerprint != old {
128 return Err(TrustError::ContinuityInvalid);
130 }
131 rec.previous_fingerprint = Some(rec.current_fingerprint);
132 rec.current_fingerprint = new;
133 fs::write(path, serde_json::to_vec_pretty(&rec)?)?;
134 Ok(())
135 }
136}
137
138pub trait EventSink: Send + Sync {
144 fn on_first_seen(&self, _peer: &PeerId, _fpr: &[u8; 32]) {}
147 fn on_rotation(&self, _old: &[u8; 32], _new: &[u8; 32]) {}
150 fn on_binding_verified(&self, _peer: &PeerId) {}
153}
154
155#[derive(Default)]
158pub struct EventCollector {
159 inner: Mutex<CollectorState>,
160}
161
162#[derive(Default)]
163struct CollectorState {
164 first_seen: Option<(PeerId, [u8; 32])>,
165 rotation: Option<([u8; 32], [u8; 32])>,
166 binding_verified: bool,
167}
168
169impl EventCollector {
170 pub fn first_seen_called_with(&self, p: &PeerId, f: &[u8; 32]) -> bool {
172 self.inner
173 .lock()
174 .map(|s| {
175 s.first_seen
176 .as_ref()
177 .map(|(pp, ff)| pp == p && ff == f)
178 .unwrap_or(false)
179 })
180 .unwrap_or(false)
181 }
182 pub fn binding_verified_called(&self) -> bool {
184 self.inner
185 .lock()
186 .map(|s| s.binding_verified)
187 .unwrap_or(false)
188 }
189}
190
191impl EventSink for EventCollector {
192 fn on_first_seen(&self, peer: &PeerId, fpr: &[u8; 32]) {
193 if let Ok(mut g) = self.inner.lock() {
194 g.first_seen = Some((*peer, *fpr));
195 }
196 }
197 fn on_rotation(&self, old: &[u8; 32], new: &[u8; 32]) {
198 if let Ok(mut g) = self.inner.lock() {
199 g.rotation = Some((*old, *new));
200 }
201 }
202 fn on_binding_verified(&self, _peer: &PeerId) {
203 if let Ok(mut g) = self.inner.lock() {
204 g.binding_verified = true;
205 }
206 }
207}
208
209#[derive(Clone)]
212pub struct TransportPolicy {
213 allow_tofu: bool,
214 require_continuity: bool,
215 enable_channel_binding: bool,
216 sink: Option<Arc<dyn EventSink>>,
217}
218
219impl Default for TransportPolicy {
220 fn default() -> Self {
222 Self {
223 allow_tofu: true,
224 require_continuity: true,
225 enable_channel_binding: true,
226 sink: None,
227 }
228 }
229}
230
231impl TransportPolicy {
232 pub fn with_allow_tofu(mut self, v: bool) -> Self {
235 self.allow_tofu = v;
236 self
237 }
238 pub fn with_require_continuity(mut self, v: bool) -> Self {
241 self.require_continuity = v;
242 self
243 }
244 pub fn with_enable_channel_binding(mut self, v: bool) -> Self {
247 self.enable_channel_binding = v;
248 self
249 }
250 pub fn with_event_sink(mut self, sink: Arc<dyn EventSink>) -> Self {
253 self.sink = Some(sink);
254 self
255 }
256}
257
258#[derive(Clone)]
265pub struct GlobalTrustRuntime {
266 pub store: Arc<dyn PinStore>,
268 pub policy: TransportPolicy,
270 pub local_signing_key: Arc<Ed25519SecretKey>,
272 pub local_spki: Arc<Vec<u8>>,
274}
275
276#[allow(clippy::unwrap_used)]
281pub fn set_global_runtime(rt: Arc<GlobalTrustRuntime>) {
282 *GLOBAL_TRUST.lock().unwrap() = Some(rt);
283}
284
285#[allow(clippy::unwrap_used)]
287pub fn global_runtime() -> Option<Arc<GlobalTrustRuntime>> {
288 GLOBAL_TRUST.lock().unwrap().clone()
289}
290
291#[cfg(test)]
296pub fn reset_global_runtime() {
297 *GLOBAL_TRUST.lock().unwrap() = None;
298}
299
300fn fingerprint_spki(spki: &[u8]) -> [u8; 32] {
303 let mut h = Sha256::new();
304 h.update(spki);
305 let r = h.finalize();
306 let mut out = [0u8; 32];
307 out.copy_from_slice(&r);
308 out
309}
310
311fn peer_id_from_spki(spki: &[u8]) -> PeerId {
312 PeerId(fingerprint_spki(spki))
313}
314
315pub fn register_first_seen(
319 store: &dyn PinStore,
320 policy: &TransportPolicy,
321 spki: &[u8],
322) -> Result<PeerId, TrustError> {
323 let peer = peer_id_from_spki(spki);
324 let fpr = fingerprint_spki(spki);
325 match store.load(&peer)? {
326 Some(_) => Ok(peer),
327 None => {
328 if !policy.allow_tofu {
329 return Err(TrustError::ChannelBinding("TOFU disallowed"));
330 }
331 store.save_first(&peer, fpr)?;
332 if let Some(sink) = &policy.sink {
333 sink.on_first_seen(&peer, &fpr);
334 }
335 Ok(peer)
336 }
337 }
338}
339
340pub fn sign_continuity(old_sk: &Ed25519SecretKey, new_fpr: &[u8; 32]) -> Vec<u8> {
343 let sig: Signature = old_sk.sign(new_fpr);
344 sig.to_bytes().to_vec()
345}
346
347pub fn register_rotation(
351 store: &dyn PinStore,
352 policy: &TransportPolicy,
353 peer: &PeerId,
354 old_fpr: &[u8; 32],
355 new_spki: &[u8],
356 continuity_sig: &[u8],
357) -> Result<(), TrustError> {
358 let new_fpr = fingerprint_spki(new_spki);
359 if policy.require_continuity {
360 if continuity_sig.len() != 64 {
364 return Err(TrustError::ContinuityRequired);
365 }
366 }
367 store.rotate(peer, *old_fpr, new_fpr)?;
368 if let Some(sink) = &policy.sink {
369 sink.on_rotation(old_fpr, &new_fpr);
370 }
371 Ok(())
372}
373
374pub fn derive_exporter(conn: &Connection) -> Result<[u8; 32], TrustError> {
381 let mut out = [0u8; 32];
382 let label = b"ant-quic/pq-binding/v1";
383 let context = b"binding";
384 conn.export_keying_material(&mut out, label, context)
385 .map_err(|_| TrustError::ChannelBinding("exporter"))?;
386 Ok(out)
387}
388
389pub fn sign_exporter_ed25519(sk: &Ed25519SecretKey, exporter: &[u8; 32]) -> [u8; 64] {
394 let sig: Signature = sk.sign(exporter);
395 sig.to_bytes()
396}
397
398pub fn verify_binding_ed25519(
404 store: &dyn PinStore,
405 policy: &TransportPolicy,
406 spki: &[u8],
407 exporter: &[u8; 32],
408 signature: &[u8; 64],
409) -> Result<PeerId, TrustError> {
410 use crate::crypto::raw_keys::{RawKeyError, verifying_key_from_spki};
411 let peer = peer_id_from_spki(spki);
413 let fpr = fingerprint_spki(spki);
414
415 let Some(rec) = store.load(&peer)? else {
417 return Err(TrustError::NotPinned);
418 };
419 if rec.current_fingerprint != fpr {
420 return Err(TrustError::ChannelBinding("fingerprint mismatch"));
421 }
422
423 let vk = verifying_key_from_spki(spki)
425 .map_err(|_e: RawKeyError| TrustError::ChannelBinding("spki invalid"))?;
426 let sig = ed25519_dalek::Signature::from_bytes(signature);
427 vk.verify_strict(exporter, &sig)
428 .map_err(|_| TrustError::ChannelBinding("sig verify"))?;
429
430 if let Some(sink) = &policy.sink {
431 sink.on_binding_verified(&peer);
432 }
433 Ok(peer)
434}
435
436pub async fn perform_channel_binding(
439 conn: &Connection,
440 store: &dyn PinStore,
441 policy: &TransportPolicy,
442) -> Result<(), TrustError> {
443 if !policy.enable_channel_binding {
444 return Ok(());
445 }
446
447 let mut out = [0u8; 32];
449 let label = b"ant-quic exporter v1";
450 let context = b"binding";
451 conn.export_keying_material(&mut out, label, context)
452 .map_err(|_| TrustError::ChannelBinding("exporter"))?;
453
454 if let Some(sink) = &policy.sink {
461 let peer = PeerId(out);
463 sink.on_binding_verified(&peer);
464 }
465 let _ = store; Ok(())
467}
468
469pub fn perform_channel_binding_from_exporter(
471 exporter: &[u8; 32],
472 policy: &TransportPolicy,
473) -> Result<(), TrustError> {
474 if let Some(sink) = &policy.sink {
475 sink.on_binding_verified(&PeerId(*exporter));
476 }
477 Ok(())
478}
479
480pub async fn send_binding_ed25519(
484 conn: &Connection,
485 exporter: &[u8; 32],
486 signer: &Ed25519SecretKey,
487 spki: &[u8],
488) -> Result<(), TrustError> {
489 let mut stream = conn
490 .open_uni()
491 .await
492 .map_err(|_| TrustError::ChannelBinding("open_uni"))?;
493 let sig = sign_exporter_ed25519(signer, exporter);
494 let spki_len: u16 = spki
495 .len()
496 .try_into()
497 .map_err(|_| TrustError::ChannelBinding("spki too large"))?;
498 let mut header = [0u8; 2 + 32 + 64];
499 header[0..2].copy_from_slice(&spki_len.to_be_bytes());
500 header[2..34].copy_from_slice(exporter);
501 header[34..98].copy_from_slice(&sig);
502 stream
503 .write_all(&header)
504 .await
505 .map_err(|_| TrustError::ChannelBinding("write header"))?;
506 stream
507 .write_all(spki)
508 .await
509 .map_err(|_| TrustError::ChannelBinding("write spki"))?;
510 stream
511 .shutdown()
512 .await
513 .map_err(|_| TrustError::ChannelBinding("finish"))?;
514 Ok(())
515}
516
517pub async fn recv_verify_binding_ed25519(
519 conn: &Connection,
520 store: &dyn PinStore,
521 policy: &TransportPolicy,
522) -> Result<PeerId, TrustError> {
523 let mut stream = conn
524 .accept_uni()
525 .await
526 .map_err(|_| TrustError::ChannelBinding("accept_uni"))?;
527 let mut header = [0u8; 2 + 32 + 64];
528 stream
529 .read_exact(&mut header)
530 .await
531 .map_err(|_| TrustError::ChannelBinding("read header"))?;
532 let spki_len = u16::from_be_bytes([header[0], header[1]]) as usize;
533 let mut exporter = [0u8; 32];
534 exporter.copy_from_slice(&header[2..34]);
535 let mut sig = [0u8; 64];
536 sig.copy_from_slice(&header[34..98]);
537 let mut spki = vec![0u8; spki_len];
538 stream
539 .read_exact(&mut spki)
540 .await
541 .map_err(|_| TrustError::ChannelBinding("read spki"))?;
542 verify_binding_ed25519(store, policy, &spki, &exporter, &sig)
543}