rustls_pin/lib.rs
1//! [](https://crates.io/crates/rustls-pin)
2//! [](https://gitlab.com/leonhard-llc/ops/-/raw/main/rustls-pin/LICENSE)
3//! [](https://github.com/rust-secure-code/safety-dance/)
4//! [](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}