1use eyre::WrapErr;
2
3pub trait SecretStore {
5 fn get(&self) -> eyre::Result<iroh::SecretKey>;
7 fn save(&self, secret_key: &iroh::SecretKey) -> eyre::Result<()>;
9 fn generate(rng: impl rand_core::CryptoRngCore) -> eyre::Result<iroh::PublicKey>;
12}
13
14#[cfg(feature = "keyring")]
15pub struct KeyringSecretStore {
16 id52: String,
17}
18
19#[cfg(feature = "keyring")]
20impl SecretStore for KeyringSecretStore {
21 fn get(&self) -> eyre::Result<iroh::SecretKey> {
22 let entry = self.keyring_entry()?;
23
24 let secret = entry
25 .get_secret()
26 .wrap_err_with(|| format!("keyring: secret not found for {}", self.id52))?;
27
28 if secret.len() != 32 {
29 return Err(eyre::anyhow!(
30 "keyring: secret has invalid length: {}",
31 secret.len()
32 ));
33 }
34
35 let bytes: [u8; 32] = secret.try_into().expect("already checked for length");
36 Ok(iroh::SecretKey::from_bytes(&bytes))
37 }
38
39 fn save(&self, secret_key: &iroh::SecretKey) -> eyre::Result<()> {
40 Ok(self.keyring_entry()?.set_secret(&secret_key.to_bytes())?)
41 }
42
43 fn generate(mut rng: impl rand_core::CryptoRngCore) -> eyre::Result<iroh::PublicKey> {
44 let secret_key = iroh::SecretKey::generate(&mut rng);
45 let store = Self::new(ftnet_utils::public_key_to_id52(&secret_key.public()));
47 store
48 .save(&secret_key)
49 .wrap_err_with(|| "failed to store secret key to keychain")?;
50
51 Ok(secret_key.public())
52 }
53}
54
55#[cfg(feature = "keyring")]
56impl KeyringSecretStore {
57 pub fn new(id52: String) -> Self {
58 KeyringSecretStore { id52 }
59 }
60
61 fn keyring_entry(&self) -> eyre::Result<keyring::Entry> {
62 keyring::Entry::new("FTNet", self.id52.as_str())
63 .wrap_err_with(|| format!("failed to create keyring Entry for {}", self.id52))
64 }
65}
66
67pub async fn read_or_create_key() -> eyre::Result<String> {
68 use ftnet_utils::SecretStore;
69
70 match tokio::fs::read_to_string(".kulfi.id52").await {
71 Ok(v) => Ok(v),
72 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
73 tracing::info!("no key found, creating new one");
74 let public_key = ftnet_utils::KeyringSecretStore::generate(rand::rngs::OsRng)?;
75 let public_key = ftnet_utils::public_key_to_id52(&public_key);
76 tokio::fs::write(".kulfi.id52", public_key.as_str()).await?;
77 Ok(public_key)
78 }
79 Err(e) => {
80 tracing::error!("failed to read key: {e}");
81 Err(e.into())
82 }
83 }
84}