scuttle/crates/scuttle-methods/src/lib.rs

305 lines
10 KiB
Rust
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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 FisherYates 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);
}
}
}