fastn_net/
secret.rs

1use eyre::WrapErr;
2
3/// Environment variable name for providing secret key.
4pub const SECRET_KEY_ENV_VAR: &str = "FASTN_SECRET_KEY";
5
6/// Default file name for storing secret key.
7pub const SECRET_KEY_FILE: &str = ".fastn.secret-key";
8
9/// Default file name for storing ID52 public key.
10pub const ID52_FILE: &str = ".fastn.id52";
11
12/// Generates a new Ed25519 secret key and returns its ID52.
13///
14/// # Returns
15///
16/// A tuple of (ID52 string, SecretKey).
17pub fn generate_secret_key() -> eyre::Result<(String, fastn_id52::SecretKey)> {
18    let secret_key = fastn_id52::SecretKey::generate();
19    let id52 = secret_key.id52();
20    Ok((id52, secret_key))
21}
22
23/// Generates a new secret key and saves it to the system keyring.
24///
25/// The key is stored in the system keyring under the ID52 identifier,
26/// and the ID52 is written to `.fastn.id52` file.
27///
28/// # Errors
29///
30/// Returns an error if keyring access or file write fails.
31pub async fn generate_and_save_key() -> eyre::Result<(String, fastn_id52::SecretKey)> {
32    let (id52, secret_key) = generate_secret_key()?;
33    let e = keyring_entry(&id52)?;
34    e.set_secret(&secret_key.to_bytes())
35        .wrap_err_with(|| format!("failed to save secret key for {id52}"))?;
36    tokio::fs::write(ID52_FILE, &id52).await?;
37    Ok((id52, secret_key))
38}
39
40fn keyring_entry(id52: &str) -> eyre::Result<keyring::Entry> {
41    keyring::Entry::new("fastn", id52)
42        .wrap_err_with(|| format!("failed to create keyring Entry for {id52}"))
43}
44
45fn handle_secret(secret: &str) -> eyre::Result<(String, fastn_id52::SecretKey)> {
46    use std::str::FromStr;
47    let secret_key = fastn_id52::SecretKey::from_str(secret).map_err(|e| eyre::anyhow!("{}", e))?;
48    let id52 = secret_key.id52();
49    Ok((id52, secret_key))
50}
51
52/// Gets a secret key for a given ID52 and path.
53///
54/// **Note**: Currently unimplemented, will be implemented in future versions.
55///
56/// # Panics
57///
58/// Always panics with "implement for fastn".
59pub fn get_secret_key(_id52: &str, _path: &str) -> eyre::Result<fastn_id52::SecretKey> {
60    // intentionally left unimplemented as design is changing in fastn
61    // this is not used in fastn
62    todo!("implement for fastn")
63}
64
65/// Reads an existing secret key or creates a new one if none exists.
66///
67/// Attempts to read the secret key in the following order:
68/// 1. From `FASTN_SECRET_KEY` environment variable
69/// 2. From `.fastn.secret-key` file
70/// 3. From system keyring using ID52 from `.fastn.id52` file
71/// 4. Generates new key if none found
72///
73/// # Errors
74///
75/// Returns an error if key reading fails (but not if key doesn't exist).
76#[tracing::instrument]
77pub async fn read_or_create_key() -> eyre::Result<(String, fastn_id52::SecretKey)> {
78    if let Ok(secret) = std::env::var(SECRET_KEY_ENV_VAR) {
79        tracing::info!("Using secret key from environment variable {SECRET_KEY_ENV_VAR}");
80        return handle_secret(&secret);
81    } else {
82        match tokio::fs::read_to_string(SECRET_KEY_FILE).await {
83            Ok(secret) => {
84                tracing::info!("Using secret key from file {SECRET_KEY_FILE}");
85                let secret = secret.trim_end();
86                return handle_secret(secret);
87            }
88            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
89            Err(e) => {
90                tracing::error!("failed to read {SECRET_KEY_FILE}: {e}");
91                return Err(e.into());
92            }
93        }
94    }
95
96    tracing::info!("No secret key found in environment or file, trying {ID52_FILE}");
97    match tokio::fs::read_to_string(ID52_FILE).await {
98        Ok(id52) => {
99            let e = keyring_entry(&id52)?;
100            match e.get_secret() {
101                Ok(secret) => {
102                    if secret.len() != 32 {
103                        return Err(eyre::anyhow!(
104                            "keyring: secret for {id52} has invalid length: {}",
105                            secret.len()
106                        ));
107                    }
108
109                    let bytes: [u8; 32] = secret.try_into().expect("already checked for length");
110                    let secret_key = fastn_id52::SecretKey::from_bytes(&bytes);
111                    let id52 = secret_key.id52();
112                    Ok((id52, secret_key))
113                }
114                Err(e) => {
115                    tracing::error!("failed to read secret for {id52} from keyring: {e}");
116                    Err(e.into())
117                }
118            }
119        }
120        Err(e) if e.kind() == std::io::ErrorKind::NotFound => generate_and_save_key().await,
121        Err(e) => {
122            tracing::error!("failed to read {ID52_FILE}: {e}");
123            Err(e.into())
124        }
125    }
126}