sed_key/
lib.rs

1//! sed-key library entry point.
2
3pub mod args;
4pub mod bindings;
5pub mod ioctl;
6pub mod opal;
7
8use anyhow::{Result, anyhow};
9use once_cell::sync::OnceCell;
10use std::{
11    fs,
12    io::{self, Read},
13};
14
15//
16// ─── BACKEND SELECTION ───────────────────────────────────────────────────────
17//
18
19// Make trait object usable in a static: must be Sync.
20trait OpalBackend: Sync {
21    fn is_opal_device(&self, dev: &str) -> Result<bool>;
22    fn unlock_device(&self, dev: &str, pw: &str) -> Result<()>;
23    fn lock_device(&self, dev: &str, pw: &str) -> Result<()>;
24    fn device_locked(&self, dev: &str) -> Result<bool>;
25    fn get_locking_features(&self, dev: &str) -> Result<u16>;
26    fn print_locking_features(&self, f: u16);
27}
28
29struct RealBackend;
30
31impl OpalBackend for RealBackend {
32    fn is_opal_device(&self, dev: &str) -> Result<bool> {
33        opal::is_opal_device(dev)
34    }
35    fn unlock_device(&self, dev: &str, pw: &str) -> Result<()> {
36        opal::unlock_device(dev, pw)
37    }
38    fn lock_device(&self, dev: &str, pw: &str) -> Result<()> {
39        opal::lock_device(dev, pw)
40    }
41    fn device_locked(&self, dev: &str) -> Result<bool> {
42        opal::device_locked(dev)
43    }
44    fn get_locking_features(&self, dev: &str) -> Result<u16> {
45        opal::get_locking_features(dev)
46    }
47    fn print_locking_features(&self, f: u16) {
48        opal::print_locking_features(f)
49    }
50}
51
52// Global selector
53static BACKEND: OnceCell<&'static (dyn OpalBackend + Sync)> = OnceCell::new();
54static REAL: RealBackend = RealBackend;
55
56fn backend() -> &'static (dyn OpalBackend + Sync) {
57    *BACKEND.get_or_init(|| &REAL as &(dyn OpalBackend + Sync))
58}
59
60/// Switch the library to a built-in mock backend.
61///
62/// This is intended for tests/integration tests to avoid touching real
63/// hardware or blocking on stdin. It’s a no-op if the backend was already set.
64pub fn use_mock_backend() {
65    static MOCK: BuiltinMockBackend = BuiltinMockBackend;
66    BACKEND.get_or_init(|| &MOCK);
67}
68
69// Simple built-in mock backend (always compiled; tiny and harmless)
70struct BuiltinMockBackend;
71
72impl OpalBackend for BuiltinMockBackend {
73    fn is_opal_device(&self, dev: &str) -> Result<bool> {
74        // simulate only nvme-like device nodes as valid
75        if dev.starts_with("/dev/nvme") {
76            Ok(true)
77        } else {
78            Err(anyhow!("{} does not support OPAL locking", dev))
79        }
80    }
81
82    fn unlock_device(&self, dev: &str, pw: &str) -> Result<()> {
83        if pw.is_empty() {
84            return Err(anyhow!("empty password"));
85        }
86        if !dev.starts_with("/dev/nvme") {
87            return Err(anyhow!("invalid device"));
88        }
89        Ok(())
90    }
91
92    fn lock_device(&self, dev: &str, pw: &str) -> Result<()> {
93        if pw.is_empty() {
94            return Err(anyhow!("empty password"));
95        }
96        if !dev.starts_with("/dev/nvme") {
97            return Err(anyhow!("invalid device"));
98        }
99        Ok(())
100    }
101
102    fn device_locked(&self, dev: &str) -> Result<bool> {
103        if !dev.starts_with("/dev/nvme") {
104            return Err(anyhow!("invalid device"));
105        }
106        // flip between locked/unlocked for realism, but deterministic
107        Ok(dev.ends_with('0'))
108    }
109
110    fn get_locking_features(&self, _dev: &str) -> Result<u16> {
111        // pretend it supports all standard bits
112        Ok(0x001F)
113    }
114
115    fn print_locking_features(&self, f: u16) {
116        println!("[mock] Locking features: 0x{:04x}", f);
117    }
118}
119
120//
121// ─── KEY READING (REAL LOGIC) ────────────────────────────────────────────────
122//
123
124fn read_key_arg(key_arg: Option<String>) -> Result<String> {
125    if let Some(arg) = key_arg {
126        if arg == "-" {
127            let mut buf = String::new();
128            io::stdin().read_to_string(&mut buf)?;
129            return Ok(buf.trim_end_matches(['\n', '\r']).to_string());
130        } else if fs::metadata(&arg).is_ok() {
131            let s = fs::read_to_string(&arg)?;
132            return Ok(s.trim_end_matches(['\n', '\r']).to_string());
133        } else {
134            return Ok(arg);
135        }
136    }
137
138    if let Ok(k) = std::env::var("SED_KEY") {
139        return Ok(k.trim_end_matches(['\n', '\r']).to_string());
140    }
141
142    Err(anyhow!("No key provided (stdin, file, or SED_KEY env var)"))
143}
144
145//
146// ─── PUBLIC API ──────────────────────────────────────────────────────────────
147//
148
149pub fn do_unlock(device: String, key_arg: Option<String>) -> Result<()> {
150    let key = read_key_arg(key_arg)?;
151    if !backend().is_opal_device(&device)? {
152        return Err(anyhow!("{device} does not support OPAL locking"));
153    }
154    backend().unlock_device(&device, &key)
155}
156
157pub fn do_lock(device: String, key_arg: Option<String>) -> Result<()> {
158    let key = read_key_arg(key_arg)?;
159    if !backend().is_opal_device(&device)? {
160        return Err(anyhow!("{device} does not support OPAL locking"));
161    }
162    backend().lock_device(&device, &key)
163}
164
165pub fn do_status(device: String) -> Result<()> {
166    if !backend().is_opal_device(&device)? {
167        return Err(anyhow!("{device} does not support OPAL locking"));
168    }
169    let locked = backend().device_locked(&device)?;
170    let features = backend().get_locking_features(&device)?;
171    backend().print_locking_features(features);
172    println!(
173        "{} is currently {}",
174        device,
175        if locked { "LOCKED" } else { "UNLOCKED" }
176    );
177    Ok(())
178}