use tracing::warn;
pub struct SecureKey {
bytes: Box<[u8; 32]>,
}
impl SecureKey {
pub fn new(bytes: [u8; 32]) -> Self {
let mut boxed = Box::new(bytes);
mlock_best_effort(boxed.as_mut_ptr() as *mut libc::c_void, 32);
Self { bytes: boxed }
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.bytes
}
}
impl Drop for SecureKey {
fn drop(&mut self) {
for byte in self.bytes.iter_mut() {
unsafe { std::ptr::write_volatile(byte, 0u8) };
}
munlock_best_effort(self.bytes.as_mut_ptr() as *mut libc::c_void, 32);
}
}
pub fn mlock_key_bytes(ptr: *mut u8, len: usize) {
mlock_best_effort(ptr as *mut libc::c_void, len);
}
fn mlock_best_effort(ptr: *mut libc::c_void, len: usize) {
#[cfg(unix)]
{
let rc = unsafe { libc::mlock(ptr, len) };
if rc != 0 {
warn!(
"mlock failed for {} bytes (errno {}): key may be swapped to disk \
under extreme memory pressure. Increase RLIMIT_MEMLOCK if this \
is a concern.",
len,
std::io::Error::last_os_error()
);
}
}
#[cfg(not(unix))]
{
let _ = (ptr, len);
}
}
fn munlock_best_effort(ptr: *mut libc::c_void, len: usize) {
#[cfg(unix)]
unsafe {
libc::munlock(ptr, len);
}
#[cfg(not(unix))]
{
let _ = (ptr, len);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secure_key_stores_bytes() {
let key = SecureKey::new([0x42u8; 32]);
assert_eq!(*key.as_bytes(), [0x42u8; 32]);
}
#[test]
fn secure_key_zeros_on_drop() {
let key = SecureKey::new([0xABu8; 32]);
drop(key);
}
#[test]
fn mlock_graceful_on_linux() {
let mut buf = [0u8; 32];
mlock_best_effort(buf.as_mut_ptr() as *mut libc::c_void, 32);
munlock_best_effort(buf.as_mut_ptr() as *mut libc::c_void, 32);
}
}