-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsecure_erase.rs
More file actions
285 lines (238 loc) · 8.32 KB
/
Copy pathsecure_erase.rs
File metadata and controls
285 lines (238 loc) · 8.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Jonathan D.A. Jewell <j.d.a.jewell@open.ac.uk>
//! Hardware-based secure erase for SSDs
//!
//! Implements NIST SP 800-88 Purge methods:
//! - ATA Secure Erase (SATA SSDs)
//! - NVMe Format/Sanitize (NVMe SSDs)
//! - Cryptographic erase (when supported)
use anyhow::{bail, Context, Result};
use std::fs;
use std::path::Path;
use std::process::Command;
/// Drive type detection
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DriveType {
/// Hard Disk Drive (magnetic)
HDD,
/// SATA Solid State Drive
SataSSD,
/// NVMe Solid State Drive
NVMeSSD,
/// Unknown or unsupported
Unknown,
}
/// Secure erase method
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SecureEraseMethod {
/// NIST Clear: Single pass overwrite (HDDs and fallback)
Clear,
/// NIST Purge: ATA Secure Erase (SATA SSDs)
ATASecureErase,
/// NIST Purge: NVMe Format with crypto erase
NVMeFormatCrypto,
/// NIST Purge: NVMe Sanitize (block erase)
NVMeSanitize,
/// Cryptographic erase (destroy encryption keys)
CryptoErase,
}
/// Detect drive type from device path
pub fn detect_drive_type(device: &str) -> Result<DriveType> {
// Check if it's an NVMe device
if device.starts_with("/dev/nvme") {
return Ok(DriveType::NVMeSSD);
}
// For SATA/SCSI devices, check rotational flag
let dev_name = device.trim_start_matches("/dev/");
let rotational_path = format!("/sys/block/{}/queue/rotational", dev_name);
if let Ok(content) = fs::read_to_string(&rotational_path) {
match content.trim() {
"0" => return Ok(DriveType::SataSSD),
"1" => return Ok(DriveType::HDD),
_ => {}
}
}
// Fallback: Check with smartctl if available
if let Ok(output) = Command::new("smartctl").arg("-i").arg(device).output() {
let info = String::from_utf8_lossy(&output.stdout);
if info.contains("Solid State Device") || info.contains("NVMe") {
return Ok(DriveType::SataSSD);
}
if info.contains("Rotation Rate") && !info.contains("Solid State") {
return Ok(DriveType::HDD);
}
}
Ok(DriveType::Unknown)
}
/// Get device path from file path
pub fn get_device_from_path(path: &Path) -> Result<String> {
// Use df to find the mount point and device
let output = Command::new("df")
.arg("--output=source")
.arg(path)
.output()
.context("Failed to run df command")?;
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.lines().collect();
if lines.len() < 2 {
bail!("Failed to determine device for path: {}", path.display());
}
let device = lines[1].trim();
// Handle partition devices (e.g., /dev/sda1 -> /dev/sda)
let base_device = if let Some(idx) = device.rfind(|c: char| !c.is_ascii_digit()) {
&device[..=idx]
} else {
device
};
Ok(base_device.to_string())
}
/// Check if ATA Secure Erase is supported
pub fn check_ata_secure_erase_support(device: &str) -> Result<bool> {
let output = Command::new("hdparm")
.arg("-I")
.arg(device)
.output()
.context("Failed to run hdparm - is it installed?")?;
let info = String::from_utf8_lossy(&output.stdout);
// Check for "supported: enhanced erase"
Ok(info.contains("supported: enhanced erase") || info.contains("SECURITY ERASE UNIT"))
}
/// Perform ATA Secure Erase
pub fn ata_secure_erase(device: &str, enhanced: bool) -> Result<()> {
// CRITICAL: This is a destructive operation
// Requires security password to be set first
// Step 1: Set user password (temporary)
let password = "Eins"; // Standard temporary password
println!("⚠️ Setting security password...");
let status = Command::new("hdparm")
.arg("--user-master")
.arg("u")
.arg("--security-set-pass")
.arg(password)
.arg(device)
.status()
.context("Failed to set security password")?;
if !status.success() {
bail!("Failed to set security password");
}
// Step 2: Issue secure erase command
let erase_cmd = if enhanced {
"--security-erase-enhanced"
} else {
"--security-erase"
};
println!("🔥 Performing ATA Secure Erase (this may take several minutes)...");
let status = Command::new("hdparm")
.arg("--user-master")
.arg("u")
.arg(erase_cmd)
.arg(password)
.arg(device)
.status()
.context("Failed to execute secure erase")?;
if !status.success() {
bail!("Secure erase failed");
}
println!("✓ ATA Secure Erase completed");
Ok(())
}
/// Check if NVMe sanitize is supported
pub fn check_nvme_sanitize_support(device: &str) -> Result<bool> {
let output = Command::new("nvme")
.arg("id-ctrl")
.arg(device)
.output()
.context("Failed to run nvme command - is nvme-cli installed?")?;
let info = String::from_utf8_lossy(&output.stdout);
// Check SANICAP field
Ok(info.contains("sanicap") || info.contains("Sanitize"))
}
/// Perform NVMe Format with crypto erase
pub fn nvme_format_crypto(device: &str) -> Result<()> {
println!("🔥 Performing NVMe Format with cryptographic erase...");
let status = Command::new("nvme")
.arg("format")
.arg(device)
.arg("--ses=1") // Secure Erase Settings: Cryptographic Erase
.status()
.context("Failed to execute NVMe format")?;
if !status.success() {
bail!("NVMe format failed");
}
println!("✓ NVMe cryptographic erase completed");
Ok(())
}
/// Perform NVMe Sanitize
pub fn nvme_sanitize(device: &str, block_erase: bool) -> Result<()> {
println!("🔥 Performing NVMe Sanitize (this may take a long time)...");
let action = if block_erase {
"--sanact=2" // Block Erase
} else {
"--sanact=1" // Exit Failure Mode
};
let status = Command::new("nvme")
.arg("sanitize")
.arg(device)
.arg(action)
.status()
.context("Failed to execute NVMe sanitize")?;
if !status.success() {
bail!("NVMe sanitize failed");
}
println!("✓ NVMe sanitize completed");
Ok(())
}
/// High-level secure erase function
pub fn secure_erase_file(file_path: &Path, method: SecureEraseMethod) -> Result<()> {
// Get the device
let device = get_device_from_path(file_path)?;
let drive_type = detect_drive_type(&device)?;
println!("📊 Device: {}", device);
println!("📊 Drive type: {:?}", drive_type);
println!("📊 Method: {:?}", method);
match (drive_type, method) {
(DriveType::SataSSD, SecureEraseMethod::ATASecureErase) => {
if !check_ata_secure_erase_support(&device)? {
bail!("ATA Secure Erase not supported on this device");
}
println!("⚠️ WARNING: This will erase the ENTIRE device: {}", device);
println!("⚠️ All data on the device will be permanently destroyed!");
// In production, require explicit confirmation here
ata_secure_erase(&device, true)?;
}
(DriveType::NVMeSSD, SecureEraseMethod::NVMeFormatCrypto) => {
println!("⚠️ WARNING: This will erase the ENTIRE namespace");
nvme_format_crypto(&device)?;
}
(DriveType::NVMeSSD, SecureEraseMethod::NVMeSanitize) => {
if !check_nvme_sanitize_support(&device)? {
bail!("NVMe Sanitize not supported on this device");
}
println!("⚠️ WARNING: This will erase the ENTIRE device");
nvme_sanitize(&device, true)?;
}
(DriveType::HDD, SecureEraseMethod::Clear) => {
// Fall back to overwrite for HDDs
println!("ℹ️ Using single-pass overwrite for HDD (NIST Clear)");
// Call overwrite function here
}
_ => {
bail!(
"Unsupported combination: {:?} drive with {:?} method",
drive_type,
method
);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore] // Requires actual hardware
fn test_detect_drive_type() {
let result = detect_drive_type("/dev/sda");
assert!(result.is_ok());
}
}