pingora_rustls/
lib.rs

1// Copyright 2024 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};
27pub use rustls::{version, ClientConfig, RootCertStore, ServerConfig, Stream};
28pub use rustls_native_certs::load_native_certs;
29use rustls_pemfile::Item;
30pub use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName};
31pub use tokio_rustls::client::TlsStream as ClientTlsStream;
32pub use tokio_rustls::server::TlsStream as ServerTlsStream;
33pub use tokio_rustls::{Accept, Connect, TlsAcceptor, TlsConnector, TlsStream};
34
35/// Load the given file from disk as a buffered reader and use the pingora Error
36/// type instead of the std::io version
37fn load_file<P>(path: P) -> Result<BufReader<File>>
38where
39    P: AsRef<Path>,
40{
41    File::open(path)
42        .or_err(ErrorType::FileReadError, "Failed to load file")
43        .map(BufReader::new)
44}
45
46/// Read the pem file at the given path from disk
47fn load_pem_file<P>(path: P) -> Result<Vec<Item>>
48where
49    P: AsRef<Path>,
50{
51    rustls_pemfile::read_all(&mut load_file(path)?)
52        .map(|item_res| {
53            item_res.or_err(
54                ErrorType::InvalidCert,
55                "Certificate in pem file could not be read",
56            )
57        })
58        .collect()
59}
60
61/// Load the certificates from the given pem file path into the given
62/// certificate store
63pub fn load_ca_file_into_store<P>(path: P, cert_store: &mut RootCertStore) -> Result<()>
64where
65    P: AsRef<Path>,
66{
67    for pem_item in load_pem_file(path)? {
68        // only loading certificates, handling a CA file
69        let Item::X509Certificate(content) = pem_item else {
70            return Error::e_explain(
71                ErrorType::InvalidCert,
72                "Pem file contains un-loadable certificate type",
73            );
74        };
75        cert_store.add(content).or_err(
76            ErrorType::InvalidCert,
77            "Failed to load X509 certificate into root store",
78        )?;
79    }
80
81    Ok(())
82}
83
84/// Attempt to load the native cas into the given root-certificate store
85pub fn load_platform_certs_incl_env_into_store(ca_certs: &mut RootCertStore) -> Result<()> {
86    // this includes handling of ENV vars SSL_CERT_FILE & SSL_CERT_DIR
87    for cert in load_native_certs()
88        .or_err(ErrorType::InvalidCert, "Failed to load native certificates")?
89        .into_iter()
90    {
91        ca_certs.add(cert).or_err(
92            ErrorType::InvalidCert,
93            "Failed to load native certificate into root store",
94        )?;
95    }
96
97    Ok(())
98}
99
100/// Load the certificates and private key files
101pub fn load_certs_and_key_files<'a>(
102    cert: &str,
103    key: &str,
104) -> Result<Option<(Vec<CertificateDer<'a>>, PrivateKeyDer<'a>)>> {
105    let certs_file = load_pem_file(cert)?;
106    let key_file = load_pem_file(key)?;
107
108    let certs = certs_file
109        .into_iter()
110        .filter_map(|item| {
111            if let Item::X509Certificate(cert) = item {
112                Some(cert)
113            } else {
114                None
115            }
116        })
117        .collect::<Vec<_>>();
118
119    // These are the currently supported pk types -
120    // [https://doc.servo.org/rustls/key/struct.PrivateKey.html]
121    let private_key_opt = key_file
122        .into_iter()
123        .filter_map(|key_item| match key_item {
124            Item::Pkcs1Key(key) => Some(PrivateKeyDer::from(key)),
125            Item::Pkcs8Key(key) => Some(PrivateKeyDer::from(key)),
126            Item::Sec1Key(key) => Some(PrivateKeyDer::from(key)),
127            _ => None,
128        })
129        .next();
130
131    if let (Some(private_key), false) = (private_key_opt, certs.is_empty()) {
132        Ok(Some((certs, private_key)))
133    } else {
134        Ok(None)
135    }
136}
137
138/// Load the certificate
139pub fn load_pem_file_ca(path: &String) -> Result<Vec<u8>> {
140    let mut reader = load_file(path)?;
141    let cas_file_items = rustls_pemfile::certs(&mut reader)
142        .map(|item_res| {
143            item_res.or_err(
144                ErrorType::InvalidCert,
145                "Failed to load certificate from file",
146            )
147        })
148        .collect::<Result<Vec<_>>>()?;
149
150    Ok(cas_file_items
151        .first()
152        .map(|ca| ca.to_vec())
153        .unwrap_or_default())
154}
155
156pub fn load_pem_file_private_key(path: &String) -> Result<Vec<u8>> {
157    Ok(rustls_pemfile::private_key(&mut load_file(path)?)
158        .or_err(
159            ErrorType::InvalidCert,
160            "Failed to load private key from file",
161        )?
162        .map(|key| key.secret_der().to_vec())
163        .unwrap_or_default())
164}
165
166pub fn hash_certificate(cert: &CertificateDer) -> Vec<u8> {
167    let hash = ring::digest::digest(&ring::digest::SHA256, cert.as_ref());
168    hash.as_ref().to_vec()
169}