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,
};
pub fn load_or_create_key(git_dir: &str) -> Result<SecretKey> {
let key_file = resolve_key_path(git_dir)?;
if !key_file.exists() {
create_key(key_file)
} else {
load_key(key_file)
}
}
fn resolve_key_path(git_dir: &str) -> Result<PathBuf> {
let git_config = File::from_git_dir(git_dir)?;
if let Some(key_file) = git_config.path_by_key("iroh.secretKey") {
Ok(key_file.value.to_string().into())
} else {
let key_dir = dirs::config_local_dir()
.unwrap_or(PathBuf::from(
std::env::var("HOME").context("HOME not set")?,
))
.join("git-remote-iroh")
.join("keys");
DirBuilder::new()
.recursive(true)
.mode(0o700)
.create(key_dir.as_path())?;
Ok(key_dir.join("default"))
}
}
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)
}