use eyre::WrapErr;
pub trait SecretStore {
fn get(&self) -> eyre::Result<iroh::SecretKey>;
fn save(&self, secret_key: &iroh::SecretKey) -> eyre::Result<()>;
fn generate(rng: impl rand_core::CryptoRngCore) -> eyre::Result<iroh::PublicKey>;
}
#[cfg(feature = "keyring")]
pub struct KeyringSecretStore {
id52: String,
}
#[cfg(feature = "keyring")]
impl SecretStore for KeyringSecretStore {
fn get(&self) -> eyre::Result<iroh::SecretKey> {
let entry = self.keyring_entry()?;
let secret = entry
.get_secret()
.wrap_err_with(|| format!("keyring: secret not found for {}", self.id52))?;
if secret.len() != 32 {
return Err(eyre::anyhow!(
"keyring: secret has invalid length: {}",
secret.len()
));
}
let bytes: [u8; 32] = secret.try_into().expect("already checked for length");
Ok(iroh::SecretKey::from_bytes(&bytes))
}
fn save(&self, secret_key: &iroh::SecretKey) -> eyre::Result<()> {
Ok(self.keyring_entry()?.set_secret(&secret_key.to_bytes())?)
}
fn generate(mut rng: impl rand_core::CryptoRngCore) -> eyre::Result<iroh::PublicKey> {
let secret_key = iroh::SecretKey::generate(&mut rng);
let store = Self::new(ftnet_utils::public_key_to_id52(&secret_key.public()));
store
.save(&secret_key)
.wrap_err_with(|| "failed to store secret key to keychain")?;
Ok(secret_key.public())
}
}
#[cfg(feature = "keyring")]
impl KeyringSecretStore {
pub fn new(id52: String) -> Self {
KeyringSecretStore { id52 }
}
fn keyring_entry(&self) -> eyre::Result<keyring::Entry> {
keyring::Entry::new("FTNet", self.id52.as_str())
.wrap_err_with(|| format!("failed to create keyring Entry for {}", self.id52))
}
}
pub async fn read_or_create_key() -> eyre::Result<String> {
use ftnet_utils::SecretStore;
match tokio::fs::read_to_string(".kulfi.id52").await {
Ok(v) => Ok(v),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
tracing::info!("no key found, creating new one");
let public_key = ftnet_utils::KeyringSecretStore::generate(rand::rngs::OsRng)?;
let public_key = ftnet_utils::public_key_to_id52(&public_key);
tokio::fs::write(".kulfi.id52", public_key.as_str()).await?;
Ok(public_key)
}
Err(e) => {
tracing::error!("failed to read key: {e}");
Err(e.into())
}
}
}