Add crypto feature, update to rustls 0.23

rustls 0.22 adds support for alternate crypto providers;
0.23 defaults to aws_lc_rs. The tls feature now pulls in NO crypto providers,
but expects a process-default crypto provider to be set before doing TLS.
The crypto feature pulls in ring and adds it as a provider for rustls,
as we'll be using ring anyway for fancier SASL auths.

Due to the 78-column limit being cumbersome on Cargo.toml,
it has been restricted to only apply to markdown files.
This commit is contained in:
TheDaemoness
2024-04-25 14:43:39 -07:00
parent 04a51463ce
commit 6267f4c777
6 changed files with 91 additions and 35 deletions

View File

@@ -3,6 +3,8 @@ charset = utf-8
insert_final_newline = true
indent_style = space
indent_size = 2
[*.md]
max_line_length = 78
[*.rs]

View File

@@ -17,19 +17,21 @@ include = ["/src", "/doc/rustdoc/*", "/README.md"]
[dependencies]
base64 = { version = "0.21.2", optional = true }
rustls = { version = "0.21.4", optional = true, features = ["dangerous_configuration"] }
rustls-native-certs = { version = "0.6.3", optional = true }
rustls-pemfile = { version = "1.0.2", optional = true }
ring = { version = "0.17.8", optional = true }
rustls = { version = "0.23.5", optional = true, default-features = false, features = ["std", "tls12"] }
rustls-native-certs = { version = "0.7.0", optional = true }
rustls-pemfile = { version = "2.1.2", optional = true }
serde = { version = "1.0", features = ["rc"], optional = true }
serde_derive = { version = ">= 1.0.184", optional = true }
tokio = { version = "1.28.2", features = ["io-util", "net", "time", "rt", "sync"], optional = true }
tokio-rustls = { version = "0.24.1", optional = true }
tokio-rustls = { version = "0.26.0", optional = true, default-features = false}
tracing = { version = "0.1.37", default-features = false, features = ["std"], optional = true }
whoami = { version = "1.5.0", optional = true }
[features]
default = ["base64", "client", "tls-tokio"]
default = ["base64", "client", "crypto", "tls-tokio"]
client = []
crypto = ["dep:ring", "rustls?/ring"]
serde = ["dep:serde", "dep:serde_derive"]
tls = ["dep:rustls", "dep:rustls-native-certs", "dep:rustls-pemfile"]
tls-tokio = ["dep:tokio-rustls", "tls", "tokio"]

View File

@@ -19,8 +19,12 @@ and includes the following:
Adds base64 encoding/decoding.
* `client`:
Adds utilities for building client-side IRC software.
* `crypto`:
Adds `ring`-based cryptography, required for some SASL authenticators.
Also adds `ring` as a crypto provider to rustls if `tls` is enabled.
* `tls`:
Adds utilities for working with rustls.
Does NOT pull in any crypto providers.
* `tls-tokio`: Implies `tls` and `tokio`.
Adds support for asynchronous TLS connections.
* `tokio`:

View File

@@ -26,10 +26,10 @@ impl<'a> super::ServerAddr<'a> {
use std::io::{Error, ErrorKind};
let string = self.utf8_address()?;
let stream = if self.tls {
let name = rustls::ServerName::try_from(string)
let name = rustls::pki_types::ServerName::try_from(string)
.map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
let config = tls_fn()?;
let conn = rustls::ClientConnection::new(config, name)
let conn = rustls::ClientConnection::new(config, name.to_owned())
.map_err(|e| Error::new(ErrorKind::Other, e))?;
let sock = std::net::TcpStream::connect((string, self.port_num()))?;
let mut tls = rustls::StreamOwned { conn, sock };

View File

@@ -1,5 +1,5 @@
use super::{timed_io, Bidir, TimeLimitedTokio};
use crate::{client::tls::TlsConfig, ircmsg::ServerMsg};
use crate::ircmsg::ServerMsg;
use std::{pin::Pin, time::Duration};
use tokio::{
io::{AsyncBufRead, AsyncWrite, BufReader},
@@ -21,17 +21,17 @@ impl<'a> super::ServerAddr<'a> {
#[cfg(feature = "tls-tokio")]
pub async fn connect_tokio(
&self,
tls_fn: impl FnOnce() -> std::io::Result<TlsConfig>,
tls_fn: impl FnOnce() -> std::io::Result<crate::client::tls::TlsConfig>,
) -> std::io::Result<BufReader<StreamTokio>> {
use std::io::{Error, ErrorKind};
let string = self.utf8_address()?;
let stream = if self.tls {
let name = rustls::ServerName::try_from(string)
let name = rustls::pki_types::ServerName::try_from(string)
.map_err(|e| Error::new(ErrorKind::InvalidInput, e))?;
let config = tls_fn()?;
let conn: tokio_rustls::TlsConnector = config.into();
let sock = tokio::net::TcpStream::connect((string, self.port_num())).await?;
let tls = conn.connect(name, sock).await?;
let tls = conn.connect(name.to_owned(), sock).await?;
StreamInner::Tls(tls)
} else {
let sock = tokio::net::TcpStream::connect((string, self.port_num())).await?;

View File

@@ -1,6 +1,9 @@
//! Helpers for creating TLS connections.
use rustls::{Certificate, ClientConfig, RootCertStore};
use rustls::{
pki_types::{CertificateDer, PrivateKeyDer},
ClientConfig, RootCertStore,
};
use std::{
path::{Path, PathBuf},
sync::Arc,
@@ -24,22 +27,62 @@ pub enum Trust {
NoVerify,
}
/// `ServerCertVerifier` that verifies literally everything.
#[derive(Clone, Copy, Debug, Default)]
struct NoVerifier;
/// `ServerCertVerifier` that doesn't care at all about the server cert.
#[derive(Clone, Copy, Debug)]
struct NoVerifier(&'static Arc<rustls::crypto::CryptoProvider>);
impl rustls::client::ServerCertVerifier for NoVerifier {
impl Default for NoVerifier {
fn default() -> Self {
NoVerifier(
rustls::crypto::CryptoProvider::get_default()
.expect("no default rustls crypto prodiver"),
)
}
}
impl rustls::client::danger::ServerCertVerifier for NoVerifier {
fn verify_server_cert(
&self,
_: &rustls::Certificate,
_: &[rustls::Certificate],
_: &rustls::ServerName,
_: &mut dyn Iterator<Item = &[u8]>,
_: &CertificateDer<'_>,
_: &[CertificateDer<'_>],
_: &rustls::pki_types::ServerName<'_>,
_: &[u8],
_: std::time::SystemTime,
) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
_: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
// :)
Ok(rustls::client::ServerCertVerified::assertion())
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls12_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls12_signature(
message,
cert,
dss,
&self.0.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self.0.signature_verification_algorithms.supported_schemes()
}
}
@@ -61,25 +104,28 @@ pub struct TlsConfigOptions {
fn load_pem(path: &Path, certs: &mut RootCertStore) -> std::io::Result<()> {
let mut file = std::io::BufReader::new(std::fs::File::open(path)?);
for cert in rustls_pemfile::certs(&mut file)? {
for cert in rustls_pemfile::certs(&mut file) {
let cert = cert?;
certs
.add(&rustls::Certificate(cert))
.add(CertificateDer::from(cert))
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
}
Ok(())
}
fn load_client_cert(path: &Path) -> std::io::Result<(Vec<Certificate>, rustls::PrivateKey)> {
let mut key = Option::<rustls::PrivateKey>::None;
let mut certs = Vec::<Certificate>::new();
fn load_client_cert(
path: &Path,
) -> std::io::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
let mut key = Option::<PrivateKeyDer>::None;
let mut certs = Vec::<CertificateDer>::new();
let mut file = std::io::BufReader::new(std::fs::File::open(path)?);
while let Some(item) = rustls_pemfile::read_one(&mut file)? {
match item {
rustls_pemfile::Item::X509Certificate(c) => {
certs.push(Certificate(c));
certs.push(CertificateDer::from(c));
}
rustls_pemfile::Item::PKCS8Key(k) => {
key = Some(rustls::PrivateKey(k));
rustls_pemfile::Item::Pkcs8Key(k) => {
key = Some(PrivateKeyDer::from(k));
}
_ => (),
}
@@ -94,10 +140,12 @@ impl TlsConfigOptions {
/// This is an expensive operation. It should ideally be done only once per network.
pub fn build(&self) -> std::io::Result<TlsConfig> {
let cli_auth =
if let Some(path) = &self.cert { Some(load_client_cert(path)?) } else { None };
let builder = ClientConfig::builder().with_safe_defaults();
if let Some(path) = self.cert.as_ref() { Some(load_client_cert(path)?) } else { None };
let builder = ClientConfig::builder();
let config = if matches!(&self.trust, Trust::NoVerify) {
let builder = builder.with_custom_certificate_verifier(Arc::new(NoVerifier));
let builder = builder
.dangerous()
.with_custom_certificate_verifier(Arc::new(NoVerifier::default()));
if let Some((certs, key)) = cli_auth {
builder
.with_client_auth_cert(certs, key)
@@ -109,7 +157,7 @@ impl TlsConfigOptions {
let mut certs = RootCertStore { roots: Vec::new() };
if matches!(&self.trust, Trust::Default | Trust::Also(_)) {
let natives = rustls_native_certs::load_native_certs()?;
certs.add_parsable_certificates(&natives);
certs.add_parsable_certificates(natives);
}
if let Trust::Only(paths) | Trust::Also(paths) = &self.trust {
certs.roots.reserve_exact(paths.len());