//! 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), /// 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, /// Default PRNG provider to use for any `PrngStream` passes. pub default_prng: Option>, } 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) -> 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) -> 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) -> 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) -> MethodSpec { // Book of 35 patterns (4 random + 27 static + 4 random). let mut passes: Vec = 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) -> 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) -> 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) -> 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) -> 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) -> Option { 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 = 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 = Arc::new(ChaCha20Prng); for n in all_names() { assert!(by_name(n, prng.clone()).is_some(), "method {} should resolve", n); } } }