1use std::sync::{Arc, Mutex};
20
21use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
22use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
23use rustls::server::danger::{ClientCertVerified, ClientCertVerifier};
24use rustls::{DigitallySignedStruct, DistinguishedName, Error as TlsError, SignatureScheme};
25use webpki::{anchor_from_trusted_cert, EndEntityCert, KeyUsage};
26
27use crate::binding::{evaluate, verify_binding_from_leaf_cert, BindingPolicy};
28use crate::ca::embedded_ca_cert_der;
29use crate::error::Result;
30use crate::identity::{peer_id_from_leaf_cert_der, PeerId};
31
32#[derive(Debug, Default, Clone)]
35pub struct CapturedPeerId(pub Arc<Mutex<Option<PeerId>>>);
36
37impl CapturedPeerId {
38 pub fn get(&self) -> Option<PeerId> {
40 *self.0.lock().unwrap()
41 }
42}
43
44#[derive(Debug, Default, Clone)]
48pub struct CapturedBlsPub(pub Arc<Mutex<Option<[u8; 48]>>>);
49
50impl CapturedBlsPub {
51 pub fn get(&self) -> Option<[u8; 48]> {
53 *self.0.lock().unwrap()
54 }
55}
56
57const CHAIN_SIG_ALGS: &[&dyn webpki::types::SignatureVerificationAlgorithm] = &[
60 webpki::ring::ECDSA_P256_SHA256,
61 webpki::ring::ECDSA_P256_SHA384,
62 webpki::ring::ECDSA_P384_SHA256,
63 webpki::ring::ECDSA_P384_SHA384,
64];
65
66fn verify_chain_to_dig_ca(
69 end_entity: &CertificateDer<'_>,
70 intermediates: &[CertificateDer<'_>],
71 now: UnixTime,
72 usage: KeyUsage,
73) -> std::result::Result<(), TlsError> {
74 let ca_der =
75 embedded_ca_cert_der().map_err(|e| TlsError::General(format!("DigNetwork CA: {e}")))?;
76 let anchor = anchor_from_trusted_cert(&ca_der).map_err(|e| {
77 TlsError::General(format!("DigNetwork CA is not a valid trust anchor: {e}"))
78 })?;
79 let ee = EndEntityCert::try_from(end_entity)
80 .map_err(|e| TlsError::General(format!("peer leaf is not a valid certificate: {e}")))?;
81 ee.verify_for_usage(
82 CHAIN_SIG_ALGS,
83 &[anchor],
84 intermediates,
85 now,
86 usage,
87 None,
88 None,
89 )
90 .map_err(|e| {
91 TlsError::General(format!(
92 "peer leaf does not chain to the DigNetwork CA: {e}"
93 ))
94 })?;
95 Ok(())
96}
97
98fn pin_and_bind(
101 end_entity: &CertificateDer<'_>,
102 expected: Option<PeerId>,
103 captured: &CapturedPeerId,
104 binding_policy: BindingPolicy,
105 captured_bls: &CapturedBlsPub,
106) -> std::result::Result<PeerId, TlsError> {
107 let derived = peer_id_from_leaf_cert_der(end_entity.as_ref()).ok_or_else(|| {
108 TlsError::General("peer leaf certificate could not be parsed as X.509".to_string())
109 })?;
110 *captured.0.lock().unwrap() = Some(derived);
112 if let Some(expected) = expected {
113 if derived != expected {
114 return Err(TlsError::General(format!(
115 "peer_id mismatch: expected {expected}, got {derived}"
116 )));
117 }
118 }
119 if binding_policy != BindingPolicy::Off {
120 let outcome = verify_binding_from_leaf_cert(end_entity.as_ref());
121 match evaluate(&outcome, binding_policy) {
122 Ok(bls_pub) => *captured_bls.0.lock().unwrap() = bls_pub,
123 Err(reason) => {
124 return Err(TlsError::General(format!(
125 "peer {derived} rejected by cert BLS binding policy: {reason}"
126 )))
127 }
128 }
129 }
130 Ok(derived)
131}
132
133fn default_signature_schemes() -> Vec<SignatureScheme> {
135 rustls::crypto::ring::default_provider()
136 .signature_verification_algorithms
137 .supported_schemes()
138}
139
140fn verify_tls12(
141 message: &[u8],
142 cert: &CertificateDer<'_>,
143 dss: &DigitallySignedStruct,
144) -> std::result::Result<HandshakeSignatureValid, TlsError> {
145 rustls::crypto::verify_tls12_signature(
146 message,
147 cert,
148 dss,
149 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
150 )
151}
152
153fn verify_tls13(
154 message: &[u8],
155 cert: &CertificateDer<'_>,
156 dss: &DigitallySignedStruct,
157) -> std::result::Result<HandshakeSignatureValid, TlsError> {
158 rustls::crypto::verify_tls13_signature(
159 message,
160 cert,
161 dss,
162 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
163 )
164}
165
166#[derive(Debug)]
169pub struct DigServerCertVerifier {
170 expected: Option<PeerId>,
171 captured: CapturedPeerId,
172 binding_policy: BindingPolicy,
173 captured_bls: CapturedBlsPub,
174 schemes: Vec<SignatureScheme>,
175}
176
177impl DigServerCertVerifier {
178 pub fn new(
181 expected: Option<PeerId>,
182 captured: CapturedPeerId,
183 binding_policy: BindingPolicy,
184 captured_bls: CapturedBlsPub,
185 ) -> Self {
186 Self {
187 expected,
188 captured,
189 binding_policy,
190 captured_bls,
191 schemes: default_signature_schemes(),
192 }
193 }
194}
195
196impl ServerCertVerifier for DigServerCertVerifier {
197 fn verify_server_cert(
198 &self,
199 end_entity: &CertificateDer<'_>,
200 intermediates: &[CertificateDer<'_>],
201 _server_name: &ServerName<'_>,
202 _ocsp_response: &[u8],
203 now: UnixTime,
204 ) -> std::result::Result<ServerCertVerified, TlsError> {
205 verify_chain_to_dig_ca(end_entity, intermediates, now, KeyUsage::server_auth())?;
206 pin_and_bind(
207 end_entity,
208 self.expected,
209 &self.captured,
210 self.binding_policy,
211 &self.captured_bls,
212 )?;
213 Ok(ServerCertVerified::assertion())
214 }
215
216 fn verify_tls12_signature(
217 &self,
218 message: &[u8],
219 cert: &CertificateDer<'_>,
220 dss: &DigitallySignedStruct,
221 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
222 verify_tls12(message, cert, dss)
223 }
224
225 fn verify_tls13_signature(
226 &self,
227 message: &[u8],
228 cert: &CertificateDer<'_>,
229 dss: &DigitallySignedStruct,
230 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
231 verify_tls13(message, cert, dss)
232 }
233
234 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
235 self.schemes.clone()
236 }
237}
238
239#[derive(Debug)]
242pub struct DigClientCertVerifier {
243 expected: Option<PeerId>,
244 captured: CapturedPeerId,
245 binding_policy: BindingPolicy,
246 captured_bls: CapturedBlsPub,
247 schemes: Vec<SignatureScheme>,
248 root_hints: Vec<DistinguishedName>,
249}
250
251impl DigClientCertVerifier {
252 pub fn new(
256 expected: Option<PeerId>,
257 captured: CapturedPeerId,
258 binding_policy: BindingPolicy,
259 captured_bls: CapturedBlsPub,
260 ) -> Self {
261 Self {
262 expected,
263 captured,
264 binding_policy,
265 captured_bls,
266 schemes: default_signature_schemes(),
267 root_hints: Vec::new(),
268 }
269 }
270}
271
272impl ClientCertVerifier for DigClientCertVerifier {
273 fn offer_client_auth(&self) -> bool {
274 true
275 }
276
277 fn client_auth_mandatory(&self) -> bool {
278 true
279 }
280
281 fn root_hint_subjects(&self) -> &[DistinguishedName] {
282 &self.root_hints
283 }
284
285 fn verify_client_cert(
286 &self,
287 end_entity: &CertificateDer<'_>,
288 intermediates: &[CertificateDer<'_>],
289 now: UnixTime,
290 ) -> std::result::Result<ClientCertVerified, TlsError> {
291 verify_chain_to_dig_ca(end_entity, intermediates, now, KeyUsage::client_auth())?;
292 pin_and_bind(
293 end_entity,
294 self.expected,
295 &self.captured,
296 self.binding_policy,
297 &self.captured_bls,
298 )?;
299 Ok(ClientCertVerified::assertion())
300 }
301
302 fn verify_tls12_signature(
303 &self,
304 message: &[u8],
305 cert: &CertificateDer<'_>,
306 dss: &DigitallySignedStruct,
307 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
308 verify_tls12(message, cert, dss)
309 }
310
311 fn verify_tls13_signature(
312 &self,
313 message: &[u8],
314 cert: &CertificateDer<'_>,
315 dss: &DigitallySignedStruct,
316 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
317 verify_tls13(message, cert, dss)
318 }
319
320 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
321 self.schemes.clone()
322 }
323}
324
325pub fn dig_ca_trust_anchor_der() -> Result<CertificateDer<'static>> {
328 embedded_ca_cert_der()
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334 use crate::bls::{public_key_bytes, SecretKey};
335 use crate::ca::DigCa;
336 use crate::node_cert::NodeCert;
337 use rcgen::{
338 CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, Ia5String, KeyPair,
339 KeyUsagePurpose, SanType, PKCS_ECDSA_P256_SHA256,
340 };
341 use sha2::{Digest, Sha256};
342 use time::OffsetDateTime;
343
344 fn bls_sk(label: &str) -> SecretKey {
345 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
346 SecretKey::from_seed(&seed)
347 }
348
349 fn unbound_dig_ca_leaf() -> CertificateDer<'static> {
351 let ca = DigCa::embedded().expect("embedded CA");
352 let leaf_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap();
353 let mut params = CertificateParams::new(Vec::<String>::new()).unwrap();
354 let mut dn = DistinguishedName::new();
355 dn.push(DnType::CommonName, "peer.dig");
356 params.distinguished_name = dn;
357 params.subject_alt_names = vec![SanType::DnsName(
358 Ia5String::try_from("peer.dig".to_string()).unwrap(),
359 )];
360 params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
361 params.extended_key_usages = vec![
362 ExtendedKeyUsagePurpose::ServerAuth,
363 ExtendedKeyUsagePurpose::ClientAuth,
364 ];
365 let cert = params.signed_by(&leaf_key, &ca.cert, &ca.key).unwrap();
366 CertificateDer::from(cert.der().to_vec())
367 }
368
369 #[test]
372 fn required_rejects_ca_signed_unbound_leaf() {
373 let leaf = unbound_dig_ca_leaf();
374 let v = DigClientCertVerifier::new(
375 None,
376 CapturedPeerId::default(),
377 BindingPolicy::Required,
378 CapturedBlsPub::default(),
379 );
380 let err = v
381 .verify_client_cert(&leaf, &[], UnixTime::now())
382 .expect_err("Required rejects an unbound leaf");
383 assert!(
384 format!("{err}").contains("BLS binding"),
385 "rejected on the binding, not the chain"
386 );
387 }
388
389 #[test]
392 fn required_accepts_bound_leaf_and_captures_identity() {
393 let sk = bls_sk("verify/bound");
394 let node = NodeCert::generate_signed(&sk).expect("node cert");
395 let leaf = CertificateDer::from(node.cert_der().to_vec());
396
397 let captured = CapturedPeerId::default();
398 let captured_bls = CapturedBlsPub::default();
399 let v = DigClientCertVerifier::new(
400 None,
401 captured.clone(),
402 BindingPolicy::Required,
403 captured_bls.clone(),
404 );
405 v.verify_client_cert(&leaf, &[], UnixTime::now())
406 .expect("a bound DIG-CA leaf verifies");
407 assert_eq!(captured.get(), Some(node.peer_id()));
408 assert_eq!(captured_bls.get(), Some(public_key_bytes(&sk)));
409 }
410
411 #[test]
413 fn foreign_ca_leaf_fails_chain() {
414 let foreign = crate::ca::generate_dig_ca(OffsetDateTime::now_utc()).unwrap();
415 let foreign_ca = DigCa::from_pem(&foreign.cert_pem, &foreign.key_pem).unwrap();
416 let node = NodeCert::generate_signed_by(
417 &foreign_ca,
418 &bls_sk("verify/foreign"),
419 OffsetDateTime::now_utc(),
420 )
421 .unwrap();
422 let leaf = CertificateDer::from(node.cert_der().to_vec());
423
424 let v = DigServerCertVerifier::new(
425 None,
426 CapturedPeerId::default(),
427 BindingPolicy::Off,
428 CapturedBlsPub::default(),
429 );
430 let name = ServerName::try_from("peer.dig").unwrap();
431 let err = v
432 .verify_server_cert(&leaf, &[], &name, &[], UnixTime::now())
433 .expect_err("foreign CA leaf is rejected");
434 assert!(format!("{err}").contains("DigNetwork CA"));
435 }
436}