rustls_pin/
lib.rs

1//! [![crates.io version](https://img.shields.io/crates/v/rustls-pin.svg)](https://crates.io/crates/rustls-pin)
2//! [![license: Apache 2.0](https://gitlab.com/leonhard-llc/ops/-/raw/main/license-apache-2.0.svg)](https://gitlab.com/leonhard-llc/ops/-/raw/main/rustls-pin/LICENSE)
3//! [![unsafe forbidden](https://gitlab.com/leonhard-llc/ops/-/raw/main/unsafe-forbidden.svg)](https://github.com/rust-secure-code/safety-dance/)
4//! [![pipeline status](https://gitlab.com/leonhard-llc/ops/badges/main/pipeline.svg)](https://gitlab.com/leonhard-llc/ops/-/pipelines)
5//!
6//! # rustls-pin
7//!
8//! Server certificate pinning with `rustls`.
9//!
10//! ## Features
11//! - Make a TLS connection to a server
12//! - Check that the server is using an allowed certificate
13//! - `forbid(unsafe_code)`
14//! - 100% test coverage
15//!
16//! ## How to Update Pinned Certificates
17//!
18//! Before switching the server to a new certificate, you need to upgrade the
19//! clients to accept both the current certificate and the new one.
20//!
21//! If your users update their client software infrequently, you may need to
22//! wait a long time before switching to a new certificate.
23//!
24//! You can change certificates frequently by having multiple pending 'new'
25//! certificates.  Example:
26//! - Server: cert1
27//! - Client v1: cert1
28//! - Client v2: cert1, cert2
29//! - Client v3: cert1, cert2, cert3
30//! - Server: cert2
31//! - Client v4: cert2, cert3, cert4
32//! - Server: cert3
33//! - Client v5: cert3, cert4, cert5
34//! - Server cert4
35//!
36//! ## Example
37//! ```
38//! # let listener = std::net::TcpListener::bind(&("127.0.0.1", 0)).unwrap();
39//! # let addr = listener.local_addr().unwrap();
40//! # std::thread::spawn(move || listener.accept().unwrap());
41//! # let server_cert1 = rustls::Certificate(Vec::new());
42//! # let server_cert2 = rustls::Certificate(Vec::new());
43//! let mut stream = rustls_pin::connect_pinned(
44//!     addr,
45//!     vec![server_cert1, server_cert2],
46//! ).unwrap();
47//! let mut response = String::new();
48//! match std::io::Read::read_to_string(
49//!     &mut stream, &mut response) {
50//!     Ok(_) => {},
51//!     Err(e) if &e.to_string() ==
52//!         "invalid certificate: UnknownIssuer"
53//!      => panic!("Update required."),
54//!     Err(e) => {
55//!         // panic!("{}", e)
56//!     }
57//! }
58//! ```
59//!
60//! When the client software reads/writes the stream and gets an
61//! `invalid certificate: UnknownIssuer` error,
62//! it can assume that it is outdated.
63//! It can tell the user to update.
64//!
65//! The rustls client terminates the TLS connection by sending the
66//! 'bad certificate' reason to the server.
67//! The server's stream read/write fails with:
68//! `"Custom { kind: InvalidData, error: AlertReceived(BadCertificate) }"`.
69//!
70//! ## Alternatives
71//! - [rustls#227 Implement support for certificate pinning](https://github.com/ctz/rustls/issues/227)
72//!
73//! ## Changelog
74//! - v0.1.2
75//!   - Add "How to Update Pinned Certificates" to docs.
76//!   - Add error handling to example
77//! - v0.1.1 - Increase test coverage
78//! - v0.1.0 - Initial version
79//!
80//! ## Happy Contributors 🙂
81//! Fixing bugs and adding features is easy and fast.
82//! Send us a pull request and we intend to:
83//! - Always respond within 24 hours
84//! - Provide clear & concrete feedback
85//! - Immediately make a new release for your accepted change
86#![forbid(unsafe_code)]
87
88use rustls::ClientSession;
89use std::net::{TcpStream, ToSocketAddrs};
90use std::sync::Arc;
91
92/// A struct for TLS clients to verify the server's certificate.
93/// Implements certificate pinning.
94/// It accepts the server's certificate if it is identical to any of the certificates in the struct.
95///
96/// The rustls library has an open issue to add something like this:
97/// "Implement support for certificate pinning" <https://github.com/ctz/rustls/issues/227>
98///
99/// # Example
100///
101/// ```
102/// use std::net::TcpStream;
103/// use std::sync::Arc;
104/// # let listener = std::net::TcpListener::bind(&("127.0.0.1", 0)).unwrap();
105/// # let addr = listener.local_addr().unwrap();
106/// # std::thread::spawn(move || listener.accept().unwrap());
107/// # let server_cert1 = rustls::Certificate(Vec::new());
108/// # let server_cert2 = rustls::Certificate(Vec::new());
109/// use rustls_pin::{
110///     arbitrary_dns_name,
111///     PinnedServerCertVerifier
112/// };
113/// let mut tcp_stream =
114///     TcpStream::connect(addr).unwrap();
115/// let mut config = rustls::ClientConfig::new();
116/// config.dangerous().set_certificate_verifier(
117///     Arc::new(
118///         PinnedServerCertVerifier::new(vec![
119///             server_cert1,
120///             server_cert2
121///         ]),
122///     )
123/// );
124/// let mut session = rustls::ClientSession::new(
125///     &Arc::new(config),
126///     arbitrary_dns_name().as_ref()
127/// );
128/// let mut stream = rustls::Stream::new(
129///     &mut session, &mut tcp_stream);
130/// ```
131pub struct PinnedServerCertVerifier<T>
132where
133    T: AsRef<[rustls::Certificate]> + Send + Sync,
134{
135    certs: T,
136}
137
138impl<T> PinnedServerCertVerifier<T>
139where
140    T: AsRef<[rustls::Certificate]> + Send + Sync,
141{
142    pub fn new(certs: T) -> Self {
143        Self { certs }
144    }
145}
146
147impl<T> rustls::ServerCertVerifier for PinnedServerCertVerifier<T>
148where
149    T: AsRef<[rustls::Certificate]> + Send + Sync,
150{
151    fn verify_server_cert(
152        &self,
153        _roots: &rustls::RootCertStore,
154        presented_certs: &[rustls::Certificate],
155        _dns_name: webpki::DNSNameRef,
156        _ocsp_response: &[u8],
157    ) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
158        // If the server sends several certificates (a certificate chain), we expect
159        // the leaf certificate to be first.
160        let presented_cert = &presented_certs[0];
161        for cert in self.certs.as_ref() {
162            if presented_cert == cert {
163                return Ok(rustls::ServerCertVerified::assertion());
164            }
165        }
166        Err(rustls::TLSError::WebPKIError(webpki::Error::UnknownIssuer))
167    }
168}
169
170/// An arbitrary `DNSName` struct, for passing to [`rustls::ClientSession::new`].
171/// `PinnedServerCertVerifier` receives the value and ignores it.
172#[must_use]
173pub fn arbitrary_dns_name() -> webpki::DNSName {
174    webpki::DNSNameRef::try_from_ascii_str("arbitrary1")
175        .unwrap()
176        .to_owned()
177}
178
179/// Make a TCP connection to `addr` and set up a TLS session.
180///
181/// The first time you try to write or read the returned stream,
182/// `rustls` will do TLS negotiation.
183/// TLS negotiation fails if the server provides a leaf cert
184/// that is not in `certs`.
185///
186/// Ignores hostnames in certificates.
187///
188/// # Errors
189/// Returns an error if it fails to open the TCP connection.
190///
191/// # Example
192/// See example in [`rustls_pin`](index.html) crate docs.
193pub fn connect_pinned(
194    addr: impl ToSocketAddrs,
195    certs: impl AsRef<[rustls::Certificate]> + Send + Sync + 'static,
196) -> Result<rustls::StreamOwned<ClientSession, TcpStream>, std::io::Error> {
197    let tcp_stream = std::net::TcpStream::connect(addr)?;
198    let mut client_config = rustls::ClientConfig::new();
199    client_config
200        .dangerous()
201        .set_certificate_verifier(Arc::new(PinnedServerCertVerifier::new(certs)));
202    let session =
203        rustls::ClientSession::new(&Arc::new(client_config), arbitrary_dns_name().as_ref());
204    Ok(rustls::StreamOwned::new(session, tcp_stream))
205}