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)]
177pub struct DigServerCertVerifier {
178 expected: Option<PeerId>,
179 captured: CapturedPeerId,
180 binding_policy: BindingPolicy,
181 captured_bls: CapturedBlsPub,
182 schemes: Vec<SignatureScheme>,
183 require_ca_chain: bool,
190}
191
192impl DigServerCertVerifier {
193 pub fn new(
197 expected: Option<PeerId>,
198 captured: CapturedPeerId,
199 binding_policy: BindingPolicy,
200 captured_bls: CapturedBlsPub,
201 ) -> Self {
202 Self {
203 expected,
204 captured,
205 binding_policy,
206 captured_bls,
207 schemes: default_signature_schemes(),
208 require_ca_chain: true,
209 }
210 }
211
212 pub fn new_spki_pinned(
225 expected: Option<PeerId>,
226 captured: CapturedPeerId,
227 binding_policy: BindingPolicy,
228 captured_bls: CapturedBlsPub,
229 ) -> Self {
230 Self {
231 expected,
232 captured,
233 binding_policy,
234 captured_bls,
235 schemes: default_signature_schemes(),
236 require_ca_chain: false,
237 }
238 }
239}
240
241impl ServerCertVerifier for DigServerCertVerifier {
242 fn verify_server_cert(
243 &self,
244 end_entity: &CertificateDer<'_>,
245 intermediates: &[CertificateDer<'_>],
246 _server_name: &ServerName<'_>,
247 _ocsp_response: &[u8],
248 now: UnixTime,
249 ) -> std::result::Result<ServerCertVerified, TlsError> {
250 if self.require_ca_chain {
251 verify_chain_to_dig_ca(end_entity, intermediates, now, KeyUsage::server_auth())?;
252 }
253 pin_and_bind(
254 end_entity,
255 self.expected,
256 &self.captured,
257 self.binding_policy,
258 &self.captured_bls,
259 )?;
260 Ok(ServerCertVerified::assertion())
261 }
262
263 fn verify_tls12_signature(
264 &self,
265 message: &[u8],
266 cert: &CertificateDer<'_>,
267 dss: &DigitallySignedStruct,
268 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
269 verify_tls12(message, cert, dss)
270 }
271
272 fn verify_tls13_signature(
273 &self,
274 message: &[u8],
275 cert: &CertificateDer<'_>,
276 dss: &DigitallySignedStruct,
277 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
278 verify_tls13(message, cert, dss)
279 }
280
281 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
282 self.schemes.clone()
283 }
284}
285
286#[derive(Debug)]
295pub struct DigClientCertVerifier {
296 expected: Option<PeerId>,
297 captured: CapturedPeerId,
298 binding_policy: BindingPolicy,
299 captured_bls: CapturedBlsPub,
300 schemes: Vec<SignatureScheme>,
301 root_hints: Vec<DistinguishedName>,
302 require_ca_chain: bool,
307}
308
309impl DigClientCertVerifier {
310 pub fn new(
314 expected: Option<PeerId>,
315 captured: CapturedPeerId,
316 binding_policy: BindingPolicy,
317 captured_bls: CapturedBlsPub,
318 ) -> Self {
319 Self {
320 expected,
321 captured,
322 binding_policy,
323 captured_bls,
324 schemes: default_signature_schemes(),
325 root_hints: Vec::new(),
326 require_ca_chain: true,
327 }
328 }
329
330 pub fn new_spki_pinned(
335 expected: Option<PeerId>,
336 captured: CapturedPeerId,
337 binding_policy: BindingPolicy,
338 captured_bls: CapturedBlsPub,
339 ) -> Self {
340 Self {
341 expected,
342 captured,
343 binding_policy,
344 captured_bls,
345 schemes: default_signature_schemes(),
346 root_hints: Vec::new(),
347 require_ca_chain: false,
348 }
349 }
350}
351
352impl ClientCertVerifier for DigClientCertVerifier {
353 fn offer_client_auth(&self) -> bool {
354 true
355 }
356
357 fn client_auth_mandatory(&self) -> bool {
358 true
359 }
360
361 fn root_hint_subjects(&self) -> &[DistinguishedName] {
362 &self.root_hints
363 }
364
365 fn verify_client_cert(
366 &self,
367 end_entity: &CertificateDer<'_>,
368 intermediates: &[CertificateDer<'_>],
369 now: UnixTime,
370 ) -> std::result::Result<ClientCertVerified, TlsError> {
371 if self.require_ca_chain {
372 verify_chain_to_dig_ca(end_entity, intermediates, now, KeyUsage::client_auth())?;
373 }
374 pin_and_bind(
375 end_entity,
376 self.expected,
377 &self.captured,
378 self.binding_policy,
379 &self.captured_bls,
380 )?;
381 Ok(ClientCertVerified::assertion())
382 }
383
384 fn verify_tls12_signature(
385 &self,
386 message: &[u8],
387 cert: &CertificateDer<'_>,
388 dss: &DigitallySignedStruct,
389 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
390 verify_tls12(message, cert, dss)
391 }
392
393 fn verify_tls13_signature(
394 &self,
395 message: &[u8],
396 cert: &CertificateDer<'_>,
397 dss: &DigitallySignedStruct,
398 ) -> std::result::Result<HandshakeSignatureValid, TlsError> {
399 verify_tls13(message, cert, dss)
400 }
401
402 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
403 self.schemes.clone()
404 }
405}
406
407pub fn dig_ca_trust_anchor_der() -> Result<CertificateDer<'static>> {
410 embedded_ca_cert_der()
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use crate::bls::{public_key_bytes, SecretKey};
417 use crate::ca::DigCa;
418 use crate::node_cert::NodeCert;
419 use rcgen::{
420 CertificateParams, DistinguishedName, DnType, ExtendedKeyUsagePurpose, Ia5String, KeyPair,
421 KeyUsagePurpose, SanType, PKCS_ECDSA_P256_SHA256,
422 };
423 use sha2::{Digest, Sha256};
424 use time::OffsetDateTime;
425
426 fn bls_sk(label: &str) -> SecretKey {
427 let seed: [u8; 32] = Sha256::digest(label.as_bytes()).into();
428 SecretKey::from_seed(&seed)
429 }
430
431 fn unbound_dig_ca_leaf() -> CertificateDer<'static> {
433 let ca = DigCa::embedded().expect("embedded CA");
434 let leaf_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap();
435 let mut params = CertificateParams::new(Vec::<String>::new()).unwrap();
436 let mut dn = DistinguishedName::new();
437 dn.push(DnType::CommonName, "peer.dig");
438 params.distinguished_name = dn;
439 params.subject_alt_names = vec![SanType::DnsName(
440 Ia5String::try_from("peer.dig".to_string()).unwrap(),
441 )];
442 params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
443 params.extended_key_usages = vec![
444 ExtendedKeyUsagePurpose::ServerAuth,
445 ExtendedKeyUsagePurpose::ClientAuth,
446 ];
447 let cert = params.signed_by(&leaf_key, &ca.cert, &ca.key).unwrap();
448 CertificateDer::from(cert.der().to_vec())
449 }
450
451 #[test]
454 fn required_rejects_ca_signed_unbound_leaf() {
455 let leaf = unbound_dig_ca_leaf();
456 let v = DigClientCertVerifier::new(
457 None,
458 CapturedPeerId::default(),
459 BindingPolicy::Required,
460 CapturedBlsPub::default(),
461 );
462 let err = v
463 .verify_client_cert(&leaf, &[], UnixTime::now())
464 .expect_err("Required rejects an unbound leaf");
465 assert!(
466 format!("{err}").contains("BLS binding"),
467 "rejected on the binding, not the chain"
468 );
469 }
470
471 #[test]
474 fn required_accepts_bound_leaf_and_captures_identity() {
475 let sk = bls_sk("verify/bound");
476 let node = NodeCert::generate_signed(&sk).expect("node cert");
477 let leaf = CertificateDer::from(node.cert_der().to_vec());
478
479 let captured = CapturedPeerId::default();
480 let captured_bls = CapturedBlsPub::default();
481 let v = DigClientCertVerifier::new(
482 None,
483 captured.clone(),
484 BindingPolicy::Required,
485 captured_bls.clone(),
486 );
487 v.verify_client_cert(&leaf, &[], UnixTime::now())
488 .expect("a bound DIG-CA leaf verifies");
489 assert_eq!(captured.get(), Some(node.peer_id()));
490 assert_eq!(captured_bls.get(), Some(public_key_bytes(&sk)));
491 }
492
493 fn self_signed_bound_leaf(label: &str) -> (CertificateDer<'static>, PeerId, [u8; 48]) {
498 let sk = bls_sk(label);
499 let leaf_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap();
500 let mut params = CertificateParams::new(Vec::<String>::new()).unwrap();
501 let mut dn = DistinguishedName::new();
502 dn.push(DnType::CommonName, "peer.dig");
503 params.distinguished_name = dn;
504 params.subject_alt_names = vec![SanType::DnsName(
505 Ia5String::try_from("peer.dig".to_string()).unwrap(),
506 )];
507 params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
508 params.extended_key_usages = vec![
509 ExtendedKeyUsagePurpose::ServerAuth,
510 ExtendedKeyUsagePurpose::ClientAuth,
511 ];
512 crate::binding::attach_binding(&mut params, &leaf_key, &sk);
513 let cert = params.self_signed(&leaf_key).unwrap();
514 let der = CertificateDer::from(cert.der().to_vec());
515 let peer_id = peer_id_from_leaf_cert_der(der.as_ref()).unwrap();
516 (der, peer_id, public_key_bytes(&sk))
517 }
518
519 fn self_signed_unbound_leaf() -> (CertificateDer<'static>, PeerId) {
522 let leaf_key = KeyPair::generate_for(&PKCS_ECDSA_P256_SHA256).unwrap();
523 let mut params = CertificateParams::new(Vec::<String>::new()).unwrap();
524 let mut dn = DistinguishedName::new();
525 dn.push(DnType::CommonName, "peer.dig");
526 params.distinguished_name = dn;
527 params.subject_alt_names = vec![SanType::DnsName(
528 Ia5String::try_from("peer.dig".to_string()).unwrap(),
529 )];
530 params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
531 params.extended_key_usages = vec![
532 ExtendedKeyUsagePurpose::ServerAuth,
533 ExtendedKeyUsagePurpose::ClientAuth,
534 ];
535 let cert = params.self_signed(&leaf_key).unwrap();
536 let der = CertificateDer::from(cert.der().to_vec());
537 let peer_id = peer_id_from_leaf_cert_der(der.as_ref()).unwrap();
538 (der, peer_id)
539 }
540
541 #[test]
546 fn spki_pinned_accepts_self_signed_leaf() {
547 let (leaf, peer_id, _bls) = self_signed_bound_leaf("verify/spki-self-signed");
548
549 let captured = CapturedPeerId::default();
551 let v = DigServerCertVerifier::new_spki_pinned(
552 None,
553 captured.clone(),
554 BindingPolicy::Opportunistic,
555 CapturedBlsPub::default(),
556 );
557 let name = ServerName::try_from("peer.dig").unwrap();
558 v.verify_server_cert(&leaf, &[], &name, &[], UnixTime::now())
559 .expect("SPKI-pinned mode accepts a self-signed leaf");
560 assert_eq!(captured.get(), Some(peer_id));
561
562 let ca_v = DigServerCertVerifier::new(
564 None,
565 CapturedPeerId::default(),
566 BindingPolicy::Opportunistic,
567 CapturedBlsPub::default(),
568 );
569 let err = ca_v
570 .verify_server_cert(&leaf, &[], &name, &[], UnixTime::now())
571 .expect_err("CA-requiring mode rejects a self-signed leaf");
572 assert!(
573 format!("{err}").contains("DigNetwork CA"),
574 "rejected on the chain, not something else: {err}"
575 );
576 }
577
578 #[test]
581 fn spki_pinned_rejects_wrong_peer_id() {
582 let (leaf, _peer_id, _bls) = self_signed_bound_leaf("verify/spki-wrong-pin");
583 let wrong = PeerId::from_bytes([0x22u8; 32]);
584 let v = DigServerCertVerifier::new_spki_pinned(
585 Some(wrong),
586 CapturedPeerId::default(),
587 BindingPolicy::Opportunistic,
588 CapturedBlsPub::default(),
589 );
590 let name = ServerName::try_from("peer.dig").unwrap();
591 let err = v
592 .verify_server_cert(&leaf, &[], &name, &[], UnixTime::now())
593 .expect_err("a wrong-peer_id pin is rejected even in SPKI-pinned mode");
594 assert!(
595 format!("{err}").contains("peer_id mismatch"),
596 "rejected on the pin: {err}"
597 );
598 }
599
600 #[test]
604 fn spki_pinned_live_case_unbound_self_signed_under_opportunistic() {
605 let (leaf, peer_id) = self_signed_unbound_leaf();
606
607 let captured = CapturedPeerId::default();
609 let captured_bls = CapturedBlsPub::default();
610 let opp = DigClientCertVerifier::new_spki_pinned(
611 None,
612 captured.clone(),
613 BindingPolicy::Opportunistic,
614 captured_bls.clone(),
615 );
616 opp.verify_client_cert(&leaf, &[], UnixTime::now())
617 .expect("Opportunistic accepts an unbound self-signed leaf");
618 assert_eq!(captured.get(), Some(peer_id));
619 assert_eq!(captured_bls.get(), None);
620
621 let req = DigClientCertVerifier::new_spki_pinned(
623 None,
624 CapturedPeerId::default(),
625 BindingPolicy::Required,
626 CapturedBlsPub::default(),
627 );
628 let err = req
629 .verify_client_cert(&leaf, &[], UnixTime::now())
630 .expect_err("Required rejects an unbound leaf");
631 assert!(
632 format!("{err}").contains("BLS binding"),
633 "rejected on the binding, not the chain: {err}"
634 );
635 }
636
637 #[test]
639 fn foreign_ca_leaf_fails_chain() {
640 let foreign = crate::ca::generate_dig_ca(OffsetDateTime::now_utc()).unwrap();
641 let foreign_ca = DigCa::from_pem(&foreign.cert_pem, &foreign.key_pem).unwrap();
642 let node = NodeCert::generate_signed_by(
643 &foreign_ca,
644 &bls_sk("verify/foreign"),
645 OffsetDateTime::now_utc(),
646 )
647 .unwrap();
648 let leaf = CertificateDer::from(node.cert_der().to_vec());
649
650 let v = DigServerCertVerifier::new(
651 None,
652 CapturedPeerId::default(),
653 BindingPolicy::Off,
654 CapturedBlsPub::default(),
655 );
656 let name = ServerName::try_from("peer.dig").unwrap();
657 let err = v
658 .verify_server_cert(&leaf, &[], &name, &[], UnixTime::now())
659 .expect_err("foreign CA leaf is rejected");
660 assert!(format!("{err}").contains("DigNetwork CA"));
661 }
662}