Propagate TLS ciphersuite config to rustls.

Closes #1563.
This commit is contained in:
Sergio Benitez 2021-06-29 11:17:59 -07:00
parent dd722cdcfc
commit f818702f9b
5 changed files with 97 additions and 56 deletions

View File

@ -5,11 +5,12 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
pub use tokio_rustls::rustls;
use rustls::internal::pemfile;
use rustls::{Certificate, PrivateKey, ServerConfig};
use tokio::net::{TcpListener, TcpStream};
use rustls::{Certificate, PrivateKey, ServerConfig, SupportedCipherSuite};
use tokio_rustls::{TlsAcceptor, Accept, server::TlsStream};
use tokio_rustls::rustls;
use tokio::net::{TcpListener, TcpStream};
use crate::listener::{Connection, Listener};
@ -94,10 +95,12 @@ impl Listener for TlsListener {
}
}
pub async fn bind_tls<C: io::BufRead + Send, K: io::BufRead + Send>(
pub async fn bind_tls(
address: SocketAddr,
mut cert_chain: C,
mut private_key: K,
mut cert_chain: impl io::BufRead + Send,
mut private_key: impl io::BufRead + Send,
ciphersuites: impl Iterator<Item = &'static SupportedCipherSuite>,
prefer_server_order: bool,
) -> io::Result<TlsListener> {
let cert_chain = load_certs(&mut cert_chain).map_err(|e| {
let msg = format!("malformed TLS certificate chain: {}", e);
@ -116,6 +119,8 @@ pub async fn bind_tls<C: io::BufRead + Send, K: io::BufRead + Send>(
let cache = rustls::ServerSessionMemoryCache::new(1024);
tls_config.set_persistence(cache);
tls_config.ticketer = rustls::Ticketer::new();
tls_config.ciphersuites = ciphersuites.collect();
tls_config.ignore_client_order = prefer_server_order;
tls_config.set_single_cert(cert_chain, key).expect("invalid key");
tls_config.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]);

View File

@ -283,7 +283,7 @@ mod tests {
[global.tls]
certs = "cert.pem"
key = "key.pem"
prefer_client_cipher_order = true
prefer_server_cipher_order = true
ciphers = [
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
@ -299,7 +299,7 @@ mod tests {
let key_path = jail.directory().join("key.pem");
assert_eq!(config, Config {
tls: Some(TlsConfig::from_paths(cert_path, key_path)
.with_preferred_client_cipher_order(true)
.with_preferred_server_cipher_order(true)
.with_ciphers([
CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_AES_256_GCM_SHA384,

View File

@ -20,11 +20,11 @@ use indexmap::IndexSet;
/// most to least. It is not required and defaults to
/// [`CipherSuite::DEFAULT_SET`], the recommended setting.
///
/// * `prefer_client_cipher_order`
/// * `prefer_server_cipher_order`
///
/// A boolean that indicates whether the server should regard the client's
/// ciphersuite preferences over its own. The default and recommended value
/// is `false`.
/// A boolean that indicates whether the server should regard its own
/// ciphersuite preferences over the client's. The default and recommended
/// value is `false`.
///
/// The following example illustrates manual configuration:
///
@ -40,11 +40,13 @@ use indexmap::IndexSet;
/// let tls_config = config.tls.as_ref().unwrap();
/// assert!(tls_config.certs().is_left());
/// assert!(tls_config.key().is_right());
/// assert_eq!(tls_config.ciphers().count(), 9);
/// assert!(!tls_config.prefer_server_cipher_order());
///
/// // From a serialized `TlsConfig`.
/// let tls_config = TlsConfig::from_paths("/ssl/certs.pem", "/ssl/key.pem")
/// .with_ciphers(CipherSuite::TLS_V13_SET)
/// .with_preferred_client_cipher_order(true);
/// .with_preferred_server_cipher_order(true);
///
/// let figment = rocket::Config::figment()
/// .merge(("tls", tls_config));
@ -52,9 +54,10 @@ use indexmap::IndexSet;
/// let config = rocket::Config::from(figment);
/// let tls_config = config.tls.as_ref().unwrap();
/// assert_eq!(tls_config.ciphers().count(), 3);
/// assert!(tls_config.prefer_client_cipher_order());
/// assert!(tls_config.prefer_server_cipher_order());
/// ```
#[derive(PartialEq, Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TlsConfig {
/// Path or raw bytes for the DER-encoded X.509 TLS certificate chain.
pub(crate) certs: Either<RelativePathBuf, Vec<u8>>,
@ -64,9 +67,9 @@ pub struct TlsConfig {
/// List of TLS cipher suites in server-preferred order.
#[serde(default = "CipherSuite::default_set")]
pub(crate) ciphers: IndexSet<CipherSuite>,
/// Whether to prefer the client's cipher suite order over the server's.
/// Whether to prefer the server's cipher suite order over the client's.
#[serde(default)]
pub(crate) prefer_client_cipher_order: bool,
pub(crate) prefer_server_cipher_order: bool,
}
/// A supported TLS cipher suite.
@ -157,7 +160,7 @@ impl TlsConfig {
certs: Either::Left(certs.as_ref().to_path_buf().into()),
key: Either::Left(key.as_ref().to_path_buf().into()),
ciphers: CipherSuite::default_set(),
prefer_client_cipher_order: false,
prefer_server_cipher_order: Default::default(),
}
}
@ -180,7 +183,7 @@ impl TlsConfig {
certs: Either::Right(certs.to_vec().into()),
key: Either::Right(key.to_vec().into()),
ciphers: CipherSuite::default_set(),
prefer_client_cipher_order: false,
prefer_server_cipher_order: Default::default(),
}
}
@ -250,19 +253,19 @@ impl TlsConfig {
self
}
/// Whether to prefer the client's cipher suite order (`true`) to the
/// server's or not (`false`). The default prefer's the server's order
/// (`false`).
/// Whether to prefer the server's cipher suite order and ignore the
/// client's preferences (`true`) or choose the first supported ciphersuite
/// in the client's preference list (`false`). The default prefer's the
/// client's order (`false`).
///
/// During TLS cipher suite negotiation, the client presents a set of
/// supported ciphers in its preferred order. From this list, the server
/// chooses one cipher suite. By default, the server chooses the first
/// cipher in order of its own preference that the client also supports,
/// ignoring the client's preferences.
/// cipher it supports from the list.
///
/// By setting `prefer_client_order` to `true`, the server instead chooses
/// the first cipher in the _client's_ preferred order that _it_ supports,
/// ignoring its own preferences.
/// By setting `prefer_server_order` to `true`, the server instead chooses
/// the first ciphersuite in it prefers that the client also supports,
/// ignoring the client's preferences.
///
/// # Example
///
@ -273,10 +276,10 @@ impl TlsConfig {
/// # let key_buf = &[];
/// let tls_config = TlsConfig::from_bytes(certs_buf, key_buf)
/// .with_ciphers(CipherSuite::TLS_V13_SET)
/// .with_preferred_client_cipher_order(true);
/// .with_preferred_server_cipher_order(true);
/// ```
pub fn with_preferred_client_cipher_order(mut self, prefer_client_order: bool) -> Self {
self.prefer_client_cipher_order = prefer_client_order;
pub fn with_preferred_server_cipher_order(mut self, prefer_server_order: bool) -> Self {
self.prefer_server_cipher_order = prefer_server_order;
self
}
@ -353,7 +356,7 @@ impl TlsConfig {
self.ciphers.iter().copied()
}
/// Whether the client's cipher suite ordering is preferred or not.
/// Whether the server's cipher suite ordering is preferred or not.
///
/// # Example
///
@ -364,43 +367,72 @@ impl TlsConfig {
/// # let key_buf = &[];
/// // The default prefers the server's order.
/// let tls_config = TlsConfig::from_bytes(certs_buf, key_buf);
/// assert!(!tls_config.prefer_client_cipher_order());
/// assert!(!tls_config.prefer_server_cipher_order());
///
/// // Which can be overriden with the eponymous builder method.
/// let tls_config = TlsConfig::from_bytes(certs_buf, key_buf)
/// .with_preferred_client_cipher_order(true);
/// .with_preferred_server_cipher_order(true);
///
/// assert!(tls_config.prefer_client_cipher_order());
/// assert!(tls_config.prefer_server_cipher_order());
/// ```
pub fn prefer_client_cipher_order(&self) -> bool {
self.prefer_client_cipher_order
pub fn prefer_server_cipher_order(&self) -> bool {
self.prefer_server_cipher_order
}
}
#[cfg(feature = "tls")]
type Reader = Box<dyn std::io::BufRead + Sync + Send>;
mod with_tls_feature {
use crate::http::private::tls::rustls::SupportedCipherSuite as RustlsCipher;
use crate::http::private::tls::rustls::ciphersuite as rustls;
#[cfg(feature = "tls")]
impl TlsConfig {
pub(crate) fn to_readers(&self) -> std::io::Result<(Reader, Reader)> {
use std::{io::{self, Error}, fs};
use yansi::Paint;
use super::*;
fn to_reader(value: &Either<RelativePathBuf, Vec<u8>>) -> io::Result<Reader> {
match value {
Either::Left(path) => {
let path = path.relative();
let file = fs::File::open(&path).map_err(move |e| {
Error::new(e.kind(), format!("error reading TLS file `{}`: {}",
Paint::white(figment::Source::File(path)), e))
})?;
type Reader = Box<dyn std::io::BufRead + Sync + Send>;
Ok(Box::new(io::BufReader::new(file)))
impl TlsConfig {
pub(crate) fn to_readers(&self) -> std::io::Result<(Reader, Reader)> {
use std::{io::{self, Error}, fs};
use yansi::Paint;
fn to_reader(value: &Either<RelativePathBuf, Vec<u8>>) -> io::Result<Reader> {
match value {
Either::Left(path) => {
let path = path.relative();
let file = fs::File::open(&path).map_err(move |e| {
Error::new(e.kind(), format!("error reading TLS file `{}`: {}",
Paint::white(figment::Source::File(path)), e))
})?;
Ok(Box::new(io::BufReader::new(file)))
}
Either::Right(vec) => Ok(Box::new(io::Cursor::new(vec.clone()))),
}
Either::Right(vec) => Ok(Box::new(io::Cursor::new(vec.clone()))),
}
Ok((to_reader(&self.certs)?, to_reader(&self.key)?))
}
Ok((to_reader(&self.certs)?, to_reader(&self.key)?))
pub(crate) fn rustls_ciphers(&self) -> impl Iterator<Item = &'static RustlsCipher> + '_ {
self.ciphers().map(|ciphersuite| match ciphersuite {
CipherSuite::TLS_CHACHA20_POLY1305_SHA256 =>
&rustls::TLS13_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_AES_256_GCM_SHA384 =>
&rustls::TLS13_AES_256_GCM_SHA384,
CipherSuite::TLS_AES_128_GCM_SHA256 =>
&rustls::TLS13_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 =>
&rustls::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 =>
&rustls::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 =>
&rustls::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 =>
&rustls::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 =>
&rustls::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 =>
&rustls::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
})
}
}
}

View File

@ -372,7 +372,11 @@ impl Rocket<Orbit> {
use crate::http::private::tls::bind_tls;
let (certs, key) = config.to_readers().map_err(ErrorKind::Io)?;
let l = bind_tls(addr, certs, key).await.map_err(ErrorKind::Bind)?;
let ciphers = config.rustls_ciphers();
let server_order = config.prefer_server_cipher_order;
let l = bind_tls(addr, certs, key, ciphers, server_order).await
.map_err(ErrorKind::Bind)?;
addr = l.local_addr().unwrap_or(addr);
self.config.address = addr.ip();
self.config.port = addr.port();

View File

@ -260,10 +260,10 @@ deserialize into the [`TlsConfig`] structure. These are:
| `key` | **_yes_** | Path or bytes to DER-encoded ASN.1 PKCS#1/#8 key. |
| `certs` | **_yes_** | Path or bytes to DER-encoded X.509 TLS cert chain. |
| `ciphers` | no | Array of [`CipherSuite`]s to enable. |
| `prefer_client_cipher_order` | no | Boolean for whether to [prefer client cipher suites]. |
| `prefer_server_cipher_order` | no | Boolean for whether to [prefer server cipher suites]. |
[`CipherSuite`]: @api/rocket/config/enum.CipherSuite.html
[prefer client cipher suites]: @api/rocket/config/struct.TlsConfig.html#method.with_preferred_client_cipher_order
[prefer server cipher suites]: @api/rocket/config/struct.TlsConfig.html#method.with_preferred_server_cipher_order
When specified via TOML or other serialized formats, each [`CipherSuite`] is
written as a string representation of the respective variant. For example,
@ -274,7 +274,7 @@ the defaults (with an arbitrary `certs` and `key`) are written:
[default.tls]
certs = "/ssl/cert.pem"
key = "/ssl/key.pem"
prefer_client_cipher_order = false
prefer_server_cipher_order = false
ciphers = [
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",