Files
k-os/crates/keystore/src/tests.rs

173 lines
4.7 KiB
Rust

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<Block> {
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()
);
}