#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use crate::FsError;
use crate::crypto::random::CryptoRng;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WipeMethod {
Dod3Pass,
Dod7Pass,
Gutmann,
CryptoErase,
AtaSecureErase,
AtaEnhancedSecureErase,
NvmeFormat,
NvmeSanitizeBlock,
NvmeSanitizeCrypto,
QuickRandom,
NistClear,
NistPurge,
}
impl WipeMethod {
pub fn pass_count(&self) -> Option<usize> {
match self {
Self::Dod3Pass => Some(3),
Self::Dod7Pass => Some(7),
Self::Gutmann => Some(35),
Self::CryptoErase => Some(0),
Self::AtaSecureErase => None,
Self::AtaEnhancedSecureErase => None,
Self::NvmeFormat => None,
Self::NvmeSanitizeBlock => None,
Self::NvmeSanitizeCrypto => None,
Self::QuickRandom => Some(1),
Self::NistClear => Some(1),
Self::NistPurge => Some(3),
}
}
pub fn requires_hardware(&self) -> bool {
matches!(
self,
Self::AtaSecureErase
| Self::AtaEnhancedSecureErase
| Self::NvmeFormat
| Self::NvmeSanitizeBlock
| Self::NvmeSanitizeCrypto
)
}
pub fn suitable_for_ssd(&self) -> bool {
matches!(
self,
Self::CryptoErase
| Self::AtaSecureErase
| Self::AtaEnhancedSecureErase
| Self::NvmeFormat
| Self::NvmeSanitizeBlock
| Self::NvmeSanitizeCrypto
)
}
pub fn description(&self) -> &'static str {
match self {
Self::Dod3Pass => "DoD 5220.22-M 3-pass (zeros, ones, random)",
Self::Dod7Pass => "DoD 5220.22-M 7-pass extended",
Self::Gutmann => "Gutmann 35-pass maximum security",
Self::CryptoErase => "Cryptographic key destruction",
Self::AtaSecureErase => "ATA Secure Erase command",
Self::AtaEnhancedSecureErase => "ATA Enhanced Secure Erase",
Self::NvmeFormat => "NVMe Format with secure erase",
Self::NvmeSanitizeBlock => "NVMe Sanitize block erase",
Self::NvmeSanitizeCrypto => "NVMe Sanitize crypto erase",
Self::QuickRandom => "Quick single random pass",
Self::NistClear => "NIST SP 800-88 Clear",
Self::NistPurge => "NIST SP 800-88 Purge",
}
}
}
pub type EraseProgressCallback = fn(progress: &EraseProgress);
#[derive(Debug, Clone)]
pub struct EraseProgress {
pub current_pass: usize,
pub total_passes: usize,
pub bytes_completed: u64,
pub bytes_total: u64,
pub eta_seconds: Option<u64>,
pub operation: String,
pub cancellable: bool,
}
impl EraseProgress {
pub fn percentage(&self) -> f64 {
if self.bytes_total == 0 {
return 100.0;
}
let pass_progress = self.bytes_completed as f64 / self.bytes_total as f64;
let pass_weight = 1.0 / self.total_passes as f64;
let completed_passes = (self.current_pass - 1) as f64 * pass_weight;
(completed_passes + pass_progress * pass_weight) * 100.0
}
}
pub struct SecureEraser {
rng: Option<CryptoRng>,
buffer_size: usize,
verify_passes: bool,
progress_callback: Option<EraseProgressCallback>,
cancelled: AtomicBool,
bytes_processed: AtomicU64,
}
impl SecureEraser {
pub fn new() -> Self {
Self {
rng: CryptoRng::new().ok(),
buffer_size: 1024 * 1024, verify_passes: false,
progress_callback: None,
cancelled: AtomicBool::new(false),
bytes_processed: AtomicU64::new(0),
}
}
fn get_rng(&mut self) -> Result<&mut CryptoRng, FsError> {
if self.rng.is_none() {
self.rng = CryptoRng::new().ok();
}
self.rng.as_mut().ok_or(FsError::InvalidArgument {
reason: "Random number generator unavailable",
})
}
pub fn with_buffer_size(mut self, size: usize) -> Self {
self.buffer_size = size.max(4096); self
}
pub fn with_verification(mut self, verify: bool) -> Self {
self.verify_passes = verify;
self
}
pub fn with_progress_callback(mut self, callback: EraseProgressCallback) -> Self {
self.progress_callback = Some(callback);
self
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::SeqCst);
}
fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
fn reset(&self) {
self.cancelled.store(false, Ordering::SeqCst);
self.bytes_processed.store(0, Ordering::SeqCst);
}
fn report_progress(&self, progress: EraseProgress) {
if let Some(callback) = self.progress_callback {
callback(&progress);
}
}
pub fn erase_buffer(&mut self, data: &mut [u8], method: WipeMethod) -> Result<(), FsError> {
if method.requires_hardware() {
return Err(FsError::InvalidArgument {
reason: "Hardware erase method not applicable to memory buffer",
});
}
self.reset();
match method {
WipeMethod::Dod3Pass => self.dod_3_pass(data),
WipeMethod::Dod7Pass => self.dod_7_pass(data),
WipeMethod::Gutmann => self.gutmann_35_pass(data),
WipeMethod::QuickRandom => self.random_pass(data),
WipeMethod::NistClear => self.nist_clear(data),
WipeMethod::NistPurge => self.nist_purge(data),
WipeMethod::CryptoErase => {
self.random_pass(data)
}
_ => Err(FsError::InvalidArgument {
reason: "Unsupported erase method for buffer",
}),
}
}
fn dod_3_pass(&mut self, data: &mut [u8]) -> Result<(), FsError> {
self.overwrite_pattern(data, 0x00, 1, 3, "Writing zeros")?;
self.overwrite_pattern(data, 0xFF, 2, 3, "Writing ones")?;
self.overwrite_random(data, 3, 3, "Writing random data")?;
Ok(())
}
fn dod_7_pass(&mut self, data: &mut [u8]) -> Result<(), FsError> {
self.overwrite_pattern(data, 0x00, 1, 7, "Pass 1: zeros")?;
self.overwrite_pattern(data, 0xFF, 2, 7, "Pass 2: ones")?;
self.overwrite_random(data, 3, 7, "Pass 3: random")?;
self.overwrite_pattern(data, 0x00, 4, 7, "Pass 4: zeros")?;
self.overwrite_pattern(data, 0xFF, 5, 7, "Pass 5: ones")?;
self.overwrite_random(data, 6, 7, "Pass 6: random")?;
self.overwrite_random(data, 7, 7, "Pass 7: random verify")?;
Ok(())
}
fn gutmann_35_pass(&mut self, data: &mut [u8]) -> Result<(), FsError> {
let patterns: [(u8, u8, u8); 27] = [
(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 pass in 1..=4 {
self.overwrite_random(data, pass, 35, &alloc::format!("Pass {}: random", pass))?;
}
for (i, pattern) in patterns.iter().enumerate() {
let pass = i + 5;
self.overwrite_3byte_pattern(
data,
*pattern,
pass,
35,
&alloc::format!("Pass {}: pattern", pass),
)?;
}
for pass in 32..=35 {
self.overwrite_random(data, pass, 35, &alloc::format!("Pass {}: random", pass))?;
}
Ok(())
}
fn random_pass(&mut self, data: &mut [u8]) -> Result<(), FsError> {
self.overwrite_random(data, 1, 1, "Writing random data")
}
fn nist_clear(&mut self, data: &mut [u8]) -> Result<(), FsError> {
self.overwrite_pattern(data, 0x00, 1, 1, "NIST Clear: zeros")
}
fn nist_purge(&mut self, data: &mut [u8]) -> Result<(), FsError> {
self.overwrite_pattern(data, 0x00, 1, 3, "NIST Purge: zeros")?;
self.overwrite_pattern(data, 0xFF, 2, 3, "NIST Purge: ones")?;
self.overwrite_random(data, 3, 3, "NIST Purge: random")?;
Ok(())
}
fn overwrite_pattern(
&self,
data: &mut [u8],
pattern: u8,
current_pass: usize,
total_passes: usize,
operation: &str,
) -> Result<(), FsError> {
if self.is_cancelled() {
return Err(FsError::ResourceBusy);
}
data.fill(pattern);
self.report_progress(EraseProgress {
current_pass,
total_passes,
bytes_completed: data.len() as u64,
bytes_total: data.len() as u64,
eta_seconds: None,
operation: String::from(operation),
cancellable: true,
});
if self.verify_passes {
for &byte in data.iter() {
if byte != pattern {
return Err(FsError::Corruption {
block: 0,
details: "Verification failed after overwrite",
});
}
}
}
Ok(())
}
fn overwrite_3byte_pattern(
&mut self,
data: &mut [u8],
pattern: (u8, u8, u8),
current_pass: usize,
total_passes: usize,
operation: &str,
) -> Result<(), FsError> {
if self.is_cancelled() {
return Err(FsError::ResourceBusy);
}
for (i, byte) in data.iter_mut().enumerate() {
*byte = match i % 3 {
0 => pattern.0,
1 => pattern.1,
_ => pattern.2,
};
}
self.report_progress(EraseProgress {
current_pass,
total_passes,
bytes_completed: data.len() as u64,
bytes_total: data.len() as u64,
eta_seconds: None,
operation: String::from(operation),
cancellable: true,
});
Ok(())
}
fn overwrite_random(
&mut self,
data: &mut [u8],
current_pass: usize,
total_passes: usize,
operation: &str,
) -> Result<(), FsError> {
if self.is_cancelled() {
return Err(FsError::ResourceBusy);
}
let rng = self.get_rng()?;
rng.fill_bytes(data).map_err(|_| FsError::InvalidArgument {
reason: "Failed to generate random data",
})?;
self.report_progress(EraseProgress {
current_pass,
total_passes,
bytes_completed: data.len() as u64,
bytes_total: data.len() as u64,
eta_seconds: None,
operation: String::from(operation),
cancellable: true,
});
Ok(())
}
}
impl Default for SecureEraser {
fn default() -> Self {
Self::new()
}
}
pub struct CryptoEraser;
impl CryptoEraser {
pub fn destroy_key(key: &mut [u8]) {
if let Ok(mut rng) = CryptoRng::new() {
let _ = rng.fill_bytes(key);
} else {
key.fill(0x55);
}
core::sync::atomic::compiler_fence(Ordering::SeqCst);
key.fill(0x00);
core::sync::atomic::compiler_fence(Ordering::SeqCst);
}
pub fn destroy_keys(keys: &mut [&mut [u8]]) {
for key in keys.iter_mut() {
Self::destroy_key(key);
}
}
}
#[derive(Debug, Clone)]
pub struct AtaSecureEraseCommand {
pub enhanced: bool,
pub master_password: Option<[u8; 32]>,
pub estimated_time_minutes: Option<u16>,
}
impl AtaSecureEraseCommand {
pub fn standard() -> Self {
Self {
enhanced: false,
master_password: None,
estimated_time_minutes: None,
}
}
pub fn enhanced() -> Self {
Self {
enhanced: true,
master_password: None,
estimated_time_minutes: None,
}
}
pub fn check_support(identify_word_128: u16) -> bool {
(identify_word_128 & 0x0001) != 0 && (identify_word_128 & 0x0002) == 0
}
pub fn get_estimated_time(identify_word_89: u16, enhanced: bool) -> u16 {
if enhanced {
identify_word_89
} else {
identify_word_89
}
}
}
#[derive(Debug, Clone)]
pub struct NvmeFormatCommand {
pub nsid: u32,
pub ses: NvmeSecureEraseSetting,
pub lbaf: u8,
pub pi: u8,
pub ms: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NvmeSecureEraseSetting {
NoSecureErase = 0,
UserDataErase = 1,
CryptographicErase = 2,
}
#[derive(Debug, Clone)]
pub struct NvmeSanitizeCommand {
pub action: NvmeSanitizeAction,
pub ause: bool,
pub owpass: u8,
pub oipbp: bool,
pub ndas: bool,
pub overwrite_pattern: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NvmeSanitizeAction {
ExitFailureMode = 1,
BlockErase = 2,
Overwrite = 3,
CryptoErase = 4,
}
impl NvmeSanitizeCommand {
pub fn block_erase() -> Self {
Self {
action: NvmeSanitizeAction::BlockErase,
ause: false,
owpass: 0,
oipbp: false,
ndas: false,
overwrite_pattern: 0,
}
}
pub fn crypto_erase() -> Self {
Self {
action: NvmeSanitizeAction::CryptoErase,
ause: false,
owpass: 0,
oipbp: false,
ndas: false,
overwrite_pattern: 0,
}
}
pub fn overwrite(passes: u8, pattern: u32, invert: bool) -> Self {
Self {
action: NvmeSanitizeAction::Overwrite,
ause: false,
owpass: passes.clamp(1, 16),
oipbp: invert,
ndas: false,
overwrite_pattern: pattern,
}
}
}
pub struct EraseVerifier {
sample_count: usize,
rng: Option<CryptoRng>,
}
impl EraseVerifier {
pub fn new(sample_count: usize) -> Self {
Self {
sample_count: sample_count.max(10),
rng: CryptoRng::new().ok(),
}
}
fn random_index(&mut self, max: usize) -> usize {
if let Some(ref mut rng) = self.rng {
if let Ok(val) = rng.next_u64() {
return (val as usize) % max;
}
}
static mut COUNTER: usize = 0;
unsafe {
COUNTER = COUNTER.wrapping_add(7919); COUNTER % max
}
}
pub fn verify_pattern(&mut self, data: &[u8], expected: u8) -> bool {
if data.is_empty() {
return true;
}
for _ in 0..self.sample_count {
let index = self.random_index(data.len());
if data[index] != expected {
return false;
}
}
true
}
pub fn verify_not_zero(&mut self, data: &[u8]) -> bool {
if data.is_empty() {
return true;
}
let mut non_zero_count = 0;
for _ in 0..self.sample_count {
let index = self.random_index(data.len());
if data[index] != 0 {
non_zero_count += 1;
}
}
non_zero_count > self.sample_count * 9 / 10
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wipe_method_pass_count() {
assert_eq!(WipeMethod::Dod3Pass.pass_count(), Some(3));
assert_eq!(WipeMethod::Dod7Pass.pass_count(), Some(7));
assert_eq!(WipeMethod::Gutmann.pass_count(), Some(35));
assert_eq!(WipeMethod::CryptoErase.pass_count(), Some(0));
assert_eq!(WipeMethod::AtaSecureErase.pass_count(), None);
assert_eq!(WipeMethod::NvmeFormat.pass_count(), None);
}
#[test]
fn test_wipe_method_hardware_requirement() {
assert!(!WipeMethod::Dod3Pass.requires_hardware());
assert!(!WipeMethod::Gutmann.requires_hardware());
assert!(WipeMethod::AtaSecureErase.requires_hardware());
assert!(WipeMethod::NvmeFormat.requires_hardware());
}
#[test]
fn test_wipe_method_ssd_suitability() {
assert!(WipeMethod::CryptoErase.suitable_for_ssd());
assert!(WipeMethod::NvmeFormat.suitable_for_ssd());
assert!(!WipeMethod::Dod3Pass.suitable_for_ssd());
assert!(!WipeMethod::Gutmann.suitable_for_ssd());
}
#[test]
fn test_secure_eraser_dod_3_pass() {
let mut eraser = SecureEraser::new();
let mut data = vec![0xAA; 1024];
let result = eraser.erase_buffer(&mut data, WipeMethod::Dod3Pass);
assert!(result.is_ok());
}
#[test]
fn test_crypto_eraser() {
let mut key = [0x42u8; 32];
CryptoEraser::destroy_key(&mut key);
assert_eq!(key, [0u8; 32]);
}
#[test]
fn test_progress_percentage() {
let progress = EraseProgress {
current_pass: 2,
total_passes: 4,
bytes_completed: 500,
bytes_total: 1000,
eta_seconds: None,
operation: String::from("Test"),
cancellable: true,
};
let pct = progress.percentage();
assert!(pct > 37.0 && pct < 38.0);
}
}