From: Ummon Date: Tue, 4 Nov 2014 12:58:45 +0000 (+0100) Subject: Add the report. X-Git-Url: http://git.euphorik.ch/?p=crypto_lab1.git;a=commitdiff_plain;h=ecdec5bf7022018eadf4a38b01890bbb3ab79c89 Add the report. --- diff --git a/.gitignore b/.gitignore index 37727f9..5139597 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,12 @@ *.exe # Generated by Cargo -/target/ +target/ + +# Tex +*.log +*.aux +*.bbl +*.blg +*.dvi +*.backup diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index dd6deb9..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,12 +0,0 @@ -[root] -name = "lab1_rust" -version = "0.0.1" -dependencies = [ - "openssl 0.0.0 (git+https://github.com/sfackler/rust-openssl.git#1e706b8ef4d4344b5297b90bcd430cc96f40fb39)", -] - -[[package]] -name = "openssl" -version = "0.0.0" -source = "git+https://github.com/sfackler/rust-openssl.git#1e706b8ef4d4344b5297b90bcd430cc96f40fb39" - diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 3f218e2..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] - -name = "lab1_rust" -version = "0.0.1" -authors = ["Ummon "] - - -[[bin]] - -name = "lab1_rust" - - -[dependencies.openssl] - -git = "https://github.com/sfackler/rust-openssl.git" \ No newline at end of file diff --git a/icr14_lab_01.pdf b/icr14_lab_01.pdf new file mode 100644 index 0000000..cf0fb9d Binary files /dev/null and b/icr14_lab_01.pdf differ diff --git a/lab1_rust/Cargo.lock b/lab1_rust/Cargo.lock new file mode 100644 index 0000000..dd6deb9 --- /dev/null +++ b/lab1_rust/Cargo.lock @@ -0,0 +1,12 @@ +[root] +name = "lab1_rust" +version = "0.0.1" +dependencies = [ + "openssl 0.0.0 (git+https://github.com/sfackler/rust-openssl.git#1e706b8ef4d4344b5297b90bcd430cc96f40fb39)", +] + +[[package]] +name = "openssl" +version = "0.0.0" +source = "git+https://github.com/sfackler/rust-openssl.git#1e706b8ef4d4344b5297b90bcd430cc96f40fb39" + diff --git a/lab1_rust/Cargo.toml b/lab1_rust/Cargo.toml new file mode 100644 index 0000000..3f218e2 --- /dev/null +++ b/lab1_rust/Cargo.toml @@ -0,0 +1,15 @@ +[package] + +name = "lab1_rust" +version = "0.0.1" +authors = ["Ummon "] + + +[[bin]] + +name = "lab1_rust" + + +[dependencies.openssl] + +git = "https://github.com/sfackler/rust-openssl.git" \ No newline at end of file diff --git a/lab1_rust/src/crypto.rs b/lab1_rust/src/crypto.rs new file mode 100644 index 0000000..d9fe182 --- /dev/null +++ b/lab1_rust/src/crypto.rs @@ -0,0 +1,54 @@ +use std::rand::{ OsRng, Rng }; +use std::io::IoResult; +use std::slice::bytes::copy_memory; +use openssl::crypto::hash::SHA256; +use openssl::crypto::hmac::HMAC; +use openssl::crypto::symm; + +// These aren't the keys you're looking for. +static KEY_A: &'static [u8] = [125, 31, 131, 118, 143, 180, 252, 53, 211, 217, 79, 240, 128, 91, 252, 87, 104, 236, 145, 198, 163, 203, 161, 12, 53, 56, 218, 40, 221, 95, 171, 140]; +static KEY_C: &'static [u8] = [75, 226, 88, 31, 223, 216, 182, 216, 178, 58, 59, 193, 245, 80, 254, 128, 125, 246, 246, 224, 194, 190, 123, 123, 10, 131, 217, 183, 112, 157, 166, 102]; + +/// Only returns the first ten bytes. +pub fn compute_mac(data: &[u8]) -> [u8, ..10] { + let mut hmac = HMAC(SHA256, KEY_A); + hmac.update(data); + let mut result = [0u8, ..10]; + copy_memory(&mut result, hmac.finalize().slice(0, 10)); + result +} + +/// Encrypt may fail if the provided data size isn't a multiple of 16. +pub fn encrypt(plaindata: &[u8], iv: &[u8]) -> Option> { + let c = symm::Crypter::new(symm::AES_256_CBC); + c.init(symm::Encrypt, KEY_C, iv.to_vec()); + c.pad(false); // Padding disabled! + let r = c.update(plaindata); + let rest = c.finalize(); + if rest.is_empty() { + Some(r) + } else { + None + } +} + +/// Decrypt may fail if the provided data size isn't a multiple of 16. +pub fn decrypt(cypherdata: &[u8], iv: &[u8]) -> Option> { + let c = symm::Crypter::new(symm::AES_256_CBC); + c.init(symm::Decrypt, KEY_C, iv.to_vec()); + c.pad(false); // Padding disabled! + let r = c.update(cypherdata); + let rest = c.finalize(); + if rest.is_empty() { + Some(r) + } else { + None + } +} + +pub fn generate_key(size_byte: uint) -> IoResult> { + let mut bytes = Vec::from_elem(size_byte, 0u8); + let mut generator = try!(OsRng::new()); // Uses '/dev/urandom' on Unix-like systems. + generator.fill_bytes(bytes.as_mut_slice_()); + Ok(bytes) +} \ No newline at end of file diff --git a/lab1_rust/src/end_point.rs b/lab1_rust/src/end_point.rs new file mode 100644 index 0000000..2d0a66c --- /dev/null +++ b/lab1_rust/src/end_point.rs @@ -0,0 +1,319 @@ +use std::io; +use std::io::{ MemWriter, Acceptor, Listener, TcpStream, IoResult, IoError, EndOfFile }; +use std::io::net::tcp::{ TcpAcceptor, TcpListener }; +use packet; +use packet::{ Packet, Command, Answer, Error, ReadingResult, PacketType }; + +// Default timeout when waiting data on a stream (for instance: 'TcpStream::read'). +static DEFAULT_TIMEOUT: Option = Some(500); // [ms]. + +pub struct Server { + acceptor: TcpAcceptor +} + +pub struct Client { + end_point: EndPoint, +} + +pub struct EndPoint { + socket: TcpStream, + current_timestamp: u64 +} + +impl Server { + pub fn new(interface: &str, port: u16) -> IoResult { + let mut acceptor = try!(TcpListener::bind(interface, port).listen()); + + let server = Server { + acceptor: acceptor.clone() + }; + + spawn(proc() { + loop { + for stream in acceptor.incoming() { + match stream { + Ok(stream) => spawn(proc() { + Server::handle_client(EndPoint::new(stream)); + }), + _ => return + } + } + } + }); + + Ok(server) + } + + pub fn close(&mut self) -> IoResult<()> { + self.acceptor.close_accept() + } + + fn handle_client(mut end_point: EndPoint) { + loop { + match end_point.read() { + Ok(packet@Packet { t: Command(..), .. }) => { + println!("[Server] Valid command received: {}", packet); + let answer = Answer(Packet::random_packet_data([])); + match end_point.send(answer.clone()) { + Ok(_) => + println!("[Server] Answer sent: {}", answer), + Err(e) => + println!("[Server] Can't send the answer. Error: {}", e) + } + }, + // Socket has been closed. + Err(packet::IOReadError(IoError { kind: EndOfFile, .. })) => { + println!("[Server] Connection closed: EOF"); + return + }, + other => + println!("[Server] Error or invalid packet: {}", other) + } + } + } +} + +impl Client { + pub fn new(address: &str, port: u16) -> IoResult { + Ok(Client { end_point: EndPoint { + socket: try!(TcpStream::connect(address, port)), + current_timestamp: 0 + }}) + } + + pub fn close(&mut self) -> IoResult<()> { + self.end_point.close() + } + + pub fn send(&mut self, packet: PacketType) { + match packet { + Command(_) => { + println!("[Client] Sending: {}", packet); + match self.end_point.send_with_result(packet) { + Ok(Ok(packet@Packet { t: Answer(..), .. })) => + println!("[Client] Command transmitted correctly, answer: {}", packet), + Ok(Ok(packet)) => + println!("[Client] Command transmitted correctly, wrong answer: {}", packet), + Ok(Err(e)) => + println!("[Client] Answer error: {}", e), + Err(e) => + println!("[Client] Can't send the packet. Error: {}", e) + } + }, + other => + println!("[Client] Cannot send this type of packet: {}", other) + } + } + + /// Send some valid and not-valid packets to the server and check the result. + /// For each test a new client is created. + pub fn start_tests(address: &str, port: u16) { + let execute = |f: &mut |&mut Client| -> bool| -> bool { + match Client::new(address, port) { + Ok(ref mut client) => (*f)(client), + Err(e) => { + println!("Unable to create a client. Error: {}", e); + false + } + } + }; + + fn raw_packet(timestamp: u64) -> Vec { + let mut m = MemWriter::new(); + match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: timestamp }).write(&mut m) { + Err(_) => vec!(), + _ => m.unwrap() + } + } + + let mut tests = vec!( + // 1) + |client: &mut Client| -> bool { + println!("Sending a valid packet..."); + match client.end_point.send_with_result(Command(Packet::random_packet_data([42]))) { + Ok(Ok(Packet { t: Answer(..), .. })) => true, + other => { + println!("Error: {}", other); + false + } + } + }, + // 2) + |client: &mut Client| -> bool { + println!("Sending a packet with an unknown type..."); + client.end_point.current_timestamp += 1; + let mut raw_packet = raw_packet(client.end_point.current_timestamp); + raw_packet[2] = 0xEE; // Alter the type. + match client.end_point.send_raw_with_result(raw_packet.as_slice()) { + Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. + other => { + println!("Error: {}", other); + false + } + } + }, + // 3) + |client: &mut Client| -> bool { + println!("Sending a packet with an old timestamp..."); + client.end_point.current_timestamp += 1; + let raw_packet = raw_packet(0); + match client.end_point.send_raw_with_result(raw_packet.as_slice()) { + Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. + other => { + println!("Error: {}", other); + false + } + } + }, + // 4) + |client: &mut Client| -> bool { + println!("Sending a packet with altered crypted data (do not alter the padding)..."); + client.end_point.current_timestamp += 1; + let mut raw_packet = raw_packet(client.end_point.current_timestamp); + raw_packet[11] = 0xDE; + raw_packet[12] = 0xAD; + raw_packet[13] = 0xBE; + raw_packet[14] = 0xEF; + match client.end_point.send_raw_with_result(raw_packet.as_slice()) { + Ok(Ok(Packet { t: Error(packet::AuthError), .. })) => true, + other => { + println!("Error: {}", other); + false + } + } + }, + // 5) + |client: &mut Client| -> bool { + println!("Sending a packet with too small data..."); + let command = Command(Packet::new_packet_data(0, Vec::from_elem(6, 0x00))); + match client.end_point.send_with_result(command) { + Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. + other => { + println!("Error: {}", other); + false + } + } + }, + // 6) + |client: &mut Client| -> bool { + println!("Sending a packet with too large data..."); + let command = Command(Packet::new_packet_data(0, Vec::from_elem(40, 0x00))); + match client.end_point.send_with_result(command) { + Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. + other => { + println!("Error: {}", other); + false + } + } + }, + // 7) + |client: &mut Client| -> bool { + println!("Sending a packet with wrong padding (all 0)..."); + client.end_point.current_timestamp += 1; + let mut m = MemWriter::new(); + let raw_packet = match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: client.end_point.current_timestamp }).write_with_padding_fun(&mut m, |_, _| -> u8 { 0 }) { + Err(_) => vec!(), + _ => m.unwrap() + }; + + match client.end_point.send_raw_with_result(raw_packet.as_slice()) { + Ok(Ok(Packet { t: Error(packet::CryptError), .. })) => true, + other => { + println!("Error: {}", other); + false + } + } + }, + ); + + let mut nb_test_passed = 0; + for (i, test) in range(1, tests.len()+1).zip(tests.iter_mut()) { + println!("===== Test case #{}:", i) + if execute(test) { + nb_test_passed += 1; + println!("===== Test passed"); + } else { + println!("===== Test failed"); + } + } + + if nb_test_passed == tests.len() { + println!("All tests passed"); + } else { + println!("#{} test(s) failed", tests.len() - nb_test_passed); + } + } +} + +impl EndPoint { + pub fn new(socket: TcpStream) -> EndPoint { + EndPoint { socket: socket, current_timestamp: 0 } + } + + fn close(&mut self) -> IoResult<()> { + try!(self.socket.close_read()); + try!(self.socket.close_write()); + Ok(()) + } + + /// Send a packet and wait for an answer synchronously. + fn send_with_result(&mut self, p: PacketType) -> IoResult { + match self.send(p) { + Err(e) => Err(e), + _ => Ok(self.read()) + } + } + + /// Send arbitrary data and wait for an answer synchronously. + /// Do not increment the current timestamp. + fn send_raw_with_result(&mut self, p: &[u8]) -> IoResult { + self.socket.set_timeout(DEFAULT_TIMEOUT); + match self.socket.write(p) { + Err(e) => Err(e), + _ => Ok(self.read()) + } + } + + fn send(&mut self, p: PacketType) -> IoResult<()> { + self.socket.set_timeout(DEFAULT_TIMEOUT); + self.current_timestamp += 1; + match (Packet { t: p, timestamp: self.current_timestamp }).write(&mut self.socket) { + Err(packet::WriteIOError(e)) => Err(e), + _ => Ok(()) + } + } + + fn read(&mut self) -> ReadingResult { + fn send_error(ep: &mut EndPoint, error_type: packet::ErrorType) { + match ep.send(Error(error_type)) { + Err(e) => println!("Unable to send error packet: {}", e), + Ok(_) => () + }; + }; + + self.socket.set_timeout(DEFAULT_TIMEOUT); + match Packet::read(&mut self.socket) { + Ok(packet) => { + if packet.timestamp <= self.current_timestamp { + println!("Error, timestamp mismatch, current timestamp: {}, packet received: {}", self.current_timestamp, packet); + Err(packet::InvalidTimestampError) + } else { + self.current_timestamp = packet.timestamp + 1; + Ok(packet) + } + }, + e @ Err(packet::PaddingError) => { + self.current_timestamp += 1; + send_error(self, packet::CryptError); + e + }, + e @ Err(packet::MACMismatchError) => { + self.current_timestamp += 1; + send_error(self, packet::AuthError); + e + }, + other => + other + } + } +} diff --git a/lab1_rust/src/main.rs b/lab1_rust/src/main.rs new file mode 100644 index 0000000..64aef28 --- /dev/null +++ b/lab1_rust/src/main.rs @@ -0,0 +1,59 @@ +#![feature(macro_rules)] + +extern crate openssl; +extern crate serialize; + +use std::io; +use std::os; + +use end_point::{ Client, Server }; + +mod crypto; +mod packet; +mod end_point; +mod oracle_machine; + +const PORT: u16 = 4221; + +fn print_usage() { + println!( + r"{} genkey | tests | oracle-weak | oracle-fixed + genkey: Generate a 256 bits key + tests: launch some tests between a client and a weak server + oracle-weak: launch a padding oracle attack against a weak server + oracle-fixed: launch a padding oracle attack against a fixed server", + os::args()[0] + ); +} + +fn main() { + let args = os::args(); + + if args.iter().any(|a| a.as_slice() == "--help" || a.as_slice() == "-h") { + print_usage(); + } else if args.len() > 1 && args[1].as_slice() == "genkey" { + match crypto::generate_key(256 / 8) { + Ok(key) => println!("key: {}", key), + Err(e) => println!("Unable to generate a key. Error: {}", e) + } + } else { + println!("Starting server..."); + + match Server::new("::1", PORT) { + Ok(mut server) => { + println!("Server started"); + + if args.len() > 1 && args[1].as_slice() == "tests" { + Client::start_tests("::1", PORT); + } + + println!("Press any key to quit"); + io::stdin().read_line().ok().expect("Failed to read line"); + + server.close().ok().expect("Failed to close the server"); + }, + Err(e) => + println!("Unable to create a new server. Error: {}", e) + } + } +} diff --git a/lab1_rust/src/oracle_machine.rs b/lab1_rust/src/oracle_machine.rs new file mode 100644 index 0000000..68aef94 --- /dev/null +++ b/lab1_rust/src/oracle_machine.rs @@ -0,0 +1,19 @@ +use std::io; +use std::io::{ TcpStream }; +use end_point::EndPoint; + +/// Try to decypher a cyphered data block by using an oracle on the provided address and port. +/// May prints some message on the stdout. +pub fn decypher(address: &str, port: u16, cypherblock: [u8, ..16]) -> Option> { + let end_point = EndPoint::new( + match TcpStream::connect(address, port) { + Ok(s) => s, + _ => { + println!("Unable to connect to the oracle on {}:{}", address, port); + return None + } + } + ); + + None +} 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() +} diff --git a/rapport/main.bib b/rapport/main.bib new file mode 100644 index 0000000..1647767 --- /dev/null +++ b/rapport/main.bib @@ -0,0 +1,35 @@ +@misc {wiki-replay-attack, + author = "Wikipedia", + title = "Replay attack --- {W}ikipedia{,} The Free Encyclopedia", + year = "2014", + url = "\url{http://en.wikipedia.org/wiki/Replay_attack}" + } + + @misc {wiki-authentication-encryption, + author = "Wikipedia", + title = "Authenticated encryption --- {W}ikipedia{,} The Free Encyclopedia", + year = "2014", + url = "\url{http://en.wikipedia.org/wiki/Authenticated_encryption}" + } + + @misc {wiki-TLS, + author = "Wikipedia", + title = "Transport Layer Security --- {W}ikipedia{,} The Free Encyclopedia", + year = "2014", + url = "\url{http://en.wikipedia.org/wiki/Transport_Layer_Security}" + } + + @misc {wiki-block-cipher-mode-of-operation, + author = "Wikipedia", + title = "Block cipher mode of operation --- {W}ikipedia{,} The Free Encyclopedia", + year = "2014", + url = "\url{http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation}" + } + + @misc {wiki-padding-oracle-attack, + author = "Wikipedia", + title = "Padding oracle attack --- {W}ikipedia{,} The Free Encyclopedia", + year = "2014", + url = "\url{http://en.wikipedia.org/wiki/Padding_oracle_attack}" + } + diff --git a/rapport/main.tex b/rapport/main.tex new file mode 100644 index 0000000..e83451d --- /dev/null +++ b/rapport/main.tex @@ -0,0 +1,102 @@ +\documentclass[a4paper,10pt]{article} + +\usepackage[francais]{babel} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{lmodern} + +\usepackage{listings} +\usepackage{url} + +\title{ICR - Labo \#1 : \textit{MAC-and-Encrypt and Padding Oracles}} +\author{G.Burri} + +\begin{document} + +\lstset{language=C} +\nocite{*} + +\maketitle + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Introduction} + +Le but de ce laboratoire est d'expérimenter le chiffrement symétrique \emph{AES} ainsi que l'authentification par \emph{MAC}, de mettre en évidence des problèmes de sécurité liés à un protocole choisi propre et de montrer des solutions afin de corriger ces problèmes. + +Nous utiliseront \emph{AES-256} en mode \emph{CBC} pour chiffrer les données ainsi que \emph{HMAC-SHA256} pour l'authentification. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Protocol simulator} + +\subsection{Structure du code} + +\subsection{Quelle est la stratégie recommendée en pratique parmi les trois listées ci après ?} + +\begin{itemize} + \item \emph{MAC-and-Encrypt} : $Enc(M)|MAC(M)$ ; + \item \emph{MAC-then-Encrypt} : $Enc(M|MAC(M))$ ; + \item \emph{Encrypt-then-MAC} : $Enc(M)|MAC(Enc(M))$. +\end{itemize} + +\subsubsection{Quelle stratégie est utilisée par \emph{TLS} ?} + +\emph{TSL} utilise la deuxième version (\emph{MAC-then-Encrypt}). À noté que le \emph{MAC} est optionnel. + +Une proposition \footnote{https://tools.ietf.org/html/draft-ietf-tls-encrypt-then-mac-02} existe afin d'utiliser du \textit{Encrypt-then-MAC} pour \emph{TSL}. + + +\subsubsection{Quelle stratégie est utilisée par \emph{SSH} ?} + +\emph{SSH} utilise la même méthode utilisée dans ce laboratoire, c'est à dire la première : \emph{MAC-and-Encrypt}. + +\subsection{Quel est le rôle du timestamp en terme de sécurité ?} + +Permet de minimiser certaines attaques comme l'attaque par rejeu (\emph{replay attack})\cite{wiki-replay-attack} où un attaquant réutilise tel-quel tout ou une partie d'un message intercepté au préalable. + +Dans notre cas un attaquant ne pourra pas rejouer une commande tel quelle, elle serait rejetée par le serveur ayant un \emph{timestamp} supérieur. Si l'attaquant essaie de renvoyer un paquet avec un timestamp modifié, alors les données décodées ne seront plus validées par la \emph{MAC} car le vecteur d'initialisation utilisé (\emph{IV}) lors du déchiffrement est composé en partie par le \emph{timestamp}. + + +\subsection{Y a-t-il un moyen d'effectuer une attaque de type \emph{denial-of-service} sur notre dispositif ?} + +Via une \emph{replay attack} en modifiant le \emph{timestamp} pour qu'il soit valide le dispositif va devoir déchiffrer les données puis calculer le \emph{MAC} avant de se rendre compte que le paquet est invalide et envoyer une réponse qui sera chiffrée et authentifiée. Dans ce cas on peut faire travailler énormément le dispositif en lui envoyant le plus de paquet à déchiffrer que le permet le débit du moyen de communication utilisé. Cela peut amener le dispositif a être surchargé. + + +\subsection{À la place d'utiliser un \emph{IV} aléatoire, le mode \emph{CBC} implémente une approche basée sur un \emph{nonce}. Que peut-on dire de sa sécurité ?} + + + +\subsection{Remarques concernant la sécurité de notre protocole} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Utilisation du serveur comme d'un Oracle de déchiffrement} + +\subsection{Historique de l'attaque par oracle à l'aide du remplissage} + + + +\subsection{Explication de l'attaque pour notre cas} + +\subsection{Calcul de la complexité moyenne de l'attaque} + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Correction du protocole} + +\subsection{Description} + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\section{Conclusion} + + +%http://crypto.stackexchange.com/a/205 +%https://en.wikipedia.org/wiki/Malleability_%28cryptography%29 + +\bibliographystyle{plain} +\bibliography{main} + +\end{document} diff --git a/src/crypto.rs b/src/crypto.rs deleted file mode 100644 index d9fe182..0000000 --- a/src/crypto.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::rand::{ OsRng, Rng }; -use std::io::IoResult; -use std::slice::bytes::copy_memory; -use openssl::crypto::hash::SHA256; -use openssl::crypto::hmac::HMAC; -use openssl::crypto::symm; - -// These aren't the keys you're looking for. -static KEY_A: &'static [u8] = [125, 31, 131, 118, 143, 180, 252, 53, 211, 217, 79, 240, 128, 91, 252, 87, 104, 236, 145, 198, 163, 203, 161, 12, 53, 56, 218, 40, 221, 95, 171, 140]; -static KEY_C: &'static [u8] = [75, 226, 88, 31, 223, 216, 182, 216, 178, 58, 59, 193, 245, 80, 254, 128, 125, 246, 246, 224, 194, 190, 123, 123, 10, 131, 217, 183, 112, 157, 166, 102]; - -/// Only returns the first ten bytes. -pub fn compute_mac(data: &[u8]) -> [u8, ..10] { - let mut hmac = HMAC(SHA256, KEY_A); - hmac.update(data); - let mut result = [0u8, ..10]; - copy_memory(&mut result, hmac.finalize().slice(0, 10)); - result -} - -/// Encrypt may fail if the provided data size isn't a multiple of 16. -pub fn encrypt(plaindata: &[u8], iv: &[u8]) -> Option> { - let c = symm::Crypter::new(symm::AES_256_CBC); - c.init(symm::Encrypt, KEY_C, iv.to_vec()); - c.pad(false); // Padding disabled! - let r = c.update(plaindata); - let rest = c.finalize(); - if rest.is_empty() { - Some(r) - } else { - None - } -} - -/// Decrypt may fail if the provided data size isn't a multiple of 16. -pub fn decrypt(cypherdata: &[u8], iv: &[u8]) -> Option> { - let c = symm::Crypter::new(symm::AES_256_CBC); - c.init(symm::Decrypt, KEY_C, iv.to_vec()); - c.pad(false); // Padding disabled! - let r = c.update(cypherdata); - let rest = c.finalize(); - if rest.is_empty() { - Some(r) - } else { - None - } -} - -pub fn generate_key(size_byte: uint) -> IoResult> { - let mut bytes = Vec::from_elem(size_byte, 0u8); - let mut generator = try!(OsRng::new()); // Uses '/dev/urandom' on Unix-like systems. - generator.fill_bytes(bytes.as_mut_slice_()); - Ok(bytes) -} \ No newline at end of file diff --git a/src/end_point.rs b/src/end_point.rs deleted file mode 100644 index 2d0a66c..0000000 --- a/src/end_point.rs +++ /dev/null @@ -1,319 +0,0 @@ -use std::io; -use std::io::{ MemWriter, Acceptor, Listener, TcpStream, IoResult, IoError, EndOfFile }; -use std::io::net::tcp::{ TcpAcceptor, TcpListener }; -use packet; -use packet::{ Packet, Command, Answer, Error, ReadingResult, PacketType }; - -// Default timeout when waiting data on a stream (for instance: 'TcpStream::read'). -static DEFAULT_TIMEOUT: Option = Some(500); // [ms]. - -pub struct Server { - acceptor: TcpAcceptor -} - -pub struct Client { - end_point: EndPoint, -} - -pub struct EndPoint { - socket: TcpStream, - current_timestamp: u64 -} - -impl Server { - pub fn new(interface: &str, port: u16) -> IoResult { - let mut acceptor = try!(TcpListener::bind(interface, port).listen()); - - let server = Server { - acceptor: acceptor.clone() - }; - - spawn(proc() { - loop { - for stream in acceptor.incoming() { - match stream { - Ok(stream) => spawn(proc() { - Server::handle_client(EndPoint::new(stream)); - }), - _ => return - } - } - } - }); - - Ok(server) - } - - pub fn close(&mut self) -> IoResult<()> { - self.acceptor.close_accept() - } - - fn handle_client(mut end_point: EndPoint) { - loop { - match end_point.read() { - Ok(packet@Packet { t: Command(..), .. }) => { - println!("[Server] Valid command received: {}", packet); - let answer = Answer(Packet::random_packet_data([])); - match end_point.send(answer.clone()) { - Ok(_) => - println!("[Server] Answer sent: {}", answer), - Err(e) => - println!("[Server] Can't send the answer. Error: {}", e) - } - }, - // Socket has been closed. - Err(packet::IOReadError(IoError { kind: EndOfFile, .. })) => { - println!("[Server] Connection closed: EOF"); - return - }, - other => - println!("[Server] Error or invalid packet: {}", other) - } - } - } -} - -impl Client { - pub fn new(address: &str, port: u16) -> IoResult { - Ok(Client { end_point: EndPoint { - socket: try!(TcpStream::connect(address, port)), - current_timestamp: 0 - }}) - } - - pub fn close(&mut self) -> IoResult<()> { - self.end_point.close() - } - - pub fn send(&mut self, packet: PacketType) { - match packet { - Command(_) => { - println!("[Client] Sending: {}", packet); - match self.end_point.send_with_result(packet) { - Ok(Ok(packet@Packet { t: Answer(..), .. })) => - println!("[Client] Command transmitted correctly, answer: {}", packet), - Ok(Ok(packet)) => - println!("[Client] Command transmitted correctly, wrong answer: {}", packet), - Ok(Err(e)) => - println!("[Client] Answer error: {}", e), - Err(e) => - println!("[Client] Can't send the packet. Error: {}", e) - } - }, - other => - println!("[Client] Cannot send this type of packet: {}", other) - } - } - - /// Send some valid and not-valid packets to the server and check the result. - /// For each test a new client is created. - pub fn start_tests(address: &str, port: u16) { - let execute = |f: &mut |&mut Client| -> bool| -> bool { - match Client::new(address, port) { - Ok(ref mut client) => (*f)(client), - Err(e) => { - println!("Unable to create a client. Error: {}", e); - false - } - } - }; - - fn raw_packet(timestamp: u64) -> Vec { - let mut m = MemWriter::new(); - match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: timestamp }).write(&mut m) { - Err(_) => vec!(), - _ => m.unwrap() - } - } - - let mut tests = vec!( - // 1) - |client: &mut Client| -> bool { - println!("Sending a valid packet..."); - match client.end_point.send_with_result(Command(Packet::random_packet_data([42]))) { - Ok(Ok(Packet { t: Answer(..), .. })) => true, - other => { - println!("Error: {}", other); - false - } - } - }, - // 2) - |client: &mut Client| -> bool { - println!("Sending a packet with an unknown type..."); - client.end_point.current_timestamp += 1; - let mut raw_packet = raw_packet(client.end_point.current_timestamp); - raw_packet[2] = 0xEE; // Alter the type. - match client.end_point.send_raw_with_result(raw_packet.as_slice()) { - Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. - other => { - println!("Error: {}", other); - false - } - } - }, - // 3) - |client: &mut Client| -> bool { - println!("Sending a packet with an old timestamp..."); - client.end_point.current_timestamp += 1; - let raw_packet = raw_packet(0); - match client.end_point.send_raw_with_result(raw_packet.as_slice()) { - Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. - other => { - println!("Error: {}", other); - false - } - } - }, - // 4) - |client: &mut Client| -> bool { - println!("Sending a packet with altered crypted data (do not alter the padding)..."); - client.end_point.current_timestamp += 1; - let mut raw_packet = raw_packet(client.end_point.current_timestamp); - raw_packet[11] = 0xDE; - raw_packet[12] = 0xAD; - raw_packet[13] = 0xBE; - raw_packet[14] = 0xEF; - match client.end_point.send_raw_with_result(raw_packet.as_slice()) { - Ok(Ok(Packet { t: Error(packet::AuthError), .. })) => true, - other => { - println!("Error: {}", other); - false - } - } - }, - // 5) - |client: &mut Client| -> bool { - println!("Sending a packet with too small data..."); - let command = Command(Packet::new_packet_data(0, Vec::from_elem(6, 0x00))); - match client.end_point.send_with_result(command) { - Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. - other => { - println!("Error: {}", other); - false - } - } - }, - // 6) - |client: &mut Client| -> bool { - println!("Sending a packet with too large data..."); - let command = Command(Packet::new_packet_data(0, Vec::from_elem(40, 0x00))); - match client.end_point.send_with_result(command) { - Ok(Err(packet::IOReadError( IoError { kind: io::TimedOut, .. }))) => true, // OK: the server should not send any packet. - other => { - println!("Error: {}", other); - false - } - } - }, - // 7) - |client: &mut Client| -> bool { - println!("Sending a packet with wrong padding (all 0)..."); - client.end_point.current_timestamp += 1; - let mut m = MemWriter::new(); - let raw_packet = match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: client.end_point.current_timestamp }).write_with_padding_fun(&mut m, |_, _| -> u8 { 0 }) { - Err(_) => vec!(), - _ => m.unwrap() - }; - - match client.end_point.send_raw_with_result(raw_packet.as_slice()) { - Ok(Ok(Packet { t: Error(packet::CryptError), .. })) => true, - other => { - println!("Error: {}", other); - false - } - } - }, - ); - - let mut nb_test_passed = 0; - for (i, test) in range(1, tests.len()+1).zip(tests.iter_mut()) { - println!("===== Test case #{}:", i) - if execute(test) { - nb_test_passed += 1; - println!("===== Test passed"); - } else { - println!("===== Test failed"); - } - } - - if nb_test_passed == tests.len() { - println!("All tests passed"); - } else { - println!("#{} test(s) failed", tests.len() - nb_test_passed); - } - } -} - -impl EndPoint { - pub fn new(socket: TcpStream) -> EndPoint { - EndPoint { socket: socket, current_timestamp: 0 } - } - - fn close(&mut self) -> IoResult<()> { - try!(self.socket.close_read()); - try!(self.socket.close_write()); - Ok(()) - } - - /// Send a packet and wait for an answer synchronously. - fn send_with_result(&mut self, p: PacketType) -> IoResult { - match self.send(p) { - Err(e) => Err(e), - _ => Ok(self.read()) - } - } - - /// Send arbitrary data and wait for an answer synchronously. - /// Do not increment the current timestamp. - fn send_raw_with_result(&mut self, p: &[u8]) -> IoResult { - self.socket.set_timeout(DEFAULT_TIMEOUT); - match self.socket.write(p) { - Err(e) => Err(e), - _ => Ok(self.read()) - } - } - - fn send(&mut self, p: PacketType) -> IoResult<()> { - self.socket.set_timeout(DEFAULT_TIMEOUT); - self.current_timestamp += 1; - match (Packet { t: p, timestamp: self.current_timestamp }).write(&mut self.socket) { - Err(packet::WriteIOError(e)) => Err(e), - _ => Ok(()) - } - } - - fn read(&mut self) -> ReadingResult { - fn send_error(ep: &mut EndPoint, error_type: packet::ErrorType) { - match ep.send(Error(error_type)) { - Err(e) => println!("Unable to send error packet: {}", e), - Ok(_) => () - }; - }; - - self.socket.set_timeout(DEFAULT_TIMEOUT); - match Packet::read(&mut self.socket) { - Ok(packet) => { - if packet.timestamp <= self.current_timestamp { - println!("Error, timestamp mismatch, current timestamp: {}, packet received: {}", self.current_timestamp, packet); - Err(packet::InvalidTimestampError) - } else { - self.current_timestamp = packet.timestamp + 1; - Ok(packet) - } - }, - e @ Err(packet::PaddingError) => { - self.current_timestamp += 1; - send_error(self, packet::CryptError); - e - }, - e @ Err(packet::MACMismatchError) => { - self.current_timestamp += 1; - send_error(self, packet::AuthError); - e - }, - other => - other - } - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 64aef28..0000000 --- a/src/main.rs +++ /dev/null @@ -1,59 +0,0 @@ -#![feature(macro_rules)] - -extern crate openssl; -extern crate serialize; - -use std::io; -use std::os; - -use end_point::{ Client, Server }; - -mod crypto; -mod packet; -mod end_point; -mod oracle_machine; - -const PORT: u16 = 4221; - -fn print_usage() { - println!( - r"{} genkey | tests | oracle-weak | oracle-fixed - genkey: Generate a 256 bits key - tests: launch some tests between a client and a weak server - oracle-weak: launch a padding oracle attack against a weak server - oracle-fixed: launch a padding oracle attack against a fixed server", - os::args()[0] - ); -} - -fn main() { - let args = os::args(); - - if args.iter().any(|a| a.as_slice() == "--help" || a.as_slice() == "-h") { - print_usage(); - } else if args.len() > 1 && args[1].as_slice() == "genkey" { - match crypto::generate_key(256 / 8) { - Ok(key) => println!("key: {}", key), - Err(e) => println!("Unable to generate a key. Error: {}", e) - } - } else { - println!("Starting server..."); - - match Server::new("::1", PORT) { - Ok(mut server) => { - println!("Server started"); - - if args.len() > 1 && args[1].as_slice() == "tests" { - Client::start_tests("::1", PORT); - } - - println!("Press any key to quit"); - io::stdin().read_line().ok().expect("Failed to read line"); - - server.close().ok().expect("Failed to close the server"); - }, - Err(e) => - println!("Unable to create a new server. Error: {}", e) - } - } -} diff --git a/src/oracle_machine.rs b/src/oracle_machine.rs deleted file mode 100644 index 68aef94..0000000 --- a/src/oracle_machine.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::io; -use std::io::{ TcpStream }; -use end_point::EndPoint; - -/// Try to decypher a cyphered data block by using an oracle on the provided address and port. -/// May prints some message on the stdout. -pub fn decypher(address: &str, port: u16, cypherblock: [u8, ..16]) -> Option> { - let end_point = EndPoint::new( - match TcpStream::connect(address, port) { - Ok(s) => s, - _ => { - println!("Unable to connect to the oracle on {}:{}", address, port); - return None - } - } - ); - - None -} diff --git a/src/packet.rs b/src/packet.rs deleted file mode 100644 index 73eecea..0000000 --- a/src/packet.rs +++ /dev/null @@ -1,268 +0,0 @@ -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() -}