diff --git a/src/main.rs b/src/main.rs index 4657ce5..265938f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,10 @@ +#![warn(clippy::pedantic)] +#![allow(clippy::cast_precision_loss)] +#![allow(clippy::items_after_statements)] +#![allow(clippy::many_single_char_names)] +#![allow(clippy::module_name_repetitions)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::unnested_or_patterns)] mod bytes; mod bytes_base64; mod cbc; @@ -15,7 +22,7 @@ mod sha1; mod utils; fn main() { - const RUN_ALL: bool = true; + const RUN_ALL: bool = false; if RUN_ALL { set1::challenge1(); set1::challenge2(); diff --git a/src/set4.rs b/src/set4.rs index 31d0553..ec1a715 100644 --- a/src/set4.rs +++ b/src/set4.rs @@ -1,3 +1,6 @@ +use std::path::Path; +use std::fs; +use std::{thread, time}; use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils}; pub fn challenge25() { @@ -299,5 +302,65 @@ pub fn challenge30() { } pub fn challenge31() { - println!("[xxxx] Challenge 31: tbd"); + fn insecure_compare(a: &[u8], b: &[u8]) -> bool { + // Write a function, call it "insecure_compare", that implements the == operation by doing + // byte-at-a-time comparisons with early exit (ie, return false at the first non-matching + // byte). + let delay = time::Duration::from_millis(8); + if a.len() != b.len() { + return false; + } + for (a, b) in a.iter().zip(b.iter()) { + if a != b { + return false; + } + // In the loop for "insecure_compare", add a 50ms sleep (sleep 50ms after each byte). + thread::sleep(delay); + } + true + } + + fn verify(file: &Path, signature: &[u8]) -> bool { + // Have the server generate an HMAC key, and then verify that the "signature" on incoming + // requests is valid for "file", using the "==" operator to compare the valid MAC for a + // file with the "signature" parameter (in other words, verify the HMAC the way any normal + // programmer would verify it). + let key = Bytes::from_utf8("sosecretbb"); + let contents = fs::read_to_string(file); + assert!(contents.is_ok(), "Could not read: {}", file.display()); + let contents = Bytes(contents.unwrap().as_bytes().to_vec()); + insecure_compare(&sha1::hmac_sha1(&key, &contents).0, signature) + } + + fn attack(file: &Path) -> Bytes { + const BLOCK_SIZE: usize = 20; + let mut sig = vec![0x0; BLOCK_SIZE]; + for i in 0..BLOCK_SIZE { + let mut max_tuple: (u128, u8) = (u128::MIN, 0); + for c in 0_u8..=255_u8 { + let now = time::Instant::now(); + sig[i] = c; + verify(file, &sig); + let elapsed = now.elapsed().as_micros(); + if elapsed > max_tuple.0 { + max_tuple = (elapsed, c); + } + } + sig[i] = max_tuple.1; + } + Bytes(sig) + } + + let key = Bytes::from_utf8("YELLOW SUBMARINE"); + let message = Bytes::from_utf8("Attact at dawn after tomorrow when it's cold inside."); + assert_eq!(sha1::hmac_sha1(&key, &message), Bytes::from_hex("8232f3d05afb6bce7e09fe764885cc158e435e36")); + + let path = Path::new("data/12.txt"); + let expected_sig = Bytes::from_hex("62f4527ea6cb716d0ad1ca0fc69135a49bc2d138"); + assert!(verify(path, &expected_sig.0), "Invalid signature"); + + let signature = attack(path); + assert_eq!(expected_sig.0, signature.0, "Recovery of HMAC-SHA1 failed"); + + println!("[okay] Challenge 31: recoverd HMAC-SHA1 via timing attack"); } diff --git a/src/sha1.rs b/src/sha1.rs index cbff060..443e9ef 100644 --- a/src/sha1.rs +++ b/src/sha1.rs @@ -264,3 +264,33 @@ pub fn authenticate(message: &Bytes, key: &Bytes) -> Bytes { pub fn verify(message: &Bytes, key: &Bytes, mac: &Bytes) -> bool { authenticate(message, key) == *mac } + +pub fn hmac_sha1(key: &Bytes, message: &Bytes) -> Bytes { + fn hash(message: Vec) -> Vec { + let mut sha1 = Sha1::default(); + sha1.hash(&Bytes(message)).0 + } + + fn compute_block_sized_key(key: &Bytes, block_size: usize) -> Bytes { + let mut key = key.0.clone(); + if key.len() > block_size { + key = hash(key); + } + + while key.len() < block_size { + key.push(0x0); + } + Bytes(key) + } + + const BLOCK_SIZE: usize = 64; + let block_sized_key = compute_block_sized_key(key, BLOCK_SIZE); + let mut o_key_pad = Bytes::xor(&block_sized_key, &Bytes(vec![0x5c; BLOCK_SIZE])).0; + let mut i_key_pad = Bytes::xor(&block_sized_key, &Bytes(vec![0x36; BLOCK_SIZE])).0; + + // hash(o_key_pad ∥ hash(i_key_pad ∥ message)) + i_key_pad.append(&mut message.0.clone()); + let mut inner_hashed = hash(i_key_pad); + o_key_pad.append(&mut inner_hashed); + Bytes(hash(o_key_pad)) +}