Skip to content

Commit

Permalink
Handle decoy packets in handshake
Browse files Browse the repository at this point in the history
  • Loading branch information
nyonson committed Sep 17, 2024
1 parent 559cadf commit bb50250
Showing 1 changed file with 88 additions and 37 deletions.
125 changes: 88 additions & 37 deletions protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ const TAG_BYTES: usize = 16;
const MAX_GARBAGE_BYTES: usize = 4095;
// Number of bytes for the garbage terminator.
const GARBAGE_TERMINTOR_BYTES: usize = 16;
// The max number of bytes a packet can be in the handshake. This allows
// the buffer to be pre-allocated and for things to remain sans-io. The
// only required packet in the hankshake is the small version packet,
// however, any number of any sized decoy packets can optionally
// be sent. These packets shouldn't be crazy big because the point
// is to resemble actual communication, but we need to cap the size
// somewhere to allow for pre-allocation.
const MAX_HANDSHAKE_PACKET_BYTES: usize = 4096;

/// Errors encountered throughout the lifetime of a V2 connection.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -219,7 +227,7 @@ impl PacketReader {
///
/// - `ciphertext` - The message from the peer.
/// - `contents` - Mutable buffer to write plaintext.
/// - `aad` - Optional authentication for the peer, currently only used for the first round of messages.
/// - `aad` - Optional authentication for the peer.
///
/// # Errors
///
Expand Down Expand Up @@ -248,7 +256,7 @@ impl PacketReader {
/// # Arguments
///
/// - `ciphertext` - The message from the peer.
/// - `aad` - Optional authentication for the peer, currently only used for the first round of messages.
/// - `aad` - Optional authentication for the peer.
///
/// # Errors
///
Expand Down Expand Up @@ -564,8 +572,10 @@ pub struct Handshake<'a> {
remote_garbage_terminator: Option<[u8; 16]>,
/// Packet handler output.
packet_handler: Option<PacketHandler>,
/// Stored state between authentication attempts, decrypted length for next packet.
authentication_packet_bytes: Option<usize>,
/// Decrypted length for next packet. Store state between authentication attempts to avoid resetting ciphers.
current_packet_length_bytes: Option<usize>,
/// Processesed message index. Store state between authentication attempts to avoid resetting ciphers.
current_message_index: usize,
}

impl<'a> Handshake<'a> {
Expand Down Expand Up @@ -640,7 +650,8 @@ impl<'a> Handshake<'a> {
garbage,
remote_garbage_terminator: None,
packet_handler: None,
authentication_packet_bytes: None,
current_packet_length_bytes: None,
current_message_index: 0,
})
}

Expand Down Expand Up @@ -708,71 +719,111 @@ impl<'a> Handshake<'a> {
/// Authenticate the channel.
///
/// Designed to be called multiple times until succesful in order to flush
/// garbage and decoy packets from channel.
/// garbage and decoy packets from channel. If a `MessageLengthTooSmall` is
/// returned, the buffer should be extended until `MessageLengthTooSmall` is
/// not returned. All other errors are fatal for the handshake and it should
/// be completely restarted.
///
/// # Arguments
///
/// - `buffer` - Should contain all garbage, the garbage terminator, and the version packet received from peer.
/// - `buffer` - Should contain all garbage, the garbage terminator, any decoy packets, and finally the version packet received from peer.
///
/// # Error
///
/// - `MessageLengthTooSmall` - The buffer did not contain all required information and should be extended (e.g. read more off a socket) and authentication re-tried.
/// - `HandshakeOutOfOrder` - The handshake sequence is in a bad state and should be restarted.
/// - `MaxGarbageLength` - Buffer did not contain the garbage terminator and contains too much garbage, should not be retried.
pub fn authenticate_garbage_and_version(&mut self, buffer: &[u8]) -> Result<(), Error> {
// Find the end of the garbage
// Find the end of the garbage.
let (garbage, message) = split_garbage_and_message(
buffer,
self.remote_garbage_terminator
.ok_or(Error::HandshakeOutOfOrder)?,
)?;

// Quickly fail if the message doesn't even have enough bytes for a length packet.
if message.len() < LENGTH_BYTES {
return Err(Error::MessageLengthTooSmall);
// The buffer used to decode any decoy packets and the version packet.
let mut packet_buffer = [0u8; MAX_HANDSHAKE_PACKET_BYTES];
// Flag to track if the version packet has been received to signal the end of the handshake.
let mut found_version_packet = false;

// The first packet, even if it is a decoy packet,
// is used to authenticate the received garbage through
// the AAD.
if self.current_message_index == 0 {
found_version_packet =
self.decrypt_packet(message, &mut packet_buffer, Some(garbage))?;
}

// If the first packet is a decoy, or if this is a follow up
// authentication attempt, the decoys need to be flushed and
// the version packet found.
//
// The version packet is essentially ignored in the current
// version of the protocol, but it does move the cipher
// states forward. It could be extended in the future.
while !found_version_packet {
found_version_packet = self.decrypt_packet(message, &mut packet_buffer, None)?;
}

Ok(())
}

/// Decrypt the next packet in the message buffer while
/// book keeping relevant lengths and indexes. This allows
/// the buffer to be re-processed without throwing off
/// the state of the ciphers.
///
/// # Returns
///
/// True if the decrypted packet is the version packet.
fn decrypt_packet(
&mut self,
message: &[u8],
packet_buffer: &mut [u8],
garbage: Option<&[u8]>,
) -> Result<bool, Error> {
let packet_handler = self
.packet_handler
.as_mut()
.ok_or(Error::HandshakeOutOfOrder)?;

// TODO: Drain decoy packets, will require some more state to be store between attempts, like a message index.

// Grab the packet length from internal statem, else decrypt it and store incase of failure.
let packet_length = if self.authentication_packet_bytes.is_some() {
self.authentication_packet_bytes
.ok_or(Error::HandshakeOutOfOrder)
} else {
if self.current_packet_length_bytes.is_none() {
// Bounds check on the message buffer.
if message.len() < self.current_message_index + LENGTH_BYTES {
return Err(Error::MessageLengthTooSmall);
}
let packet_length = packet_handler.decypt_len(
message[0..LENGTH_BYTES]
message[self.current_message_index..LENGTH_BYTES]
.try_into()
.map_err(|_| Error::MessageLengthTooSmall)?,
);
// Hang on to decrypted length incase next steps fail to avoid using the cipher again re-attempting authentication.
self.authentication_packet_bytes = Some(packet_length);
Ok(packet_length)
}?;

// Fail if there is not enough bytes to parse the message.
if message.len() < LENGTH_BYTES + packet_length {
return Err(Error::MessageLengthTooSmall);
// Hang on to decrypted length incase follow up steps fail
// and another authentication attempt is required. Avoids
// throwing off the cipher state.
self.current_packet_length_bytes = Some(packet_length);
}

// Authenticate received garbage and get version packet.
// Assuming no decoy packets so AAD is set on version packet.
// The version packet is ignored in this version of the protocol, but
// moves along state in the ciphers.
let packet_length = self
.current_packet_length_bytes
.ok_or(Error::HandshakeOutOfOrder)?;

// Version packets have 0 contents.
let mut version_packet = [0u8; NUM_DECOY_BYTES + TAG_BYTES];
// Bounds check on the message buffer.
if message.len() < self.current_message_index + LENGTH_BYTES + packet_length {
return Err(Error::MessageLengthTooSmall);
}
packet_handler.packet_reader.decrypt_contents(
&message[LENGTH_BYTES..packet_length + LENGTH_BYTES],
&mut version_packet,
Some(garbage),
&message[self.current_message_index + LENGTH_BYTES
..self.current_message_index + LENGTH_BYTES + packet_length],
packet_buffer,
garbage,
)?;

Ok(())
// Mark current decryption point in the buffer.
self.current_message_index = self.current_message_index + LENGTH_BYTES + packet_length + 1;
self.current_packet_length_bytes = None;

// The version packet is currently just an empty packet.
Ok(packet_buffer[0] != DECOY_BYTE)
}

/// Complete the handshake and return the packet handler for further communication.
Expand Down

0 comments on commit bb50250

Please sign in to comment.