use std::cell::Cell;
use std::sync::Mutex;
use inillucent_driver::EncryptionKey;
pub const KEY_VARIABLE: &str = "INILLUCENT_KEY";
pub const KEY_FILE_VARIABLE: &str = "INILLUCENT_KEY_FILE";
pub const NEW_KEY_VARIABLE: &str = "INILLUCENT_NEW_KEY";
static CONFIGURED: Mutex<Option<EncryptionKey>> = Mutex::new(None);
thread_local! {
static WITHOUT_KEY: Cell<bool> = const { Cell::new(false) };
}
pub fn read_key_file(path: &str) -> Result<EncryptionKey, String> {
let text = std::fs::read_to_string(path)
.map_err(|error| format!("cannot read the key file {path}: {error}"))?;
let key = text.trim_end_matches(['\r', '\n']);
if key.is_empty() {
return Err(format!("the key file {path} is empty"));
}
Ok(EncryptionKey::parse(key))
}
pub fn install_from(key_file: Option<&str>) -> Result<(), String> {
let key = match key_file {
Some(path) => Some(read_key_file(path)?),
None => match std::env::var(KEY_FILE_VARIABLE) {
Ok(path) if !path.is_empty() => Some(read_key_file(&path)?),
_ => match std::env::var(KEY_VARIABLE) {
Ok(text) if !text.is_empty() => Some(EncryptionKey::parse(&text)),
_ => None,
},
},
};
install(key);
Ok(())
}
pub fn install(key: Option<EncryptionKey>) {
if let Ok(mut held) = CONFIGURED.lock() {
*held = key;
}
}
pub fn configured() -> Option<EncryptionKey> {
CONFIGURED.lock().ok().and_then(|held| held.clone())
}
pub fn for_opening() -> Option<EncryptionKey> {
match WITHOUT_KEY.with(Cell::get) {
true => None,
false => configured(),
}
}
pub fn opening_without_key<T>(work: impl FnOnce() -> T) -> T {
let before = WITHOUT_KEY.with(|flag| flag.replace(true));
let result = work();
WITHOUT_KEY.with(|flag| flag.set(before));
result
}
pub fn opens_source_without_key(command: &str) -> bool {
command == "encrypt"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_key_file_is_read_without_its_line_end() {
let directory =
std::env::temp_dir().join(format!("inillucent-keys-{}", std::process::id()));
std::fs::create_dir_all(&directory).unwrap();
let path = directory.join("key.txt");
std::fs::write(&path, "hunter2\r\n").unwrap();
let key = read_key_file(path.to_str().unwrap()).unwrap();
assert!(format!("{key:?}").contains("passphrase"));
std::fs::write(&path, "\n").unwrap();
assert!(read_key_file(path.to_str().unwrap()).is_err());
std::fs::write(&path, format!("x'{}'\n", "ab".repeat(32))).unwrap();
let key = read_key_file(path.to_str().unwrap()).unwrap();
assert!(format!("{key:?}").contains("raw"));
let _ = std::fs::remove_dir_all(&directory);
}
#[test]
fn opening_without_key_hides_the_key_only_inside() {
install(Some(EncryptionKey::raw([1; 32])));
assert!(for_opening().is_some());
assert!(opening_without_key(for_opening).is_none());
assert!(for_opening().is_some());
install(None);
}
}