use crate::ToolError;
use std::fs;
use std::path::{Path, PathBuf};
#[cfg(test)]
const TEST_PRIVATE_KEY_ENV: &str = "TEST_PRIVATE_KEY";
pub fn load_private_key_from_file<P: AsRef<Path>>(path: P) -> Result<String, ToolError> {
let path = path.as_ref();
let expanded_path = expand_home_dir(path)?;
if !expanded_path.exists() {
return Err(ToolError::permanent_string(format!(
"Key file not found: {}",
expanded_path.display()
)));
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let metadata = fs::metadata(&expanded_path).map_err(|e| {
ToolError::permanent_string(format!("Failed to read file metadata: {}", e))
})?;
let permissions = metadata.permissions();
let mode = permissions.mode();
if mode & 0o077 != 0 {
tracing::warn!(
"Key file {} has insecure permissions: {:o}. Consider using chmod 600",
expanded_path.display(),
mode & 0o777
);
}
}
let key_content = fs::read_to_string(&expanded_path)
.map_err(|e| ToolError::permanent_string(format!("Failed to read key file: {}", e)))?;
Ok(key_content.trim().to_string())
}
pub fn load_private_key_with_fallback<P: AsRef<Path>>(
file_path: P,
env_var: &str,
) -> Result<String, ToolError> {
match load_private_key_from_file(&file_path) {
Ok(key) => {
tracing::debug!("Loaded key from file: {}", file_path.as_ref().display());
Ok(key)
}
Err(_) => {
tracing::debug!(
"Key file not found, trying environment variable: {}",
env_var
);
std::env::var(env_var).map_err(|_| {
ToolError::permanent_string(format!(
"Private key not found in file {} or environment variable {}",
file_path.as_ref().display(),
env_var
))
})
}
}
}
const APPDATA_ENV: &str = "APPDATA";
pub fn get_default_key_directory() -> PathBuf {
let base_dir = if cfg!(target_os = "windows") {
std::env::var(APPDATA_ENV).map_or_else(
|_| {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("AppData")
.join("Roaming")
},
PathBuf::from,
)
} else {
dirs::home_dir().unwrap_or_else(|| PathBuf::from("."))
};
base_dir.join(".riglr").join("keys")
}
fn expand_home_dir(path: &Path) -> Result<PathBuf, ToolError> {
if let Some(path_str) = path.to_str() {
if path_str.starts_with("~/") || path_str == "~" {
if let Some(home) = dirs::home_dir() {
let relative = path_str.strip_prefix("~/").unwrap_or("");
return Ok(home.join(relative));
} else {
return Err(ToolError::permanent_string(
"Unable to determine home directory".to_string(),
));
}
}
}
Ok(path.to_path_buf())
}
pub fn ensure_key_directory() -> Result<PathBuf, ToolError> {
let key_dir = get_default_key_directory();
if !key_dir.exists() {
fs::create_dir_all(&key_dir).map_err(|e| {
ToolError::permanent_string(format!("Failed to create key directory: {}", e))
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let permissions = fs::Permissions::from_mode(0o700);
fs::set_permissions(&key_dir, permissions).map_err(|e| {
ToolError::permanent_string(format!("Failed to set directory permissions: {}", e))
})?;
}
}
Ok(key_dir)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_load_private_key_from_file() {
let mut temp_file = NamedTempFile::new().unwrap();
writeln!(temp_file, "test-private-key-content").unwrap();
let key = load_private_key_from_file(temp_file.path()).unwrap();
assert_eq!(key, "test-private-key-content");
}
#[test]
fn test_load_private_key_with_fallback() {
std::env::set_var(TEST_PRIVATE_KEY_ENV, "env-key-content");
let key = load_private_key_with_fallback("/nonexistent/path/to/key", TEST_PRIVATE_KEY_ENV)
.unwrap();
assert_eq!(key, "env-key-content");
std::env::remove_var(TEST_PRIVATE_KEY_ENV);
}
#[test]
fn test_get_default_key_directory() {
let key_dir = get_default_key_directory();
assert!(key_dir.ends_with(".riglr/keys") || key_dir.ends_with("riglr\\keys"));
}
#[test]
fn test_expand_home_dir() {
let expanded = expand_home_dir(Path::new("~/test")).unwrap();
assert!(!expanded.to_str().unwrap().starts_with("~"));
let not_expanded = expand_home_dir(Path::new("/absolute/path")).unwrap();
assert_eq!(not_expanded, Path::new("/absolute/path"));
}
}