feat(filecrypt): per-area keys (BLAKE2s), per-file salt, XChaCha20-Poly1305 chunks bound to index+last, seed DRBG w/ fast key erasure
This commit is contained in:
130
crates/filecrypt/src/tests.rs
Normal file
130
crates/filecrypt/src/tests.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
extern crate std;
|
||||
|
||||
use crate::*;
|
||||
use std::vec;
|
||||
|
||||
fn master() -> Key {
|
||||
Key([7; KEY_SIZE])
|
||||
}
|
||||
|
||||
fn rng() -> Drbg {
|
||||
Drbg::new([1; KEY_SIZE])
|
||||
}
|
||||
|
||||
fn sealed(key: &Key, index: u64, last: bool, plain: &[u8]) -> std::vec::Vec<u8> {
|
||||
let mut out = vec![0u8; plain.len() + OVERHEAD];
|
||||
let n = seal(key, index, last, plain, &mut out, &mut rng());
|
||||
out.truncate(n);
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_chunk_opens_only_as_itself() {
|
||||
let key = Header::new(&mut rng()).file_key(&area_key(&master(), b"home"));
|
||||
let chunk = sealed(&key, 3, true, b"hello, k-os");
|
||||
let mut out = [0u8; PLAIN_CHUNK];
|
||||
assert_eq!(open(&key, 3, true, &chunk, &mut out), Some(11));
|
||||
assert_eq!(&out[..11], b"hello, k-os");
|
||||
assert_eq!(
|
||||
open(&key, 4, true, &chunk, &mut out),
|
||||
None,
|
||||
"moved elsewhere in the file"
|
||||
);
|
||||
assert_eq!(
|
||||
open(&key, 3, false, &chunk, &mut out),
|
||||
None,
|
||||
"no longer the last: something was cut off or added"
|
||||
);
|
||||
let other = Header::new(&mut Drbg::new([2; KEY_SIZE])).file_key(&area_key(&master(), b"home"));
|
||||
assert_eq!(
|
||||
open(&other, 3, true, &chunk, &mut out),
|
||||
None,
|
||||
"another file's key"
|
||||
);
|
||||
for at in [0, 23, 24, 30, chunk.len() - 1] {
|
||||
let mut bent = chunk.clone();
|
||||
bent[at] ^= 1;
|
||||
assert_eq!(
|
||||
open(&key, 3, true, &bent, &mut out),
|
||||
None,
|
||||
"byte {at} changed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_apps_area_key_opens_nothing_of_anothers() {
|
||||
let header = Header::new(&mut rng());
|
||||
let notes = header.file_key(&area_key(&master(), b"data/kos.notes"));
|
||||
let chat = header.file_key(&area_key(&master(), b"data/kos.chat"));
|
||||
let chunk = sealed(¬es, 0, true, b"private");
|
||||
let mut out = [0u8; 16];
|
||||
assert_eq!(open(&chat, 0, true, &chunk, &mut out), None);
|
||||
let other_master = header.file_key(&area_key(&Key([8; KEY_SIZE]), b"data/kos.notes"));
|
||||
assert_eq!(
|
||||
open(&other_master, 0, true, &chunk, &mut out),
|
||||
None,
|
||||
"another master key (a wiped device)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_seal_takes_a_fresh_nonce() {
|
||||
let key = Key([3; KEY_SIZE]);
|
||||
let mut r = rng();
|
||||
let (mut a, mut b) = ([0u8; 64], [0u8; 64]);
|
||||
seal(&key, 0, true, b"same", &mut a, &mut r);
|
||||
seal(&key, 0, true, b"same", &mut b, &mut r);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_mark_encrypted_files() {
|
||||
let h = Header::new(&mut rng());
|
||||
assert_eq!(Header::decode(&h.encode()), Some(h));
|
||||
assert_eq!(
|
||||
Header::decode(b"Welcome to k-os. This is a plaintext file."),
|
||||
None
|
||||
);
|
||||
assert_eq!(Header::decode(b"KOSFILE"), None, "too short");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sizes_round_trip_across_chunk_boundaries() {
|
||||
for plain in [
|
||||
0,
|
||||
1,
|
||||
100,
|
||||
PLAIN_CHUNK - 1,
|
||||
PLAIN_CHUNK,
|
||||
PLAIN_CHUNK + 1,
|
||||
3 * PLAIN_CHUNK,
|
||||
3 * PLAIN_CHUNK + 7,
|
||||
] {
|
||||
let disk = disk_len(plain);
|
||||
assert_eq!(plain_len(disk), Some(plain), "{plain}");
|
||||
}
|
||||
assert_eq!(chunks(0), 1, "an empty file still has its last chunk");
|
||||
for cut in [0, HEADER, HEADER + OVERHEAD - 1, HEADER + CHUNK + 5] {
|
||||
assert_eq!(plain_len(cut), None, "{cut} bytes is no encrypted file");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_drbg_never_repeats_and_depends_on_its_seed() {
|
||||
let mut a = Drbg::new([1; KEY_SIZE]);
|
||||
let (mut x, mut y) = ([0u8; 32], [0u8; 32]);
|
||||
a.fill(&mut x);
|
||||
a.fill(&mut y);
|
||||
assert_ne!(x, y);
|
||||
let mut b = Drbg::new([1; KEY_SIZE]);
|
||||
let mut z = [0u8; 32];
|
||||
b.fill(&mut z);
|
||||
assert_eq!(
|
||||
x, z,
|
||||
"the same seed, the same stream (so the seed must be fresh)"
|
||||
);
|
||||
let mut c = Drbg::new([2; KEY_SIZE]);
|
||||
c.fill(&mut z);
|
||||
assert_ne!(x, z);
|
||||
}
|
||||
Reference in New Issue
Block a user