use std::marker::PhantomData;
use aead::{AeadCore, AeadInPlace, Buffer, Nonce, Tag};
use boring::aead::{AeadCtx, Algorithm};
use boring::error::ErrorStack;
#[cfg(feature = "tls12")]
use rustls::crypto::cipher::make_tls12_aad;
use rustls::crypto::cipher::{self, BorrowedPayload, Iv, PrefixedPayload, make_tls13_aad};
use rustls::{ConnectionTrafficSecrets, ContentType, ProtocolVersion};
use crate::helper::log_and_map;
pub(crate) mod aes;
pub(crate) mod chacha20;
pub(crate) trait BoringCipher {
const EXPLICIT_NONCE_LEN: usize;
#[cfg(feature = "tls12")]
const FIXED_IV_LEN: usize;
const KEY_SIZE: usize;
const TAG_LEN: usize;
const INTEGRITY_LIMIT: u64;
const CONFIDENTIALITY_LIMIT: u64;
const FIPS_APPROVED: bool;
fn new_cipher() -> Algorithm;
fn extract_keys(key: cipher::AeadKey, iv: cipher::Iv) -> ConnectionTrafficSecrets;
}
pub(crate) trait QuicCipher: Send + Sync {
const KEY_SIZE: usize;
const SAMPLE_LEN: usize;
fn header_protection_mask(hp_key: &[u8], sample: &[u8]) -> Result<[u8; 5], rustls::Error>;
}
pub(crate) trait BoringAead: BoringCipher + AeadCore + Send + Sync {}
pub(crate) struct BoringAeadCrypter<T: BoringAead> {
ctx: AeadCtx,
max_overhead: usize,
iv: Iv,
tls_version: ProtocolVersion,
phantom: PhantomData<T>,
}
impl<T: BoringAead> AeadCore for BoringAeadCrypter<T> {
type NonceSize = T::NonceSize;
type TagSize = T::TagSize;
type CiphertextOverhead = T::CiphertextOverhead;
}
impl<T: BoringAead> BoringAeadCrypter<T> {
pub fn new(iv: Iv, key: &[u8], tls_version: ProtocolVersion) -> Result<Self, ErrorStack> {
let tls_version_supported = match tls_version {
#[cfg(feature = "tls12")]
ProtocolVersion::TLSv1_2 => true,
ProtocolVersion::TLSv1_3 => true,
_ => false,
};
if !tls_version_supported {
return Err(ErrorStack::get());
}
let cipher = <T as BoringCipher>::new_cipher();
if cipher.nonce_len() != rustls::crypto::cipher::Nonce::new(&iv, 0).0.len() {
return Err(ErrorStack::get());
}
let max_overhead = cipher.max_overhead();
let crypter = BoringAeadCrypter {
ctx: AeadCtx::new_default_tag(&cipher, key)?,
max_overhead,
iv,
tls_version,
phantom: PhantomData,
};
Ok(crypter)
}
}
impl<T: BoringAead> aead::AeadInPlace for BoringAeadCrypter<T> {
fn encrypt_in_place_detached(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: &mut [u8],
) -> aead::Result<Tag<Self>> {
let mut tag = Tag::<Self>::default();
self.ctx
.seal_in_place(nonce, buffer, &mut tag, associated_data)
.map_err(|e| log_and_map("seal_in_place", e, aead::Error))?;
Ok(tag)
}
fn decrypt_in_place_detached(
&self,
nonce: &Nonce<Self>,
associated_data: &[u8],
buffer: &mut [u8],
tag: &Tag<Self>,
) -> aead::Result<()> {
self.ctx
.open_in_place(nonce, buffer, tag, associated_data)
.map_err(|e| log_and_map("open_in_place", e, aead::Error))?;
Ok(())
}
}
impl<T> cipher::MessageEncrypter for BoringAeadCrypter<T>
where
T: BoringAead,
{
fn encrypt(
&mut self,
msg: cipher::OutboundPlainMessage,
seq: u64,
) -> Result<cipher::OutboundOpaqueMessage, 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 = self.encrypted_payload_len(msg.payload.len());
let mut full_payload = PrefixedPayload::with_capacity(total_len);
full_payload.extend_from_slice(&nonce.0.as_ref()[fixed_iv_len..]);
full_payload.extend_from_chunks(&msg.payload);
full_payload.extend_from_slice(&vec![0u8; self.max_overhead]);
let (_, payload) = full_payload.as_mut().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.ctx
.seal_in_place(&nonce.0, payload, tag, &aad)
.map_err(|_| rustls::Error::EncryptError)?;
Ok(cipher::OutboundOpaqueMessage::new(
msg.typ,
msg.version,
full_payload,
))
}
ProtocolVersion::TLSv1_3 => {
let total_len = self.encrypted_payload_len(msg.payload.len());
let mut payload = PrefixedPayload::with_capacity(total_len);
payload.extend_from_chunks(&msg.payload);
payload.extend_from_slice(&msg.typ.to_array());
let aad = cipher::make_tls13_aad(total_len);
self.encrypt_in_place(
Nonce::<T>::from_slice(&nonce.0),
&aad,
&mut EncryptBufferAdapter(&mut payload),
)
.map_err(|_| rustls::Error::EncryptError)
.map(|_| {
cipher::OutboundOpaqueMessage::new(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_2,
payload,
)
})
}
_ => Err(rustls::Error::EncryptError),
}
}
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
let per_record_overhead = match self.tls_version {
ProtocolVersion::TLSv1_2 => self
.max_overhead
.saturating_add(<T as BoringCipher>::EXPLICIT_NONCE_LEN),
ProtocolVersion::TLSv1_3 => 1usize.saturating_add(self.max_overhead),
_ => return payload_len,
};
payload_len.saturating_add(per_record_overhead)
}
}
impl<T> cipher::MessageDecrypter for BoringAeadCrypter<T>
where
T: BoringAead,
{
fn decrypt<'a>(
&mut self,
mut m: cipher::InboundOpaqueMessage<'a>,
seq: u64,
) -> Result<cipher::InboundPlainMessage<'a>, rustls::Error> {
match self.tls_version {
#[cfg(feature = "tls12")]
ProtocolVersion::TLSv1_2 => {
let explicit_nonce_len = <T as BoringCipher>::EXPLICIT_NONCE_LEN;
let tag_len = self.max_overhead;
let min_payload_len = explicit_nonce_len
.checked_add(tag_len)
.ok_or(rustls::Error::DecryptError)?;
if m.payload.len() < min_payload_len {
return Err(rustls::Error::DecryptError);
}
let actual_payload_length = m.payload.len() - tag_len - explicit_nonce_len;
let aad = make_tls12_aad(seq, m.typ, m.version, actual_payload_length);
let payload = &mut m.payload;
let (explicit_nonce, payload) = payload.split_at_mut(explicit_nonce_len);
let nonce = {
let fixed_iv_len = <T as BoringCipher>::FIXED_IV_LEN;
if explicit_nonce_len + fixed_iv_len != 12 {
return Err(rustls::Error::DecryptError);
}
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);
nonce
};
let (payload, tag) = payload.split_at_mut(payload.len() - tag_len);
self.ctx
.open_in_place(&nonce, payload, tag, &aad)
.map_err(|e| log_and_map("open_in_place", e, rustls::Error::DecryptError))
.map(|_| {
m.payload.rotate_left(explicit_nonce_len);
m.payload.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,
&mut DecryptBufferAdapter(&mut m.payload),
)
.map_err(|_| rustls::Error::DecryptError)
.and_then(|_| m.into_tls13_unpadded_message())
}
_ => Err(rustls::Error::DecryptError),
}
}
}
impl<T> rustls::quic::PacketKey for BoringAeadCrypter<T>
where
T: QuicCipher + BoringAead,
{
fn encrypt_in_place(
&self,
packet_number: u64,
header: &[u8],
payload: &mut [u8],
) -> Result<rustls::quic::Tag, rustls::Error> {
let associated_data = header;
let nonce = cipher::Nonce::new(&self.iv, packet_number);
let tag = self
.encrypt_in_place_detached(Nonce::<T>::from_slice(&nonce.0), associated_data, payload)
.map_err(|_| rustls::Error::EncryptError)?;
Ok(rustls::quic::Tag::from(tag.as_ref()))
}
fn decrypt_in_place<'a>(
&self,
packet_number: u64,
header: &[u8],
payload: &'a mut [u8],
) -> Result<&'a [u8], rustls::Error> {
let associated_data = header;
let nonce = cipher::Nonce::new(&self.iv, packet_number);
if payload.len() < self.max_overhead {
return Err(rustls::Error::DecryptError);
}
let (buffer, tag) = payload.split_at_mut(payload.len() - self.max_overhead);
self.ctx
.open_in_place(&nonce.0, buffer, tag, associated_data)
.map_err(|_| rustls::Error::DecryptError)?;
Ok(buffer)
}
fn tag_len(&self) -> usize {
<T as BoringCipher>::TAG_LEN
}
fn confidentiality_limit(&self) -> u64 {
<T as BoringCipher>::CONFIDENTIALITY_LIMIT
}
fn integrity_limit(&self) -> u64 {
<T as BoringCipher>::INTEGRITY_LIMIT
}
}
struct InvalidMessageEncrypter;
impl cipher::MessageEncrypter for InvalidMessageEncrypter {
fn encrypt(
&mut self,
_msg: cipher::OutboundPlainMessage<'_>,
_seq: u64,
) -> Result<cipher::OutboundOpaqueMessage, rustls::Error> {
Err(rustls::Error::EncryptError)
}
fn encrypted_payload_len(&self, payload_len: usize) -> usize {
payload_len
}
}
struct InvalidMessageDecrypter;
impl cipher::MessageDecrypter for InvalidMessageDecrypter {
fn decrypt<'a>(
&mut self,
_msg: cipher::InboundOpaqueMessage<'a>,
_seq: u64,
) -> Result<cipher::InboundPlainMessage<'a>, rustls::Error> {
Err(rustls::Error::DecryptError)
}
}
struct InvalidPacketKey<T>(PhantomData<T>);
impl<T: BoringCipher + QuicCipher> rustls::quic::PacketKey for InvalidPacketKey<T> {
fn encrypt_in_place(
&self,
_packet_number: u64,
_header: &[u8],
_payload: &mut [u8],
) -> Result<rustls::quic::Tag, rustls::Error> {
Err(rustls::Error::EncryptError)
}
fn decrypt_in_place<'a>(
&self,
_packet_number: u64,
_header: &[u8],
_payload: &'a mut [u8],
) -> Result<&'a [u8], rustls::Error> {
Err(rustls::Error::DecryptError)
}
fn tag_len(&self) -> usize {
<T as BoringCipher>::TAG_LEN
}
fn confidentiality_limit(&self) -> u64 {
0
}
fn integrity_limit(&self) -> u64 {
0
}
}
pub(crate) struct Aead<T>(PhantomData<T>);
impl<T> Aead<T> {
pub const DEFAULT: Self = Self(PhantomData);
}
impl<T: BoringAead + 'static> cipher::Tls13AeadAlgorithm for Aead<T> {
fn encrypter(&self, key: cipher::AeadKey, iv: cipher::Iv) -> Box<dyn cipher::MessageEncrypter> {
match BoringAeadCrypter::<T>::new(iv, key.as_ref(), ProtocolVersion::TLSv1_3) {
Ok(crypter) => Box::new(crypter),
Err(err) => {
log_and_map(
"Tls13AeadAlgorithm::encrypter BoringAeadCrypter::new",
err,
(),
);
Box::new(InvalidMessageEncrypter)
}
}
}
fn decrypter(&self, key: cipher::AeadKey, iv: cipher::Iv) -> Box<dyn cipher::MessageDecrypter> {
match BoringAeadCrypter::<T>::new(iv, key.as_ref(), ProtocolVersion::TLSv1_3) {
Ok(crypter) => Box::new(crypter),
Err(err) => {
log_and_map(
"Tls13AeadAlgorithm::decrypter BoringAeadCrypter::new",
err,
(),
);
Box::new(InvalidMessageDecrypter)
}
}
}
fn key_len(&self) -> usize {
<T as BoringCipher>::KEY_SIZE
}
fn extract_keys(
&self,
key: cipher::AeadKey,
iv: cipher::Iv,
) -> Result<ConnectionTrafficSecrets, cipher::UnsupportedOperationError> {
Ok(<T as BoringCipher>::extract_keys(key, iv))
}
fn fips(&self) -> bool {
cfg!(feature = "fips") && <T as BoringCipher>::FIPS_APPROVED
}
}
#[cfg(feature = "tls12")]
impl<T: BoringAead + 'static> cipher::Tls12AeadAlgorithm for Aead<T> {
fn encrypter(
&self,
key: cipher::AeadKey,
iv: &[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);
match BoringAeadCrypter::<T>::new(
Iv::copy(&full_iv),
key.as_ref(),
ProtocolVersion::TLSv1_2,
) {
Ok(crypter) => Box::new(crypter),
Err(err) => {
log_and_map(
"Tls12AeadAlgorithm::encrypter BoringAeadCrypter::new",
err,
(),
);
Box::new(InvalidMessageEncrypter)
}
}
}
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]);
match BoringAeadCrypter::<T>::new(
Iv::copy(&pseudo_iv),
key.as_ref(),
ProtocolVersion::TLSv1_2,
) {
Ok(crypter) => Box::new(crypter),
Err(err) => {
log_and_map(
"Tls12AeadAlgorithm::decrypter BoringAeadCrypter::new",
err,
(),
);
Box::new(InvalidMessageDecrypter)
}
}
}
fn key_block_shape(&self) -> cipher::KeyBlockShape {
cipher::KeyBlockShape {
enc_key_len: <T as BoringCipher>::KEY_SIZE,
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],
) -> Result<ConnectionTrafficSecrets, cipher::UnsupportedOperationError> {
let nonce = {
let fixed_iv_len = <T as BoringCipher>::FIXED_IV_LEN;
let explicit_nonce_len = <T as BoringCipher>::EXPLICIT_NONCE_LEN;
if explicit_nonce_len + fixed_iv_len != 12 {
return Err(cipher::UnsupportedOperationError);
}
if iv.len() != fixed_iv_len || explicit.len() != explicit_nonce_len {
return Err(cipher::UnsupportedOperationError);
}
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)))
}
fn fips(&self) -> bool {
cfg!(feature = "fips") && <T as BoringCipher>::FIPS_APPROVED
}
}
struct QuicHeaderProtector<T: QuicCipher> {
key: cipher::AeadKey,
phantom: PhantomData<T>,
}
impl<T: QuicCipher> QuicHeaderProtector<T> {
const MAX_PN_LEN: usize = 4;
fn rfc9001_header_protection(
&self,
sample: &[u8],
first: &mut u8,
packet_number: &mut [u8],
remove: bool,
) -> Result<(), rustls::Error> {
let mask = T::header_protection_mask(self.key.as_ref(), sample)?;
const LONG_HEADER_FORMAT: u8 = 0x80;
let bits_to_mask = if (*first & LONG_HEADER_FORMAT) == LONG_HEADER_FORMAT {
0x0f
} else {
0x1f
};
let pn_length = if remove {
*first ^= mask[0] & bits_to_mask;
(*first & 0x03) as usize + 1
} else {
let pn_length = (*first & 0x03) as usize + 1;
*first ^= mask[0] & bits_to_mask;
pn_length
};
for (pn_byte, m) in packet_number.iter_mut().zip(&mask[1..]).take(pn_length) {
*pn_byte ^= m;
}
Ok(())
}
}
impl<T: QuicCipher> rustls::quic::HeaderProtectionKey for QuicHeaderProtector<T> {
fn encrypt_in_place(
&self,
sample: &[u8],
first: &mut u8,
packet_number: &mut [u8],
) -> Result<(), rustls::Error> {
if packet_number.len() > Self::MAX_PN_LEN {
return Err(rustls::Error::General("packet number too long".into()));
}
self.rfc9001_header_protection(sample, first, packet_number, false)?;
Ok(())
}
fn decrypt_in_place(
&self,
sample: &[u8],
first: &mut u8,
packet_number: &mut [u8],
) -> Result<(), rustls::Error> {
if packet_number.len() > Self::MAX_PN_LEN {
return Err(rustls::Error::General("packet number too long".into()));
}
self.rfc9001_header_protection(sample, first, packet_number, true)?;
Ok(())
}
fn sample_len(&self) -> usize {
T::SAMPLE_LEN
}
}
impl<T> rustls::quic::Algorithm for Aead<T>
where
T: QuicCipher + BoringAead + 'static,
{
fn packet_key(&self, key: cipher::AeadKey, iv: Iv) -> Box<dyn rustls::quic::PacketKey> {
match BoringAeadCrypter::<T>::new(iv, key.as_ref(), ProtocolVersion::TLSv1_3) {
Ok(crypter) => Box::new(crypter),
Err(err) => {
log_and_map("QuicAlgorithm::packet_key BoringAeadCrypter::new", err, ());
Box::new(InvalidPacketKey::<T>(PhantomData))
}
}
}
fn header_protection_key(
&self,
key: cipher::AeadKey,
) -> Box<dyn rustls::quic::HeaderProtectionKey> {
Box::new(QuicHeaderProtector {
key,
phantom: PhantomData::<T>,
})
}
fn aead_key_len(&self) -> usize {
<T as QuicCipher>::KEY_SIZE
}
fn fips(&self) -> bool {
cfg!(feature = "fips") && <T as BoringCipher>::FIPS_APPROVED
}
}
struct DecryptBufferAdapter<'a, 'p>(&'a mut BorrowedPayload<'p>);
impl AsRef<[u8]> for DecryptBufferAdapter<'_, '_> {
fn as_ref(&self) -> &[u8] {
self.0
}
}
impl AsMut<[u8]> for DecryptBufferAdapter<'_, '_> {
fn as_mut(&mut self) -> &mut [u8] {
self.0
}
}
impl Buffer for DecryptBufferAdapter<'_, '_> {
fn extend_from_slice(&mut self, _: &[u8]) -> aead::Result<()> {
Err(aead::Error)
}
fn truncate(&mut self, len: usize) {
self.0.truncate(len)
}
}
struct EncryptBufferAdapter<'a>(&'a mut PrefixedPayload);
impl AsRef<[u8]> for EncryptBufferAdapter<'_> {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl AsMut<[u8]> for EncryptBufferAdapter<'_> {
fn as_mut(&mut self) -> &mut [u8] {
self.0.as_mut()
}
}
impl Buffer for EncryptBufferAdapter<'_> {
fn extend_from_slice(&mut self, other: &[u8]) -> aead::Result<()> {
self.0.extend_from_slice(other);
Ok(())
}
fn truncate(&mut self, len: usize) {
self.0.truncate(len)
}
}
#[cfg(test)]
mod tests {
use hex_literal::hex;
use rustls::ContentType;
use rustls::ProtocolVersion;
use rustls::crypto::cipher::Tls13AeadAlgorithm;
use rustls::crypto::cipher::{AeadKey, InboundOpaqueMessage, Iv};
#[cfg(feature = "tls12")]
use rustls::crypto::cipher::{MessageDecrypter, Tls12AeadAlgorithm};
use rustls::quic::Algorithm as QuicAlgorithm;
use rustls::quic::HeaderProtectionKey;
use crate::aead::BoringAeadCrypter;
use rustls::quic::PacketKey;
use super::aes::Aes128;
use super::{BoringCipher, QuicHeaderProtector, chacha20::ChaCha20Poly1305};
#[test]
fn quic_header_protection_short() {
let hp_key = hex!("25a282b9e82f06f21f488917a4fc8f1b73573685608597d0efcb076b0ab7a7a4");
let sample = hex!("5e5cd55c41f69080575d7999c25a5bfb");
let unprotected_header = hex!("4200bff4");
let mut header = unprotected_header;
let (first, packet_number) = header.split_at_mut(1);
let protected_header = hex!("4cfe4189");
let protector = QuicHeaderProtector {
key: AeadKey::from(hp_key),
phantom: std::marker::PhantomData::<ChaCha20Poly1305>,
};
protector
.rfc9001_header_protection(&sample, &mut first[0], packet_number, false)
.expect("valid sample should protect QUIC header");
assert_eq!(&header[..], &protected_header[..]);
let (first, packet_number) = header.split_at_mut(1);
protector
.rfc9001_header_protection(&sample, &mut first[0], packet_number, true)
.expect("valid sample should unprotect QUIC header");
assert_eq!(&header[..], &unprotected_header[..]);
}
#[test]
fn quic_header_protection_rejects_invalid_sample_without_mutation() {
let hp_key = hex!("25a282b9e82f06f21f488917a4fc8f1b73573685608597d0efcb076b0ab7a7a4");
let sample = hex!("5e5cd55c41f69080575d7999c25a5bfb");
let mut header = hex!("4200bff4");
let original = header;
let (first, packet_number) = header.split_at_mut(1);
let protector = QuicHeaderProtector {
key: AeadKey::from(hp_key),
phantom: std::marker::PhantomData::<ChaCha20Poly1305>,
};
let err = protector
.encrypt_in_place(&sample[..sample.len() - 1], &mut first[0], packet_number)
.expect_err("short sample must be rejected");
assert!(matches!(err, rustls::Error::General(_)));
assert_eq!(header, original);
}
#[test]
fn tls13_decrypter_constructor_failure_returns_erroring_decrypter() {
let mut decrypter = Tls13AeadAlgorithm::decrypter(
&super::Aead::<Aes128>::DEFAULT,
AeadKey::from([0u8; 32]),
Iv::from([0u8; 12]),
);
let mut payload = vec![0u8; 8];
let msg = InboundOpaqueMessage::new(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_3,
&mut payload,
);
let err = decrypter
.decrypt(msg, 0)
.expect_err("invalid constructor inputs should produce decrypt errors");
assert!(matches!(err, rustls::Error::DecryptError));
}
#[test]
fn quic_packet_key_constructor_failure_returns_erroring_key() {
let packet_key = super::Aead::<Aes128>::DEFAULT
.packet_key(AeadKey::from([0u8; 32]), Iv::from([0u8; 12]));
let mut payload = [0u8; 0];
let enc_err = packet_key.encrypt_in_place(0, &[], &mut payload);
assert!(matches!(enc_err, Err(rustls::Error::EncryptError)));
let dec_err = packet_key
.decrypt_in_place(0, &[], &mut payload)
.expect_err("invalid constructor inputs should produce decrypt errors");
assert!(matches!(dec_err, rustls::Error::DecryptError));
}
#[cfg(feature = "tls12")]
#[test]
fn tls12_decrypter_constructor_failure_returns_erroring_decrypter() {
let mut decrypter = Tls12AeadAlgorithm::decrypter(
&super::Aead::<Aes128>::DEFAULT,
AeadKey::from([0u8; 32]),
&[0u8; 4],
);
let mut payload = vec![0u8; 8];
let msg = InboundOpaqueMessage::new(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_2,
&mut payload,
);
let err = decrypter
.decrypt(msg, 0)
.expect_err("invalid constructor inputs should produce decrypt errors");
assert!(matches!(err, rustls::Error::DecryptError));
}
#[test]
fn quic_chacha20_crypt() {
let expected_cleartext = hex!("01");
let expected_ciphertext = hex!("655e5cd55c41f69080575d7999c25a5bfb");
let key = hex!("c6d98ff3441c3fe1b2182094f69caa2ed4b716b65488960a7a984979fb23e1c8");
let iv = hex!("e0459b3474bdd0e44a41c144");
let packet_number = 654360564;
let unprotected_header = hex!("4200bff4");
let protector = BoringAeadCrypter::<ChaCha20Poly1305>::new(
Iv::from(iv),
&key,
ProtocolVersion::TLSv1_3,
)
.unwrap();
let mut payload = expected_cleartext;
let tag = protector
.encrypt_in_place(packet_number, &unprotected_header, &mut payload)
.unwrap();
let mut ciphertext = [&payload, tag.as_ref()].concat();
assert_eq!(ciphertext, expected_ciphertext);
let cleartext = protector
.decrypt_in_place(packet_number, &unprotected_header, &mut ciphertext)
.unwrap();
assert_eq!(cleartext, expected_cleartext);
}
#[test]
fn quic_decrypt_rejects_truncated_payload() {
let key = [0u8; <ChaCha20Poly1305 as BoringCipher>::KEY_SIZE];
let iv = [0u8; 12];
let crypter = BoringAeadCrypter::<ChaCha20Poly1305>::new(
Iv::from(iv),
&key,
ProtocolVersion::TLSv1_3,
)
.unwrap();
let mut payload = [0u8; <ChaCha20Poly1305 as BoringCipher>::TAG_LEN - 1];
let err = crypter
.decrypt_in_place(0, &[], &mut payload)
.expect_err("truncated QUIC payload must fail decryption");
assert!(matches!(err, rustls::Error::DecryptError));
}
#[cfg(feature = "tls12")]
#[test]
fn tls12_decrypt_rejects_truncated_payload() {
let key = [0u8; <Aes128 as BoringCipher>::KEY_SIZE];
let iv = [0u8; 12];
let mut decrypter =
BoringAeadCrypter::<Aes128>::new(Iv::from(iv), &key, ProtocolVersion::TLSv1_2).unwrap();
let min_payload_len =
<Aes128 as BoringCipher>::EXPLICIT_NONCE_LEN + <Aes128 as BoringCipher>::TAG_LEN;
let mut payload = vec![0u8; min_payload_len - 1];
let msg = InboundOpaqueMessage::new(
ContentType::ApplicationData,
ProtocolVersion::TLSv1_2,
&mut payload,
);
let err = decrypter
.decrypt(msg, 0)
.expect_err("truncated TLS1.2 payload must fail decryption");
assert!(matches!(err, rustls::Error::DecryptError));
}
}