ati/security/
sealed_file.rs1use std::path::Path;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum SealedFileError {
6 #[error("Key file not found at {0} — was it already consumed?")]
7 NotFound(String),
8 #[error("Permission denied reading key file: {0}")]
9 PermissionDenied(String),
10 #[error("Invalid base64 in key file: {0}")]
11 InvalidBase64(#[from] base64::DecodeError),
12 #[error("Invalid key length: expected 32 bytes, got {0}")]
13 InvalidKeyLength(usize),
14 #[error("IO error: {0}")]
15 Io(#[from] std::io::Error),
16}
17
18pub const DEFAULT_KEY_PATH: &str = "/run/ati/.key";
20
21pub fn read_and_delete_key() -> Result<[u8; 32], SealedFileError> {
28 let key_path = std::env::var("ATI_KEY_FILE").unwrap_or_else(|_| DEFAULT_KEY_PATH.to_string());
29
30 read_and_delete_key_from(Path::new(&key_path))
31}
32
33pub fn read_and_delete_key_from(path: &Path) -> Result<[u8; 32], SealedFileError> {
40 use std::io::Read;
41
42 let mut file = match std::fs::File::open(path) {
44 Ok(f) => f,
45 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
46 return Err(SealedFileError::NotFound(path.display().to_string()));
47 }
48 Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
49 return Err(SealedFileError::PermissionDenied(
50 path.display().to_string(),
51 ));
52 }
53 Err(e) => return Err(SealedFileError::Io(e)),
54 };
55
56 if let Err(e) = std::fs::remove_file(path) {
59 tracing::warn!(path = %path.display(), error = %e, "could not delete key file");
60 }
61
62 let mut contents = String::new();
64 file.read_to_string(&mut contents)?;
65
66 let decoded =
68 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, contents.trim())?;
69
70 if decoded.len() != 32 {
72 return Err(SealedFileError::InvalidKeyLength(decoded.len()));
73 }
74
75 let mut key = [0u8; 32];
76 key.copy_from_slice(&decoded);
77 Ok(key)
78}