Skip to main content

pingora_rustls/
lib.rs

1// Copyright 2026 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This module contains all the rustls specific pingora integration for things
16//! like loading certificates and private keys
17
18#![warn(clippy::all)]
19
20use std::fs::File;
21use std::io::BufReader;
22use std::path::Path;
23
24use log::warn;
25pub use no_debug::{Ellipses, NoDebug, WithTypeInfo};
26use pingora_error::{Error, ErrorType, OrErr, Result};
27
28pub use rustls::server::danger::{ClientCertVerified, ClientCertVerifier};
29pub use rustls::server::{
30    ClientCertVerifierBuilder, ClientHello, ResolvesServerCert, WebPkiClientVerifier,
31};
32pub use rustls::sign;
33pub use rustls::{
34    client::WebPkiServerVerifier, crypto::CryptoProvider, version, CertificateError, ClientConfig,
35    DigitallySignedStruct, Error as RusTlsError, KeyLogFile, RootCertStore, ServerConfig,
36    SignatureScheme, Stream,
37};
38
39/// Install the default `ring` CryptoProvider for rustls.
40///
41/// rustls 0.23+ requires an explicit provider. This function installs `ring`
42/// as the process-level default. Safe to call multiple times — subsequent
43/// calls are no-ops.
44pub fn install_default_crypto_provider() {
45    let _ = CryptoProvider::install_default(rustls::crypto::ring::default_provider());
46}
47pub use rustls_native_certs::load_native_certs;
48use rustls_pemfile::Item;
49pub use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
50pub use tokio_rustls::client::TlsStream as ClientTlsStream;
51pub use tokio_rustls::server::TlsStream as ServerTlsStream;
52pub use tokio_rustls::{Accept, Connect, TlsAcceptor, TlsConnector, TlsStream};
53
54// This allows to skip certificate verification. Be highly cautious.
55pub use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
56
57/// Load the given file from disk as a buffered reader and use the pingora Error
58/// type instead of the std::io version
59fn load_file<P>(path: P) -> Result<BufReader<File>>
60where
61    P: AsRef<Path>,
62{
63    File::open(path)
64        .or_err(ErrorType::FileReadError, "Failed to load file")
65        .map(BufReader::new)
66}
67
68/// Read the pem file at the given path from disk
69fn load_pem_file<P>(path: P) -> Result<Vec<Item>>
70where
71    P: AsRef<Path>,
72{
73    rustls_pemfile::read_all(&mut load_file(path)?)
74        .map(|item_res| {
75            item_res.or_err(
76                ErrorType::InvalidCert,
77                "Certificate in pem file could not be read",
78            )
79        })
80        .collect()
81}
82
83/// Load the certificates from the given pem file path into the given
84/// certificate store
85pub fn load_ca_file_into_store<P>(path: P, cert_store: &mut RootCertStore) -> Result<()>
86where
87    P: AsRef<Path>,
88{
89    for pem_item in load_pem_file(path)? {
90        // only loading certificates, handling a CA file
91        let Item::X509Certificate(content) = pem_item else {
92            return Error::e_explain(
93                ErrorType::InvalidCert,
94                "Pem file contains un-loadable certificate type",
95            );
96        };
97        cert_store.add(content).or_err(
98            ErrorType::InvalidCert,
99            "Failed to load X509 certificate into root store",
100        )?;
101    }
102
103    Ok(())
104}
105
106/// Attempt to load the native cas into the given root-certificate store
107pub fn load_platform_certs_incl_env_into_store(ca_certs: &mut RootCertStore) -> Result<()> {
108    // this includes handling of ENV vars SSL_CERT_FILE & SSL_CERT_DIR
109    for cert in load_native_certs()
110        .or_err(ErrorType::InvalidCert, "Failed to load native certificates")?
111        .into_iter()
112    {
113        ca_certs.add(cert).or_err(
114            ErrorType::InvalidCert,
115            "Failed to load native certificate into root store",
116        )?;
117    }
118
119    Ok(())
120}
121
122/// Load the certificates and private key files
123pub fn load_certs_and_key_files<'a>(
124    cert: &str,
125    key: &str,
126) -> Result<Option<(Vec<CertificateDer<'a>>, PrivateKeyDer<'a>)>> {
127    let certs_file = load_pem_file(cert)?;
128    let key_file = load_pem_file(key)?;
129
130    let certs = certs_file
131        .into_iter()
132        .filter_map(|item| {
133            if let Item::X509Certificate(cert) = item {
134                Some(cert)
135            } else {
136                None
137            }
138        })
139        .collect::<Vec<_>>();
140
141    // These are the currently supported pk types -
142    // [https://doc.servo.org/rustls/key/struct.PrivateKey.html]
143    let private_key_opt = key_file
144        .into_iter()
145        .filter_map(|key_item| match key_item {
146            Item::Pkcs1Key(key) => Some(PrivateKeyDer::from(key)),
147            Item::Pkcs8Key(key) => Some(PrivateKeyDer::from(key)),
148            Item::Sec1Key(key) => Some(PrivateKeyDer::from(key)),
149            _ => None,
150        })
151        .next();
152
153    if let (Some(private_key), false) = (private_key_opt, certs.is_empty()) {
154        Ok(Some((certs, private_key)))
155    } else {
156        Ok(None)
157    }
158}
159
160/// Load the certificate
161pub fn load_pem_file_ca(path: &String) -> Result<Vec<u8>> {
162    let mut reader = load_file(path)?;
163    let cas_file_items = rustls_pemfile::certs(&mut reader)
164        .map(|item_res| {
165            item_res.or_err(
166                ErrorType::InvalidCert,
167                "Failed to load certificate from file",
168            )
169        })
170        .collect::<Result<Vec<_>>>()?;
171
172    Ok(cas_file_items
173        .first()
174        .map(|ca| ca.to_vec())
175        .unwrap_or_default())
176}
177
178pub fn load_pem_file_private_key(path: &String) -> Result<Vec<u8>> {
179    Ok(rustls_pemfile::private_key(&mut load_file(path)?)
180        .or_err(
181            ErrorType::InvalidCert,
182            "Failed to load private key from file",
183        )?
184        .map(|key| key.secret_der().to_vec())
185        .unwrap_or_default())
186}
187
188pub fn hash_certificate(cert: &CertificateDer) -> Vec<u8> {
189    let hash = ring::digest::digest(&ring::digest::SHA256, cert.as_ref());
190    hash.as_ref().to_vec()
191}