Skip to content

Commit 1d6ec8f

Browse files
committed
tests!
1 parent 038b0ff commit 1d6ec8f

File tree

3 files changed

+231
-6
lines changed

3 files changed

+231
-6
lines changed

Cargo.lock

+75
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ typenum = { version = "1.14" }
2121
chacha20 = { version = "0.9.0", features = [ "zeroize" ] }
2222
zeroize = { version = "1.1.1", features = [ "zeroize_derive" ] }
2323
rpassword = "5.0"
24+
quickcheck = "1.0.3"
2425
rand = "0.8.5"
2526
conv = "0.3.3"
2627
generic-array = "0.14.4"
@@ -29,3 +30,6 @@ rand_chacha = { version = "0.3.1" }
2930
subtle = { version = "2.4.1" }
3031
digest = { version = "0.10.6" }
3132

33+
[dev-dependencies]
34+
quickcheck_macros = "*"
35+

src/main.rs

+152-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#![deny(warnings)]
22

3+
#[cfg(test)]
4+
extern crate quickcheck_macros;
5+
36
use core::cmp::{min,max};
47
use secrecy::{Secret,SecretString,ExposeSecret};
58
use std::path::PathBuf;
@@ -203,8 +206,7 @@ where
203206
});
204207

205208
let max_pad_length = <u64 as ApproxFrom<f32>>::approx_from(max_pad * max(64, bytes_written) as f32)?;
206-
207-
assert!(max_pad_length > 0);
209+
let max_pad_length = max(max_pad_length,1);
208210

209211
// I'm not worried about the tiny bias here
210212
let pad_len = prng.next_u64() % max_pad_length;
@@ -482,6 +484,150 @@ where
482484
Ok(())
483485
}
484486

487+
#[cfg(test)]
488+
mod test {
489+
use super::*;
490+
use quickcheck::Arbitrary;
491+
use quickcheck_macros::quickcheck;
492+
493+
#[derive(Debug,Clone)]
494+
struct ScrombleFile {
495+
pw: String,
496+
max_pad_factor: Option<f32>,
497+
plaintext: Vec<u8>,
498+
}
499+
500+
impl Arbitrary for ScrombleFile {
501+
fn arbitrary(g: &mut quickcheck::Gen) -> Self {
502+
let pw = String::arbitrary(g).into();
503+
let max_pad_factor = if bool::arbitrary(g) {
504+
Some((u16::arbitrary(g) as f32)/(u16::MAX as f32))
505+
} else {
506+
None
507+
};
508+
let plaintext: Vec<u8> = <_>::arbitrary(g);
509+
Self {
510+
pw,
511+
max_pad_factor,
512+
plaintext,
513+
}
514+
}
515+
516+
fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
517+
Box::new(self.plaintext.shrink()
518+
.map({ let zelf = self.clone();
519+
move |plaintext|
520+
Self {
521+
pw: zelf.pw.clone(),
522+
max_pad_factor: zelf.max_pad_factor.clone(),
523+
plaintext } })
524+
.chain(self.max_pad_factor.shrink()
525+
.map({ let zelf = self.clone();
526+
move |max_pad_factor|
527+
Self {
528+
pw: zelf.pw.clone(),
529+
max_pad_factor,
530+
plaintext: zelf.plaintext.clone() } }))
531+
.chain(self.pw.shrink()
532+
.map({ let zelf = self.clone();
533+
move |pw|
534+
Self { pw,
535+
max_pad_factor: zelf.max_pad_factor.clone(),
536+
plaintext: zelf.plaintext.clone() } })))
537+
}
538+
}
539+
540+
#[quickcheck]
541+
fn enc_dec_loop(file: ScrombleFile) {
542+
let mut enc_file = vec![];
543+
scromble(Passphrase(file.pw.clone().into()), file.max_pad_factor, Box::new(&file.plaintext[..]), Box::new(&mut enc_file)).unwrap();
544+
let enc_file_len = enc_file.len() as f32;
545+
let max_pad_factor = file.max_pad_factor.unwrap_or(1.0f32);
546+
let enc_file_len_limit = (1f32 + max_pad_factor)*(file.plaintext.len() as f32) + (4*64 + 8) as f32;
547+
assert!(enc_file_len <= enc_file_len_limit,"{} <= {}",enc_file_len,enc_file_len_limit);
548+
let mut dec_file = vec![];
549+
descromble(Passphrase(file.pw.into()), Box::new(std::io::Cursor::new(&enc_file)), Box::new(&mut dec_file)).unwrap();
550+
assert_eq!(file.plaintext,dec_file);
551+
}
552+
553+
#[quickcheck]
554+
fn decrypt_authentic(corruption_mask: (u8,Vec<u8>), corruption_pos: u16, file: ScrombleFile) {
555+
let mut enc_file = vec![];
556+
scromble(Passphrase(file.pw.clone().into()), file.max_pad_factor, Box::new(&file.plaintext[..]), Box::new(&mut enc_file)).unwrap();
557+
558+
let corruption_pos = (corruption_pos as usize) % enc_file.len();
559+
for (i,x) in core::iter::once(corruption_mask.0).chain((corruption_mask.1).into_iter()).enumerate() {
560+
let ix = corruption_pos+i;
561+
if ix >= enc_file.len() { break; }
562+
enc_file[corruption_pos+i] ^= x;
563+
}
564+
565+
let mut dec_file = vec![];
566+
descromble(Passphrase(file.pw.into()), Box::new(std::io::Cursor::new(&enc_file)), Box::new(&mut dec_file)).unwrap_err();
567+
}
568+
569+
#[quickcheck]
570+
fn decrypt_wrong_pw(wrong_pw: String, file: ScrombleFile) {
571+
let mut enc_file = vec![];
572+
scromble(Passphrase(file.pw.clone().into()), file.max_pad_factor, Box::new(&file.plaintext[..]), Box::new(&mut enc_file)).unwrap();
573+
574+
let mut dec_file = vec![];
575+
descromble(Passphrase(wrong_pw.into()), Box::new(std::io::Cursor::new(&enc_file)), Box::new(&mut dec_file)).unwrap_err();
576+
}
577+
578+
// TODO: come up with better statistical tests
579+
#[quickcheck]
580+
fn encrypt_uncorrelated(file: ScrombleFile) {
581+
let mut enc_file1 = vec![];
582+
scromble(Passphrase(file.pw.clone().into()), file.max_pad_factor, Box::new(&file.plaintext[..]), Box::new(&mut enc_file1)).unwrap();
583+
let mut enc_file2 = vec![];
584+
scromble(Passphrase(file.pw.clone().into()), file.max_pad_factor, Box::new(&file.plaintext[..]), Box::new(&mut enc_file2)).unwrap();
585+
586+
assert!(enc_file1.len() < u32::MAX as usize);
587+
588+
let mut hist1 = [0usize; 256];
589+
let mut hist1_samples = 0usize;
590+
let mut hist2 = [0usize; 256];
591+
let mut hist2_samples = 0usize;
592+
let mut hist_diff = [0usize; 256];
593+
let mut hist_diff_samples = 0usize;
594+
595+
for i in 0..max(enc_file1.len(),enc_file2.len()) {
596+
if i < enc_file1.len() {
597+
if i < enc_file2.len() {
598+
hist_diff_samples += 1;
599+
hist_diff[enc_file1[i].wrapping_sub(enc_file2[i]) as usize] += 1;
600+
}
601+
602+
hist1_samples += 1;
603+
hist1[enc_file1[i] as usize] += 1;
604+
}
605+
606+
if i < enc_file2.len() {
607+
hist2_samples += 1;
608+
hist2[enc_file2[i] as usize] += 1;
609+
}
610+
}
611+
612+
let hists = vec![(hist1_samples, hist1), (hist2_samples, hist2),
613+
(hist_diff_samples, hist_diff)];
614+
615+
for (i,(n,hist)) in hists.into_iter().enumerate() {
616+
assert!(n > 0);
617+
let mut ent = 0f64;
618+
for c in hist {
619+
let p = (c as f64)/(n as f64);
620+
let lg_p = p.log2();
621+
if lg_p.is_finite() {
622+
ent += -(p*lg_p/8f64);
623+
}
624+
}
625+
626+
assert!(ent > 0.85, "{}: ent = {}", i, ent);
627+
}
628+
}
629+
}
630+
485631
#[derive(Debug, StructOpt)]
486632
#[structopt(name = "scromble",
487633
about = concat!(
@@ -577,10 +723,10 @@ impl fmt::Debug for ScrombleError {
577723

578724
impl std::error::Error for ScrombleError {}
579725

580-
#[test]
581-
fn all_sizes_agree() {
582-
assert_eq!(blake2b_simd::OUTBYTES, chacha20::BLOCK_SIZE);
583-
}
726+
// #[test]
727+
// fn all_sizes_agree() {
728+
// assert_eq!(blake2b_simd::OUTBYTES, chacha20::BLOCK_SIZE);
729+
// }
584730

585731
fn main() -> Result<(), Box<dyn std::error::Error>> {
586732
let args = Command::from_args();

0 commit comments

Comments
 (0)