|
1 | | -//! Dangerous TLS implementation of accepting invalid certificates for Rustls. |
| 1 | +//! Custom TLS verification. |
| 2 | +//! |
| 3 | +//! We want to accept expired certificates. |
2 | 4 |
|
| 5 | +use rustls::RootCertStore; |
| 6 | +use rustls::client::{verify_server_cert_signed_by_trust_anchor, verify_server_name}; |
3 | 7 | use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; |
| 8 | +use rustls::server::ParsedCertificate; |
4 | 9 | use tokio_rustls::rustls; |
5 | 10 |
|
| 11 | +use crate::net::tls::spki::spki_hash; |
| 12 | + |
6 | 13 | #[derive(Debug)] |
7 | | -pub(super) struct NoCertificateVerification(); |
| 14 | +pub(super) struct CustomCertificateVerifier { |
| 15 | + /// Root certificates. |
| 16 | + root_cert_store: RootCertStore, |
| 17 | + |
| 18 | + /// Expected SPKI hash as a base64 of SHA-256. |
| 19 | + spki_hash: Option<String>, |
| 20 | +} |
8 | 21 |
|
9 | | -impl NoCertificateVerification { |
10 | | - pub(super) fn new() -> Self { |
11 | | - Self() |
| 22 | +impl CustomCertificateVerifier { |
| 23 | + pub(super) fn new(spki_hash: Option<String>) -> Self { |
| 24 | + let root_cert_store = |
| 25 | + RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); |
| 26 | + Self { |
| 27 | + root_cert_store, |
| 28 | + spki_hash, |
| 29 | + } |
12 | 30 | } |
13 | 31 | } |
14 | 32 |
|
15 | | -impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { |
| 33 | +impl rustls::client::danger::ServerCertVerifier for CustomCertificateVerifier { |
16 | 34 | fn verify_server_cert( |
17 | 35 | &self, |
18 | | - _end_entity: &CertificateDer<'_>, |
19 | | - _intermediates: &[CertificateDer<'_>], |
20 | | - _server_name: &ServerName<'_>, |
| 36 | + end_entity: &CertificateDer<'_>, |
| 37 | + intermediates: &[CertificateDer<'_>], |
| 38 | + server_name: &ServerName<'_>, |
21 | 39 | _ocsp_response: &[u8], |
22 | | - _now: UnixTime, |
| 40 | + now: UnixTime, |
23 | 41 | ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> { |
| 42 | + let parsed_certificate = ParsedCertificate::try_from(end_entity)?; |
| 43 | + |
| 44 | + let spki = parsed_certificate.subject_public_key_info(); |
| 45 | + |
| 46 | + let provider = rustls::crypto::ring::default_provider(); |
| 47 | + |
| 48 | + if let ServerName::DnsName(dns_name) = server_name |
| 49 | + && dns_name.as_ref().starts_with("_") |
| 50 | + { |
| 51 | + // Do not verify certificates for hostnames starting with `_`. |
| 52 | + // They are used for servers with self-signed certificates, e.g. for local testing. |
| 53 | + // Hostnames starting with `_` can have only self-signed TLS certificates or wildcard certificates. |
| 54 | + // It is not possible to get valid non-wildcard TLS certificates because CA/Browser Forum requirements |
| 55 | + // explicitly state that domains should start with a letter, digit or hyphen: |
| 56 | + // https://github.com/cabforum/servercert/blob/24f38fd4765e019db8bb1a8c56bf63c7115ce0b0/docs/BR.md |
| 57 | + } else if let Some(hash) = &self.spki_hash |
| 58 | + && spki_hash(&spki) == *hash |
| 59 | + { |
| 60 | + // Last time we successfully connected to this hostname with TLS checks, |
| 61 | + // SPKI had this hash. |
| 62 | + // It does not matter if certificate has now expired. |
| 63 | + } else { |
| 64 | + // verify_server_cert_signed_by_trust_anchor does no revocation checking: |
| 65 | + // <https://docs.rs/rustls/0.23.37/rustls/client/fn.verify_server_cert_signed_by_trust_anchor.html> |
| 66 | + // We don't do it either. |
| 67 | + verify_server_cert_signed_by_trust_anchor( |
| 68 | + &parsed_certificate, |
| 69 | + &self.root_cert_store, |
| 70 | + intermediates, |
| 71 | + now, |
| 72 | + provider.signature_verification_algorithms.all, |
| 73 | + )?; |
| 74 | + } |
| 75 | + |
| 76 | + // Verify server name unconditionally. |
| 77 | + // |
| 78 | + // We do this even for self-signed certificates when hostname starts with `_` |
| 79 | + // so we don't try to connect to captive portals |
| 80 | + // and fail on MITM certificates if they are generated once |
| 81 | + // and reused for all hostnames. |
| 82 | + verify_server_name(&parsed_certificate, server_name)?; |
24 | 83 | Ok(rustls::client::danger::ServerCertVerified::assertion()) |
25 | 84 | } |
26 | 85 |
|
|
0 commit comments