async_nats/
tls.rs

1// Copyright 2020-2022 The NATS Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14use crate::connector::ConnectorOptions;
15use crate::tls;
16use rustls_webpki::types::{CertificateDer, PrivateKeyDer};
17use std::io::{self, BufReader, ErrorKind};
18use std::path::PathBuf;
19use tokio_rustls::rustls::{ClientConfig, RootCertStore};
20
21/// Loads client certificates from a `.pem` file.
22/// If the pem file is found, but does not contain any certificates, it will return
23/// empty set of Certificates, not error.
24/// Can be used to parse only client certificates from .pem file containing both client key and certs.
25pub(crate) async fn load_certs(path: PathBuf) -> io::Result<Vec<CertificateDer<'static>>> {
26    tokio::task::spawn_blocking(move || {
27        let file = std::fs::File::open(path)?;
28        let mut reader = BufReader::new(file);
29        rustls_pemfile::certs(&mut reader).collect::<io::Result<Vec<_>>>()
30    })
31    .await?
32}
33
34/// Loads client key from a `.pem` file.
35/// Can be used to parse only client key from .pem file containing both client key and certs.
36pub(crate) async fn load_key(path: PathBuf) -> io::Result<PrivateKeyDer<'static>> {
37    tokio::task::spawn_blocking(move || {
38        let file = std::fs::File::open(path)?;
39        let mut reader = BufReader::new(file);
40        rustls_pemfile::private_key(&mut reader)?.ok_or_else(|| {
41            io::Error::new(ErrorKind::NotFound, "could not find client key in the path")
42        })
43    })
44    .await?
45}
46
47pub(crate) async fn config_tls(options: &ConnectorOptions) -> io::Result<ClientConfig> {
48    let mut root_store = RootCertStore::empty();
49    // load native system certs only if user did not specify them.
50    if options.tls_client_config.is_some() || options.certificates.is_empty() {
51        let certs_iter = rustls_native_certs::load_native_certs().map_err(|err| {
52            io::Error::new(
53                ErrorKind::Other,
54                format!("could not load platform certs: {err}"),
55            )
56        })?;
57        root_store.add_parsable_certificates(certs_iter);
58    }
59
60    // use provided ClientConfig or built it from options.
61    let tls_config = {
62        if let Some(config) = &options.tls_client_config {
63            Ok(config.to_owned())
64        } else {
65            // Include user-provided certificates.
66            for cafile in &options.certificates {
67                let trust_anchors = load_certs(cafile.to_owned())
68                    .await?
69                    .into_iter()
70                    .map(|cert| {
71                        rustls_webpki::anchor_from_trusted_cert(&cert).map(|ta| ta.to_owned())
72                    })
73                    .collect::<Result<Vec<_>, rustls_webpki::Error>>()
74                    .map_err(|err| {
75                        io::Error::new(
76                            ErrorKind::InvalidInput,
77                            format!("could not load certs: {err}"),
78                        )
79                    })?;
80                root_store.extend(trust_anchors);
81            }
82            let builder = ClientConfig::builder().with_root_certificates(root_store);
83            if let Some(cert) = options.client_cert.clone() {
84                if let Some(key) = options.client_key.clone() {
85                    let key = tls::load_key(key).await?;
86                    let cert = tls::load_certs(cert).await?;
87                    builder.with_client_auth_cert(cert, key).map_err(|_| {
88                        io::Error::new(ErrorKind::Other, "could not add certificate or key")
89                    })
90                } else {
91                    Err(io::Error::new(
92                        ErrorKind::Other,
93                        "found certificate, but no key",
94                    ))
95                }
96            } else {
97                // if there are no client certs provided, connect with just TLS.
98                Ok(builder.with_no_client_auth())
99            }
100        }
101    }?;
102    Ok(tls_config)
103}