From d6da26110f7308f6efad65bdf5df6f400c60fab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20R=C3=BCth?= Date: Sun, 26 Nov 2023 17:28:48 +0100 Subject: [PATCH 1/2] Fix signer and add e2e tls13 test --- boring-rustls-provider/Cargo.toml | 3 + boring-rustls-provider/src/lib.rs | 8 +- boring-rustls-provider/src/sign.rs | 26 +++--- boring-rustls-provider/tests/e2e.rs | 129 ++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 15 deletions(-) create mode 100644 boring-rustls-provider/tests/e2e.rs diff --git a/boring-rustls-provider/Cargo.toml b/boring-rustls-provider/Cargo.toml index 9017577..50ffd8d 100644 --- a/boring-rustls-provider/Cargo.toml +++ b/boring-rustls-provider/Cargo.toml @@ -33,6 +33,9 @@ webpki = { package = "rustls-webpki", version = "0.102.0-alpha.1", default-featu [dev-dependencies] hex-literal = "0.4" +rcgen = "0.11.3" +tokio = { version = "1.34", features = ["macros", "rt", "net", "io-util", "io-std"] } +tokio-rustls = "0.25.0-alpha.2" diff --git a/boring-rustls-provider/src/lib.rs b/boring-rustls-provider/src/lib.rs index 00f2747..0183125 100644 --- a/boring-rustls-provider/src/lib.rs +++ b/boring-rustls-provider/src/lib.rs @@ -15,11 +15,11 @@ mod hmac; mod kx; #[cfg(feature = "tls12")] mod prf; -mod sign; +pub mod sign; #[cfg(feature = "tls12")] -mod tls12; -mod tls13; -mod verify; +pub mod tls12; +pub mod tls13; +pub mod verify; /// The boringssl-based Rustls Crypto provider pub static PROVIDER: &'static dyn CryptoProvider = &Provider; diff --git a/boring-rustls-provider/src/sign.rs b/boring-rustls-provider/src/sign.rs index 736ddd1..7817b81 100644 --- a/boring-rustls-provider/src/sign.rs +++ b/boring-rustls-provider/src/sign.rs @@ -174,17 +174,21 @@ impl BoringSigner { impl rustls::sign::Signer for BoringSigner { fn sign(&self, message: &[u8]) -> Result, rustls::Error> { - let signer = self.get_signer(); - let mut msg_with_sig = - Vec::::with_capacity(message.len() + boring_sys::EVP_MAX_MD_SIZE as usize); - msg_with_sig.extend_from_slice(message); - msg_with_sig.extend_from_slice(&[0u8; boring_sys::EVP_MAX_MD_SIZE as usize]); - - let toatl_len = signer - .sign(&mut msg_with_sig[..]) - .map_err(|e| log_and_map("sign", e, rustls::Error::General("failed signing".into())))?; - msg_with_sig.truncate(toatl_len); - Ok(msg_with_sig) + let mut signer = self.get_signer(); + let max_sig_len = signer + .len() + .map_err(|e| log_and_map("len", e, rustls::Error::General("failed signing".into())))?; + let mut sig = vec![0u8; max_sig_len]; + + let sig_len = signer.sign_oneshot(&mut sig[..], message).map_err(|e| { + log_and_map( + "sign_oneshot", + e, + rustls::Error::General("failed signing".into()), + ) + })?; + sig.truncate(sig_len); + Ok(sig) } fn scheme(&self) -> rustls::SignatureScheme { diff --git a/boring-rustls-provider/tests/e2e.rs b/boring-rustls-provider/tests/e2e.rs new file mode 100644 index 0000000..05022ae --- /dev/null +++ b/boring-rustls-provider/tests/e2e.rs @@ -0,0 +1,129 @@ +use std::sync::Arc; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, +}; + +use boring_rustls_provider::{tls13, PROVIDER}; +use rustls::{version::TLS13, ServerConfig, SupportedCipherSuite}; +use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; +use tokio::net::TcpListener; +use tokio_rustls::{TlsAcceptor, TlsConnector}; + +#[tokio::test] +async fn test_tls13_crypto() { + let pki = TestPki::new(&rcgen::PKCS_ECDSA_P256_SHA256); + + let root_store = pki.client_root_store(); + let server_config = pki.server_config(); + + let ciphers = [ + SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256), + SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384), + ]; + + for cipher in ciphers { + let config = rustls::ClientConfig::builder_with_provider(PROVIDER) + .with_cipher_suites(&[cipher]) + .with_safe_default_kx_groups() + .with_protocol_versions(&[&TLS13]) + .unwrap() + .with_root_certificates(root_store.clone()) + .with_no_client_auth(); + + let listener = new_listener().await; + let addr = listener.local_addr().unwrap(); + tokio::spawn(spawn_echo_server(listener, server_config.clone())); + + let connector = TlsConnector::from(Arc::new(config)); + let stream = TcpStream::connect(&addr).await.unwrap(); + + let mut stream = connector + .connect(rustls::ServerName::try_from("localhost").unwrap(), stream) + .await + .unwrap(); + + stream.write_all(b"HELLO").await.unwrap(); + let mut buf = Vec::new(); + let bytes = stream.read_to_end(&mut buf).await.unwrap(); + assert_eq!(&buf[..bytes], b"HELLO"); + } +} + +async fn new_listener() -> TcpListener { + TcpListener::bind("localhost:0").await.unwrap() +} + +async fn spawn_echo_server(listener: TcpListener, config: Arc) { + let acceptor = TlsAcceptor::from(config); + + let (stream, _) = listener.accept().await.unwrap(); + let acceptor = acceptor.clone(); + let mut stream = acceptor.accept(stream).await.unwrap(); + + let mut buf = vec![0u8; 5]; + let bytes = stream.read_exact(buf.as_mut_slice()).await.unwrap(); + stream.write_all(&buf[..bytes]).await.unwrap(); + stream.flush().await.unwrap(); + stream.shutdown().await.unwrap(); +} + +struct TestPki { + ca_cert_der: CertificateDer<'static>, + server_cert_der: CertificateDer<'static>, + server_key_der: PrivateKeyDer<'static>, +} + +impl TestPki { + fn new(alg: &'static rcgen::SignatureAlgorithm) -> Self { + let mut ca_params = rcgen::CertificateParams::new(Vec::new()); + ca_params + .distinguished_name + .push(rcgen::DnType::OrganizationName, "Provider Server Example"); + ca_params + .distinguished_name + .push(rcgen::DnType::CommonName, "Example CA"); + ca_params.is_ca = rcgen::IsCa::Ca(rcgen::BasicConstraints::Unconstrained); + ca_params.key_usages = vec![ + rcgen::KeyUsagePurpose::KeyCertSign, + rcgen::KeyUsagePurpose::DigitalSignature, + ]; + ca_params.alg = alg; + let ca_cert = rcgen::Certificate::from_params(ca_params).unwrap(); + + let ca_cert_der = CertificateDer::from(ca_cert.serialize_der().unwrap()); + // Create a server end entity cert issued by the CA. + let mut server_ee_params = rcgen::CertificateParams::new(vec!["localhost".to_string()]); + server_ee_params.is_ca = rcgen::IsCa::NoCa; + server_ee_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth]; + server_ee_params.alg = alg; + let server_cert = rcgen::Certificate::from_params(server_ee_params).unwrap(); + let server_cert_der = + CertificateDer::from(server_cert.serialize_der_with_signer(&ca_cert).unwrap()); + let server_key_der = + PrivatePkcs8KeyDer::from(server_cert.serialize_private_key_der()).into(); + Self { + ca_cert_der, + server_cert_der, + server_key_der, + } + } + + fn server_config(self) -> Arc { + let mut server_config = ServerConfig::builder_with_provider(PROVIDER) + .with_safe_defaults() + .with_no_client_auth() + .with_single_cert(vec![self.server_cert_der], self.server_key_der) + .unwrap(); + + server_config.key_log = Arc::new(rustls::KeyLogFile::new()); + + Arc::new(server_config) + } + + fn client_root_store(&self) -> rustls::RootCertStore { + let mut root_store = rustls::RootCertStore::empty(); + root_store.add(self.ca_cert_der.clone()).unwrap(); + root_store + } +} From cb5ad946b3ba65aae003f950f025217116a46630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20R=C3=BCth?= Date: Sun, 26 Nov 2023 19:41:53 +0100 Subject: [PATCH 2/2] Fix TLS 1.2 crypto and add end-to-end tests --- Readme.md | 2 +- boring-rustls-provider/src/aead.rs | 119 +++++++++++++++++++++++----- boring-rustls-provider/src/hash.rs | 18 +++++ boring-rustls-provider/tests/e2e.rs | 49 +++++++++++- 4 files changed, 163 insertions(+), 25 deletions(-) diff --git a/Readme.md b/Readme.md index b2551d4..fdd2682 100644 --- a/Readme.md +++ b/Readme.md @@ -17,7 +17,7 @@ AES_256_GCM_SHA384 CHACHA20_POLY1305_SHA256 ``` -TLS 1.2 prepared for (doesn't work yet): +TLS 1.2 (only ECDSA is tested): ``` ECDHE_ECDSA_AES128_GCM_SHA256 ECDHE_RSA_AES128_GCM_SHA256 diff --git a/boring-rustls-provider/src/aead.rs b/boring-rustls-provider/src/aead.rs index 4235e5b..780b794 100644 --- a/boring-rustls-provider/src/aead.rs +++ b/boring-rustls-provider/src/aead.rs @@ -111,23 +111,38 @@ where msg: cipher::BorrowedPlainMessage, seq: u64, ) -> Result { - let total_len = msg.payload.len() + 1 + self.crypter.max_overhead(); - - let mut payload = Vec::with_capacity(total_len); - payload.extend_from_slice(msg.payload); - payload.push(msg.typ.get_u8()); - let nonce = cipher::Nonce::new(&self.iv, seq); match self.tls_version { #[cfg(feature = "tls12")] ProtocolVersion::TLSv1_2 => { - let aad = cipher::make_tls12_aad(seq, msg.typ, msg.version, total_len); - self.encrypt_in_place(Nonce::::from_slice(&nonce.0), &aad, &mut payload) + let fixed_iv_len = ::fixed_iv_len(); + let explicit_nonce_len = ::explicit_nonce_len(); + + let total_len = + msg.payload.len() + self.crypter.max_overhead() + explicit_nonce_len; + + let mut full_payload = Vec::with_capacity(total_len); + full_payload.extend_from_slice(&nonce.0.as_ref()[fixed_iv_len..]); + full_payload.extend_from_slice(msg.payload); + full_payload.extend_from_slice(&vec![0u8; self.crypter.max_overhead()]); + + let (_, payload) = full_payload.split_at_mut(explicit_nonce_len); + let (payload, tag) = payload.split_at_mut(msg.payload.len()); + let aad = cipher::make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len()); + self.crypter + .seal_in_place(&nonce.0, &aad, payload, tag) .map_err(|_| rustls::Error::EncryptError) - .map(|_| cipher::OpaqueMessage::new(msg.typ, msg.version, payload)) + .map(|_| cipher::OpaqueMessage::new(msg.typ, msg.version, full_payload)) } + ProtocolVersion::TLSv1_3 => { + let total_len = msg.payload.len() + 1 + self.crypter.max_overhead(); + + let mut payload = Vec::with_capacity(total_len); + payload.extend_from_slice(msg.payload); + payload.push(msg.typ.get_u8()); + let aad = cipher::make_tls13_aad(total_len); self.encrypt_in_place(Nonce::::from_slice(&nonce.0), &aad, &mut payload) .map_err(|_| rustls::Error::EncryptError) @@ -159,18 +174,56 @@ where seq: u64, ) -> Result { // construct nonce - let nonce = cipher::Nonce::new(&self.iv, seq); // construct the aad and decrypt match self.tls_version { #[cfg(feature = "tls12")] ProtocolVersion::TLSv1_2 => { - let aad = make_tls12_aad(seq, m.typ, m.version, m.payload().len()); - self.decrypt_in_place(Nonce::::from_slice(&nonce.0), &aad, m.payload_mut()) + let explicit_nonce_len = ::explicit_nonce_len(); + + // payload is: [nonce] | [ciphertext] | [auth tag] + let actual_payload_length = + m.payload().len() - self.crypter.max_overhead() - explicit_nonce_len; + + let aad = make_tls12_aad(seq, m.typ, m.version, actual_payload_length); + + let payload = m.payload_mut(); + + // get the nonce + let (explicit_nonce, payload) = payload.split_at_mut(explicit_nonce_len); + + let nonce = { + let fixed_iv_len = ::fixed_iv_len(); + + assert_eq!(explicit_nonce_len + fixed_iv_len, 12); + + // grab the IV by constructing a nonce, this is just an xor + let iv = cipher::Nonce::new(&self.iv, 0).0; + let mut nonce = [0u8; 12]; + nonce[..fixed_iv_len].copy_from_slice(&iv[..fixed_iv_len]); + nonce[fixed_iv_len..].copy_from_slice(explicit_nonce); + nonce + }; + + // split off the authentication tag + let (payload, tag) = + payload.split_at_mut(payload.len() - self.crypter.max_overhead()); + + self.crypter + .open_in_place(&nonce, &aad, payload, tag) .map_err(|_| rustls::Error::DecryptError) - .map(|_| m.into_plain_message()) + .map(|_| { + // rotate the nonce to the end + m.payload_mut().rotate_left(explicit_nonce_len); + + // truncate buffer to the actual payload + m.payload_mut().truncate(actual_payload_length); + + m.into_plain_message() + }) } ProtocolVersion::TLSv1_3 => { + let nonce = cipher::Nonce::new(&self.iv, seq); let aad = make_tls13_aad(m.payload().len()); self.decrypt_in_place(Nonce::::from_slice(&nonce.0), &aad, m.payload_mut()) .map_err(|_| rustls::Error::DecryptError) @@ -221,18 +274,29 @@ impl cipher::Tls12AeadAlgorithm for Aead { &self, key: cipher::AeadKey, iv: &[u8], - _extra: &[u8], + extra: &[u8], ) -> Box { + let mut full_iv = Vec::with_capacity(iv.len() + extra.len()); + full_iv.extend_from_slice(iv); + full_iv.extend_from_slice(extra); Box::new( - BoringAeadCrypter::::new(Iv::copy(iv), key.as_ref(), ProtocolVersion::TLSv1_2) + BoringAeadCrypter::::new(Iv::copy(&full_iv), key.as_ref(), ProtocolVersion::TLSv1_2) .expect("failed to create AEAD crypter"), ) } fn decrypter(&self, key: cipher::AeadKey, iv: &[u8]) -> Box { + let mut pseudo_iv = + Vec::with_capacity(iv.len() + ::explicit_nonce_len()); + pseudo_iv.extend_from_slice(iv); + pseudo_iv.extend_from_slice(&vec![0u8; ::explicit_nonce_len()]); Box::new( - BoringAeadCrypter::::new(Iv::copy(iv), key.as_ref(), ProtocolVersion::TLSv1_2) - .expect("failed to create AEAD crypter"), + BoringAeadCrypter::::new( + Iv::copy(&pseudo_iv), + key.as_ref(), + ProtocolVersion::TLSv1_2, + ) + .expect("failed to create AEAD crypter"), ) } @@ -241,9 +305,8 @@ impl cipher::Tls12AeadAlgorithm for Aead { enc_key_len: ::key_size(), // there is no benefit of splitting these up here, we'd need to stich them anyways // by only setting fixed_iv_len we get the full lengths - fixed_iv_len: ::fixed_iv_len() - + ::explicit_nonce_len(), - explicit_nonce_len: 0, + fixed_iv_len: ::fixed_iv_len(), + explicit_nonce_len: ::explicit_nonce_len(), } } @@ -251,8 +314,20 @@ impl cipher::Tls12AeadAlgorithm for Aead { &self, key: cipher::AeadKey, iv: &[u8], - _explicit: &[u8], + explicit: &[u8], ) -> Result { - Ok(::extract_keys(key, Iv::copy(iv))) + let nonce = { + let fixed_iv_len = ::fixed_iv_len(); + let explicit_nonce_len = ::explicit_nonce_len(); + assert_eq!(explicit_nonce_len + fixed_iv_len, 12); + + // grab the IV by constructing a nonce, this is just an xor + + let mut nonce = [0u8; 12]; + nonce[..fixed_iv_len].copy_from_slice(&iv[..fixed_iv_len]); + nonce[fixed_iv_len..].copy_from_slice(explicit); + nonce + }; + Ok(::extract_keys(key, Iv::copy(&nonce))) } } diff --git a/boring-rustls-provider/src/hash.rs b/boring-rustls-provider/src/hash.rs index f576c6c..44bad44 100644 --- a/boring-rustls-provider/src/hash.rs +++ b/boring-rustls-provider/src/hash.rs @@ -62,6 +62,24 @@ mod tests { use super::SHA256; use hex_literal::hex; + #[test] + fn test_context() { + let mut hash = SHA256.start(); + hash.update(b"ABCDE"); + let abcde = hash.fork_finish(); + hash.update(b"FGHIJ"); + let abcdefghij = hash.finish(); + + assert_eq!( + abcde.as_ref(), + hex!("f0393febe8baaa55e32f7be2a7cc180bf34e52137d99e056c817a9c07b8f239a") + ); + assert_eq!( + abcdefghij.as_ref(), + hex!("261305762671a58cae5b74990bcfc236c2336fb04a0fbac626166d9491d2884c") + ); + } + #[test] fn test_sha256() { let hash = SHA256.hash("test".as_bytes()); diff --git a/boring-rustls-provider/tests/e2e.rs b/boring-rustls-provider/tests/e2e.rs index 05022ae..1e52e0d 100644 --- a/boring-rustls-provider/tests/e2e.rs +++ b/boring-rustls-provider/tests/e2e.rs @@ -4,8 +4,11 @@ use tokio::{ net::TcpStream, }; -use boring_rustls_provider::{tls13, PROVIDER}; -use rustls::{version::TLS13, ServerConfig, SupportedCipherSuite}; +use boring_rustls_provider::{tls12, tls13, PROVIDER}; +use rustls::{ + version::{TLS12, TLS13}, + ServerConfig, SupportedCipherSuite, +}; use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; use tokio::net::TcpListener; use tokio_rustls::{TlsAcceptor, TlsConnector}; @@ -20,6 +23,7 @@ async fn test_tls13_crypto() { let ciphers = [ SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256), SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384), + SupportedCipherSuite::Tls13(&tls13::CHACHA20_POLY1305_SHA256), ]; for cipher in ciphers { @@ -50,6 +54,47 @@ async fn test_tls13_crypto() { } } +#[tokio::test] +async fn test_tls12_ec_crypto() { + let pki = TestPki::new(&rcgen::PKCS_ECDSA_P256_SHA256); + + let root_store = pki.client_root_store(); + let server_config = pki.server_config(); + + let ciphers = [ + SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES128_GCM_SHA256), + SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES256_GCM_SHA384), + SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), + ]; + + for cipher in ciphers { + let config = rustls::ClientConfig::builder_with_provider(PROVIDER) + .with_cipher_suites(&[cipher]) + .with_safe_default_kx_groups() + .with_protocol_versions(&[&TLS12]) + .unwrap() + .with_root_certificates(root_store.clone()) + .with_no_client_auth(); + + let listener = new_listener().await; + let addr = listener.local_addr().unwrap(); + tokio::spawn(spawn_echo_server(listener, server_config.clone())); + + let connector = TlsConnector::from(Arc::new(config)); + let stream = TcpStream::connect(&addr).await.unwrap(); + + let mut stream = connector + .connect(rustls::ServerName::try_from("localhost").unwrap(), stream) + .await + .unwrap(); + + stream.write_all(b"HELLO").await.unwrap(); + let mut buf = Vec::new(); + let bytes = stream.read_to_end(&mut buf).await.unwrap(); + assert_eq!(&buf[..bytes], b"HELLO"); + } +} + async fn new_listener() -> TcpListener { TcpListener::bind("localhost:0").await.unwrap() }