diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index ceb5eda..29ea96e 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -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)] @@ -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 /// @@ -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 /// @@ -564,8 +572,10 @@ pub struct Handshake<'a> { remote_garbage_terminator: Option<[u8; 16]>, /// Packet handler output. packet_handler: Option, - /// Stored state between authentication attempts, decrypted length for next packet. - authentication_packet_bytes: Option, + /// Decrypted length for next packet. Store state between authentication attempts to avoid resetting ciphers. + current_packet_length_bytes: Option, + /// Processesed message index. Store state between authentication attempts to avoid resetting ciphers. + current_message_index: usize, } impl<'a> Handshake<'a> { @@ -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, }) } @@ -708,11 +719,14 @@ 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 /// @@ -720,59 +734,96 @@ impl<'a> Handshake<'a> { /// - `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 { 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 follup 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.