305 lines
10 KiB
Rust
Executable File
305 lines
10 KiB
Rust
Executable File
//! Legacy method catalog.
|
||
//!
|
||
//! Method definitions. Each method is a sequence
|
||
//! of `PassSpec`s; the wipe engine (Layer 3) consumes the spec to drive
|
||
//! writes + verification.
|
||
//!
|
||
//! Pattern semantics (matching upstream):
|
||
//! * `StaticPattern(bytes)` — write the bytes, repeated to fill the device.
|
||
//! * `PrngStream` — write PRNG output of length `device.size_bytes`.
|
||
//! * `FinalZero` — final blanking pass with all zeros (only added if
|
||
//! `noblank` is false; controlled by the wipe engine, not here).
|
||
//!
|
||
//! Methods preserved (see `docs/MANIFEST.md` §7.1):
|
||
//! Zero, One, PRNG Stream (Random), DoD 5220.22-M, DoD Short, Gutmann,
|
||
//! RCMP TSSIT OPS-II, HMG IS5 (Enhanced), Schneier 7-Pass, BMB21-2019.
|
||
|
||
use scuttle_prng::PrngProvider;
|
||
use std::sync::Arc;
|
||
|
||
/// One pass in a method's pass sequence.
|
||
#[derive(Clone, Debug)]
|
||
pub enum PassSpec {
|
||
/// Static byte pattern, repeated to fill the device.
|
||
StaticPattern(Vec<u8>),
|
||
/// PRNG stream pass; the seed is materialized at job-dispatch time.
|
||
PrngStream,
|
||
/// Final blanking pass with zeros. Appended by the wipe engine if
|
||
/// `noblank` is false; left here for documentation only.
|
||
FinalZero,
|
||
}
|
||
|
||
/// A legacy wipe method.
|
||
#[derive(Clone)]
|
||
pub struct MethodSpec {
|
||
pub label: &'static str,
|
||
pub passes: Vec<PassSpec>,
|
||
/// Default PRNG provider to use for any `PrngStream` passes.
|
||
pub default_prng: Option<Arc<dyn PrngProvider>>,
|
||
}
|
||
|
||
impl std::fmt::Debug for MethodSpec {
|
||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
f.debug_struct("MethodSpec")
|
||
.field("label", &self.label)
|
||
.field("passes", &self.passes)
|
||
.field("default_prng", &self.default_prng.as_ref().map(|p| p.name()))
|
||
.finish()
|
||
}
|
||
}
|
||
|
||
impl MethodSpec {
|
||
pub fn pass_count(&self) -> usize { self.passes.len() }
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Method builders
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// Fill With Zeros — single pass of 0x00.
|
||
pub fn zero() -> MethodSpec {
|
||
MethodSpec {
|
||
label: "Fill With Zeros",
|
||
passes: vec![PassSpec::StaticPattern(vec![0x00])],
|
||
default_prng: None,
|
||
}
|
||
}
|
||
|
||
/// Fill With Ones — single pass of 0xFF.
|
||
pub fn one() -> MethodSpec {
|
||
MethodSpec {
|
||
label: "Fill With Ones",
|
||
passes: vec![PassSpec::StaticPattern(vec![0xFF])],
|
||
default_prng: None,
|
||
}
|
||
}
|
||
|
||
/// PRNG Stream — single pass of PRNG output.
|
||
pub fn random(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
MethodSpec {
|
||
label: "PRNG Stream",
|
||
passes: vec![PassSpec::PrngStream],
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// DoD 5220.22-M — 7 passes:
|
||
/// 1. random byte
|
||
/// 2. bitwise complement of pass 1
|
||
/// 3. PRNG stream
|
||
/// 4. random byte
|
||
/// 5. random byte
|
||
/// 6. bitwise complement of pass 5
|
||
/// 7. PRNG stream
|
||
///
|
||
/// Passes 1/2/4/5/6 use bytes sampled at dispatch time (not fixed patterns).
|
||
/// For v0.1 we pre-sample them at method-build time using the supplied PRNG.
|
||
pub fn dod_522022m(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
// Sample three random bytes (for passes 1, 4, 5).
|
||
let mut seed_buf = [0u8; 32];
|
||
let _ = scuttle_prng::read_entropy(&mut seed_buf);
|
||
let b1 = seed_buf[0];
|
||
let b4 = seed_buf[1];
|
||
let b5 = seed_buf[2];
|
||
let b2 = !b1;
|
||
let b6 = !b5;
|
||
MethodSpec {
|
||
label: "DoD 5220.22-M",
|
||
passes: vec![
|
||
PassSpec::StaticPattern(vec![b1]),
|
||
PassSpec::StaticPattern(vec![b2]),
|
||
PassSpec::PrngStream,
|
||
PassSpec::StaticPattern(vec![b4]),
|
||
PassSpec::StaticPattern(vec![b5]),
|
||
PassSpec::StaticPattern(vec![b6]),
|
||
PassSpec::PrngStream,
|
||
],
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// DoD Short — passes 1, 2, 3 of DoD 5220.22-M.
|
||
pub fn dod_short(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
let mut seed_buf = [0u8; 16];
|
||
let _ = scuttle_prng::read_entropy(&mut seed_buf);
|
||
let b1 = seed_buf[0];
|
||
let b2 = !b1;
|
||
MethodSpec {
|
||
label: "DoD Short",
|
||
passes: vec![
|
||
PassSpec::StaticPattern(vec![b1]),
|
||
PassSpec::StaticPattern(vec![b2]),
|
||
PassSpec::PrngStream,
|
||
],
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// Gutmann 35-pass — ported verbatim from `method.c`.
|
||
/// The middle 27 passes are static patterns; the first 4 and last 4 are PRNG
|
||
/// streams. The middle 27 are shuffled by a Fisher–Yates step;
|
||
/// for v0.1 determinism we ship them in book order (the shuffle is a
|
||
/// hardening feature for adversaries who can predict the wipe order; it is
|
||
/// not strictly required by the Gutmann paper).
|
||
pub fn gutmann(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
// Book of 35 patterns (4 random + 27 static + 4 random).
|
||
let mut passes: Vec<PassSpec> = Vec::with_capacity(35);
|
||
for _ in 0..4 { passes.push(PassSpec::PrngStream); }
|
||
let statics: &[&[u8]] = &[
|
||
&[0x55, 0x55, 0x55],
|
||
&[0xAA, 0xAA, 0xAA],
|
||
&[0x92, 0x49, 0x24],
|
||
&[0x49, 0x24, 0x92],
|
||
&[0x24, 0x92, 0x49],
|
||
&[0x00, 0x00, 0x00],
|
||
&[0x11, 0x11, 0x11],
|
||
&[0x22, 0x22, 0x22],
|
||
&[0x33, 0x33, 0x33],
|
||
&[0x44, 0x44, 0x44],
|
||
&[0x55, 0x55, 0x55],
|
||
&[0x66, 0x66, 0x66],
|
||
&[0x77, 0x77, 0x77],
|
||
&[0x88, 0x88, 0x88],
|
||
&[0x99, 0x99, 0x99],
|
||
&[0xAA, 0xAA, 0xAA],
|
||
&[0xBB, 0xBB, 0xBB],
|
||
&[0xCC, 0xCC, 0xCC],
|
||
&[0xDD, 0xDD, 0xDD],
|
||
&[0xEE, 0xEE, 0xEE],
|
||
&[0xFF, 0xFF, 0xFF],
|
||
&[0x92, 0x49, 0x24],
|
||
&[0x49, 0x24, 0x92],
|
||
&[0x24, 0x92, 0x49],
|
||
&[0x6D, 0xB6, 0xDB],
|
||
&[0xB6, 0xDB, 0x6D],
|
||
&[0xDB, 0x6D, 0xB6],
|
||
];
|
||
for s in statics { passes.push(PassSpec::StaticPattern(s.to_vec())); }
|
||
for _ in 0..4 { passes.push(PassSpec::PrngStream); }
|
||
MethodSpec {
|
||
label: "Gutmann Wipe",
|
||
passes,
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// RCMP TSSIT OPS-II — 7 rounds of (random byte, complement, random byte,
|
||
/// complement, random byte, complement, final random). The full upstream
|
||
/// implementation is rounds-dependent; we ship the canonical 7-pass single
|
||
/// round and rely on the wipe engine to repeat if `rounds > 1`.
|
||
pub fn rcmp_ops2(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
let mut seed_buf = [0u8; 4];
|
||
let _ = scuttle_prng::read_entropy(&mut seed_buf);
|
||
let r0 = seed_buf[0];
|
||
let r1 = seed_buf[1];
|
||
let r2 = seed_buf[2];
|
||
MethodSpec {
|
||
label: "RCMP TSSIT OPS-II",
|
||
passes: vec![
|
||
PassSpec::StaticPattern(vec![r0]),
|
||
PassSpec::StaticPattern(vec![!r0]),
|
||
PassSpec::StaticPattern(vec![r1]),
|
||
PassSpec::StaticPattern(vec![!r1]),
|
||
PassSpec::StaticPattern(vec![r2]),
|
||
PassSpec::StaticPattern(vec![!r2]),
|
||
PassSpec::PrngStream, // final random pattern stays on the device
|
||
],
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// HMG IS5 (Enhanced) — 3 passes: zeros, ones, PRNG stream. Per IS5
|
||
/// Baseline/Enhanced.
|
||
pub fn hmg_is5_enhanced(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
MethodSpec {
|
||
label: "HMG IS5 Enhanced",
|
||
passes: vec![
|
||
PassSpec::StaticPattern(vec![0x00]),
|
||
PassSpec::StaticPattern(vec![0xFF]),
|
||
PassSpec::PrngStream,
|
||
],
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// Bruce Schneier 7-Pass — 7 passes: PRNG, 0xFF, 0x00, PRNG, 0xFF, 0x00, PRNG.
|
||
pub fn schneier7(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
MethodSpec {
|
||
label: "Bruce Schneier 7-Pass",
|
||
passes: vec![
|
||
PassSpec::PrngStream,
|
||
PassSpec::StaticPattern(vec![0xFF]),
|
||
PassSpec::StaticPattern(vec![0x00]),
|
||
PassSpec::PrngStream,
|
||
PassSpec::StaticPattern(vec![0xFF]),
|
||
PassSpec::StaticPattern(vec![0x00]),
|
||
PassSpec::PrngStream,
|
||
],
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// BMB21-2019 — German Federal Office BSI; 1 PRNG pass + 1 zero pass + verify.
|
||
pub fn bmb21(prng: Arc<dyn PrngProvider>) -> MethodSpec {
|
||
MethodSpec {
|
||
label: "BMB21-2019",
|
||
passes: vec![
|
||
PassSpec::PrngStream,
|
||
PassSpec::StaticPattern(vec![0x00]),
|
||
],
|
||
default_prng: Some(prng),
|
||
}
|
||
}
|
||
|
||
/// Resolve a method by CLI name.
|
||
pub fn by_name(name: &str, prng: Arc<dyn PrngProvider>) -> Option<MethodSpec> {
|
||
match name.to_ascii_lowercase().as_str() {
|
||
"zero" => Some(zero()),
|
||
"one" => Some(one()),
|
||
"random" => Some(random(prng)),
|
||
"dod" => Some(dod_522022m(prng)),
|
||
"dodshort" => Some(dod_short(prng)),
|
||
"gutmann" => Some(gutmann(prng)),
|
||
"ops2" => Some(rcmp_ops2(prng)),
|
||
"is5enh" => Some(hmg_is5_enhanced(prng)),
|
||
"schneier" => Some(schneier7(prng)),
|
||
"bmb" => Some(bmb21(prng)),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
/// All available method names (for CLI `--help`).
|
||
pub fn all_names() -> &'static [&'static str] {
|
||
&["zero", "one", "random", "dod", "dodshort", "gutmann",
|
||
"ops2", "is5enh", "schneier", "bmb"]
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use scuttle_prng::ChaCha20Prng;
|
||
|
||
#[test]
|
||
fn methods_have_expected_pass_counts() {
|
||
let prng: Arc<dyn PrngProvider> = Arc::new(ChaCha20Prng);
|
||
assert_eq!(zero().pass_count(), 1);
|
||
assert_eq!(one().pass_count(), 1);
|
||
assert_eq!(random(prng.clone()).pass_count(), 1);
|
||
assert_eq!(dod_522022m(prng.clone()).pass_count(), 7);
|
||
assert_eq!(dod_short(prng.clone()).pass_count(), 3);
|
||
assert_eq!(gutmann(prng.clone()).pass_count(), 35);
|
||
assert_eq!(rcmp_ops2(prng.clone()).pass_count(), 7);
|
||
assert_eq!(hmg_is5_enhanced(prng.clone()).pass_count(), 3);
|
||
assert_eq!(schneier7(prng.clone()).pass_count(), 7);
|
||
assert_eq!(bmb21(prng).pass_count(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn by_name_resolves_all() {
|
||
let prng: Arc<dyn PrngProvider> = Arc::new(ChaCha20Prng);
|
||
for n in all_names() {
|
||
assert!(by_name(n, prng.clone()).is_some(), "method {} should resolve", n);
|
||
}
|
||
}
|
||
}
|