use std::path::Path;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum SealedFileError {
#[error("Key file not found at {0} — was it already consumed?")]
NotFound(String),
#[error("Permission denied reading key file: {0}")]
PermissionDenied(String),
#[error("Invalid base64 in key file: {0}")]
InvalidBase64(#[from] base64::DecodeError),
#[error("Invalid key length: expected 32 bytes, got {0}")]
InvalidKeyLength(usize),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
pub const DEFAULT_KEY_PATH: &str = "/run/ati/.key";
pub fn read_and_delete_key() -> Result<[u8; 32], SealedFileError> {
let key_path = std::env::var("ATI_KEY_FILE").unwrap_or_else(|_| DEFAULT_KEY_PATH.to_string());
read_and_delete_key_from(Path::new(&key_path))
}
pub fn read_and_delete_key_from(path: &Path) -> Result<[u8; 32], SealedFileError> {
use std::io::Read;
let mut file = match std::fs::File::open(path) {
Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
return Err(SealedFileError::NotFound(path.display().to_string()));
}
Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
return Err(SealedFileError::PermissionDenied(
path.display().to_string(),
));
}
Err(e) => return Err(SealedFileError::Io(e)),
};
if let Err(e) = std::fs::remove_file(path) {
tracing::warn!(path = %path.display(), error = %e, "could not delete key file");
}
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let decoded =
base64::Engine::decode(&base64::engine::general_purpose::STANDARD, contents.trim())?;
if decoded.len() != 32 {
return Err(SealedFileError::InvalidKeyLength(decoded.len()));
}
let mut key = [0u8; 32];
key.copy_from_slice(&decoded);
Ok(key)
}