extern crate std; use crate::*; use std::vec; use std::vec::Vec; /// Fast enough for tests; the real ones are `KdfParams::DEFAULT`. const FAST: KdfParams = KdfParams { memory_kib: 8, passes: 1, }; /// A deterministic stand-in for the hardware's randomness. struct Counter(u8); impl Random for Counter { fn fill(&mut self, buf: &mut [u8]) -> bool { for b in buf { self.0 = self.0.wrapping_mul(31).wrapping_add(7); *b = self.0; } true } } struct Broken; impl Random for Broken { fn fill(&mut self, _buf: &mut [u8]) -> bool { false } } fn memory() -> Vec { vec![Block::default(); FAST.memory_kib as usize] } fn setup(duress: Option<&[u8]>, seed: u8) -> (Record, Key) { Record::setup(b"1234", duress, 5, FAST, &mut memory(), &mut Counter(seed)).unwrap() } #[test] fn the_real_passcode_opens_the_master_key_and_nothing_else_does() { let (record, master) = setup(Some(b"0000"), 1); let mem = &mut memory(); assert_eq!(record.unlock(b"1234", mem), Outcome::Real(master)); assert_eq!(record.unlock(b"0000", mem), Outcome::Duress); for wrong in [&b"1235"[..], b"", b"12345", b"123"] { assert_eq!(record.unlock(wrong, mem), Outcome::Wrong, "{wrong:?}"); } } #[test] fn without_a_duress_passcode_its_slot_opens_for_nothing() { let (record, master) = setup(None, 2); let mem = &mut memory(); assert_eq!(record.unlock(b"1234", mem), Outcome::Real(master)); assert_eq!(record.unlock(b"0000", mem), Outcome::Wrong); assert_eq!( record.unlock(b"", mem), Outcome::Wrong, "not even an empty one" ); } #[test] fn which_slot_is_real_is_not_fixed() { // Different randomness puts the real slot first in some setups, second // in others: its position gives nothing away. let mem = &mut memory(); let mut firsts = [false; 2]; for seed in 0..16 { let (record, _) = setup(Some(b"0000"), seed); let only_first = Record { slots: [record.slots[0], Slot(record.slots[1].0.map(|b| !b))], ..record }; firsts[matches!(only_first.unlock(b"1234", mem), Outcome::Real(_)) as usize] = true; } assert_eq!(firsts, [true, true]); } #[test] fn records_round_trip_and_damage_is_detected() { let (record, _) = setup(Some(b"0000"), 3); let bytes = record.encode(); assert_eq!(Record::decode(&bytes), Some(record)); for at in [0, 9, 18, 40, 200] { let mut torn = bytes; torn[at] ^= 1; assert_eq!(Record::decode(&torn), None, "byte {at}"); } assert_eq!( Record::decode(&[0; RECORD_SIZE]), None, "a blank device holds no record" ); } #[test] fn the_newest_whole_copy_wins_and_writes_go_to_the_other() { let (old, _) = setup(None, 4); let new = old.next(|r| r.failures = 1); let (a, b) = (old.encode(), new.encode()); let copies = Copies { copies: [&a, &b] }; assert_eq!(copies.newest(), Some((new, 1))); assert_eq!(copies.next_copy(), 0, "the older copy is overwritten"); // A torn write of the newer copy leaves the older one. let mut torn = b; torn[100] ^= 0xFF; let copies = Copies { copies: [&a, &torn], }; assert_eq!(copies.newest(), Some((old, 0))); assert_eq!(copies.next_copy(), 1); // Newest wins wherever it is. let copies = Copies { copies: [&b, &a] }; assert_eq!(copies.newest(), Some((new, 0))); let blank = [0u8; RECORD_SIZE]; assert_eq!( Copies { copies: [&blank, &blank] } .newest(), None ); } #[test] fn wiping_leaves_nothing_any_passcode_opens() { let (record, _) = setup(Some(b"0000"), 5); let wiped = record.wiping(&mut Counter(99)).unwrap(); assert_eq!(wiped.state, State::Wiping); assert!( wiped.must_wipe(), "an interrupted wipe is finished next boot" ); assert!( wiped.generation > record.generation, "it is the newest copy" ); let mem = &mut memory(); for p in [&b"1234"[..], b"0000"] { assert_eq!(wiped.unlock(p, mem), Outcome::Wrong); } } #[test] fn too_many_failures_mean_a_wipe() { let (record, _) = setup(None, 6); assert!(!record.must_wipe()); let worn = record.next(|r| r.failures = r.threshold); assert!(worn.must_wipe()); } #[test] fn no_randomness_means_no_keystore() { assert!(Record::setup(b"1234", None, 5, FAST, &mut memory(), &mut Broken).is_none()); } #[test] fn the_default_parameters_are_accepted_by_argon2() { assert!( argon2::Params::new( KdfParams::DEFAULT.memory_kib, KdfParams::DEFAULT.passes, 1, Some(KEY_SIZE) ) .is_ok() ); }