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 3b6b2a4
Showing 1 changed file with 82 additions and 40 deletions.
122 changes: 82 additions & 40 deletions protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const TAG_BYTES: usize = 16;
const MAX_GARBAGE_BYTES: usize = 4095;
// Number of bytes for the garbage terminator.
const GARBAGE_TERMINTOR_BYTES: usize = 16;
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 +220,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 +249,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 +565,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 authentciation attempts to avoid resetting ciphers.
current_packet_length_bytes: Option<usize>,
/// Processesed message index. Store state between authentciation attempts to avoid resetting ciphers.
current_message_index: usize,
}

impl<'a> Handshake<'a> {
Expand Down Expand Up @@ -640,7 +643,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,69 +712,107 @@ 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);
}

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.
let mut packet_buffer = [0u8; MAX_HANDSHAKE_PACKET_BYTES];

// 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 {
// 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 {
if self.current_packet_length_bytes.is_none() {
// Quickly fail if the message doesn't even have enough bytes for a length packet.
if message.len() < LENGTH_BYTES {
return Err(Error::MessageLengthTooSmall);
}
let packet_length = packet_handler.decypt_len(
message[0..LENGTH_BYTES]
.try_into()
.map_err(|_| Error::MessageLengthTooSmall)?,
);
// Hang on to decrypted length incase next steps fail to avoid using
// the cipher again and throwing off the state.
self.current_packet_length_bytes = Some(packet_length);
}

let first_packet_length = self
.current_packet_length_bytes
.ok_or(Error::HandshakeOutOfOrder)?;

// Fail if there is not enough bytes to parse the message.
if message.len() < LENGTH_BYTES + first_packet_length {
return Err(Error::MessageLengthTooSmall);
}

// Authenticate received garbage with the first packet.
packet_handler.packet_reader.decrypt_contents(
&message[LENGTH_BYTES..first_packet_length + LENGTH_BYTES],
&mut packet_buffer,
Some(garbage),
)?;

self.current_message_index = LENGTH_BYTES + first_packet_length + 1;
}

// Assume that the first packet is also the version packet,
// however, if it is a decoy packet then the version packet
// needs to be found. Flush anymore decoy packets before
// the handshake can complete.
//
// The version packet is essentially ignored in the current
// version of the protocol, it just moves the cipher
// states forward, but could be extended in the future. It is
// just an empty packet at the current version.
let mut version_packet: [u8; NUM_DECOY_BYTES + TAG_BYTES] = packet_buffer
[..NUM_DECOY_BYTES + TAG_BYTES]
.try_into()
.unwrap();
while version_packet[0] == DECOY_BYTE {
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);
}
packet_handler.packet_reader.decrypt_contents(
&message[self.current_message_index + LENGTH_BYTES
..self.current_message_index + LENGTH_BYTES + packet_length],
&mut packet_buffer,
None,
)?;

// 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.

// Version packets have 0 contents.
let mut version_packet = [0u8; NUM_DECOY_BYTES + TAG_BYTES];
packet_handler.packet_reader.decrypt_contents(
&message[LENGTH_BYTES..packet_length + LENGTH_BYTES],
&mut version_packet,
Some(garbage),
)?;
self.current_message_index =
self.current_message_index + LENGTH_BYTES + packet_length + 1;

version_packet = packet_buffer[..NUM_DECOY_BYTES + TAG_BYTES]
.try_into()
.unwrap();
}

Ok(())
}
Expand Down

0 comments on commit 3b6b2a4

Please sign in to comment.