use kleos_lib::config::{Config, EncryptionMode};
use crate::crypto::derive_key;
pub fn resolve_at_rest_key(
config: &Config,
precomputed_response: Option<&[u8]>,
) -> anyhow::Result<Option<[u8; 32]>> {
match config.encryption.mode {
EncryptionMode::None => Ok(None),
EncryptionMode::Yubikey => {
tracing::info!("at-rest encryption mode: yubikey");
let derived = match precomputed_response {
Some(response) => derive_key(0, b"", Some(response)),
None => {
let challenge = crate::yubikey::get_or_create_challenge()
.map_err(|e| anyhow::anyhow!("YubiKey challenge: {e}"))?;
let response = crate::yubikey::challenge_response(&challenge)
.map_err(|e| anyhow::anyhow!("YubiKey response: {e}"))?;
derive_key(0, b"", Some(&response))
}
};
let mut key = [0u8; 32];
key.copy_from_slice(&derived[..]);
Ok(Some(key))
}
_ => kleos_lib::encryption::resolve_key(config)
.map_err(|e| anyhow::anyhow!("encryption key: {e}")),
}
}
fn config_dir() -> std::path::PathBuf {
directories::ProjectDirs::from("", "", "cred")
.map(|d| d.config_dir().to_path_buf())
.unwrap_or_else(|| {
directories::BaseDirs::new()
.map(|d| d.home_dir().join(".config").join("cred"))
.unwrap_or_else(|| std::path::PathBuf::from(".").join(".config").join("cred"))
})
}
fn mode_file_path() -> std::path::PathBuf {
config_dir().join("encryption-mode")
}
fn mode_to_token(mode: &EncryptionMode) -> &'static str {
match mode {
EncryptionMode::None => "none",
EncryptionMode::Keyfile => "keyfile",
EncryptionMode::Env => "env",
EncryptionMode::Yubikey => "yubikey",
}
}
fn mode_from_token(s: &str) -> Option<EncryptionMode> {
match s.trim().to_ascii_lowercase().as_str() {
"none" => Some(EncryptionMode::None),
"keyfile" => Some(EncryptionMode::Keyfile),
"env" => Some(EncryptionMode::Env),
"yubikey" => Some(EncryptionMode::Yubikey),
_ => None,
}
}
pub fn read_persisted_encryption_mode() -> Option<EncryptionMode> {
let raw = std::fs::read_to_string(mode_file_path()).ok()?;
mode_from_token(&raw)
}
pub fn persist_encryption_mode(mode: &EncryptionMode) -> std::io::Result<()> {
let path = mode_file_path();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let tmp = path.with_extension("tmp");
std::fs::write(&tmp, format!("{}\n", mode_to_token(mode)))?;
std::fs::rename(&tmp, &path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mode_token_roundtrips() {
for m in [
EncryptionMode::None,
EncryptionMode::Keyfile,
EncryptionMode::Env,
EncryptionMode::Yubikey,
] {
assert_eq!(mode_from_token(mode_to_token(&m)), Some(m));
}
}
#[test]
fn mode_token_is_case_insensitive_and_trimmed() {
assert_eq!(
mode_from_token(" YubiKey \n"),
Some(EncryptionMode::Yubikey)
);
assert_eq!(mode_from_token("KEYFILE"), Some(EncryptionMode::Keyfile));
}
#[test]
fn unknown_token_is_none() {
assert_eq!(mode_from_token("garbage"), None);
assert_eq!(mode_from_token(""), None);
}
}