Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_net/src/tls.rs

27.8 KiB, 96 runs

created by r1870400018:13329, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Client-side TLS plumbing shared by the protocol clients.
2//!
3//! Every outbound protocol client in this crate faces the same two
4//! problems: it must be able to speak plain TCP and TLS over one socket
5//! (because STARTTLS upgrades in place), and it must validate the peer
6//! against the host's trust anchors. [`ClientStream`] solves the first
7//! and [`default_client_config`] the second, so SMTP, IMAP and anything
8//! that follows share one implementation rather than each carrying its
9//! own copy.
10//!
11//! Server-side TLS is a different concrete type -- `tokio_rustls`
12//! distinguishes the client and server halves of a `TlsStream` -- so the
13//! SMTP and IMAP servers keep their own `MaybeTls` and are unaffected.
14//!
15//! [Written with AI entirely](https://need2know.ai/entirely-ai/code)\
16//! Anthropic Claude
17
18use oxedyne_fe2o3_core::prelude::*;
19
20use std::{
21 pin::Pin,
22 sync::Arc,
23 task::{
24 Context,
25 Poll,
26 },
27};
28
29use std::time::Duration;
30
31use tokio::{
32 io::{
33 AsyncRead,
34 AsyncWrite,
35 ReadBuf,
36 },
37 net::TcpStream,
38 sync::Semaphore,
39};
40use tokio_rustls::{
41 rustls::{
42 ClientConfig,
43 pki_types::{
44 CertificateDer,
45 ServerName,
46 },
47 RootCertStore,
48 },
49 TlsAcceptor,
50 TlsConnector,
51};
52
53
54// ┌───────────────────────────────────────────────────────────────────────────┐
55// │ BOUNDED SERVER-SIDE TLS ACCEPTOR │
56// └───────────────────────────────────────────────────────────────────────────┘
57
58/// The result of a bounded handshake, distinguishing the two ways it can fail.
59///
60/// A `TimedOut` is an admission event -- the permit wait or the handshake ran
61/// past the deadline -- and a caller that keeps admission counters records it as
62/// a drop. A `Failed` is an ordinary broken handshake (a client that hung up, a
63/// malformed record) and is logged, not counted against admission.
64pub enum Handshake<IO> {
65 Ok(tokio_rustls::server::TlsStream<IO>),
66 TimedOut,
67 Failed(Error<ErrTag>),
68}
69
70/// A `TlsAcceptor` with a shared cap on how many handshakes run at once and a
71/// deadline on each.
72///
73/// The same rustls server configuration serves HTTPS and the mail listeners
74/// (SMTP STARTTLS, implicit-TLS IMAP), so the bound lives here, once, and every
75/// listener shares one `BoundedTlsAcceptor` rather than each carrying its own.
76/// Two shapes are closed together: a flood of concurrent handshakes exhausting
77/// the CPU on a single-vCPU box (the semaphore), and a drip-fed handshake that
78/// pins a permit indefinitely and turns the semaphore itself into a slowloris
79/// amplifier (the deadline, which covers both the wait for a permit and the
80/// handshake it guards). Both bounds are optional: a zero permit count or an
81/// absent deadline disables that half, which is the inert default.
82#[derive(Clone)]
83pub struct BoundedTlsAcceptor {
84 acceptor: TlsAcceptor,
85 sem: Option<std::sync::Arc<Semaphore>>, // None disables the concurrency bound
86 deadline: Option<Duration>, // None disables the handshake deadline
87}
88
89impl BoundedTlsAcceptor {
90
91 /// `max_handshakes` of 0 leaves handshake concurrency unbounded; a `None`
92 /// deadline leaves each handshake untimed. Both are the inert defaults, so a
93 /// caller that opts into neither gets exactly a bare `TlsAcceptor`'s behaviour.
94 pub fn new(
95 acceptor: TlsAcceptor,
96 max_handshakes: usize,
97 deadline: Option<Duration>,
98 )
99 -> Self
100 {
101 let sem = if max_handshakes == 0 {
102 None
103 } else {
104 Some(std::sync::Arc::new(Semaphore::new(max_handshakes)))
105 };
106 Self { acceptor, sem, deadline }
107 }
108
109 /// An acceptor with neither bound, for a caller that wants the plain thing.
110 pub fn unbounded(acceptor: TlsAcceptor) -> Self {
111 Self { acceptor, sem: None, deadline: None }
112 }
113
114 /// The per-handshake deadline, so a caller can time an adjacent step (a peek
115 /// before the handshake, say) to the same bound.
116 pub fn deadline(&self) -> Option<Duration> {
117 self.deadline
118 }
119
120 /// Complete one server-side handshake within the shared concurrency cap and
121 /// the deadline.
122 ///
123 /// A permit is taken immediately before the handshake and released the moment
124 /// it returns, so it bounds only the expensive handshake and never the whole
125 /// session that follows. The deadline wraps both the wait for a permit and
126 /// the handshake, so no single connection can hold a slot open indefinitely.
127 pub async fn accept<IO>(&self, stream: IO) -> Handshake<IO>
128 where
129 IO: AsyncRead + AsyncWrite + Unpin,
130 {
131 // A permit, bounded by the deadline. Held across the handshake below and
132 // dropped when this function returns.
133 let _permit = match &self.sem {
134 Some(sem) => match self.deadline {
135 Some(d) => match tokio::time::timeout(d, sem.acquire()).await {
136 Ok(Ok(p)) => Some(p),
137 Ok(Err(_)) => return Handshake::Failed(err!(
138 "The TLS handshake semaphore was closed."; Bug, Network)),
139 Err(_) => return Handshake::TimedOut,
140 },
141 None => match sem.acquire().await {
142 Ok(p) => Some(p),
143 Err(_) => return Handshake::Failed(err!(
144 "The TLS handshake semaphore was closed."; Bug, Network)),
145 },
146 },
147 None => None,
148 };
149 match self.deadline {
150 Some(d) => match tokio::time::timeout(d, self.acceptor.accept(stream)).await {
151 Ok(Ok(tls)) => Handshake::Ok(tls),
152 Ok(Err(e)) => Handshake::Failed(err!(e,
153 "TLS handshake failed."; IO, Network, Init)),
154 Err(_) => Handshake::TimedOut,
155 },
156 None => match self.acceptor.accept(stream).await {
157 Ok(tls) => Handshake::Ok(tls),
158 Err(e) => Handshake::Failed(err!(e,
159 "TLS handshake failed."; IO, Network, Init)),
160 },
161 }
162 }
163}
164
165
166/// Either a plain TCP stream or a client-side TLS-wrapped TCP stream.
167///
168/// A protocol client holds one of these and can replace a `Plain` with a
169/// `Tls` in place, which is exactly what a STARTTLS upgrade is.
170pub enum ClientStream {
171 Plain(TcpStream),
172 Tls(Box<tokio_rustls::client::TlsStream<TcpStream>>),
173}
174
175impl ClientStream {
176
177 /// `None` once TLS has wrapped it, there being nothing plain left to take.
178 pub fn into_plain(self) -> Option<TcpStream> {
179 match self {
180 Self::Plain(s) => Some(s),
181 Self::Tls(_) => None,
182 }
183 }
184
185 pub fn is_tls(&self) -> bool {
186 matches!(self, Self::Tls(_))
187 }
188}
189
190impl AsyncRead for ClientStream {
191 fn poll_read(
192 self: Pin<&mut Self>,
193 cx: &mut Context<'_>,
194 buf: &mut ReadBuf<'_>,
195 )
196 -> Poll<std::io::Result<()>>
197 {
198 match self.get_mut() {
199 Self::Plain(s) => Pin::new(s).poll_read(cx, buf),
200 Self::Tls(s) => Pin::new(s.as_mut()).poll_read(cx, buf),
201 }
202 }
203}
204
205impl AsyncWrite for ClientStream {
206 fn poll_write(
207 self: Pin<&mut Self>,
208 cx: &mut Context<'_>,
209 buf: &[u8],
210 )
211 -> Poll<std::io::Result<usize>>
212 {
213 match self.get_mut() {
214 Self::Plain(s) => Pin::new(s).poll_write(cx, buf),
215 Self::Tls(s) => Pin::new(s.as_mut()).poll_write(cx, buf),
216 }
217 }
218
219 fn poll_flush(
220 self: Pin<&mut Self>,
221 cx: &mut Context<'_>,
222 )
223 -> Poll<std::io::Result<()>>
224 {
225 match self.get_mut() {
226 Self::Plain(s) => Pin::new(s).poll_flush(cx),
227 Self::Tls(s) => Pin::new(s.as_mut()).poll_flush(cx),
228 }
229 }
230
231 fn poll_shutdown(
232 self: Pin<&mut Self>,
233 cx: &mut Context<'_>,
234 )
235 -> Poll<std::io::Result<()>>
236 {
237 match self.get_mut() {
238 Self::Plain(s) => Pin::new(s).poll_shutdown(cx),
239 Self::Tls(s) => Pin::new(s.as_mut()).poll_shutdown(cx),
240 }
241 }
242}
243
244/// The peer certificate is validated against `cfg` for the name `host`, and the handshake must
245/// finish within `deadline`. A peer that takes the connection and never answers the hello would
246/// otherwise hold the caller for as long as the socket lived.
247pub async fn upgrade(
248 plain: TcpStream,
249 host: &str,
250 cfg: Arc<ClientConfig>,
251 deadline: Duration,
252)
253 -> Outcome<ClientStream>
254{
255 let name = match ServerName::try_from(host.to_string()) {
256 Ok(n) => n,
257 Err(_) => return Err(err!(
258 "Cannot construct a TLS server name from '{}'.", host;
259 Invalid, Input)),
260 };
261 let connector = TlsConnector::from(cfg);
262 match tokio::time::timeout(deadline, connector.connect(name, plain)).await {
263 Ok(Ok(s)) => Ok(ClientStream::Tls(Box::new(s))),
264 Ok(Err(e)) => Err(err!(e,
265 "TLS handshake to {}.", host;
266 IO, Network, Init)),
267 Err(_) => Err(err!(
268 "The TLS handshake to {} did not finish within {:?}.", host, deadline;
269 IO, Network, Init, Timeout)),
270 }
271}
272
273/// Ensure rustls has a process-wide crypto provider.
274///
275/// rustls resolves its provider from process-global state, and its builders
276/// **panic** when they cannot pick one unambiguously -- either because none
277/// is installed, or because the build enabled more than one and so has no
278/// default. An application is expected to install one in `main`, and the
279/// Steel binary does; but a library that panics because its *caller's*
280/// `main` omitted a line is a landmine, and it goes off deep inside a
281/// request path rather than at start-up.
282///
283/// So install one here if nobody has. `install_default` fails when a
284/// provider is already installed, which is not an error -- whichever one
285/// the application chose is the one we want to keep.
286pub fn ensure_crypto_provider() {
287 if tokio_rustls::rustls::crypto::CryptoProvider::get_default().is_some() {
288 return;
289 }
290 let _ = tokio_rustls::rustls::crypto::ring::default_provider()
291 .install_default();
292}
293
294/// The host's CA bundle, and nothing else: the "trust what the operating system
295/// trusts" default every public-internet client wants. A caller needing its own
296/// root store builds the `ClientConfig` itself.
297pub fn default_client_config() -> Outcome<ClientConfig> {
298 ensure_crypto_provider();
299 let ca_paths = [
300 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu
301 "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL
302 "/etc/ssl/cert.pem", // Alpine/macOS
303 ];
304 let ca_file = match ca_paths.iter().find(|p| std::path::Path::new(p).exists()) {
305 Some(p) => *p,
306 None => return Err(err!(
307 "No system CA bundle found. Tried: {:?}", ca_paths;
308 Init, Missing, File)),
309 };
310 let pem = match std::fs::read(ca_file) {
311 Ok(d) => d,
312 Err(e) => return Err(err!(e,
313 "Failed to read CA bundle '{}'.", ca_file;
314 IO, File, Read)),
315 };
316 let mut store = RootCertStore::empty();
317 let mut count = 0u32;
318 for der in parse_pem_certificates(&pem) {
319 let cert = CertificateDer::from(der);
320 if store.add(cert).is_ok() {
321 count += 1;
322 }
323 }
324 if count == 0 {
325 return Err(err!(
326 "CA bundle '{}' contained no usable certificates.", ca_file;
327 Init, Invalid, File));
328 }
329 Ok(ClientConfig::builder()
330 .with_root_certificates(store)
331 .with_no_client_auth())
332}
333
334/// The decoded DER of every `-----BEGIN CERTIFICATE-----` block. A tiny in-tree
335/// substitute for `rustls_pemfile::certs`, so the crate need not carry it.
336pub fn parse_pem_certificates(pem: &[u8]) -> Vec<Vec<u8>> {
337 const BEGIN: &str = "-----BEGIN CERTIFICATE-----";
338 const END: &str = "-----END CERTIFICATE-----";
339 let text = String::from_utf8_lossy(pem);
340 let mut out: Vec<Vec<u8>> = Vec::new();
341 let mut search_from = 0usize;
342 while let Some(b) = text[search_from..].find(BEGIN) {
343 let start = search_from + b + BEGIN.len();
344 let e = match text[start..].find(END) {
345 Some(i) => i,
346 None => break,
347 };
348 let block = &text[start..start + e];
349 let stripped: String = block.chars().filter(|c| !c.is_whitespace()).collect();
350 if let Ok(der) = base64::decode(&stripped) {
351 out.push(der);
352 }
353 search_from = start + e + END.len();
354 }
355 out
356}
357
358
359// ┌───────────────────────────────────────────────────────────────────────────┐
360// │ CERTIFICATE EXPIRY │
361// └───────────────────────────────────────────────────────────────────────────┘
362
363/// When the first certificate in a PEM chain expires, as Unix seconds.
364///
365/// A server that renews its own certificate has to know when the one it holds
366/// runs out. The obvious shortcut -- ask the filesystem how old the file is --
367/// is wrong in the one case that matters: a certificate restored from a backup,
368/// or copied from another host, has a fresh mtime and an old expiry, and a
369/// server trusting the mtime will serve an expired certificate and never notice.
370/// The certificate itself is the only thing that knows.
371///
372/// This walks just enough DER to reach the field. An X.509 certificate is
373///
374/// ```text
375/// Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, ... }
376/// TBSCertificate ::= SEQUENCE {
377/// version [0] EXPLICIT Version DEFAULT v1, -- optional
378/// serialNumber INTEGER,
379/// signature AlgorithmIdentifier, -- SEQUENCE
380/// issuer Name, -- SEQUENCE
381/// validity Validity, -- SEQUENCE <- here
382/// ... }
383/// Validity ::= SEQUENCE { notBefore Time, notAfter Time }
384/// ```
385///
386/// so the walk is: into the certificate, into the TBS, skip the optional
387/// version and the serial and the two SEQUENCEs before it, and take the second
388/// time in the validity.
389pub fn certificate_not_after(pem: &[u8]) -> Outcome<i64> {
390 let der = match parse_pem_certificates(pem).into_iter().next() {
391 Some(d) => d,
392 None => return Err(err!(
393 "No certificate found in the PEM data.";
394 Invalid, Input, Missing)),
395 };
396
397 let (tbs, _) = res!(der_expect(&der, 0, TAG_SEQUENCE)); // Certificate
398 let (fields, _) = res!(der_expect(tbs, 0, TAG_SEQUENCE)); // TBSCertificate
399
400 let mut pos = 0usize;
401 // The version is [0] EXPLICIT and only present for v2 and v3. Every
402 // certificate a public CA issues today is v3, but a v1 certificate is legal
403 // and simply omits it.
404 if fields.get(pos) == Some(&TAG_VERSION) {
405 let (_, next) = res!(der_element(fields, pos));
406 pos = next;
407 }
408 for tag in [TAG_INTEGER, TAG_SEQUENCE, TAG_SEQUENCE] { // serial, sig alg, issuer
409 let (_, next) = res!(der_expect(fields, pos, tag));
410 pos = next;
411 }
412 let (validity, _) = res!(der_expect(fields, pos, TAG_SEQUENCE));
413
414 // notBefore, then notAfter. Only the second is wanted.
415 let (_, after_nb) = res!(der_element(validity, 0));
416 let tag = match validity.get(after_nb) {
417 Some(t) => *t,
418 None => return Err(err!(
419 "Certificate validity has a notBefore but no notAfter.";
420 Invalid, Input, Missing)),
421 };
422 let (not_after, _) = res!(der_element(validity, after_nb));
423
424 parse_asn1_time(not_after, tag)
425}
426
427/// Every DNS name the first certificate in a PEM chain is valid for, read from
428/// its `subjectAltName` extension.
429///
430/// A renewer needs this as much as it needs the expiry: a certificate that is
431/// years from expiring still cannot serve a host it does not name, so adding a
432/// virtual host must force a reissue even though nothing has aged.
433///
434/// Walks the TBS to the `[3] EXPLICIT Extensions`, finds the extension whose OID
435/// is 2.5.29.17, and collects the `[2] IMPLICIT IA5String` entries -- the
436/// `dNSName` form of a `GeneralName`. Other forms (IP, email, URI) are skipped:
437/// a caller asking this question is asking about names.
438pub fn certificate_dns_names(pem: &[u8]) -> Outcome<Vec<String>> {
439 let der = match parse_pem_certificates(pem).into_iter().next() {
440 Some(d) => d,
441 None => return Err(err!(
442 "No certificate found in the PEM data.";
443 Invalid, Input, Missing)),
444 };
445 let (tbs, _) = res!(der_expect(&der, 0, TAG_SEQUENCE));
446 let (fields, _) = res!(der_expect(tbs, 0, TAG_SEQUENCE));
447
448 let mut pos = 0usize;
449 if fields.get(pos) == Some(&TAG_VERSION) {
450 let (_, next) = res!(der_element(fields, pos));
451 pos = next;
452 }
453 // serial, signature, issuer, validity, subject, subjectPublicKeyInfo.
454 for tag in [TAG_INTEGER, TAG_SEQUENCE, TAG_SEQUENCE, TAG_SEQUENCE,
455 TAG_SEQUENCE, TAG_SEQUENCE] {
456 let (_, next) = res!(der_expect(fields, pos, tag));
457 pos = next;
458 }
459 // The unique-id fields are legal and vanishingly rare; skip them if present.
460 for tag in [TAG_ISSUER_UID, TAG_SUBJECT_UID] {
461 if fields.get(pos) == Some(&tag) {
462 let (_, next) = res!(der_element(fields, pos));
463 pos = next;
464 }
465 }
466 let exts = match fields.get(pos) {
467 Some(t) if *t == TAG_EXTENSIONS => {
468 let (inner, _) = res!(der_element(fields, pos));
469 let (list, _) = res!(der_expect(inner, 0, TAG_SEQUENCE));
470 list
471 }
472 // A v1 certificate has no extensions, and so no subject alternative
473 // names. That is not an error; it simply names nothing.
474 _ => return Ok(Vec::new()),
475 };
476
477 let mut at = 0usize;
478 while at < exts.len() {
479 let (ext, next) = res!(der_expect(exts, at, TAG_SEQUENCE));
480 at = next;
481
482 let (oid, mut p) = res!(der_expect(ext, 0, TAG_OID));
483 if oid != OID_SUBJECT_ALT_NAME {
484 continue;
485 }
486 // `critical` is optional and defaults to false.
487 if ext.get(p) == Some(&TAG_BOOLEAN) {
488 let (_, n) = res!(der_element(ext, p));
489 p = n;
490 }
491 let (value, _) = res!(der_expect(ext, p, TAG_OCTET_STRING));
492 let (names, _) = res!(der_expect(value, 0, TAG_SEQUENCE));
493
494 let mut out = Vec::new();
495 let mut q = 0usize;
496 while q < names.len() {
497 let tag = match names.get(q) {
498 Some(t) => *t,
499 None => break,
500 };
501 let (bytes, n) = res!(der_element(names, q));
502 q = n;
503 if tag == TAG_DNS_NAME {
504 out.push(String::from_utf8_lossy(bytes).into_owned());
505 }
506 }
507 return Ok(out);
508 }
509 Ok(Vec::new())
510}
511
512/// Whether the certificate expires within `lead` seconds of now -- which is the
513/// question a renewer is actually asking. An unparseable or unreadable
514/// certificate is treated as expiring, because a server that cannot tell should
515/// renew rather than gamble.
516pub fn certificate_expires_within(pem: &[u8], lead_secs: i64) -> bool {
517 let not_after = match certificate_not_after(pem) {
518 Ok(t) => t,
519 Err(_) => return true,
520 };
521 let now = match std::time::SystemTime::now()
522 .duration_since(std::time::UNIX_EPOCH)
523 {
524 Ok(d) => d.as_secs() as i64,
525 Err(_) => return true,
526 };
527 not_after - now <= lead_secs
528}
529
530
531const TAG_BOOLEAN: u8 = 0x01;
532const TAG_INTEGER: u8 = 0x02;
533const TAG_OCTET_STRING: u8 = 0x04;
534const TAG_OID: u8 = 0x06;
535const TAG_SEQUENCE: u8 = 0x30;
536const TAG_UTCTIME: u8 = 0x17;
537const TAG_GENTIME: u8 = 0x18;
538const TAG_VERSION: u8 = 0xa0; // `[0] EXPLICIT`, which the version carries
539// `[1]` and `[2] IMPLICIT`, the deprecated unique identifiers.
540const TAG_ISSUER_UID: u8 = 0x81;
541const TAG_SUBJECT_UID: u8 = 0x82;
542const TAG_EXTENSIONS: u8 = 0xa3; // `[3] EXPLICIT Extensions`
543const TAG_DNS_NAME: u8 = 0x82; // `[2] IMPLICIT IA5String`, a dNSName GeneralName
544const OID_SUBJECT_ALT_NAME: &[u8] = &[0x55, 0x1d, 0x11]; // subjectAltName, 2.5.29.17
545
546/// The element's contents, and the offset just past it. Only the length
547/// encodings a certificate actually uses are handled -- an indefinite length is
548/// not legal in DER anyway.
549fn der_element(buf: &[u8], pos: usize) -> Outcome<(&[u8], usize)> {
550 if pos + 2 > buf.len() {
551 return Err(err!(
552 "DER element at {} runs past the end of the buffer.", pos;
553 Invalid, Input, Decode));
554 }
555 let first = buf[pos + 1];
556 let (len, header) = if first < 0x80 {
557 (first as usize, 2usize)
558 } else {
559 let n = (first & 0x7f) as usize;
560 if n == 0 || n > 4 || pos + 2 + n > buf.len() {
561 return Err(err!(
562 "DER element at {} has an unsupported length encoding.", pos;
563 Invalid, Input, Decode));
564 }
565 let mut len = 0usize;
566 for i in 0..n {
567 len = (len << 8) | buf[pos + 2 + i] as usize;
568 }
569 (len, 2 + n)
570 };
571 let start = pos + header;
572 let end = match start.checked_add(len) {
573 Some(e) if e <= buf.len() => e,
574 _ => return Err(err!(
575 "DER element at {} claims {} bytes, past the end of the buffer.",
576 pos, len;
577 Invalid, Input, Decode)),
578 };
579 Ok((&buf[start..end], end))
580}
581
582/// As [`der_element`], but insisting on a tag. A certificate whose shape
583/// departs from X.509 is not one to guess about.
584fn der_expect(buf: &[u8], pos: usize, tag: u8) -> Outcome<(&[u8], usize)> {
585 match buf.get(pos) {
586 Some(t) if *t == tag => der_element(buf, pos),
587 Some(t) => Err(err!(
588 "Expected DER tag {:#04x} at {}, found {:#04x}.", tag, pos, t;
589 Invalid, Input, Decode)),
590 None => Err(err!(
591 "Expected DER tag {:#04x} at {}, found the end of the buffer.",
592 tag, pos;
593 Invalid, Input, Decode)),
594 }
595}
596
597/// Parse an ASN.1 `UTCTime` (`YYMMDDHHMMSSZ`) or `GeneralizedTime`
598/// (`YYYYMMDDHHMMSSZ`) into Unix seconds.
599fn parse_asn1_time(bytes: &[u8], tag: u8) -> Outcome<i64> {
600 let s = match std::str::from_utf8(bytes) {
601 Ok(s) => s.trim_end_matches('Z'),
602 Err(e) => return Err(err!(e,
603 "Certificate time is not valid UTF-8.";
604 Invalid, Input, Decode)),
605 };
606 let num = |a: usize, b: usize| -> Outcome<i64> {
607 match s.get(a..b).and_then(|t| t.parse::<i64>().ok()) {
608 Some(n) => Ok(n),
609 None => Err(err!(
610 "Certificate time '{}' is malformed.", s;
611 Invalid, Input, Decode)),
612 }
613 };
614 let (year, off) = match tag {
615 TAG_UTCTIME => {
616 // Two digits, so the century is inferred: RFC 5280 §4.1.2.5.1 puts
617 // 50-99 in the 1900s and 00-49 in the 2000s.
618 let yy = res!(num(0, 2));
619 (if yy >= 50 { 1900 + yy } else { 2000 + yy }, 2usize)
620 }
621 TAG_GENTIME => (res!(num(0, 4)), 4usize),
622 other => return Err(err!(
623 "Certificate validity has tag {:#04x}, which is neither a UTCTime \
624 nor a GeneralizedTime.", other;
625 Invalid, Input, Decode)),
626 };
627 let month = res!(num(off, off + 2));
628 let day = res!(num(off + 2, off + 4));
629 let hour = res!(num(off + 4, off + 6));
630 let min = res!(num(off + 6, off + 8));
631 // Seconds are optional in a UTCTime, though every CA emits them.
632 let sec = if s.len() >= off + 10 { res!(num(off + 8, off + 10)) } else { 0 };
633
634 Ok(days_from_civil(year, month, day) * 86_400 + hour * 3_600 + min * 60 + sec)
635}
636
637/// Days since the Unix epoch for a proleptic-Gregorian date. Howard Hinnant's
638/// `days_from_civil`, which is exact and needs no table.
639fn days_from_civil(y: i64, m: i64, d: i64) -> i64 {
640 let y = if m <= 2 { y - 1 } else { y };
641 let era = if y >= 0 { y } else { y - 399 } / 400;
642 let yoe = y - era * 400; // [0, 399]
643 let mp = (m + 9) % 12; // March = 0
644 let doy = (153 * mp + 2) / 5 + d - 1; // [0, 365]
645 let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
646 era * 146_097 + doe - 719_468
647}
648
649
650#[cfg(test)]
651mod cert_tests {
652 use super::*;
653
654 /// A certificate whose expiry we choose, so the parser is checked against a
655 /// known answer rather than against itself.
656 fn cert_expiring(year: i32, month: u8, day: u8) -> Vec<u8> {
657 use rcgen::{Certificate, CertificateParams};
658 let mut params = CertificateParams::new(vec![fmt!("example.com")]);
659 params.not_after = rcgen::date_time_ymd(year, month, day);
660 let cert = Certificate::from_params(params).expect("test cert");
661 cert.serialize_pem().expect("test cert pem").into_bytes()
662 }
663
664 #[test]
665 fn test_not_after_is_read_from_the_certificate() {
666 let pem = cert_expiring(2031, 3, 14);
667 let t = certificate_not_after(&pem).expect("parse");
668 assert_eq!(t, 1_931_212_800, "2031-03-14T00:00:00Z, got {}", t);
669
670 // A GeneralizedTime, which is what a CA must use past 2049.
671 let pem = cert_expiring(2060, 12, 31);
672 let t = certificate_not_after(&pem).expect("parse");
673 assert_eq!(t, 2_871_676_800, "2060-12-31T00:00:00Z, got {}", t);
674 }
675
676 #[test]
677 fn test_expiry_is_the_question_a_renewer_asks() {
678 let soon = cert_expiring(2026, 7, 20); // in the past by the time this ages
679 let far = cert_expiring(2099, 1, 1);
680 assert!(certificate_expires_within(&soon, 30 * 24 * 3600));
681 assert!(!certificate_expires_within(&far, 30 * 24 * 3600));
682 }
683
684 #[test]
685 fn test_dns_names_are_read_from_the_san() {
686 use rcgen::{Certificate, CertificateParams};
687 let params = CertificateParams::new(vec![
688 fmt!("example.com"),
689 fmt!("www.example.com"),
690 fmt!("api.example.com"),
691 ]);
692 let cert = Certificate::from_params(params).expect("test cert");
693 let pem = cert.serialize_pem().expect("test pem").into_bytes();
694
695 let names = certificate_dns_names(&pem).expect("parse");
696 assert_eq!(names, vec![
697 fmt!("example.com"),
698 fmt!("www.example.com"),
699 fmt!("api.example.com"),
700 ]);
701 }
702
703 #[test]
704 fn test_a_certificate_that_names_one_host_does_not_name_another() {
705 // The case that matters: a vhost is added, the certificate is nowhere
706 // near expiry, and it still cannot serve the new name.
707 let pem = cert_expiring(2099, 1, 1); // names example.com only
708 let names = certificate_dns_names(&pem).expect("parse");
709 assert!(names.iter().any(|n| n == "example.com"));
710 assert!(!names.iter().any(|n| n == "new.example.com"));
711 assert!(!certificate_expires_within(&pem, 30 * 24 * 3600));
712 }
713
714 #[test]
715 fn test_rubbish_is_treated_as_expiring() {
716 // A server that cannot tell must renew, not gamble.
717 assert!(certificate_expires_within(b"not a certificate", 0));
718 assert!(certificate_not_after(b"not a certificate").is_err());
719 }
720
721 #[test]
722 fn test_days_from_civil_epoch_and_leap_years() {
723 assert_eq!(days_from_civil(1970, 1, 1), 0);
724 assert_eq!(days_from_civil(2000, 3, 1), 11_017);
725 assert_eq!(days_from_civil(2026, 7, 12), 20_646);
726 }
727}