Skip to content

Commit

Permalink
Fix TLS 1.2 crypto and add end-to-end tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan Rüth committed Nov 26, 2023
1 parent d6da261 commit 569692a
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 24 deletions.
119 changes: 97 additions & 22 deletions boring-rustls-provider/src/aead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,23 +111,38 @@ where
msg: cipher::BorrowedPlainMessage,
seq: u64,
) -> Result<cipher::OpaqueMessage, rustls::Error> {
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)
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, 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::<T>::from_slice(&nonce.0), &aad, &mut payload)
.map_err(|_| rustls::Error::EncryptError)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -221,18 +274,29 @@ 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)
.expect("failed to create AEAD crypter"),
BoringAeadCrypter::<T>::new(
Iv::copy(&pseudo_iv),
key.as_ref(),
ProtocolVersion::TLSv1_2,
)
.expect("failed to create AEAD crypter"),
)
}

Expand All @@ -241,18 +305,29 @@ 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(),
}
}

fn extract_keys(
&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)))
}
}
18 changes: 18 additions & 0 deletions boring-rustls-provider/src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
49 changes: 47 additions & 2 deletions boring-rustls-provider/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
}
Expand Down

0 comments on commit 569692a

Please sign in to comment.