ftnet_utils/
secret.rs

1use eyre::WrapErr;
2
3/// A simple secret store to manage [iroh::SecretKey]
4pub trait SecretStore {
5    /// Get the secret key from the underlying store
6    fn get(&self) -> eyre::Result<iroh::SecretKey>;
7    /// Save the secret key to the underlying store
8    fn save(&self, secret_key: &iroh::SecretKey) -> eyre::Result<()>;
9    /// Generate a new secret key and save it to the underlying store
10    /// returns the public key portion
11    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        // we do not want to keep secret key in memory, only in keychain
46        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}