use anyhow::{Context, Result};
use gix_config::File;
use iroh::SecretKey;
use std::{
fs::{DirBuilder, OpenOptions},
io::Write,
os::unix::fs::{DirBuilderExt, OpenOptionsExt, PermissionsExt},
path::PathBuf,
};
use crate::keys_dir;
pub fn load_or_create_key(config_dir: &PathBuf, git_dir: &PathBuf) -> Result<(PathBuf, SecretKey)> {
let key_file = resolve_key_path(config_dir, git_dir)?;
let key = if !key_file.exists() {
create_key(&key_file).context(format!("create key: {:?}", key_file))
} else {
load_key(&key_file).context(format!("load key: {:?}", key_file))
}?;
Ok((key_file, key))
}
fn resolve_key_path(config_dir: &PathBuf, git_dir: &PathBuf) -> Result<PathBuf> {
let git_config = if git_dir.exists() {
File::from_git_dir(git_dir)?
} else {
File::from_globals()?
};
let key_file = match git_config.path_by_key("iroh.secretKey") {
Some(key_file) => key_file.value.to_string().into(),
None => keys_dir(&config_dir).join("default"),
};
let key_dir = key_file.parent().ok_or(anyhow::anyhow!(
"failed to resolve parent directory: {:?}",
key_file
))?;
DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(key_dir)?;
Ok(key_file)
}
pub fn load_key(key_path: &PathBuf) -> Result<SecretKey> {
if key_path.metadata()?.permissions().mode() & 0o077 != 0 {
anyhow::bail!("unsafe permissions on {}", key_path.to_string_lossy());
}
let bytes = std::fs::read(&key_path).context("failed to read iroh key")?;
let arr: [u8; 32] = bytes
.try_into()
.map_err(|_| anyhow::anyhow!("invalid key length"))?;
Ok(SecretKey::from(arr))
}
pub fn create_key(key_path: &PathBuf) -> Result<SecretKey> {
let key = SecretKey::generate();
let mut key_file = OpenOptions::new()
.mode(0o600)
.create(true)
.write(true)
.open(&key_path)?;
key_file
.write(key.to_bytes().as_slice())
.context("failed to write iroh key")?;
key_file.flush()?;
Ok(key)
}