1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#![warn(clippy::all)]
#![warn(clippy::nursery)]

//! # ocsp-stapler
//!
//! The `ocsp-stapler` crate provides two structs: `Client` and `Stapler`.
//!
//! ## `Client`
//! `Client` is an OCSP client that you can use to do OCSP requests to OCSP responders of the Certificate Authorities.
//!
//! ## `Stapler`
//! `Stapler` uses `Client` internally and provides a Rustls-compatible API to attach (staple) OCSP responses to the certificates.
//! It wraps whatever that implements Rustls' `ResolvesServerCert` trait and also implements the same trait.
//!
//! The workflow is the following:
//! - `Stapler` receives a `ClientHello` from Rustls and forwards it to ther wrapped trait object to get the certificate chain
//! - It calculates the SHA-1 fingerprint over the whole end-entity certificate and uses that to check if it has the same certificate
//!   in the local storage
//! - If not - it sends the certificate to the background worker for eventual processing & stapling.
//!   Meanwhilte it returns to Rustls the original unstapled certificate
//! - If found - it respondes with a stapled version of the certificate
//!
//! Background worker duties:
//! - Receieves the certificates from `Stapler`, processes them and inserts into the local storage
//! - Wakes up every 60s (or when a new certificate is added) to do the following:
//!   - Renew the OCSP responses that are already past 50% of their validity interval
//!   - Check for expired certificates & purge them
//!   - Check for expired OCSP responses and clear them
//!   - Post an updated version of storage that is shared with `Stapler`
//!
//! Background worker is spawned by `Stapler::new()` using `tokio::spawn` so it must be executed in Tokio context.
//! It runs indefinitely unless stopped with `Stapler.stop()`.
//!
//! # Example
//!
//! ```rust
//! // Inner service that provides certificates to Rustls, can be anything
//! let inner: Arc<dyn ResolvesCerverCert> = ...;
//!
//! let stapler = Arc::new(ocsp_stapler::Stapler::new(inner));
//!
//! let server_config = rustls::server::ServerConfig::builder()
//!         .with_no_client_auth()
//!         .with_cert_resolver(stapler.clone());
//!
//! // Then you can use server_config wherever applicable
//!
//! // Stop the background worker to clean up
//! stapler.stop().await;
//! ```

pub mod client;
pub mod stapler;

pub use client::Client;
pub use stapler::Stapler;

use chrono::{DateTime, FixedOffset};

/// OCSP response validity interval
#[derive(Clone)]
pub struct OcspValidity {
    pub this_update: DateTime<FixedOffset>,
    pub next_update: DateTime<FixedOffset>,
}

impl OcspValidity {
    // Check if we're already past the half of this validity duration
    pub fn time_to_update(&self, now: DateTime<FixedOffset>) -> bool {
        now >= self.this_update + ((self.next_update - self.this_update) / 2)
    }
}