From 7d7b1b3f99380ef5225c00c9fe484b311c400ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20R=C3=BCth?= Date: Sun, 26 Nov 2023 21:56:15 +0100 Subject: [PATCH] RSA tests and fix chacha20 in tls 1.2 --- Readme.md | 2 +- boring-rustls-provider/src/aead.rs | 52 ++++++++--------- boring-rustls-provider/src/aead/aes.rs | 36 ++++-------- boring-rustls-provider/src/aead/chacha20.rs | 18 ++---- boring-rustls-provider/src/helper.rs | 1 + boring-rustls-provider/tests/e2e.rs | 64 +++++++++++++++++++++ 6 files changed, 108 insertions(+), 65 deletions(-) diff --git a/Readme.md b/Readme.md index fdd2682..855d785 100644 --- a/Readme.md +++ b/Readme.md @@ -17,7 +17,7 @@ AES_256_GCM_SHA384 CHACHA20_POLY1305_SHA256 ``` -TLS 1.2 (only ECDSA is tested): +TLS 1.2: ``` 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 780b794..2976b9d 100644 --- a/boring-rustls-provider/src/aead.rs +++ b/boring-rustls-provider/src/aead.rs @@ -12,17 +12,19 @@ pub(crate) mod aes; pub(crate) mod chacha20; pub(crate) trait BoringCipher { - /// Constructs a new instance of this cipher as an AEAD algorithm - fn new_cipher() -> Algorithm; - /// The key size in bytes - fn key_size() -> usize; + /// The lengths of the explicit nonce. (Not the full nonce length, only the part that changes) + /// See also [`BoringCipher::fixed_iv_len`] + const EXPLICIT_NONCE_LEN: usize; /// The IV's fixed length (Not the full IV length, only the part that doesn't change). /// Together with [`BoringCipher::explicit_nonce_len`] it determines the total /// lengths of the used nonce. - fn fixed_iv_len() -> usize; - /// The lengths of the explicit nonce. (Not the full nonce length, only the part that changes) - /// See also [`BoringCipher::fixed_iv_len`] - fn explicit_nonce_len() -> usize; + const FIXED_IV_LEN: usize; + /// The key size in bytes + const KEY_SIZE: usize; + + /// Constructs a new instance of this cipher as an AEAD algorithm + fn new_cipher() -> Algorithm; + /// Extract keys fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets; } @@ -116,8 +118,8 @@ where match self.tls_version { #[cfg(feature = "tls12")] ProtocolVersion::TLSv1_2 => { - let fixed_iv_len = ::fixed_iv_len(); - let explicit_nonce_len = ::explicit_nonce_len(); + 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; @@ -173,13 +175,10 @@ where mut m: cipher::OpaqueMessage, seq: u64, ) -> Result { - // construct nonce - - // construct the aad and decrypt match self.tls_version { #[cfg(feature = "tls12")] ProtocolVersion::TLSv1_2 => { - let explicit_nonce_len = ::explicit_nonce_len(); + let explicit_nonce_len = ::EXPLICIT_NONCE_LEN; // payload is: [nonce] | [ciphertext] | [auth tag] let actual_payload_length = @@ -193,12 +192,12 @@ where let (explicit_nonce, payload) = payload.split_at_mut(explicit_nonce_len); let nonce = { - let fixed_iv_len = ::fixed_iv_len(); + 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 iv = cipher::Nonce::new(&self.iv, seq).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); @@ -211,7 +210,7 @@ where self.crypter .open_in_place(&nonce, &aad, payload, tag) - .map_err(|_| rustls::Error::DecryptError) + .map_err(|e| log_and_map("open_in_place", e, rustls::Error::DecryptError)) .map(|_| { // rotate the nonce to the end m.payload_mut().rotate_left(explicit_nonce_len); @@ -256,7 +255,7 @@ impl cipher::Tls13AeadAlgorithm for Aead { } fn key_len(&self) -> usize { - ::key_size() + ::KEY_SIZE } fn extract_keys( @@ -286,10 +285,9 @@ impl cipher::Tls12AeadAlgorithm for Aead { } fn decrypter(&self, key: cipher::AeadKey, iv: &[u8]) -> Box { - let mut pseudo_iv = - Vec::with_capacity(iv.len() + ::explicit_nonce_len()); + 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()]); + pseudo_iv.extend_from_slice(&vec![0u8; ::EXPLICIT_NONCE_LEN]); Box::new( BoringAeadCrypter::::new( Iv::copy(&pseudo_iv), @@ -302,11 +300,9 @@ impl cipher::Tls12AeadAlgorithm for Aead { fn key_block_shape(&self) -> cipher::KeyBlockShape { cipher::KeyBlockShape { - 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(), + enc_key_len: ::KEY_SIZE, + fixed_iv_len: ::FIXED_IV_LEN, + explicit_nonce_len: ::EXPLICIT_NONCE_LEN, } } @@ -317,8 +313,8 @@ impl cipher::Tls12AeadAlgorithm for Aead { explicit: &[u8], ) -> Result { let nonce = { - let fixed_iv_len = ::fixed_iv_len(); - let explicit_nonce_len = ::explicit_nonce_len(); + 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 diff --git a/boring-rustls-provider/src/aead/aes.rs b/boring-rustls-provider/src/aead/aes.rs index 457cd6a..a503194 100644 --- a/boring-rustls-provider/src/aead/aes.rs +++ b/boring-rustls-provider/src/aead/aes.rs @@ -9,25 +9,19 @@ pub struct Aes128 {} impl BoringAead for Aes128 {} impl BoringCipher for Aes128 { + const EXPLICIT_NONCE_LEN: usize = 8; + + const FIXED_IV_LEN: usize = 4; + + const KEY_SIZE: usize = 16; + fn new_cipher() -> Algorithm { Algorithm::aes_128_gcm() } - fn key_size() -> usize { - 16 - } - fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets { ConnectionTrafficSecrets::Aes128Gcm { key, iv } } - - fn fixed_iv_len() -> usize { - 4 - } - - fn explicit_nonce_len() -> usize { - 8 - } } impl aead::AeadCore for Aes128 { @@ -42,25 +36,19 @@ pub struct Aes256 {} impl BoringAead for Aes256 {} impl BoringCipher for Aes256 { + const EXPLICIT_NONCE_LEN: usize = 8; + + const FIXED_IV_LEN: usize = 4; + + const KEY_SIZE: usize = 32; + fn new_cipher() -> Algorithm { Algorithm::aes_256_gcm() } - fn key_size() -> usize { - 32 - } - fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets { ConnectionTrafficSecrets::Aes256Gcm { key, iv } } - - fn fixed_iv_len() -> usize { - 4 - } - - fn explicit_nonce_len() -> usize { - 8 - } } impl aead::AeadCore for Aes256 { diff --git a/boring-rustls-provider/src/aead/chacha20.rs b/boring-rustls-provider/src/aead/chacha20.rs index 6179a80..8825449 100644 --- a/boring-rustls-provider/src/aead/chacha20.rs +++ b/boring-rustls-provider/src/aead/chacha20.rs @@ -12,25 +12,19 @@ pub struct ChaCha20Poly1305 {} impl BoringAead for ChaCha20Poly1305 {} impl BoringCipher for ChaCha20Poly1305 { + const EXPLICIT_NONCE_LEN: usize = 0; + + const FIXED_IV_LEN: usize = 12; + + const KEY_SIZE: usize = 32; + fn new_cipher() -> Algorithm { Algorithm::chacha20_poly1305() } - fn key_size() -> usize { - 32 - } - fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets { ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv } } - - fn fixed_iv_len() -> usize { - 4 - } - - fn explicit_nonce_len() -> usize { - 8 - } } impl AeadCore for ChaCha20Poly1305 { diff --git a/boring-rustls-provider/src/helper.rs b/boring-rustls-provider/src/helper.rs index 36a6318..6d7f173 100644 --- a/boring-rustls-provider/src/helper.rs +++ b/boring-rustls-provider/src/helper.rs @@ -31,6 +31,7 @@ pub(crate) fn cvt(r: c_int) -> Result { } #[cfg(feature = "log")] +#[inline(always)] pub(crate) fn log_and_map(func: &'static str, e: E, mapped: T) -> T { trace!("failed {}, error: {}", func, e); mapped diff --git a/boring-rustls-provider/tests/e2e.rs b/boring-rustls-provider/tests/e2e.rs index 1e52e0d..eb291b0 100644 --- a/boring-rustls-provider/tests/e2e.rs +++ b/boring-rustls-provider/tests/e2e.rs @@ -1,3 +1,4 @@ +use rcgen::CertificateParams; use std::sync::Arc; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, @@ -95,6 +96,47 @@ async fn test_tls12_ec_crypto() { } } +#[tokio::test] +async fn test_tls12_rsa_crypto() { + let pki = TestPki::new(&rcgen::PKCS_RSA_SHA256); + + let root_store = pki.client_root_store(); + let server_config = pki.server_config(); + + let ciphers = [ + SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES128_GCM_SHA256), + SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES256_GCM_SHA384), + SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_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() } @@ -133,6 +175,7 @@ impl TestPki { rcgen::KeyUsagePurpose::KeyCertSign, rcgen::KeyUsagePurpose::DigitalSignature, ]; + keypair_for_alg(&mut ca_params, alg); ca_params.alg = alg; let ca_cert = rcgen::Certificate::from_params(ca_params).unwrap(); @@ -142,6 +185,7 @@ impl TestPki { server_ee_params.is_ca = rcgen::IsCa::NoCa; server_ee_params.extended_key_usages = vec![rcgen::ExtendedKeyUsagePurpose::ServerAuth]; server_ee_params.alg = alg; + keypair_for_alg(&mut server_ee_params, 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()); @@ -172,3 +216,23 @@ impl TestPki { root_store } } + +fn gen_rsa_key(bits: u32) -> rcgen::KeyPair { + let rsa = boring::rsa::Rsa::generate(bits).unwrap(); + + let der_pkcs8 = boring::pkey::PKey::from_rsa(rsa) + .unwrap() + .private_key_to_der_pkcs8() + .unwrap(); + rcgen::KeyPair::from_der(&der_pkcs8).unwrap() +} + +fn keypair_for_alg(params: &mut CertificateParams, alg: &rcgen::SignatureAlgorithm) { + if alg == &rcgen::PKCS_RSA_SHA256 { + params.key_pair = Some(gen_rsa_key(2048)); + } else if alg == &rcgen::PKCS_RSA_SHA384 { + params.key_pair = Some(gen_rsa_key(3072)); + } else if alg == &rcgen::PKCS_RSA_SHA512 { + params.key_pair = Some(gen_rsa_key(4096)); + } +}