use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use core::cmp::min;
use crate::crypto::cipher::{
EncodedMessage, InboundOpaque, MessageDecrypter, MessageEncrypter, OutboundPlain,
encode_record_header,
};
use crate::error::Error;
use crate::log::trace;
use crate::msgs::{HEADER_SIZE, HandshakeAlignedProof};
pub(crate) struct EncryptionState {
message_encrypter: Option<Box<dyn MessageEncrypter>>,
write_seq_max: u64,
write_seq: u64,
}
impl EncryptionState {
pub(crate) fn new() -> Self {
Self {
message_encrypter: None,
write_seq_max: 0,
write_seq: 0,
}
}
pub(crate) fn encrypt_outgoing(
&mut self,
plain: EncodedMessage<OutboundPlain<'_>>,
mut record: Vec<u8>,
) -> Vec<u8> {
let needed = HEADER_SIZE + self.encrypted_len(plain.payload.len());
if record.capacity() == 0 {
record = vec![0u8; needed];
} else {
record.resize(needed, 0);
}
let written = self.encrypt_outgoing_into(plain, &mut record);
record.truncate(written);
record
}
pub(crate) fn encrypt_outgoing_into(
&mut self,
plain: EncodedMessage<OutboundPlain<'_>>,
out: &mut [u8],
) -> usize {
assert!(self.pre_encrypt_action(0) != Some(PreEncryptAction::Refuse));
let encrypter = self.message_encrypter.as_mut().unwrap();
let seq = self.write_seq;
self.write_seq += 1;
#[cfg(debug_assertions)]
let (out_ptr, out_len) = (out.as_ptr(), out.len());
let encrypted = encrypter
.encrypt(plain, seq, &mut out[HEADER_SIZE..])
.unwrap();
#[cfg(debug_assertions)]
{
debug_assert_eq!(
encrypted.payload.as_ptr(),
out_ptr.wrapping_add(HEADER_SIZE)
);
debug_assert!(encrypted.payload.len() <= out_len - HEADER_SIZE);
}
let (typ, version, len) = (encrypted.typ, encrypted.version, encrypted.payload.len());
debug_assert!(len <= usize::from(u16::MAX));
out[..HEADER_SIZE].copy_from_slice(&encode_record_header(typ, version, len as u16));
HEADER_SIZE + len
}
pub(crate) fn set_message_encrypter(
&mut self,
cipher: Box<dyn MessageEncrypter>,
max_messages: u64,
) {
*self = Self {
message_encrypter: Some(cipher),
write_seq_max: min(SEQ_SOFT_LIMIT, max_messages),
write_seq: 0,
};
}
pub(crate) fn pre_encrypt_action(&self, add: u64) -> Option<PreEncryptAction> {
match self.write_seq.saturating_add(add) {
v if v == self.write_seq_max => Some(PreEncryptAction::RefreshOrClose),
SEQ_HARD_LIMIT.. => Some(PreEncryptAction::Refuse),
_ => None,
}
}
pub(crate) fn encrypted_len(&self, payload_len: usize) -> usize {
self.message_encrypter
.as_ref()
.map(|enc| enc.encrypted_payload_len(payload_len))
.unwrap_or_default()
}
pub(crate) fn is_encrypting(&self) -> bool {
self.message_encrypter.is_some()
}
pub(crate) fn write_seq(&self) -> u64 {
self.write_seq
}
}
pub(crate) struct DecryptionState {
message_decrypter: Option<Box<dyn MessageDecrypter>>,
read_seq: u64,
has_decrypted: bool,
trial_decryption_len: Option<usize>,
}
impl DecryptionState {
pub(crate) fn new() -> Self {
Self {
message_decrypter: None,
read_seq: 0,
has_decrypted: false,
trial_decryption_len: None,
}
}
pub(crate) fn decrypt_incoming<'a>(
&mut self,
encr: EncodedMessage<InboundOpaque<'a>>,
) -> Result<Option<Decrypted<'a>>, Error> {
let Some(decrypter) = &mut self.message_decrypter else {
return Ok(Some(Decrypted {
want_close_before_decrypt: false,
plaintext: encr.into_plain_message(),
}));
};
let want_close_before_decrypt = self.read_seq == SEQ_SOFT_LIMIT;
let encrypted_len = encr.payload.len();
match decrypter.decrypt(encr, self.read_seq) {
Ok(plaintext) => {
self.read_seq += 1;
if !self.has_decrypted {
self.has_decrypted = true;
}
Ok(Some(Decrypted {
want_close_before_decrypt,
plaintext,
}))
}
Err(Error::DecryptError) if self.doing_trial_decryption(encrypted_len) => {
trace!("Dropping undecryptable message after aborted early_data");
Ok(None)
}
Err(err) => Err(err),
}
}
pub(crate) fn set_message_decrypter(
&mut self,
cipher: Box<dyn MessageDecrypter>,
_proof: &HandshakeAlignedProof,
) {
self.message_decrypter = Some(cipher);
self.read_seq = 0;
self.trial_decryption_len = None;
}
pub(crate) fn set_message_decrypter_with_trial_decryption(
&mut self,
cipher: Box<dyn MessageDecrypter>,
max_length: usize,
_proof: &HandshakeAlignedProof,
) {
self.message_decrypter = Some(cipher);
self.read_seq = 0;
self.trial_decryption_len = Some(max_length);
}
pub(crate) fn finish_trial_decryption(&mut self) {
self.trial_decryption_len = None;
}
pub(crate) fn has_decrypted(&self) -> bool {
self.has_decrypted
}
pub(crate) fn read_seq(&self) -> u64 {
self.read_seq
}
fn doing_trial_decryption(&mut self, requested: usize) -> bool {
match self
.trial_decryption_len
.and_then(|value| value.checked_sub(requested))
{
Some(remaining) => {
self.trial_decryption_len = Some(remaining);
true
}
_ => false,
}
}
}
#[derive(Debug)]
pub(crate) struct Decrypted<'a> {
pub(crate) want_close_before_decrypt: bool,
pub(crate) plaintext: EncodedMessage<&'a [u8]>,
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum PreEncryptAction {
RefreshOrClose,
Refuse,
}
const SEQ_SOFT_LIMIT: u64 = u64::MAX - 0xffff;
const SEQ_HARD_LIMIT: u64 = u64::MAX - 1;
#[cfg(test)]
mod tests {
use super::*;
use crate::enums::{ContentType, ProtocolVersion};
use crate::msgs::Deframer;
#[test]
fn test_has_decrypted() {
struct PassThroughDecrypter;
impl MessageDecrypter for PassThroughDecrypter {
fn decrypt<'a>(
&mut self,
m: EncodedMessage<InboundOpaque<'a>>,
_: u64,
) -> Result<EncodedMessage<&'a [u8]>, Error> {
Ok(m.into_plain_message())
}
}
let mut record_layer = DecryptionState::new();
assert!(record_layer.message_decrypter.is_none());
assert_eq!(record_layer.read_seq, 0);
assert!(!record_layer.has_decrypted());
let deframer = Deframer::default();
record_layer
.set_message_decrypter(Box::new(PassThroughDecrypter), &deframer.aligned().unwrap());
assert!(record_layer.message_decrypter.is_some());
assert_eq!(record_layer.read_seq, 0);
assert!(!record_layer.has_decrypted());
record_layer
.decrypt_incoming(EncodedMessage::new(
ContentType::Handshake,
ProtocolVersion::TLSv1_2,
InboundOpaque(&mut [0xC0, 0xFF, 0xEE]),
))
.unwrap();
assert_eq!(record_layer.read_seq, 1);
assert!(record_layer.has_decrypted());
record_layer
.set_message_decrypter(Box::new(PassThroughDecrypter), &deframer.aligned().unwrap());
assert_eq!(record_layer.read_seq, 0);
assert!(record_layer.has_decrypted());
}
}