* Add a fixed version of the protocol.
--- /dev/null
+time cargo run --release -- oracle-weak
pub struct EndPoint {
socket: TcpStream,
- current_timestamp: u64
+ current_timestamp: u64,
+ variant: packet::Variant,
}
impl Server {
- pub fn new(interface: &str, port: u16) -> IoResult<Server> {
+ pub fn new(interface: &str, port: u16, variant: packet::Variant) -> IoResult<Server> {
let mut acceptor = try!(TcpListener::bind(interface, port).listen());
let server = Server {
for stream in acceptor.incoming() {
match stream {
Ok(stream) => spawn(proc() {
- Server::handle_client(EndPoint::new(stream));
+ Server::handle_client(EndPoint::new(stream, variant));
}),
_ => return
}
loop {
match end_point.read() {
Ok(packet@Packet { t: Command(..), .. }) => {
- println!("[Server] Valid command received: {}", packet);
+ end_point.print("Server", format!("Valid command received: {}", packet));
let answer = Answer(Packet::random_packet_data([]));
match end_point.send(answer.clone()) {
Ok(_) =>
- println!("[Server] Answer sent: {}", answer),
+ end_point.print("Server", format!("Answer sent: {}", answer)),
Err(e) =>
- println!("[Server] Can't send the answer. Error: {}", e)
+ end_point.print("Server", format!("Can't send the answer. Error: {}", e))
}
},
// Socket has been closed.
Err(packet::IOReadError(IoError { kind: EndOfFile, .. })) => {
- println!("[Server] Connection closed: EOF");
+ end_point.print("Server", format!("Connection closed: EOF"));
return
},
other =>
- println!("[Server] Error or invalid packet: {}", other)
+ end_point.print("Server", format!("Error or invalid packet: {}", other))
}
}
}
}
impl Client {
- pub fn new(address: &str, port: u16) -> IoResult<Client> {
+ pub fn new(address: &str, port: u16, variant: packet::Variant) -> IoResult<Client> {
Ok(Client { end_point: EndPoint {
socket: try!(TcpStream::connect(address, port)),
- current_timestamp: 0
+ current_timestamp: 0,
+ variant: variant,
}})
}
self.end_point.close()
}
- pub fn send(&mut self, packet: PacketType) {
+ pub fn send(&mut self, packet: PacketType) -> bool {
match packet {
Command(_) => {
- println!("[Client] Sending: {}", packet);
+ self.end_point.print("Client", format!("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)
+ Ok(Ok(packet@Packet { t: Answer(..), .. })) => {
+ self.end_point.print("Client", format!("Command transmitted correctly, answer: {}", packet));
+ true
+ },
+ Ok(Ok(packet)) => {
+ self.end_point.print("Client", format!("Command transmitted correctly, wrong answer: {}", packet));
+ false
+ }
+ Ok(Err(e)) => {
+ self.end_point.print("Client", format!("Answer error: {}", e));
+ false
+ }
+ Err(e) => {
+ self.end_point.print("Client", format!("Can't send the packet. Error: {}", e));
+ false
+ }
}
},
- other =>
- println!("[Client] Cannot send this type of packet: {}", other)
+ other => {
+ self.end_point.print("Client", format!("Cannot send this type of packet: {}", other));
+ false
+ }
}
}
/// 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) {
+ pub fn start_tests(address: &str, port: u16, variant: packet::Variant) {
let execute = |f: &mut |&mut Client| -> bool| -> bool {
- match Client::new(address, port) {
+ match Client::new(address, port, variant) {
Ok(ref mut client) => (*f)(client),
Err(e) => {
println!("Unable to create a client. Error: {}", e);
fn raw_packet(timestamp: u64) -> Vec<u8> {
let mut m = MemWriter::new();
- match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: timestamp }).write(&mut m) {
+ match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: timestamp }).write(&mut m, packet::Weak) {
Err(_) => vec!(),
_ => m.unwrap()
}
// 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
- }
- }
+ client.send(Command(Packet::random_packet_data([42])))
},
// 2)
|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 }) {
+ let raw_packet = match (Packet { t: Command(Packet::random_packet_data([42])), timestamp: client.end_point.current_timestamp }).write_with_padding_fun(&mut m, packet::Weak, |_, _| -> u8 { 0 }) {
Err(_) => vec!(),
_ => m.unwrap()
};
}
impl EndPoint {
- pub fn new(socket: TcpStream) -> EndPoint {
- EndPoint { socket: socket, current_timestamp: 0 }
+ pub fn new(mut socket: TcpStream, variant: packet::Variant) -> EndPoint {
+ let _ = socket.set_nodelay(true);
+ EndPoint { socket: socket, current_timestamp: 0 , variant: variant}
}
fn close(&mut self) -> IoResult<()> {
Ok(())
}
+ fn print(&self, prefix: &str, s: String) {
+ println!("[{}] time: {}. {}", prefix, self.current_timestamp, s);
+ }
+
/// Send a packet and wait for an answer synchronously.
fn send_with_result(&mut self, p: PacketType) -> IoResult<ReadingResult> {
match self.send(p) {
/// 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<ReadingResult> {
+ pub fn send_raw_with_result(&mut self, p: &[u8]) -> IoResult<ReadingResult> {
self.socket.set_timeout(DEFAULT_TIMEOUT);
match self.socket.write(p) {
Err(e) => Err(e),
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) {
+ match (Packet { t: p, timestamp: self.current_timestamp }).write(&mut self.socket, self.variant) {
Err(packet::WriteIOError(e)) => Err(e),
_ => Ok(())
}
};
self.socket.set_timeout(DEFAULT_TIMEOUT);
- match Packet::read(&mut self.socket) {
+ match Packet::read(&mut self.socket, self.variant) {
Ok(packet) => {
if packet.timestamp <= self.current_timestamp {
println!("Error, timestamp mismatch, current timestamp: {}, packet received: {}", self.current_timestamp, packet);
fn print_usage() {
println!(
- r"{} genkey | tests | oracle-weak | oracle-fixed
+ 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
);
}
-fn main() {
+fn do_oracle_attack(address: &str, variant: packet::Variant) {
+ // 16 bytes encrypted data from 'Packet::random_packet_data([4])'.
+ let cypher_block: [u8, ..16] = [254, 9, 228, 149, 60, 42, 165, 34, 233, 75, 112, 57, 37, 9, 116, 103]; // Known by the attacker.
+ let xor_operand: [u8, ..16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]; // This is the IV or the previous 16 bytes cypherblock. In our case we took the IV.
+
+ let expected_clear_block: [u8, ..16] = [44, 92, 31, 98, 220, 84, 226, 53, 58, 94, 45, 25, 242, 6, 199, 1]; // To be found by the attacker.
+
+ match oracle_machine::decypher(address, PORT, &xor_operand, &cypher_block, variant) {
+ Some(ref decyphered) if decyphered.as_slice() == expected_clear_block => {
+ println!("The oracle machine has found the clear block!:");
+ println!(" Expected block: {}", expected_clear_block.to_vec());
+ println!(" Decrypted block: {}", decyphered)
+ }
+ Some(ref other) =>
+ println!("The oracle machine hasn't found the clear block: {}", other),
+ _ =>
+ println!("The oracle machine hasn't found the clear block"),
+ }
+}
+
+enum Mode {
+ Help,
+ ServerAlone,
+ GenKey,
+ Tests,
+ OracleWeak,
+ OracleFixed,
+}
+
+fn mode() -> Mode {
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)
- }
+ return Help
+ }
+
+ if args.len() <= 1 {
+ ServerAlone
} else {
- println!("Starting server...");
+ match args[1].as_slice() {
+ "genkey" => GenKey,
+ "tests" => Tests,
+ "oracle-weak" => OracleWeak,
+ "oracle-fixed" => OracleFixed,
+ _ => ServerAlone,
+ }
+ }
+}
- match Server::new("::1", PORT) {
- Ok(mut server) => {
- println!("Server started");
+fn main() {
+ let mode = mode();
+
+ match mode {
+ Help => print_usage(),
+ GenKey =>
+ match crypto::generate_key(256 / 8) {
+ Ok(key) => println!("key: {}", key),
+ Err(e) => println!("Unable to generate a key. Error: {}", e)
+ },
+ _ => {
+ let address = "::1";
+ println!("Starting server on [{}]:{}...", address, PORT);
- if args.len() > 1 && args[1].as_slice() == "tests" {
- Client::start_tests("::1", PORT);
- }
+ match Server::new(address, PORT, match mode { OracleFixed => packet::Fixed, _ => packet::Weak }) {
+ Ok(mut server) => {
+ println!("Server started");
- println!("Press any key to quit");
- io::stdin().read_line().ok().expect("Failed to read line");
+ match mode {
+ Tests => Client::start_tests(address, PORT, packet::Weak),
+ OracleWeak => do_oracle_attack(address, packet::Weak),
+ OracleFixed => do_oracle_attack(address, packet::Fixed),
+ _ => {
+ 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)
+ server.close().ok().expect("Failed to close the server");
+ },
+ Err(e) =>
+ println!("Unable to create a new server. Error: {}", e)
+ }
}
}
}
use std::io;
use std::io::{ TcpStream };
+use std::iter::{ range_inclusive };
+use std::slice::bytes::copy_memory;
+use packet;
+use packet::{ Packet, Error };
use end_point::EndPoint;
-/// Try to decypher a cyphered data block by using an oracle on the provided address and port.
+/// Try to decypher a cyphered data block by using the previous xor operand and 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<Vec<u8>> {
- let end_point = EndPoint::new(
+pub fn decypher(address: &str, port: u16, original_xor_operand: &[u8, ..16], cypherblock: &[u8, ..16], variant: packet::Variant) -> Option<Vec<u8>> {
+ let mut end_point = EndPoint::new(
match TcpStream::connect(address, port) {
Ok(s) => s,
_ => {
- println!("Unable to connect to the oracle on {}:{}", address, port);
+ println!("Unable to connect to the oracle on [{}]:{}", address, port);
return None
}
- }
+ },
+ variant,
);
- None
-}
+ let mut final_packet = [0u8, ..2 + 1 + 8 + 32 + 10];
+ final_packet[1] = 1 + 8 + 32 + 10; // Data length.
+ copy_memory(final_packet.slice_mut(2 + 1 + 8 + 16, 2 + 1 + 8 + 32), cypherblock);
+
+ let mut decypher_block = [0u8, ..16]; // The result.
+ let mut x_prime_block = [0u8, ..16]; // The cypher block ('cypherblock') after AES and before XOR.
+ let mut current_timestamp = 0u64;
+ let mut first_byte = 0u8; // Used to save the first byte for the first iteration.
+
+ #[inline(always)]
+ fn forged_xor_operand(packet: &mut [u8]) -> &mut [u8] { packet.slice_mut(2 + 1 + 8, 2 + 1 + 8 + 16) }
+
+ // For each bytes.
+ let mut byte = 15;
+ 'main_loop: loop {
+ let mut get_mac_mismatch_error = false; // For the first byte we need to insure there is only one AUTH error (one valid padding).
+
+ for v in range_inclusive(0u8, 255) { // For each values of the current byte.
+
+ // Compute and write timestamp.
+ current_timestamp += 2;
+ {
+ let mut timestamp_writer = io::BufWriter::new(final_packet.slice_mut(2 + 1, 2 + 1 + 8));
+ let _ = timestamp_writer.write_be_u64(current_timestamp - 1);
+ }
+
+ forged_xor_operand(&mut final_packet)[byte] = v;
+
+ match end_point.send_raw_with_result(final_packet) {
+ Ok(Ok(Packet { t: Error(packet::AuthError), .. })) => {
+
+ // If we already got a MAC mismatch for the first byte then the second byte is incremented and the loop is replayed.
+ if byte == 15 && get_mac_mismatch_error {
+ forged_xor_operand(&mut final_packet)[14] += 1;
+ continue 'main_loop;
+ }
+
+ get_mac_mismatch_error = true;
+
+ let padding_value = 16 - byte;
+ x_prime_block[byte] = v ^ (padding_value as u8);
+ decypher_block[byte] = x_prime_block[byte] ^ original_xor_operand[byte];
+
+ // We set the processed bytes of the forged XOR operand to have the next padding value.
+ for i in range(16 - padding_value, 16) {
+ forged_xor_operand(&mut final_packet)[i] = x_prime_block[i] ^ ((padding_value as u8) + 1);
+ }
+
+ // Special case for the first byte: we have to test all the values.
+ if byte == 15 {
+ first_byte = forged_xor_operand(&mut final_packet)[15];
+ } else {
+ break;
+ }
+ },
+ Ok(Ok(Packet { t: Error(packet::CryptError), .. })) => (), // Ignored case: the padding is wrong.
+ other => {
+ println!("Unexcepted response, aborting. {}", other);
+ return None
+ }
+ }
+ }
+
+ if byte == 15 {
+ forged_xor_operand(&mut final_packet)[15] = first_byte;
+ }
+
+ // It was the last byte.
+ if byte == 0 { break; }
+
+ byte -= 1;
+ }
+
+ Some(decypher_block.to_vec())
+}
\ No newline at end of file
use serialize::hex::{ ToHex };
use crypto;
+pub enum Variant {
+ Weak, // The MAC is computed on data without padding.
+ Fixed // The MAC is computed on data and padding.
+}
+
// There are all the errors that may occur when reading an encrypted and authenticated packet.
#[deriving(Show)]
pub enum ReadingError {
/// 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
+/// for command and answer packet:
+/// |I|C...C| for weak variant
+/// |I|C...C|P...P|for fixed variant
/// |0000000000000000| for error packet
#[deriving(Show)]
pub struct Packet {
PacketData { id: id, payload: payload }
}
- pub fn write(&self, output: &mut io::Writer) -> WritingResult {
- self.write_with_padding_fun(output, |_, padding_length: uint| -> u8 {
+ pub fn write(&self, output: &mut io::Writer, variant: Variant) -> WritingResult {
+ self.write_with_padding_fun(output, variant, |_, 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 {
+ pub fn write_with_padding_fun(&self, output: &mut io::Writer, variant: Variant, padd_fun: |uint, uint| -> u8) -> WritingResult {
fn packet_data(p: &PacketData) -> Vec<u8> {
let mut d = Vec::new();
d.push(p.id);
Error(_) => Vec::from_elem(16, 0) // Padding as data: 16 * 0.
};
- // Compute the MAC
- let mac = crypto::compute_mac(data.as_slice());
+ let data_size = data.len();
// Padding.
match self.t {
_ => ()
}
+ // Compute the MAC. It depends the choosen variant.
+ let mac = crypto::compute_mac(data.slice_to(match variant { Weak => data_size, _ => data.len() }));
+
// Encrypt.
let encrypted_data = match crypto::encrypt(data.as_slice(), iv_from_timestamp(self.timestamp).as_slice()) {
Some(d) => d,
Ok(())
}
- pub fn read(input: &mut io::Reader) -> ReadingResult {
+ pub fn read(input: &mut io::Reader, variant: Variant) -> ReadingResult {
fn consume(input: &mut io::Reader, nb_byte: uint) {
let _ = input.read_exact(nb_byte);
}
_ => return Err(UnconsistentEncryptedSizeError)
};
+ // Read the MAC.
+ let mut mac_read = [0u8, ..10];
+ if try_read_io!(input.read(mac_read)) != mac_read.len() {
+ return Err(UnconsistentMACSizeError)
+ }
+
+ match variant { Fixed if mac_read != crypto::compute_mac(data.as_slice()) => return Err(MACMismatchError), _ => () };
+
// 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);
+ if padding_size as uint > data.len() || padding_size == 0 || data.slice_from(data.len() - padding_size as uint).iter().any(|b| *b != padding_size) {
return Err(PaddingError)
}
let data_length = data.len() - padding_size as uint;
}
}
- // 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)
- }
+ match variant { Weak if mac_read != crypto::compute_mac(data.as_slice()) => return Err(MACMismatchError), _ => () };
Ok(Packet {
t: match packet_type {