diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0f4970224..a2f0b02622 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,6 +229,7 @@ jobs: target: native env: S2N_QUIC_PLATFORM_FEATURES_OVERRIDE="mtu_disc,pktinfo,tos,socket_msg" steps: + - uses: ilammy/setup-nasm@v1 - uses: actions/checkout@v4 with: lfs: true diff --git a/examples/rustls-mtls/Cargo.toml b/examples/rustls-mtls/Cargo.toml index f6c1b66aaf..c049e876e7 100644 --- a/examples/rustls-mtls/Cargo.toml +++ b/examples/rustls-mtls/Cargo.toml @@ -7,7 +7,7 @@ authors = ["Rick Richardson ", "AWS s2n"] [dependencies] # Remove the `provider-tls-default` feature and add `provider-tls-rustls` in order to use the rustls backend s2n-quic = { version = "1", path = "../../quic/s2n-quic", default-features = false, features = ["provider-address-token-default", "provider-tls-rustls", "provider-event-tracing"] } -rustls-pemfile = "1" +rustls-pemfile = "2" tokio = { version = "1", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["ansi"] } diff --git a/examples/rustls-mtls/src/lib.rs b/examples/rustls-mtls/src/lib.rs index 947ae74456..75e4521952 100644 --- a/examples/rustls-mtls/src/lib.rs +++ b/examples/rustls-mtls/src/lib.rs @@ -1,22 +1,20 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use rustls::{ - cipher_suite, ClientConfig, Error, RootCertStore, ServerConfig, SupportedCipherSuite, +use s2n_quic::provider::tls::{ + self as s2n_quic_tls_provider, + rustls::rustls::{ + // types from the external rustls crate + pki_types::{CertificateDer, PrivateKeyDer}, + server::WebPkiClientVerifier, + Error as RustlsError, + RootCertStore, + }, }; -use s2n_quic::provider::{tls, tls::rustls::rustls}; use std::{io::Cursor, path::Path, sync::Arc}; use tokio::{fs::File, io::AsyncReadExt}; use tracing::Level; -static PROTOCOL_VERSIONS: &[&rustls::SupportedProtocolVersion] = &[&rustls::version::TLS13]; - -pub static DEFAULT_CIPHERSUITES: &[SupportedCipherSuite] = &[ - cipher_suite::TLS13_AES_128_GCM_SHA256, - cipher_suite::TLS13_AES_256_GCM_SHA384, - cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, -]; - pub fn initialize_logger(endpoint: &str) { use std::sync::Once; @@ -40,23 +38,26 @@ pub fn initialize_logger(endpoint: &str) { } pub struct MtlsProvider { - root_store: rustls::RootCertStore, - my_cert_chain: Vec, - my_private_key: rustls::PrivateKey, + root_store: RootCertStore, + my_cert_chain: Vec>, + my_private_key: PrivateKeyDer<'static>, } -impl tls::Provider for MtlsProvider { - type Server = tls::rustls::Server; - type Client = tls::rustls::Client; - type Error = rustls::Error; +impl s2n_quic_tls_provider::Provider for MtlsProvider { + type Server = s2n_quic_tls_provider::rustls::Server; + type Client = s2n_quic_tls_provider::rustls::Client; + type Error = RustlsError; fn start_server(self) -> Result { - let verifier = rustls::server::AllowAnyAuthenticatedClient::new(self.root_store); - let mut cfg = ServerConfig::builder() - .with_cipher_suites(DEFAULT_CIPHERSUITES) - .with_safe_default_kx_groups() - .with_protocol_versions(PROTOCOL_VERSIONS)? - .with_client_cert_verifier(Arc::new(verifier)) + let default_crypto_provider = s2n_quic_tls_provider::rustls::default_crypto_provider()?; + let verifier = WebPkiClientVerifier::builder_with_provider( + Arc::new(self.root_store), + default_crypto_provider.into(), + ) + .build() + .unwrap(); + let mut cfg = s2n_quic_tls_provider::rustls::server_config_builder()? + .with_client_cert_verifier(verifier) .with_single_cert(self.my_cert_chain, self.my_private_key)?; cfg.ignore_client_order = true; @@ -66,10 +67,7 @@ impl tls::Provider for MtlsProvider { } fn start_client(self) -> Result { - let mut cfg = ClientConfig::builder() - .with_cipher_suites(DEFAULT_CIPHERSUITES) - .with_safe_default_kx_groups() - .with_protocol_versions(PROTOCOL_VERSIONS)? + let mut cfg = s2n_quic_tls_provider::rustls::client_config_builder()? .with_root_certificates(self.root_store) .with_client_auth_cert(self.my_cert_chain, self.my_private_key)?; @@ -84,71 +82,88 @@ impl MtlsProvider { ca_cert_pem: A, my_cert_pem: B, my_key_pem: C, - ) -> Result { + ) -> Result { let root_store = into_root_store(ca_cert_pem.as_ref()).await?; let cert_chain = into_certificate(my_cert_pem.as_ref()).await?; let private_key = into_private_key(my_key_pem.as_ref()).await?; Ok(MtlsProvider { root_store, - my_cert_chain: cert_chain.into_iter().map(rustls::Certificate).collect(), - my_private_key: rustls::PrivateKey(private_key), + my_cert_chain: cert_chain.into_iter().map(CertificateDer::from).collect(), + my_private_key: private_key, }) } } -async fn into_certificate(path: &Path) -> Result>, Error> { +async fn read_file(path: &Path) -> Result, RustlsError> { let mut f = File::open(path) .await - .map_err(|e| Error::General(format!("Failed to load file: {}", e)))?; + .map_err(|e| RustlsError::General(format!("Failed to load file: {}", e)))?; let mut buf = Vec::new(); f.read_to_end(&mut buf) .await - .map_err(|e| Error::General(format!("Failed to read file: {}", e)))?; + .map_err(|e| RustlsError::General(format!("Failed to read file: {}", e)))?; + Ok(buf) +} + +async fn into_certificate(path: &Path) -> Result>, RustlsError> { + let buf = &read_file(path).await?; let mut cursor = Cursor::new(buf); - let certs = rustls_pemfile::certs(&mut cursor) - .map(|certs| certs.into_iter().collect()) - .map_err(|_| Error::General("Could not read certificate".to_string()))?; - Ok(certs) + rustls_pemfile::certs(&mut cursor) + .map(|cert| { + cert.map_err(|_| RustlsError::General("Could not read certificate".to_string())) + }) + .collect() } -async fn into_root_store(path: &Path) -> Result { - let ca_certs = into_certificate(path).await?; +async fn into_root_store(path: &Path) -> Result { + let ca_certs: Vec> = into_certificate(path) + .await + .map(|certs| certs.into_iter().map(CertificateDer::from))? + .collect(); let mut cert_store = RootCertStore::empty(); - cert_store.add_parsable_certificates(ca_certs.as_slice()); + cert_store.add_parsable_certificates(ca_certs); Ok(cert_store) } -async fn into_private_key(path: &Path) -> Result, Error> { - let mut f = File::open(path) - .await - .map_err(|e| Error::General(format!("Failed to load file: {}", e)))?; - let mut buf = Vec::new(); - f.read_to_end(&mut buf) - .await - .map_err(|e| Error::General(format!("Failed to read file: {}", e)))?; +async fn into_private_key(path: &Path) -> Result, RustlsError> { + let buf = &read_file(path).await?; let mut cursor = Cursor::new(buf); - let parsers = [ - rustls_pemfile::rsa_private_keys, - rustls_pemfile::pkcs8_private_keys, - ]; - for parser in parsers.iter() { - cursor.set_position(0); - - match parser(&mut cursor) { - Ok(keys) if keys.is_empty() => continue, - Ok(mut keys) if keys.len() == 1 => return Ok(rustls::PrivateKey(keys.pop().unwrap()).0), - Ok(keys) => { - return Err(Error::General(format!( - "Unexpected number of keys: {} (only 1 supported)", - keys.len() - ))); + macro_rules! parse_key { + ($parser:ident, $key_type:expr) => { + cursor.set_position(0); + + let keys: Result, RustlsError> = rustls_pemfile::$parser(&mut cursor) + .map(|key| { + key.map_err(|_| { + RustlsError::General("Could not load any private keys".to_string()) + }) + }) + .collect(); + match keys { + // try the next parser + Err(_) => (), + // try the next parser + Ok(keys) if keys.is_empty() => (), + Ok(mut keys) if keys.len() == 1 => { + return Ok($key_type(keys.pop().unwrap())); + } + Ok(keys) => { + return Err(RustlsError::General(format!( + "Unexpected number of keys: {} (only 1 supported)", + keys.len() + ))); + } } - // try the next parser - Err(_) => continue, - } + }; } - Err(Error::General( + + // attempt to parse PKCS8 encoded key. Returns early if a key is found + parse_key!(pkcs8_private_keys, PrivateKeyDer::Pkcs8); + // attempt to parse RSA key. Returns early if a key is found + parse_key!(rsa_private_keys, PrivateKeyDer::Pkcs1); + + Err(RustlsError::General( "could not load any valid private keys".to_string(), )) } diff --git a/quic/s2n-quic-core/certs/generate.sh b/quic/s2n-quic-core/certs/generate.sh new file mode 100755 index 0000000000..bfad838521 --- /dev/null +++ b/quic/s2n-quic-core/certs/generate.sh @@ -0,0 +1,9 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +# immediately bail if any command fails +set -e + +# generate PKCS#1 encoded RSA key (openSSL 1.1.1) +echo "generating PKCS #1 encoded RSA key" +openssl genrsa -f4 -out key_pkcs1.pem 2048 diff --git a/quic/s2n-quic-core/certs/key_pkcs1.pem b/quic/s2n-quic-core/certs/key_pkcs1.pem new file mode 100644 index 0000000000..a4530b9b03 --- /dev/null +++ b/quic/s2n-quic-core/certs/key_pkcs1.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA6mOufxxreztVa/JdBkgADQoPpj5WDmfjnJEU4SOrvKQ3TPAX +ITd1dLMSKRzxIYkUlOS9oNngzxTr7nbgFUtn08sgHfxl6KTbhQgqw9caPXWGtnaQ +YgdNFxfaU03fAhREnnGjmzlpBUFMVZEfa5ETOOGPsLIRrOs8uzBIhk+8IksrNkuX +izuRsQ9UThceDjLxO4wnDC+NRS5P+/rf0VOLfWNnxo+ZOlTsOEflvUkVwkiCQXE/ +9ELz3pLlH7vZwefBwv8RiVEVxUiNQUAIp1YudBF2Rfy7OMwL0DKWxpUo6SW3h5uA +HPlf6UEDhfCFszG8R96E4oxe23ISIUWjRSabHQIDAQABAoIBAQCJckDeWy0QC6Aw +9+PjDg+xlcfNhdpzAirwIgeran6H1Yh0PophuSLQdz3cDBO+xaLjGSu2Cm4RIUSl +BuKquhg9k2zXCK49+YadAUgKpbXGeoevseFDCuMC+sLVfOGcV4RRoXP+T3EtWrhH +qR1St7Uc8vCO/iztSNjJMCwnTtSVCn4Mr7j8Tqf8EMoDq9zx4mZaOpeUfcb23Hp7 +NpRkNRU5P1RCQFrw5gjp3SwUSzJfzgewovr2U2Je4naNPgE/VjQrSB7WBPDHJ0rO +hiuKHq3e5T3DCL6SAzcwRHS0omB5TQFpt3H3yjmW3D1srUNcF1Ma8namhO8DM3B5 +0QORLzUhAoGBAPkmYBvqaOOpSZw2i/ZlR27hA4D/sM5IEBsfwHIZcjFvkbRB1lOe +4OW3WhYYX/2pIHJpqyoZrkI6LYTzbTSHBhSD+Szqr+TJUWEbHjG+pXhrJSuZJNub +Qo4JU5QfbLZ3EXosAyl3Bhh3wmTw+sGqCPmq2EarKquvW9IcQJCauGi5AoGBAPDV +ak80MWvfTnfapZpQzVhli5ZoVPhWk38Fv2WvqeuZ16jjy9Q3kvLHvOXL6RiqGnue +OqWzy+eafeSZ+ZdAmcBeFSqmGuhjjgSCQ765/kFU97ECvm4U/R3bk6h1YKydOJXo +fStO7r8zDYWuulwyfp93c3AEKLqgE7G9S2FY+kuFAoGBAJ4N6TRsXUPndcoClIGn +uEwvtJBWJGyILKd4WhZH7DhORclrZrK/fH5d9WlE50g8zVenjyzzA6gBzjSkVGfZ +LFNBcYY8D3988wI+OMZn4gLlPbxNt6MU+ICwi/PQh5+tI0o0t4DLf/NvrcNpWDoZ +Sv5PkkoqdbRaV06QG6lgbZIJAoGBAL1Znk8NoznEBcbYItH0a67vj9M+zme+chMx +qq1BcuCpQVUpXQ3Kb//wKMWoD+nzfATrgALeHRhIcGj24rUX9oq3rf6tQISsGXHU +FX10cMJfEquak6yrVeNOy4ZsWwj8SAwdEaSmV7H+ashLe3yXutSYLyvIKVmqGDHr +ucOdGJWdAoGACpQMjbj44yWD8W3FE2cC6dwet2gkjWEuZPDcXrmUzAy90fyT+x07 +dMqansk4ThlHsfHld+znwJQ20CRCmXWrmVH4vIUNjJK6c+jrvaGYGcoW/bD3Xoeg +qjjmXua7hGVx7wrPIwXfxEG1tJzrDRtEROnCiaeMmzNbVvkQkZTzzkQ= +-----END RSA PRIVATE KEY----- diff --git a/quic/s2n-quic-core/src/crypto/tls/testing.rs b/quic/s2n-quic-core/src/crypto/tls/testing.rs index fe7676184f..23dafc6084 100644 --- a/quic/s2n-quic-core/src/crypto/tls/testing.rs +++ b/quic/s2n-quic-core/src/crypto/tls/testing.rs @@ -39,6 +39,7 @@ pub mod certificates { } pem!(KEY_PEM, "key.pem"); + pem!(KEY_PKCS1_PEM, "key_pkcs1.pem"); pem!(CERT_PEM, "cert.pem"); der!(KEY_DER, "key.der"); der!(CERT_DER, "cert.der"); diff --git a/quic/s2n-quic-qns/Cargo.toml b/quic/s2n-quic-qns/Cargo.toml index 72c511f8f6..6120476882 100644 --- a/quic/s2n-quic-qns/Cargo.toml +++ b/quic/s2n-quic-qns/Cargo.toml @@ -23,8 +23,6 @@ http = "1.0" humansize = "2" lru = "0.10" rand = "0.8" -# dangerous_configuration is used to allow for cert verification to be disabled for the amplification limit interop test -rustls = { version = "0.21", features = ["dangerous_configuration", "quic"] } s2n-codec = { path = "../../common/s2n-codec" } s2n-quic-core = { path = "../s2n-quic-core", features = ["testing"] } s2n-quic-h3 = { path = "../s2n-quic-h3" } diff --git a/quic/s2n-quic-qns/src/tls.rs b/quic/s2n-quic-qns/src/tls.rs index d81d198a8d..428e173b97 100644 --- a/quic/s2n-quic-qns/src/tls.rs +++ b/quic/s2n-quic-qns/src/tls.rs @@ -1,7 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use crate::Result; +use crate::{tls::rustls::DisabledVerifier, Result}; +use s2n_quic::provider::tls::{self as s2n_quic_tls_provider, rustls::rustls as rustls_crate}; use std::{path::PathBuf, str::FromStr}; use structopt::StructOpt; @@ -113,14 +114,12 @@ impl Client { pub fn build_rustls(&self, alpns: &[String]) -> Result { let tls = if self.disable_cert_verification { - use ::rustls::{version, ClientConfig, KeyLogFile}; + use rustls_crate::KeyLogFile; use std::sync::Arc; - let mut config = ClientConfig::builder() - .with_cipher_suites(rustls::DEFAULT_CIPHERSUITES) - .with_safe_default_kx_groups() - .with_protocol_versions(&[&version::TLS13])? - .with_custom_certificate_verifier(Arc::new(rustls::DisabledVerifier)) + let mut config = s2n_quic_tls_provider::rustls::client_config_builder()? + .dangerous() + .with_custom_certificate_verifier(Arc::new(DisabledVerifier)) .with_no_client_auth(); config.max_fragment_size = None; config.alpn_protocols = alpns.iter().map(|p| p.as_bytes().to_vec()).collect(); @@ -265,9 +264,13 @@ pub mod s2n_tls { pub mod rustls { use super::*; + use rustls_crate::{ + client::danger, + pki_types::{CertificateDer, ServerName, UnixTime}, + }; pub use s2n_quic::provider::tls::rustls::{ certificate::{Certificate, IntoCertificate, IntoPrivateKey, PrivateKey}, - Client, Server, DEFAULT_CIPHERSUITES, + default_crypto_provider, Client, Server, }; pub fn ca(ca: Option<&PathBuf>) -> Result { @@ -286,19 +289,54 @@ pub mod rustls { }) } + #[derive(Debug)] pub struct DisabledVerifier; - impl ::rustls::client::ServerCertVerifier for DisabledVerifier { + impl danger::ServerCertVerifier for DisabledVerifier { fn verify_server_cert( &self, - _end_entity: &::rustls::Certificate, - _intermediates: &[::rustls::Certificate], - _server_name: &::rustls::ServerName, - _scts: &mut dyn Iterator, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName, _ocsp_response: &[u8], - _now: std::time::SystemTime, - ) -> Result<::rustls::client::ServerCertVerified, ::rustls::Error> { - Ok(::rustls::client::ServerCertVerified::assertion()) + _now: UnixTime, + ) -> Result { + Ok(danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls_crate::DigitallySignedStruct, + ) -> Result { + rustls_crate::crypto::verify_tls12_signature( + message, + cert, + dss, + &default_crypto_provider()?.signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls_crate::DigitallySignedStruct, + ) -> Result { + rustls_crate::crypto::verify_tls13_signature( + message, + cert, + dss, + &default_crypto_provider()?.signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + default_crypto_provider() + .unwrap() + .signature_verification_algorithms + .supported_schemes() } } } diff --git a/quic/s2n-quic-rustls/Cargo.toml b/quic/s2n-quic-rustls/Cargo.toml index 66d7b2a55d..b1550502a1 100644 --- a/quic/s2n-quic-rustls/Cargo.toml +++ b/quic/s2n-quic-rustls/Cargo.toml @@ -12,8 +12,8 @@ exclude = ["corpus.tar.gz"] [dependencies] bytes = { version = "1", default-features = false } -rustls = { version = "0.21", features = ["quic"] } -rustls-pemfile = "1" +rustls = "0.23" +rustls-pemfile = "2" s2n-codec = { version = "=0.34.0", path = "../../common/s2n-codec", default-features = false, features = ["alloc"] } s2n-quic-core = { version = "=0.34.0", path = "../s2n-quic-core", default-features = false, features = ["alloc"] } s2n-quic-crypto = { version = "=0.34.0", path = "../s2n-quic-crypto", default-features = false } diff --git a/quic/s2n-quic-rustls/src/certificate.rs b/quic/s2n-quic-rustls/src/certificate.rs index a71a00efe6..050e0ccb88 100644 --- a/quic/s2n-quic-rustls/src/certificate.rs +++ b/quic/s2n-quic-rustls/src/certificate.rs @@ -57,9 +57,9 @@ macro_rules! cert_type { fn $method(self) -> Result<$name, Error> { match self.extension() { Some(ext) if ext == "der" => { - let pem = + let der = std::fs::read(self).map_err(|err| Error::General(err.to_string()))?; - pem.$method() + der.$method() } _ => { let pem = std::fs::read_to_string(self) @@ -76,53 +76,66 @@ cert_type!( PrivateKey, IntoPrivateKey, into_private_key, - rustls::PrivateKey + rustls::pki_types::PrivateKeyDer<'static> ); cert_type!( Certificate, IntoCertificate, into_certificate, - Vec + Vec> ); mod pem { - use super::*; + use rustls::{ + pki_types::{CertificateDer, PrivateKeyDer}, + Error, + }; - pub fn into_certificate(contents: &[u8]) -> Result, Error> { + pub fn into_certificate(contents: &[u8]) -> Result>, Error> { let mut cursor = std::io::Cursor::new(contents); - let certs = rustls_pemfile::certs(&mut cursor) - .map(|certs| certs.into_iter().map(rustls::Certificate).collect()) - .map_err(|_| Error::General("Could not read certificate".to_string()))?; - Ok(certs) + + rustls_pemfile::certs(&mut cursor) + .map(|cert| cert.map_err(|_| Error::General("Could not read certificate".to_string()))) + .collect() } - pub fn into_private_key(contents: &[u8]) -> Result { + pub fn into_private_key(contents: &[u8]) -> Result, Error> { let mut cursor = std::io::Cursor::new(contents); - let parsers = [ - rustls_pemfile::rsa_private_keys, - rustls_pemfile::pkcs8_private_keys, - ]; - - for parser in parsers.iter() { - cursor.set_position(0); - - match parser(&mut cursor) { - Ok(keys) if keys.is_empty() => continue, - Ok(mut keys) if keys.len() == 1 => { - return Ok(rustls::PrivateKey(keys.pop().unwrap())) - } - Ok(keys) => { - return Err(Error::General(format!( - "Unexpected number of keys: {} (only 1 supported)", - keys.len() - ))); + macro_rules! parse_key { + ($parser:ident, $key_type:expr) => { + cursor.set_position(0); + + let keys: Result, Error> = rustls_pemfile::$parser(&mut cursor) + .map(|key| { + key.map_err(|_| { + Error::General("Could not load any private keys".to_string()) + }) + }) + .collect(); + match keys { + // try the next parser + Err(_) => (), + // try the next parser + Ok(keys) if keys.is_empty() => (), + Ok(mut keys) if keys.len() == 1 => { + return Ok($key_type(keys.pop().unwrap())); + } + Ok(keys) => { + return Err(Error::General(format!( + "Unexpected number of keys: {} (only 1 supported)", + keys.len() + ))); + } } - // try the next parser - Err(_) => continue, - } + }; } + // attempt to parse PKCS8 encoded key. Returns early if a key is found + parse_key!(pkcs8_private_keys, PrivateKeyDer::Pkcs8); + // attempt to parse RSA key. Returns early if a key is found + parse_key!(rsa_private_keys, PrivateKeyDer::Pkcs1); + Err(Error::General( "could not load any valid private keys".to_string(), )) @@ -130,15 +143,23 @@ mod pem { } mod der { - use super::*; + use rustls::{ + pki_types::{CertificateDer, PrivateKeyDer}, + Error, + }; - pub fn into_certificate(contents: Vec) -> Result, Error> { + pub fn into_certificate(contents: Vec) -> Result>, Error> { // der files only have a single cert - Ok(vec![rustls::Certificate(contents)]) + Ok(vec![CertificateDer::from(contents)]) } - pub fn into_private_key(contents: Vec) -> Result { - Ok(rustls::PrivateKey(contents)) + pub fn into_private_key(contents: Vec) -> Result, Error> { + // PKCS #8 is used since it's capable of encoding RSA as well as other key + // types (eg. ECDSA). Additionally, multiple attacks have been discovered + // against PKCS #1 so PKCS #8 should be preferred. + // + // https://stackoverflow.com/a/48960291 + Ok(PrivateKeyDer::Pkcs8(contents.into())) } } @@ -148,11 +169,17 @@ mod tests { use s2n_quic_core::crypto::tls::testing::certificates::*; #[test] - fn load() { + fn load_pem() { let _ = CERT_PEM.into_certificate().unwrap(); - let _ = CERT_DER.into_certificate().unwrap(); - + // PKCS #8 encoded key let _ = KEY_PEM.into_private_key().unwrap(); + // PKCS #1 encoded key + let _ = KEY_PKCS1_PEM.into_private_key().unwrap(); + } + + #[test] + fn load_der() { + let _ = CERT_DER.into_certificate().unwrap(); let _ = KEY_DER.into_private_key().unwrap(); } } diff --git a/quic/s2n-quic-rustls/src/cipher_suite.rs b/quic/s2n-quic-rustls/src/cipher_suite.rs index 84b2512d86..199b0e3872 100644 --- a/quic/s2n-quic-rustls/src/cipher_suite.rs +++ b/quic/s2n-quic-rustls/src/cipher_suite.rs @@ -1,12 +1,24 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use rustls::{cipher_suite as ciphers, quic, CipherSuite, SupportedCipherSuite}; +use rustls::{ + crypto::{aws_lc_rs, CryptoProvider}, + quic, CipherSuite, SupportedCipherSuite, +}; use s2n_codec::Encoder; use s2n_quic_core::crypto::{self, packet_protection, scatter, tls, HeaderProtectionMask, Key}; +/// `aws_lc_rs` is the default crypto provider since that is also the +/// default used by rustls. +pub fn default_crypto_provider() -> Result { + Ok(CryptoProvider { + cipher_suites: DEFAULT_CIPHERSUITES.to_vec(), + ..aws_lc_rs::default_provider() + }) +} + pub struct PacketKey { - key: quic::PacketKey, + key: Box, cipher_suite: tls::CipherSuite, } @@ -159,7 +171,7 @@ impl crypto::Key for PacketKeys { impl crypto::HandshakeKey for PacketKeys {} -pub struct HeaderProtectionKey(quic::HeaderProtectionKey); +pub struct HeaderProtectionKey(Box); impl HeaderProtectionKey { /// Returns the header protection mask for the given ciphertext sample @@ -331,10 +343,10 @@ impl crypto::OneRttKey for OneRttKey { //# negotiated unless a header protection scheme is defined for the //# cipher suite. // All of the cipher_suites from the current exported list have HP schemes for QUIC -pub static DEFAULT_CIPHERSUITES: &[SupportedCipherSuite] = &[ - ciphers::TLS13_AES_128_GCM_SHA256, - ciphers::TLS13_AES_256_GCM_SHA384, - ciphers::TLS13_CHACHA20_POLY1305_SHA256, +static DEFAULT_CIPHERSUITES: &[SupportedCipherSuite] = &[ + aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256, + aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384, + aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256, ]; #[test] diff --git a/quic/s2n-quic-rustls/src/client.rs b/quic/s2n-quic-rustls/src/client.rs index a5dc35c541..a322cd4086 100644 --- a/quic/s2n-quic-rustls/src/client.rs +++ b/quic/s2n-quic-rustls/src/client.rs @@ -1,13 +1,23 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use crate::{certificate, session::Session}; +use crate::{certificate, cipher_suite::default_crypto_provider, session::Session}; use core::convert::TryFrom; -use rustls::ClientConfig; +use rustls::{ClientConfig, ConfigBuilder, WantsVerifier}; use s2n_codec::EncoderValue; use s2n_quic_core::{application::ServerName, crypto::tls}; use std::sync::Arc; +/// Create a QUIC client specific [rustls::ConfigBuilder]. +/// +/// Uses aws_lc_rs as the crypto provider and sets QUIC specific protocol versions. +pub fn default_config_builder() -> Result, rustls::Error> +{ + let tls13_cipher_suite_crypto_provider = default_crypto_provider()?; + ClientConfig::builder_with_provider(tls13_cipher_suite_crypto_provider.into()) + .with_protocol_versions(crate::PROTOCOL_VERSIONS) +} + #[derive(Clone)] pub struct Client { config: Arc, @@ -65,8 +75,8 @@ impl tls::Endpoint for Client { //# Endpoints MUST send the quic_transport_parameters extension; let transport_parameters = transport_parameters.encode_to_vec(); - let rustls_server_name = - rustls::ServerName::try_from(server_name.as_ref()).expect("invalid server name"); + let rustls_server_name = rustls::pki_types::ServerName::try_from(server_name.to_string()) + .expect("invalid server name"); let session = rustls::quic::ClientConnection::new( self.config.clone(), @@ -114,7 +124,7 @@ impl Builder { rustls::Error::General("Certificate chain needs to have at least one entry".to_string()) })?; self.cert_store - .add(root_certificate) + .add(root_certificate.to_owned()) .map_err(|err| rustls::Error::General(err.to_string()))?; Ok(self) } @@ -148,10 +158,7 @@ impl Builder { )); } - let mut config = ClientConfig::builder() - .with_cipher_suites(crate::cipher_suite::DEFAULT_CIPHERSUITES) - .with_safe_default_kx_groups() - .with_protocol_versions(crate::PROTOCOL_VERSIONS)? + let mut config = default_config_builder()? .with_root_certificates(self.cert_store) .with_no_client_auth(); diff --git a/quic/s2n-quic-rustls/src/error.rs b/quic/s2n-quic-rustls/src/error.rs index ad4b6f4fba..9c34f5b8c8 100644 --- a/quic/s2n-quic-rustls/src/error.rs +++ b/quic/s2n-quic-rustls/src/error.rs @@ -12,7 +12,6 @@ pub fn reason(error: rustls::Error) -> &'static str { Error::DecryptError => "cannot decrypt peer's message", Error::EncryptError => "cannot encrypt local message", Error::AlertReceived(_) => "received fatal alert", - Error::InvalidSct(_) => "invalid certificate timestamp", Error::FailedToGetCurrentTime => "failed to get current time", Error::FailedToGetRandomBytes => "failed to get random bytes", Error::HandshakeNotComplete => "handshake not complete", @@ -20,6 +19,12 @@ pub fn reason(error: rustls::Error) -> &'static str { Error::NoApplicationProtocol => "peer doesn't support any known protocol", Error::BadMaxFragmentSize => "bad max fragment size", Error::General(_) => "unexpected error", + Error::InvalidMessage(_) => "invalid message received", + Error::PeerIncompatible(_) => "peer doesn't support a protocol version/feature", + Error::PeerMisbehaved(_) => "peer TLS protocol error", + Error::InvalidCertificate(_) => "invalid certificate", + Error::InvalidCertRevocationList(_) => "invalid crl", + Error::Other(_) => "some other error occurred", // rustls may add a new variant in the future that breaks us so do a wildcard #[allow(unreachable_patterns)] _ => "unexpected error", diff --git a/quic/s2n-quic-rustls/src/lib.rs b/quic/s2n-quic-rustls/src/lib.rs index b06604174f..dce20875e5 100644 --- a/quic/s2n-quic-rustls/src/lib.rs +++ b/quic/s2n-quic-rustls/src/lib.rs @@ -3,7 +3,8 @@ #![forbid(unsafe_code)] -pub use rustls::{self, Certificate, PrivateKey}; +// re-export rustls +pub use rustls; mod cipher_suite; mod error; @@ -13,9 +14,9 @@ pub mod certificate; pub mod client; pub mod server; -pub use cipher_suite::DEFAULT_CIPHERSUITES; -pub use client::Client; -pub use server::Server; +pub use cipher_suite::default_crypto_provider; +pub use client::{default_config_builder as client_config_builder, Client}; +pub use server::{default_config_builder as server_config_builder, Server}; //= https://www.rfc-editor.org/rfc/rfc9001#section-4.2 //# Clients MUST NOT offer TLS versions older than 1.3. @@ -24,27 +25,54 @@ static PROTOCOL_VERSIONS: &[&rustls::SupportedProtocolVersion] = &[&rustls::vers /// The supported version of quic const QUIC_VERSION: rustls::quic::Version = rustls::quic::Version::V1; -#[test] -fn client_server_test() { +#[cfg(test)] +mod tests { + use super::*; use s2n_quic_core::crypto::tls::{self, testing::certificates::*}; - let mut client = client::Builder::new() - .with_certificate(CERT_PEM) - .unwrap() - .build() - .unwrap(); + #[test] + fn client_server_test() { + let mut client = client::Builder::new() + .with_certificate(CERT_PEM) + .unwrap() + .build() + .unwrap(); - let mut server = server::Builder::new() - .with_certificate(CERT_PEM, KEY_PEM) - .unwrap() - .build() - .unwrap(); + let mut server = server::Builder::new() + .with_certificate(CERT_PEM, KEY_PEM) + .unwrap() + .build() + .unwrap(); - let mut pair = tls::testing::Pair::new(&mut server, &mut client, "localhost".into()); + let mut pair = tls::testing::Pair::new(&mut server, &mut client, "localhost".into()); - while pair.is_handshaking() { - pair.poll(None).unwrap(); + while pair.is_handshaking() { + pair.poll(None).unwrap(); + } + + pair.finish(); } - pair.finish(); + #[test] + fn client_server_der_test() { + let mut client = client::Builder::new() + .with_certificate(CERT_DER) + .unwrap() + .build() + .unwrap(); + + let mut server = server::Builder::new() + .with_certificate(CERT_DER, KEY_DER) + .unwrap() + .build() + .unwrap(); + + let mut pair = tls::testing::Pair::new(&mut server, &mut client, "localhost".into()); + + while pair.is_handshaking() { + pair.poll(None).unwrap(); + } + + pair.finish(); + } } diff --git a/quic/s2n-quic-rustls/src/server.rs b/quic/s2n-quic-rustls/src/server.rs index 19ec1b9dc2..6dee11fc6f 100644 --- a/quic/s2n-quic-rustls/src/server.rs +++ b/quic/s2n-quic-rustls/src/server.rs @@ -1,12 +1,22 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use crate::{certificate, session::Session}; -use rustls::ServerConfig; +use crate::{certificate, cipher_suite::default_crypto_provider, session::Session}; +use rustls::{crypto::aws_lc_rs, ConfigBuilder, ServerConfig, WantsVerifier}; use s2n_codec::EncoderValue; use s2n_quic_core::{application::ServerName, crypto::tls}; use std::sync::Arc; +/// Create a QUIC server specific [rustls::ConfigBuilder]. +/// +/// Uses aws_lc_rs as the crypto provider and sets QUIC specific protocol versions. +pub fn default_config_builder() -> Result, rustls::Error> +{ + let tls13_cipher_suite_crypto_provider = default_crypto_provider()?; + ServerConfig::builder_with_provider(tls13_cipher_suite_crypto_provider.into()) + .with_protocol_versions(crate::PROTOCOL_VERSIONS) +} + #[derive(Clone)] pub struct Server { config: Arc, @@ -133,11 +143,7 @@ impl Builder { } pub fn build(self) -> Result { - let builder = ServerConfig::builder() - .with_cipher_suites(crate::cipher_suite::DEFAULT_CIPHERSUITES) - .with_safe_default_kx_groups() - .with_protocol_versions(crate::PROTOCOL_VERSIONS)? - .with_no_client_auth(); + let builder = default_config_builder()?.with_no_client_auth(); let mut config = if let Some(cert_resolver) = self.cert_resolver { builder.with_cert_resolver(cert_resolver) @@ -159,6 +165,7 @@ impl Builder { } } +#[derive(Debug)] struct AlwaysResolvesChain(Arc); impl AlwaysResolvesChain { @@ -166,7 +173,7 @@ impl AlwaysResolvesChain { chain: certificate::Certificate, priv_key: certificate::PrivateKey, ) -> Result { - let key = rustls::sign::any_supported_type(&priv_key.0) + let key = aws_lc_rs::sign::any_supported_type(&priv_key.0) .map_err(|_| rustls::Error::General("invalid private key".into()))?; Ok(Self(Arc::new(rustls::sign::CertifiedKey::new( chain.0, key, diff --git a/quic/s2n-quic-rustls/src/session.rs b/quic/s2n-quic-rustls/src/session.rs index e3c0dd1954..2ba3a75eba 100644 --- a/quic/s2n-quic-rustls/src/session.rs +++ b/quic/s2n-quic-rustls/src/session.rs @@ -6,9 +6,7 @@ use crate::cipher_suite::{ }; use bytes::Bytes; use core::{fmt, fmt::Debug, task::Poll}; -use rustls::quic::{ - Connection, {self}, -}; +use rustls::quic::{self, Connection}; use s2n_quic_core::{ application::ServerName, crypto::{self, tls, tls::CipherSuite}, @@ -104,9 +102,12 @@ impl Session { self.connection .alert() - .map(|alert| tls::Error { - code: alert.get_u8(), - reason, + .map(|alert| { + // Explicitly annotate the type to detect if rustls starts + // returning a large array + let code: [u8; 1] = alert.to_array(); + let code = code[0]; + tls::Error { code, reason } }) .unwrap_or(tls::Error::INTERNAL_ERROR) })?; diff --git a/quic/s2n-quic-tls/src/certificate.rs b/quic/s2n-quic-tls/src/certificate.rs index c05a3f59be..5f85ef7be6 100644 --- a/quic/s2n-quic-tls/src/certificate.rs +++ b/quic/s2n-quic-tls/src/certificate.rs @@ -91,8 +91,8 @@ macro_rules! cert_type { fn $method(self) -> Result<$name, Error> { match self.extension() { Some(ext) if ext == "der" => { - let pem = std::fs::read(self).map_err(|err| Error::io_error(err))?; - pem.$method() + let der = std::fs::read(self).map_err(|err| Error::io_error(err))?; + der.$method() } // assume it's in pem format _ => {