X-Git-Url: http://git.euphorik.ch/?p=crypto_lab1.git;a=blobdiff_plain;f=lab1_rust%2Fsrc%2Fpacket.rs;fp=lab1_rust%2Fsrc%2Fpacket.rs;h=73eecea9ad1cb7948f7bd83948296fdd00ee92dc;hp=0000000000000000000000000000000000000000;hb=ecdec5bf7022018eadf4a38b01890bbb3ab79c89;hpb=c9318a07ce0ec00f999ff17943b83048d536ecd1 diff --git a/lab1_rust/src/packet.rs b/lab1_rust/src/packet.rs new file mode 100644 index 0000000..73eecea --- /dev/null +++ b/lab1_rust/src/packet.rs @@ -0,0 +1,268 @@ +use std::io; +use std::fmt; +use std::rand::{ Rng, StdRng, SeedableRng, distributions }; +use std::rand::distributions::IndependentSample; +use serialize::hex::{ ToHex }; +use crypto; + +// There are all the errors that may occur when reading an encrypted and authenticated packet. +#[deriving(Show)] +pub enum ReadingError { + IOReadError(io::IoError), + UnknownPacketTypeError, // If the first byte is unknown. + UnconsistentEncryptedSizeError, + UnconsistentDataSizeError, // The data size is not valid. + UnconsistentMACSizeError, // The MAC hasn't the correct size. + MACMismatchError, // The uncrypted received data doesn't match to the received MAC. + PaddingError, // Padding format error. + DataError, // The data are invalid. + InvalidTimestampError +} + +// A macro to return a 'IOReadError' in case of error. +macro_rules! try_read_io( + ($e:expr) => (match $e { Ok(e) => e, Err(e) => return Err(IOReadError(e)) }) +) + +// There are all the errors that may occur when encrypting, authenticating and writing a packet. +#[deriving(Show)] +pub enum WritingError { + WriteIOError(io::IoError), + EncryptError, +} + +// A macro to return a 'IOWritingError' in case of error. +macro_rules! try_write_io( + ($e:expr) => (match $e { Ok(e) => e, Err(e) => return Err(WriteIOError(e)) }) +) + +pub type ReadingResult = Result; +pub type WritingResult = Result<(), WritingError>; + +static MIN_PAYLOAD_SIZE: uint = 7; +static MAX_PAYLOAD_SIZE: uint = 39; +static FIXED_PACKET_SIZE: uint = 1 + 8 + 10; // Packet type + timestamp + MAC. + +#[deriving(Show, Clone)] +pub struct PacketData { + id: u8, + payload: Vec // The size can vary from 'MIN_PAYLOAD_SIZE' to 'MAX_PAYLOAD_SIZE' bytes. +} + +#[deriving(Show, Clone)] +pub enum ErrorType { + CryptError, + AuthError +} + +#[deriving(Clone)] +pub enum PacketType { + Command(PacketData), + Answer(PacketData), + Error(ErrorType), +} + +/// Serialized packet format : |LL|P|TTTTTTTT|D...D|MMMMMMMMMM| +/// Where: +/// LL: Size of the following data +/// P: Packet type: +/// 0x00: Command +/// OxFF: Answer +/// 0x0A: Decrypt error +/// 0x0B: Authentication error +/// TTTTTTTT: Timestamp (64 bits) +/// D...D: Encrypted data (AES-256 CBC mode) of: +/// |I|C...C|P...P| for command and answer packet: +/// I: Command ID +/// C: Command payload (from 7 to 39 bytes) +/// P: Padding from 1 to 16, |I|C...C|P...P| size must be a multiple of 16 +/// |0000000000000000| for error packet (16 bytes length) +/// MMMMMMMMMM: first 10 bytes (most significant) of the HMAC-SHA256 of: +/// |I|C...C| for command and answer packet +/// |0000000000000000| for error packet +#[deriving(Show)] +pub struct Packet { + pub t: PacketType, + pub timestamp: u64 +} + +impl fmt::Show for PacketType { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn data_to_str(data: &PacketData) -> String { + format!("id: {}, payload({}): \"{}\"", data.id, data.payload.len(), data.payload.as_slice().to_hex()) + } + match self { + &Command(ref data) => write!(formatter, "Command {{ {} }}", data_to_str(data)), + &Answer(ref data) => write!(formatter, "Answer {{ {} }}", data_to_str(data)), + &Error(error_type) => write!(formatter, "Error {{ errorType: {} }}", error_type) + } + } +} + +impl Packet { + pub fn random_packet_data(seed: &[uint]) -> PacketData { + let mut rng = if seed.is_empty() { StdRng::new().unwrap() } else { SeedableRng::from_seed(seed) }; + let mut payload = Vec::from_elem(distributions::Range::new(MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE + 1).ind_sample(&mut rng), 0u8); + rng.fill_bytes(payload.as_mut_slice_()); + PacketData { + id: rng.gen::(), + payload: payload + } + } + + pub fn new_packet_data(id: u8, payload: Vec) -> PacketData { + PacketData { id: id, payload: payload } + } + + pub fn write(&self, output: &mut io::Writer) -> WritingResult { + self.write_with_padding_fun(output, |_, padding_length: uint| -> u8 { + padding_length as u8 + }) + } + + /// 'padd_fun' is function defining the padding. The first argument is the index of the current byte, starting at 0. + /// The second argument is the padding length. + pub fn write_with_padding_fun(&self, output: &mut io::Writer, padd_fun: |uint, uint| -> u8) -> WritingResult { + fn packet_data(p: &PacketData) -> Vec { + let mut d = Vec::new(); + d.push(p.id); + d.push_all(p.payload.as_slice()); + d + } + + // Data to be encrypted. + let mut data = + match self.t { + Command(ref p) | Answer(ref p) => packet_data(p), + Error(_) => Vec::from_elem(16, 0) // Padding as data: 16 * 0. + }; + + // Compute the MAC + let mac = crypto::compute_mac(data.as_slice()); + + // Padding. + match self.t { + Command(_) | Answer(_) => { + let padding_size = if data.len() % 16 == 0 { 16 } else { 16 - data.len() % 16 } ; + data.reserve_additional(padding_size); + for i in range(0, padding_size) { + data.push(padd_fun(i, padding_size)); + } + }, + _ => () + } + + // Encrypt. + let encrypted_data = match crypto::encrypt(data.as_slice(), iv_from_timestamp(self.timestamp).as_slice()) { + Some(d) => d, + _ => return Err(EncryptError) + }; + + // Write packet length. + try_write_io!(output.write_be_u16((encrypted_data.len() + FIXED_PACKET_SIZE) as u16)); + + // Write packet type. + try_write_io!(output.write_u8( + match self.t { + Command(_) => 0x00, + Answer(_) => 0xFF, + Error(CryptError) => 0x0A, + Error(AuthError) => 0x0B + } + )); + + // Write timestamp. + try_write_io!(output.write_be_u64(self.timestamp)); + + // Write encrypted data. + try_write_io!(output.write(encrypted_data.as_slice())); + + // Write the MAC. + try_write_io!(output.write(mac)); + + Ok(()) + } + + pub fn read(input: &mut io::Reader) -> ReadingResult { + fn consume(input: &mut io::Reader, nb_byte: uint) { + let _ = input.read_exact(nb_byte); + } + + let data_size = try_read_io!(input.read_be_u16()); + + // Read and check the packet type. + let packet_type = try_read_io!(input.read_u8()); + if ![0x00, 0xFF, 0x0A, 0x0B].iter().any(|p| *p == packet_type) { + consume(input, data_size as uint - 1); + return Err(UnknownPacketTypeError) + } + + let timestamp = try_read_io!(input.read_be_u64()); + + let mut encrypted_data = Vec::from_elem(data_size as uint - FIXED_PACKET_SIZE, 0u8); + if try_read_io!(input.read(encrypted_data.as_mut_slice_())) != encrypted_data.len() { + return Err(UnconsistentEncryptedSizeError) + } + let mut data = match crypto::decrypt(encrypted_data.as_slice(), iv_from_timestamp(timestamp).as_slice()) { + Some(d) => d, + _ => return Err(UnconsistentEncryptedSizeError) + }; + + // Control the size and the content of the padding then remove it. + if packet_type == 0x00 || packet_type == 0xFF { + match data.last() { + Some(&padding_size) => { + if padding_size as uint > data.len() || !data.slice_from(data.len() - padding_size as uint).iter().any(|b| *b == padding_size) { + consume(input, 10); + return Err(PaddingError) + } + let data_length = data.len() - padding_size as uint; + data.truncate(data_length); + }, + None => + return Err(PaddingError) + } + } + + // Read an verify the MAC. + let mut mac_read = [0u8, ..10]; + if try_read_io!(input.read(mac_read)) != mac_read.len() { + return Err(UnconsistentMACSizeError) + } + let mac_data = crypto::compute_mac(data.as_slice()); + if mac_read != mac_data { + return Err(MACMismatchError) + } + + Ok(Packet { + t: match packet_type { + // Command or answer. + 0x00 | 0xFF => { + if data.len() < MIN_PAYLOAD_SIZE + 1 || data.len() > MAX_PAYLOAD_SIZE + 1 { + return Err(UnconsistentDataSizeError) + } + let pd = PacketData { id: data[0], payload: data.tail().to_vec() }; // match data.as_slice() { [id, payload..] => PacketData { id: id, payload: payload.to_vec() } }; + match packet_type { 0x00 => Command(pd), _ => Answer(pd) } + }, + // Error. + _ => { + if data.len() != 16 { + return Err(UnconsistentDataSizeError) + } else if data != Vec::from_elem(16, 0) { + return Err(DataError) + } + match packet_type { 0x0A => Error(CryptError), _ => Error(AuthError) } + } + }, + timestamp: timestamp + }) + } +} + +// Build an initialization vector: 64 * 0u8 + timestamp (128 bits). +fn iv_from_timestamp(timestamp: u64) -> Vec { + let mut iv = io::MemWriter::with_capacity(16); + let _ = iv.write_be_u64(0u64); + let _ = iv.write_be_u64(timestamp); + iv.unwrap() +}