hashiverse_server_lib/transport/
https_transport_ownership_proof.rs1use bytes::Bytes;
27use hashiverse_lib::protocol::peer::Peer;
28use hashiverse_lib::tools::cert_validation::is_cert_valid;
29use hashiverse_lib::tools::time::TimeMillis;
30use hashiverse_lib::transport::transport_ownership_proof::TransportOwnershipProof;
31use parking_lot::{Mutex, RwLock};
32use rustls::sign::CertifiedKey;
33use serde::{Deserialize, Serialize};
34use std::sync::Arc;
35
36#[derive(Serialize, Deserialize)]
41struct HttpsAcmeProof {
42 chain_der: Vec<Vec<u8>>,
45}
46
47struct CachedProofPayload {
53 certified_key: Arc<CertifiedKey>,
54 payload_bytes: Bytes,
55}
56
57pub struct HttpsTransportOwnershipProof {
65 base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
66 cached_payload: Mutex<Option<CachedProofPayload>>,
67}
68
69impl HttpsTransportOwnershipProof {
70 pub fn new(base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>) -> Self {
71 Self { base_cert, cached_payload: Mutex::new(None) }
72 }
73}
74
75impl TransportOwnershipProof for HttpsTransportOwnershipProof {
76 fn make_ownership_proof_payload(&self) -> Option<Bytes> {
77 let current_certified_key: Arc<CertifiedKey> = {
81 let base_cert_guard = self.base_cert.read();
82 base_cert_guard.as_ref()?.clone()
83 };
84
85 {
89 let cache_guard = self.cached_payload.lock();
90 if let Some(cached) = cache_guard.as_ref() {
91 if Arc::ptr_eq(&cached.certified_key, ¤t_certified_key) {
92 return Some(cached.payload_bytes.clone());
93 }
94 }
95 }
96
97 let chain_der: Vec<Vec<u8>> = current_certified_key.cert.iter().map(|der| der.to_vec()).collect();
99 if chain_der.is_empty() {
100 return None;
101 }
102
103 let proof: HttpsAcmeProof = HttpsAcmeProof { chain_der };
104 let encoded: Vec<u8> = postcard::to_allocvec(&proof).ok()?;
105 let payload_bytes: Bytes = Bytes::from(encoded);
106
107 *self.cached_payload.lock() = Some(CachedProofPayload {
108 certified_key: current_certified_key,
109 payload_bytes: payload_bytes.clone(),
110 });
111
112 Some(payload_bytes)
113 }
114
115 fn prove(&self, peer: &Peer, proof_payload: &[u8], now: TimeMillis) -> bool {
116 let proof: HttpsAcmeProof = match postcard::from_bytes(proof_payload) {
117 Ok(p) => p,
118 Err(_) => return false,
119 };
120
121 is_cert_valid(&proof.chain_der, &peer.address, now)
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use hashiverse_lib::tools::types::Id;
129
130 fn peer_at(address: &str) -> Peer {
131 let mut peer: Peer = Peer::zero();
132 peer.address = address.to_string();
133 peer.id = Id([42u8; 32]);
134 peer
135 }
136
137 fn empty_base_cert_lock() -> Arc<RwLock<Option<Arc<CertifiedKey>>>> {
138 Arc::new(RwLock::new(None))
139 }
140
141 #[test]
142 fn make_returns_none_when_no_cert_loaded_yet() {
143 let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(empty_base_cert_lock());
144 assert!(proof.make_ownership_proof_payload().is_none());
145 }
146
147 #[test]
148 fn prove_rejects_garbage_bytes() {
149 let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(empty_base_cert_lock());
150 let peer: Peer = peer_at("1.2.3.4:443");
151 let now: TimeMillis = TimeMillis(1_700_000_000_000);
152 assert!(!proof.prove(&peer, &[0xff, 0xfe, 0xfd], now));
153 }
154
155 #[test]
156 fn prove_rejects_empty_bytes() {
157 let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(empty_base_cert_lock());
158 let peer: Peer = peer_at("1.2.3.4:443");
159 let now: TimeMillis = TimeMillis(1_700_000_000_000);
160 assert!(!proof.prove(&peer, &[], now));
165 }
166
167 fn placeholder_certified_key(der_bytes: Vec<u8>) -> Arc<CertifiedKey> {
171 use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};
172 let _ = rustls::crypto::ring::default_provider().install_default();
173 let cert_chain: Vec<CertificateDer<'static>> = vec![CertificateDer::from(der_bytes)];
174 let key_pair: rcgen::KeyPair = rcgen::KeyPair::generate().expect("rcgen key gen never fails on supported algorithms");
178 let pkcs8: Vec<u8> = key_pair.serialize_der();
179 let key_der: PrivateKeyDer<'static> = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(pkcs8));
180 let signing: Arc<dyn rustls::sign::SigningKey> = rustls::crypto::ring::sign::any_supported_type(&key_der).expect("rcgen key is a supported type");
181 Arc::new(CertifiedKey { cert: cert_chain, key: signing, ocsp: None })
182 }
183
184 #[test]
185 fn cache_hit_returns_identical_bytes_when_cert_unchanged() {
186 let base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>> = Arc::new(RwLock::new(Some(placeholder_certified_key(vec![0x30, 0x82, 0x01, 0x00, 0xAA]))));
187 let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(base_cert);
188
189 let first_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
190 let second_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
191
192 assert_eq!(first_bytes.as_ptr(), second_bytes.as_ptr());
195 assert_eq!(first_bytes, second_bytes);
196 }
197
198 #[test]
199 fn cache_miss_when_cert_rotates() {
200 let base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>> = Arc::new(RwLock::new(Some(placeholder_certified_key(vec![0x01, 0x02, 0x03]))));
201 let proof: HttpsTransportOwnershipProof = HttpsTransportOwnershipProof::new(base_cert.clone());
202
203 let first_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
204
205 *base_cert.write() = Some(placeholder_certified_key(vec![0x04, 0x05, 0x06]));
207
208 let second_bytes: Bytes = proof.make_ownership_proof_payload().expect("placeholder cert is non-empty");
209
210 assert_ne!(first_bytes.as_ptr(), second_bytes.as_ptr());
211 assert_ne!(first_bytes, second_bytes);
212 }
213}