1use crate::crypto::cert::{fingerprint_from_cert_der, verify_cert_from_der};
4use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
5use rustls::client::WebPkiServerVerifier;
6use rustls::pki_types::pem::PemObject;
7use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
8use rustls::server::danger::{ClientCertVerified, ClientCertVerifier};
9use rustls::server::WebPkiClientVerifier;
10use rustls::{
11 CertificateError, DigitallySignedStruct, DistinguishedName, Error, OtherError, RootCertStore,
12 SignatureScheme,
13};
14use std::fmt::{Debug, Display, Formatter};
15use std::sync::Arc;
16
17#[derive(Clone, Eq, PartialEq)]
19pub struct RejectedCert(pub String);
20
21impl Display for RejectedCert {
22 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{}", self.0)
24 }
25}
26
27impl Debug for RejectedCert {
28 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
29 write!(f, "{}", self.0)
30 }
31}
32
33impl std::error::Error for RejectedCert {}
34
35fn rejected(reason: String) -> Error {
36 tracing::warn!("{reason}");
37 Error::InvalidCertificate(CertificateError::Other(OtherError(Arc::new(RejectedCert(
38 reason,
39 )))))
40}
41
42#[derive(Debug)]
52pub struct PinnedServerCertVerifier {
53 inner: Arc<dyn ServerCertVerifier>,
54 expected_fingerprint: Option<String>,
55}
56
57impl PinnedServerCertVerifier {
58 pub fn new(own_cert_pem: &str, expected_fingerprint: Option<String>) -> anyhow::Result<Self> {
63 let mut root_store = RootCertStore::empty();
64 root_store.add(CertificateDer::from_pem_slice(own_cert_pem.as_bytes())?)?;
65
66 let inner = WebPkiServerVerifier::builder(Arc::new(root_store)).build()?;
67 Ok(Self {
68 inner,
69 expected_fingerprint: expected_fingerprint.map(|f| f.to_ascii_uppercase()),
70 })
71 }
72
73 pub fn expected_fingerprint(&self) -> Option<&str> {
75 self.expected_fingerprint.as_deref()
76 }
77}
78
79impl ServerCertVerifier for PinnedServerCertVerifier {
80 fn verify_server_cert(
81 &self,
82 end_entity: &CertificateDer<'_>,
83 _intermediates: &[CertificateDer<'_>],
84 _server_name: &ServerName<'_>,
85 _ocsp_response: &[u8],
86 _now: UnixTime,
87 ) -> Result<ServerCertVerified, Error> {
88 verify_cert_from_der(end_entity.as_ref(), None)
90 .map_err(|e| rejected(format!("Server certificate is not valid: {e:#}")))?;
91
92 if let Some(expected) = &self.expected_fingerprint {
93 let actual = fingerprint_from_cert_der(end_entity.as_ref());
94 if actual != *expected {
95 return Err(rejected(format!(
96 "Server certificate fingerprint mismatch: expected {expected}, got {actual}"
97 )));
98 }
99 }
100
101 Ok(ServerCertVerified::assertion())
102 }
103
104 fn verify_tls12_signature(
105 &self,
106 message: &[u8],
107 cert: &CertificateDer<'_>,
108 dss: &DigitallySignedStruct,
109 ) -> Result<HandshakeSignatureValid, Error> {
110 self.inner.verify_tls12_signature(message, cert, dss)
111 }
112
113 fn verify_tls13_signature(
114 &self,
115 message: &[u8],
116 cert: &CertificateDer<'_>,
117 dss: &DigitallySignedStruct,
118 ) -> Result<HandshakeSignatureValid, Error> {
119 self.inner.verify_tls13_signature(message, cert, dss)
120 }
121
122 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
123 self.inner.supported_verify_schemes()
124 }
125}
126
127#[derive(Debug)]
132pub struct PinnedClientCertVerifier {
133 inner: Arc<dyn ClientCertVerifier>,
134 mandatory: bool,
135 expected_fingerprint: Option<String>,
136}
137
138impl PinnedClientCertVerifier {
139 pub fn new(
143 own_cert_pem: &str,
144 mandatory: bool,
145 expected_fingerprint: Option<String>,
146 ) -> anyhow::Result<Self> {
147 let mut root_store = RootCertStore::empty();
148 root_store.add(CertificateDer::from_pem_slice(own_cert_pem.as_bytes())?)?;
149
150 let inner = WebPkiClientVerifier::builder(Arc::new(root_store)).build()?;
151 Ok(Self {
152 inner,
153 mandatory,
154 expected_fingerprint: expected_fingerprint.map(|f| f.to_ascii_uppercase()),
155 })
156 }
157
158 pub fn is_mandatory(&self) -> bool {
160 self.mandatory
161 }
162
163 pub fn expected_fingerprint(&self) -> Option<&str> {
165 self.expected_fingerprint.as_deref()
166 }
167}
168
169impl ClientCertVerifier for PinnedClientCertVerifier {
170 fn offer_client_auth(&self) -> bool {
171 true
172 }
173
174 fn client_auth_mandatory(&self) -> bool {
175 self.mandatory
176 }
177
178 fn root_hint_subjects(&self) -> &[DistinguishedName] {
179 self.inner.root_hint_subjects()
180 }
181
182 fn verify_client_cert(
183 &self,
184 end_entity: &CertificateDer<'_>,
185 _intermediates: &[CertificateDer<'_>],
186 _now: UnixTime,
187 ) -> Result<ClientCertVerified, Error> {
188 verify_cert_from_der(end_entity.as_ref(), None)
189 .map_err(|e| rejected(format!("Client certificate is not valid: {e:#}")))?;
190
191 if let Some(expected) = &self.expected_fingerprint {
192 let actual = fingerprint_from_cert_der(end_entity.as_ref());
193 if actual != *expected {
194 return Err(rejected(format!(
195 "Client certificate fingerprint mismatch: expected {expected}, got {actual}"
196 )));
197 }
198 }
199
200 Ok(ClientCertVerified::assertion())
201 }
202
203 fn verify_tls12_signature(
204 &self,
205 message: &[u8],
206 cert: &CertificateDer<'_>,
207 dss: &DigitallySignedStruct,
208 ) -> Result<HandshakeSignatureValid, Error> {
209 self.inner.verify_tls12_signature(message, cert, dss)
210 }
211
212 fn verify_tls13_signature(
213 &self,
214 message: &[u8],
215 cert: &CertificateDer<'_>,
216 dss: &DigitallySignedStruct,
217 ) -> Result<HandshakeSignatureValid, Error> {
218 self.inner.verify_tls13_signature(message, cert, dss)
219 }
220
221 fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
222 self.inner.supported_verify_schemes()
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use crate::crypto::cert::generate_self_signed;
230
231 static SPEC_CERT: &str = "-----BEGIN CERTIFICATE-----\n\
232MIIDGTCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQsFADBQMRcwFQYDVQQDEw5Mb2Nh\n\
233bFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQLEwAxCTAHBgNVBAcTADEJMAcG\n\
234A1UECBMAMQkwBwYDVQQGEwAwHhcNMjUwMjA5MDAwMzE0WhcNMzUwMjA3MDAwMzE0\n\
235WjBQMRcwFQYDVQQDEw5Mb2NhbFNlbmQgVXNlcjEJMAcGA1UEChMAMQkwBwYDVQQL\n\
236EwAxCTAHBgNVBAcTADEJMAcGA1UECBMAMQkwBwYDVQQGEwAwggEiMA0GCSqGSIb3\n\
237DQEBAQUAA4IBDwAwggEKAoIBAQCL24MxhGfrdJm0Q8ZGiBkZ27ldcEChB4w7rSbJ\n\
238yiKeosoNbJl2kyj5dZjfBhWGgDLGDMM5w+Mh/5SrWgTL/QrhbB+lsrxILLznWqBi\n\
239R8wJP0P2YW9fBahQskJQcUXt/3jsCsMTWea4rWc3HZGh03bAkJfLM+PDSOfTpvAZ\n\
2406DQSp9QLzC9bgVNnq3W0SvOZGpF0xRa4InCyTUgxsNsV4+GIrmN5w4EbRFVVYu7D\n\
2415OS5fxNSCukiS0fb6oQzUp0vIAycvvWHHbAy8T6UMoUor2nfvNcryiaOX5WBMLyh\n\
242yMZ5gMOyXjdm1bT1XSlvtXPYUzxvsGAzTqS8mXjw8h7mm5htAgMBAAEwDQYJKoZI\n\
243hvcNAQELBQADggEBABZ+I7D6wkeSrsi1NBLP2zoZ5oGh+INNcGTravfOQHs4Fbas\n\
244/CysaUYjsD3fmaDh4MxgWEqAmWnnBiojfpGX2SGuFqRBKyT9DgitBt0L7Ezg1k3h\n\
245bfSiFW4hXWp75grVO8xfML7ZcWMlhKrOsOMUGiy1qs3qsyJ3w7B2Tz78HhXGO5dd\n\
246jyPmZarhixKO92UpEvKGxjO0E/3UUNUzxKTAAgFfhKpuwHUgIijM/EppZtA8OcSh\n\
247fEztiV0xKfcPVx4d6dqRt/NMElK1Ivw2vUuxTymphZkkFOzht9m73/kyKaeFp8Ij\n\
248VRus1zGVD8IVpIdPMyz01WJyS7M0fWaHXKWo+Bo=\n\
249-----END CERTIFICATE-----";
250
251 const SPEC_FINGERPRINT: &str =
252 "4BADDE53A7F7CDEEED93189FD898E02BF6B4806CA4C05DE0ACE08319B86552FA";
253
254 fn init_provider() {
255 let _ = rustls::crypto::ring::default_provider().install_default();
256 }
257
258 #[test]
259 fn test_server_verifier_accepts_matching_fingerprint() {
260 init_provider();
261 let verifier =
262 PinnedServerCertVerifier::new(SPEC_CERT, Some(SPEC_FINGERPRINT.to_string())).unwrap();
263
264 let cert_der = CertificateDer::from_pem_slice(SPEC_CERT.as_bytes()).unwrap();
265 let server_name = ServerName::try_from("192.168.1.50").unwrap();
266
267 let res = verifier.verify_server_cert(
268 &cert_der,
269 &[],
270 &server_name,
271 &[],
272 UnixTime::now(),
273 );
274 assert!(res.is_ok());
275 }
276
277 #[test]
278 fn test_server_verifier_accepts_case_insensitive_fingerprint() {
279 init_provider();
280 let lowercase_fp = SPEC_FINGERPRINT.to_ascii_lowercase();
281 let verifier =
282 PinnedServerCertVerifier::new(SPEC_CERT, Some(lowercase_fp)).unwrap();
283
284 let cert_der = CertificateDer::from_pem_slice(SPEC_CERT.as_bytes()).unwrap();
285 let server_name = ServerName::try_from("192.168.1.50").unwrap();
286
287 let res = verifier.verify_server_cert(
288 &cert_der,
289 &[],
290 &server_name,
291 &[],
292 UnixTime::now(),
293 );
294 assert!(res.is_ok());
295 }
296
297 #[test]
298 fn test_server_verifier_rejects_mismatching_fingerprint() {
299 init_provider();
300 let rogue_fp = "0".repeat(64);
301 let verifier =
302 PinnedServerCertVerifier::new(SPEC_CERT, Some(rogue_fp.clone())).unwrap();
303
304 let cert_der = CertificateDer::from_pem_slice(SPEC_CERT.as_bytes()).unwrap();
305 let server_name = ServerName::try_from("192.168.1.50").unwrap();
306
307 let err = verifier
308 .verify_server_cert(&cert_der, &[], &server_name, &[], UnixTime::now())
309 .unwrap_err();
310 let msg = format!("{err:?}");
311 assert!(msg.contains("fingerprint mismatch"), "{msg}");
312 assert!(msg.contains(&rogue_fp), "{msg}");
313 assert!(msg.contains(SPEC_FINGERPRINT), "{msg}");
314 }
315
316 #[test]
317 fn test_server_verifier_accepts_any_valid_cert_when_unpinned() {
318 init_provider();
319 let verifier = PinnedServerCertVerifier::new(SPEC_CERT, None).unwrap();
320
321 let cert_der = CertificateDer::from_pem_slice(SPEC_CERT.as_bytes()).unwrap();
322 let server_name = ServerName::try_from("192.168.1.50").unwrap();
323
324 let res = verifier.verify_server_cert(
325 &cert_der,
326 &[],
327 &server_name,
328 &[],
329 UnixTime::now(),
330 );
331 assert!(res.is_ok());
332 }
333
334 #[test]
335 fn test_client_verifier_accepts_matching_fingerprint() {
336 init_provider();
337 let verifier =
338 PinnedClientCertVerifier::new(SPEC_CERT, true, Some(SPEC_FINGERPRINT.to_string()))
339 .unwrap();
340
341 assert!(verifier.is_mandatory());
342 let cert_der = CertificateDer::from_pem_slice(SPEC_CERT.as_bytes()).unwrap();
343
344 let res = verifier.verify_client_cert(&cert_der, &[], UnixTime::now());
345 assert!(res.is_ok());
346 }
347
348 #[test]
349 fn test_client_verifier_rejects_mismatched_fingerprint() {
350 init_provider();
351 let verifier =
352 PinnedClientCertVerifier::new(SPEC_CERT, true, Some("A".repeat(64))).unwrap();
353
354 let cert_der = CertificateDer::from_pem_slice(SPEC_CERT.as_bytes()).unwrap();
355 let err = verifier.verify_client_cert(&cert_der, &[], UnixTime::now()).unwrap_err();
356 let msg = format!("{err:?}");
357 assert!(msg.contains("fingerprint mismatch"), "{msg}");
358 }
359
360 #[test]
361 fn test_client_verifier_with_newly_generated_identity() {
362 init_provider();
363 let identity = generate_self_signed().unwrap();
364 let verifier = PinnedClientCertVerifier::new(
365 &identity.cert_pem,
366 false,
367 Some(identity.fingerprint.clone()),
368 )
369 .unwrap();
370
371 assert!(!verifier.is_mandatory());
372 let cert_der = CertificateDer::from_pem_slice(identity.cert_pem.as_bytes()).unwrap();
373 assert!(verifier.verify_client_cert(&cert_der, &[], UnixTime::now()).is_ok());
374 }
375}