1use fuchsia_hyper;
6use fuchsia_sync::Mutex;
7use hyper;
8use rustls::client::danger::{ServerCertVerified, ServerCertVerifier};
9use rustls::pki_types::{
10 CertificateDer, ServerName, SignatureVerificationAlgorithm, TrustAnchor, UnixTime,
11};
12use std::cell::RefCell;
13use std::sync::Arc;
14use thiserror::Error;
15
16type DateTime = chrono::DateTime<chrono::FixedOffset>;
17#[derive(Debug, PartialEq, Clone, Copy, Hash, Eq)]
18pub enum HttpsDateErrorType {
19 InvalidHostname,
20 SchemeNotHttps,
21 NoCertificatesPresented,
22 NetworkError,
23 NoDateInResponse,
24 InvalidCertificateChain,
25 CorruptLeafCertificate,
26 DateFormatError,
27}
28
29#[derive(Error)]
31pub struct HttpsDateError {
32 error_type: HttpsDateErrorType,
34 source: Option<anyhow::Error>,
36}
37
38impl HttpsDateError {
39 pub fn new(error_type: HttpsDateErrorType) -> Self {
41 Self { error_type, source: None }
42 }
43
44 pub fn with_source(mut self, source: anyhow::Error) -> Self {
46 self.source = Some(source);
47 self
48 }
49
50 pub fn error_type(&self) -> HttpsDateErrorType {
51 self.error_type
52 }
53}
54
55trait HttpsDateResultExt<T> {
57 fn httpsdate_err(self, error_type: HttpsDateErrorType) -> Result<T, HttpsDateError>;
59}
60
61impl<T, E> HttpsDateResultExt<T> for Result<T, E>
62where
63 E: std::error::Error + Send + Sync + 'static,
64{
65 fn httpsdate_err(self, error_type: HttpsDateErrorType) -> Result<T, HttpsDateError> {
66 self.map_err(|e| HttpsDateError::new(error_type).with_source(anyhow::Error::new(e)))
67 }
68}
69
70impl std::fmt::Debug for HttpsDateError {
72 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self.source.as_ref() {
74 None => self.error_type.fmt(formatter),
75 Some(source) => {
76 formatter.write_fmt(format_args!("{:?}: {:?}", self.error_type, source))
77 }
78 }
79 }
80}
81
82impl std::fmt::Display for HttpsDateError {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 std::fmt::Debug::fmt(self, f)
85 }
86}
87
88static ALLOWED_SIG_ALGS: &[&dyn SignatureVerificationAlgorithm] = &[
90 webpki::ring::ECDSA_P256_SHA256,
91 webpki::ring::ECDSA_P256_SHA384,
92 webpki::ring::ECDSA_P384_SHA256,
93 webpki::ring::ECDSA_P384_SHA384,
94 webpki::ring::RSA_PKCS1_2048_8192_SHA256,
95 webpki::ring::RSA_PKCS1_2048_8192_SHA384,
96 webpki::ring::RSA_PKCS1_2048_8192_SHA512,
97 webpki::ring::RSA_PKCS1_3072_8192_SHA384,
98];
99
100#[derive(Default, Debug)]
104struct RecordingVerifier {
105 presented_certs: Mutex<RefCell<Vec<CertificateDer<'static>>>>,
106}
107
108impl RecordingVerifier {
109 pub fn verify(
112 &self,
113 dns_name: &ServerName<'_>,
114 time: UnixTime,
115 trust_anchors: &'static [TrustAnchor<'static>],
116 ) -> Result<(), HttpsDateError> {
117 let presented_certs = self.presented_certs.lock();
118 let presented_certs = presented_certs.borrow();
119 if presented_certs.len() == 0 {
120 return Err(HttpsDateError::new(HttpsDateErrorType::NoCertificatesPresented));
121 };
122
123 let leaf = webpki::EndEntityCert::try_from(&presented_certs[0])
124 .httpsdate_err(HttpsDateErrorType::CorruptLeafCertificate)?;
125
126 leaf.verify_for_usage(
127 ALLOWED_SIG_ALGS,
128 trust_anchors,
129 &presented_certs[1..],
130 time,
131 webpki::KeyUsage::server_auth(),
132 None,
133 None,
134 )
135 .httpsdate_err(HttpsDateErrorType::InvalidCertificateChain)?;
136
137 leaf.verify_is_valid_for_subject_name(dns_name)
138 .httpsdate_err(HttpsDateErrorType::InvalidCertificateChain)
139 }
140}
141
142impl ServerCertVerifier for RecordingVerifier {
143 fn verify_server_cert(
144 &self,
145 end_entity: &CertificateDer<'_>,
146 intermediates: &[CertificateDer<'_>],
147 _server_name: &ServerName<'_>,
148 _ocsp_response: &[u8],
149 _now: UnixTime,
150 ) -> Result<ServerCertVerified, rustls::Error> {
151 let mut presented_certs = Vec::with_capacity(1 + intermediates.len());
154 presented_certs.push(end_entity.clone().into_owned());
155 presented_certs.extend(intermediates.iter().cloned().map(|c| c.into_owned()));
156 *self.presented_certs.lock().borrow_mut() = presented_certs;
157 Ok(ServerCertVerified::assertion())
158 }
159
160 fn verify_tls12_signature(
161 &self,
162 message: &[u8],
163 cert: &CertificateDer<'_>,
164 dss: &rustls::DigitallySignedStruct,
165 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
166 rustls::crypto::verify_tls12_signature(
167 message,
168 cert,
169 dss,
170 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
171 )
172 }
173
174 fn verify_tls13_signature(
175 &self,
176 message: &[u8],
177 cert: &CertificateDer<'_>,
178 dss: &rustls::DigitallySignedStruct,
179 ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
180 rustls::crypto::verify_tls13_signature(
181 message,
182 cert,
183 dss,
184 &rustls::crypto::ring::default_provider().signature_verification_algorithms,
185 )
186 }
187
188 fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
189 rustls::crypto::ring::default_provider()
190 .signature_verification_algorithms
191 .supported_schemes()
192 }
193}
194
195pub struct NetworkTimeClient {
197 verifier: Arc<RecordingVerifier>,
199 trust_anchors: &'static [TrustAnchor<'static>],
201 client: fuchsia_hyper::HttpsClient,
203}
204
205impl NetworkTimeClient {
206 pub fn new() -> Self {
209 Self::new_with_trust_anchors(&webpki_roots_fuchsia::TLS_SERVER_ROOTS)
210 }
211
212 fn new_with_trust_anchors(trust_anchors: &'static [TrustAnchor<'static>]) -> Self {
213 let mut root_store = rustls::RootCertStore::empty();
214 root_store.extend(trust_anchors.iter().cloned());
215
216 let verifier = Arc::new(RecordingVerifier::default());
220 let mut config = rustls::ClientConfig::builder()
221 .with_root_certificates(root_store)
222 .with_no_client_auth();
223
224 config
225 .dangerous()
226 .set_certificate_verifier(Arc::clone(&verifier) as Arc<dyn ServerCertVerifier>);
227
228 let client = fuchsia_hyper::new_https_client_dangerous(config, Default::default());
229
230 NetworkTimeClient { verifier, client, trust_anchors }
231 }
232
233 pub async fn get_network_time(&mut self, uri: hyper::Uri) -> Result<DateTime, HttpsDateError> {
254 match uri.scheme_str() {
255 Some("https") => (),
256 _ => return Err(HttpsDateError::new(HttpsDateErrorType::SchemeNotHttps)),
257 }
258 let dns_name = match uri.host() {
259 Some(host) => ServerName::try_from(host)
260 .map_err(|_| HttpsDateError::new(HttpsDateErrorType::InvalidHostname))?
261 .to_owned(),
262 None => return Err(HttpsDateError::new(HttpsDateErrorType::InvalidHostname)),
263 };
264
265 let response =
266 self.client.get(uri.clone()).await.httpsdate_err(HttpsDateErrorType::NetworkError)?;
267
268 let date_header: String = match response.headers().get("date") {
276 Some(date) => {
277 date.to_str().httpsdate_err(HttpsDateErrorType::DateFormatError)?.to_string()
278 }
279 _ => return Err(HttpsDateError::new(HttpsDateErrorType::NoDateInResponse)),
280 };
281
282 let response_time = DateTime::parse_from_rfc2822(&date_header)
284 .httpsdate_err(HttpsDateErrorType::DateFormatError)?;
285 if response_time.timezone().utc_minus_local() != 0 {
286 return Err(HttpsDateError::new(HttpsDateErrorType::DateFormatError));
287 }
288
289 let webpki_time = UnixTime::since_unix_epoch(std::time::Duration::from_secs(
291 response_time.timestamp() as u64,
292 ));
293 self.verifier.verify(&dns_name, webpki_time, self.trust_anchors)?;
294 Ok(response_time)
295 }
296}
297
298#[cfg(test)]
299mod test {
300 use super::*;
301 use base64::engine::Engine as _;
302 use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
303 use fuchsia_async as fasync;
304 use futures::future::ready;
305 use futures::stream::StreamExt;
306 use hyper::{Response, StatusCode};
307 use std::convert::Infallible;
308 use std::net::{Ipv6Addr, SocketAddr};
309 use std::sync::LazyLock;
310
311 static TEST_CERT_CHAIN: LazyLock<Vec<CertificateDer<'static>>> = LazyLock::new(|| {
312 parse_pem(&include_str!("../certs/server.certchain"))
313 .into_iter()
314 .map(CertificateDer::from)
315 .collect()
316 });
317 static TEST_PRIVATE_KEY: LazyLock<rustls::pki_types::PrivateKeyDer<'static>> =
318 LazyLock::new(|| {
319 rustls::pki_types::PrivateKeyDer::Pkcs1(
320 parse_pem(&include_str!("../certs/server.rsa")).pop().unwrap().into(),
321 )
322 });
323 static CERT_NOT_BEFORE: LazyLock<DateTime> = LazyLock::new(|| {
324 DateTime::parse_from_rfc3339(include_str!("../certs/notbefore").trim()).unwrap()
325 });
326 static CERT_NOT_AFTER: LazyLock<DateTime> = LazyLock::new(|| {
327 DateTime::parse_from_rfc3339(include_str!("../certs/notafter").trim()).unwrap()
328 });
329 static TEST_CERT_ROOT: LazyLock<CertificateDer<'static>> = LazyLock::new(|| {
330 CertificateDer::from(parse_pem(&include_str!("../certs/ca.cert")).pop().unwrap())
331 });
332 static TEST_TRUST_ANCHORS: LazyLock<Vec<TrustAnchor<'static>>> = LazyLock::new(|| {
333 vec![webpki::anchor_from_trusted_cert(&TEST_CERT_ROOT).unwrap().to_owned()]
334 });
335
336 fn serve_fake(served_time: DateTime) -> u16 {
340 let addr = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0);
341 let listener = fasync::net::TcpListener::bind(&addr).unwrap();
342 let server_port = listener.local_addr().unwrap().port();
343
344 let tls_config = rustls::ServerConfig::builder()
346 .with_no_client_auth()
347 .with_single_cert(TEST_CERT_CHAIN.clone(), TEST_PRIVATE_KEY.clone_key())
348 .unwrap();
349
350 let tls_acceptor = tokio_rustls::TlsAcceptor::from(Arc::new(tls_config));
351 let served_time_arc = Arc::new(served_time);
352
353 fasync::Task::spawn(async move {
354 let mut listener = listener;
355 loop {
356 match listener.accept().await {
357 Ok((next_listener, conn, _)) => {
358 listener = next_listener;
359 let tls_acceptor = tls_acceptor.clone();
360 let time_arc = Arc::clone(&served_time_arc);
361 fasync::Task::spawn(async move {
362 if let Ok(tls_stream) =
363 tls_acceptor.accept(fuchsia_hyper::TcpStream { stream: conn }).await
364 {
365 let io = hyper_util::rt::TokioIo::new(tls_stream);
366 let service = hyper::service::service_fn(
367 move |_req: hyper::Request<hyper::body::Incoming>| {
368 let time = Arc::clone(&time_arc);
369 ready(Ok::<_, Infallible>(
370 Response::builder()
371 .header("Date", time.to_rfc2822())
372 .status(StatusCode::OK)
373 .body(http_body_util::Full::new(
374 hyper::body::Bytes::from(""),
375 ))
376 .unwrap(),
377 ))
378 },
379 );
380 let _ = hyper_util::server::conn::auto::Builder::new(
381 fuchsia_hyper::Executor,
382 )
383 .serve_connection(io, service)
384 .await;
385 }
386 })
387 .detach();
388 }
389 Err(_) => break,
390 }
391 }
392 })
393 .detach();
394
395 server_port
396 }
397
398 fn serve_crash() -> u16 {
400 let addr = SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0);
401 let listener = fasync::net::TcpListener::bind(&addr).unwrap();
402 let server_port = listener.local_addr().unwrap().port();
403
404 let connection_dropper =
405 listener.accept_stream().for_each(|conn_result| ready(drop(conn_result)));
406
407 fasync::Task::spawn(connection_dropper).detach();
408
409 server_port
410 }
411
412 fn parse_pem(contents: &str) -> Vec<Vec<u8>> {
414 let mut parsed = vec![];
416 let mut current_encoded = vec![];
417 for line in contents.split('\n') {
418 if line.starts_with("-----BEGIN") {
419 ()
420 } else if line.starts_with("-----END") {
421 let encoded = current_encoded.join("");
422 current_encoded = vec![];
423 parsed.push(BASE64_STANDARD.decode(&encoded).unwrap());
424 } else {
425 current_encoded.push(line.trim());
426 }
427 }
428 parsed
429 }
430
431 #[fuchsia::test]
432 async fn test_get_network_time() {
433 let set_time = *CERT_NOT_BEFORE + chrono::Duration::days(1);
434 let open_port = serve_fake(set_time.clone());
435
436 let mut client = NetworkTimeClient::new_with_trust_anchors(&TEST_TRUST_ANCHORS);
437
438 let url = format!("https://localhost:{}/", open_port).parse::<hyper::Uri>().unwrap();
439 let date = client.get_network_time(url).await.unwrap();
440 assert_eq!(date, set_time);
441 }
442
443 #[fuchsia::test]
444 async fn test_network_err() {
445 let open_port = serve_crash();
446
447 let mut client = NetworkTimeClient::new_with_trust_anchors(&TEST_TRUST_ANCHORS);
448
449 let url = format!("https://localhost:{}/", open_port).parse::<hyper::Uri>().unwrap();
450 assert_eq!(
451 client.get_network_time(url).await.unwrap_err().error_type(),
452 HttpsDateErrorType::NetworkError
453 );
454 }
455
456 #[fuchsia::test]
457 async fn test_untrusted_cert() {
458 let time = *CERT_NOT_BEFORE + chrono::Duration::days(1);
459 let open_port = serve_fake(time);
460
461 let mut client =
464 NetworkTimeClient::new_with_trust_anchors(&webpki_roots_fuchsia::TLS_SERVER_ROOTS);
465
466 let url = format!("https://localhost:{}/", open_port).parse::<hyper::Uri>().unwrap();
467 assert_eq!(
468 client.get_network_time(url).await.unwrap_err().error_type(),
469 HttpsDateErrorType::InvalidCertificateChain
470 );
471 }
472
473 #[fuchsia::test]
474 async fn test_time_after_cert_expired() {
475 let time = *CERT_NOT_AFTER + chrono::Duration::days(2);
476 let open_port = serve_fake(time);
477
478 let mut client = NetworkTimeClient::new_with_trust_anchors(&TEST_TRUST_ANCHORS);
479
480 let url = format!("https://localhost:{}/", open_port).parse::<hyper::Uri>().unwrap();
481 assert_eq!(
482 client.get_network_time(url).await.unwrap_err().error_type(),
483 HttpsDateErrorType::InvalidCertificateChain
484 );
485 }
486
487 #[fuchsia::test]
488 async fn test_http_rejected() {
489 let mut client = NetworkTimeClient::new_with_trust_anchors(&TEST_TRUST_ANCHORS);
490 let url = "http://localhost/".parse::<hyper::Uri>().unwrap();
491 assert_eq!(
492 client.get_network_time(url).await.unwrap_err().error_type(),
493 HttpsDateErrorType::SchemeNotHttps
494 );
495 }
496
497 #[fuchsia::test]
498 async fn test_bad_timezone() {
499 let set_time = (*CERT_NOT_BEFORE + chrono::Duration::days(1))
500 .with_timezone(&chrono::FixedOffset::east_opt(1 * 60 * 60).unwrap());
501 let open_port = serve_fake(set_time.clone());
502
503 let mut client = NetworkTimeClient::new_with_trust_anchors(&TEST_TRUST_ANCHORS);
504
505 let url = format!("https://localhost:{}/", open_port).parse::<hyper::Uri>().unwrap();
506 assert_eq!(
507 client.get_network_time(url).await.unwrap_err().error_type(),
508 HttpsDateErrorType::DateFormatError
509 );
510 }
511}