|
1 | 1 | #![deny(warnings)]
|
2 | 2 |
|
| 3 | +#[cfg(test)] |
| 4 | +extern crate quickcheck_macros; |
| 5 | + |
3 | 6 | use core::cmp::{min,max};
|
4 | 7 | use secrecy::{Secret,SecretString,ExposeSecret};
|
5 | 8 | use std::path::PathBuf;
|
@@ -203,8 +206,7 @@ where
|
203 | 206 | });
|
204 | 207 |
|
205 | 208 | 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); |
208 | 210 |
|
209 | 211 | // I'm not worried about the tiny bias here
|
210 | 212 | let pad_len = prng.next_u64() % max_pad_length;
|
@@ -482,6 +484,150 @@ where
|
482 | 484 | Ok(())
|
483 | 485 | }
|
484 | 486 |
|
| 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 | + |
485 | 631 | #[derive(Debug, StructOpt)]
|
486 | 632 | #[structopt(name = "scromble",
|
487 | 633 | about = concat!(
|
@@ -577,10 +723,10 @@ impl fmt::Debug for ScrombleError {
|
577 | 723 |
|
578 | 724 | impl std::error::Error for ScrombleError {}
|
579 | 725 |
|
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 | +// } |
584 | 730 |
|
585 | 731 | fn main() -> Result<(), Box<dyn std::error::Error>> {
|
586 | 732 | let args = Command::from_args();
|
|
0 commit comments