use ed25519_dalek::{SigningKey, VerifyingKey};
use eyre::{Report, eyre};
use getrandom;
use hex;
use rand_core::OsRng;
use std::fs;
use std::path::{Path, PathBuf};
use volli_core::profile as core_profile;
pub fn default_secret_dir() -> PathBuf {
core_profile::role_dir("manager")
}
pub fn secret_dir(profile: Option<&str>) -> PathBuf {
match profile {
Some(p) => core_profile::profile_dir("manager", p),
None => core_profile::role_dir("manager"),
}
}
pub fn bootstrap_keypair(dir: Option<&Path>) -> Result<(), Report> {
let dir = dir.map(PathBuf::from).unwrap_or_else(default_secret_dir);
fs::create_dir_all(&dir)?;
let sk_path = dir.join("manager_sk");
let pk_path = dir.join("manager_pk");
if sk_path.exists() || pk_path.exists() {
return Err(eyre!("keypair already exists"));
}
let mut rng = OsRng;
let signing = SigningKey::generate(&mut rng);
let verifying: VerifyingKey = signing.verifying_key();
fs::write(&sk_path, hex::encode(signing.to_bytes()))?;
fs::write(&pk_path, hex::encode(verifying.to_bytes()))?;
let mut csk = [0u8; 32];
getrandom::fill(&mut csk)?;
fs::write(dir.join("csk"), hex::encode(csk))?;
fs::write(dir.join("csk_ver"), "1")?;
tracing::debug!("Generated manager keypair at {}", dir.display());
Ok(())
}
pub fn load_signing_key(dir: Option<&Path>) -> Result<SigningKey, Report> {
let dir = dir.map(PathBuf::from).unwrap_or_else(default_secret_dir);
let data = fs::read(dir.join("manager_sk"))?;
let bytes = hex::decode(data)?;
let arr: [u8; 32] = bytes.as_slice().try_into().map_err(|_| eyre!("bad sk"))?;
Ok(SigningKey::from_bytes(&arr))
}
pub fn load_verifying_key(dir: Option<&Path>) -> Result<VerifyingKey, Report> {
let dir = dir.map(PathBuf::from).unwrap_or_else(default_secret_dir);
let data = fs::read(dir.join("manager_pk"))?;
let bytes = hex::decode(data)?;
let arr: [u8; 32] = bytes.as_slice().try_into().map_err(|_| eyre!("bad pk"))?;
Ok(VerifyingKey::from_bytes(&arr)?)
}
pub fn save_csk(profile: &str, csk: &[u8; 32], ver: u32) -> Result<(), Report> {
let dir = secret_dir(Some(profile));
fs::create_dir_all(&dir)?;
fs::write(dir.join("csk"), hex::encode(csk))?;
fs::write(dir.join("csk_ver"), ver.to_string())?;
Ok(())
}
pub fn load_csk(profile: &str) -> Result<Option<([u8; 32], u32)>, Report> {
let dir = secret_dir(Some(profile));
let key_path = dir.join("csk");
let ver_path = dir.join("csk_ver");
if key_path.exists() && ver_path.exists() {
let data = fs::read_to_string(key_path)?;
let bytes = hex::decode(data)?;
let arr: [u8; 32] = bytes.as_slice().try_into().map_err(|_| eyre!("bad csk"))?;
let ver: u32 = fs::read_to_string(ver_path)?.trim().parse()?;
Ok(Some((arr, ver)))
} else {
Ok(None)
}
}