Fix TLS 1.2 crypto and add end-to-end tests

This commit is contained in:
Jan Rüth 2023-11-26 19:41:53 +01:00 committed by Jan
commit 624eda8168
4 changed files with 163 additions and 25 deletions

View file

@ -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

View file

@ -111,23 +111,38 @@ where
msg: cipher::BorrowedPlainMessage,
seq: u64,
) -> Result<cipher::OpaqueMessage, rustls::Error> {
let nonce = cipher::Nonce::new(&self.iv, seq);
match self.tls_version {
#[cfg(feature = "tls12")]
ProtocolVersion::TLSv1_2 => {
let fixed_iv_len = <T as BoringCipher>::fixed_iv_len();
let explicit_nonce_len = <T as BoringCipher>::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, 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 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::<T>::from_slice(&nonce.0), &aad, &mut payload)
.map_err(|_| rustls::Error::EncryptError)
.map(|_| cipher::OpaqueMessage::new(msg.typ, msg.version, payload))
}
ProtocolVersion::TLSv1_3 => {
let aad = cipher::make_tls13_aad(total_len);
self.encrypt_in_place(Nonce::<T>::from_slice(&nonce.0), &aad, &mut payload)
.map_err(|_| rustls::Error::EncryptError)
@ -159,18 +174,56 @@ where
seq: u64,
) -> Result<cipher::PlainMessage, rustls::Error> {
// 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::<T>::from_slice(&nonce.0), &aad, m.payload_mut())
let explicit_nonce_len = <T as BoringCipher>::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 = <T as BoringCipher>::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::<T>::from_slice(&nonce.0), &aad, m.payload_mut())
.map_err(|_| rustls::Error::DecryptError)
@ -221,17 +274,28 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
&self,
key: cipher::AeadKey,
iv: &[u8],
_extra: &[u8],
extra: &[u8],
) -> Box<dyn cipher::MessageEncrypter> {
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::<T>::new(Iv::copy(iv), key.as_ref(), ProtocolVersion::TLSv1_2)
BoringAeadCrypter::<T>::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<dyn cipher::MessageDecrypter> {
let mut pseudo_iv =
Vec::with_capacity(iv.len() + <T as BoringCipher>::explicit_nonce_len());
pseudo_iv.extend_from_slice(iv);
pseudo_iv.extend_from_slice(&vec![0u8; <T as BoringCipher>::explicit_nonce_len()]);
Box::new(
BoringAeadCrypter::<T>::new(Iv::copy(iv), key.as_ref(), ProtocolVersion::TLSv1_2)
BoringAeadCrypter::<T>::new(
Iv::copy(&pseudo_iv),
key.as_ref(),
ProtocolVersion::TLSv1_2,
)
.expect("failed to create AEAD crypter"),
)
}
@ -241,9 +305,8 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
enc_key_len: <T as BoringCipher>::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: <T as BoringCipher>::fixed_iv_len()
+ <T as BoringCipher>::explicit_nonce_len(),
explicit_nonce_len: 0,
fixed_iv_len: <T as BoringCipher>::fixed_iv_len(),
explicit_nonce_len: <T as BoringCipher>::explicit_nonce_len(),
}
}
@ -251,8 +314,20 @@ impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
&self,
key: cipher::AeadKey,
iv: &[u8],
_explicit: &[u8],
explicit: &[u8],
) -> Result<ConnectionTrafficSecrets, cipher::UnsupportedOperationError> {
Ok(<T as BoringCipher>::extract_keys(key, Iv::copy(iv)))
let nonce = {
let fixed_iv_len = <T as BoringCipher>::fixed_iv_len();
let explicit_nonce_len = <T as BoringCipher>::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(<T as BoringCipher>::extract_keys(key, Iv::copy(&nonce)))
}
}

View file

@ -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());

View file

@ -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()
}