1use std::path::PathBuf;
4
5use anyhow::{bail, Context};
6use iroh::SecretKey;
7use tokio::io::AsyncWriteExt;
8
9pub async fn load_secret_key(key_path: PathBuf) -> anyhow::Result<SecretKey> {
12 if key_path.exists() {
13 let keystr = tokio::fs::read(key_path).await?;
14
15 let ser_key = ssh_key::private::PrivateKey::from_openssh(keystr)?;
16 let ssh_key::private::KeypairData::Ed25519(kp) = ser_key.key_data() else {
17 bail!("invalid key format");
18 };
19 let secret_key = SecretKey::from_bytes(&kp.private.to_bytes());
20 Ok(secret_key)
21 } else {
22 let secret_key = SecretKey::generate(rand::rngs::OsRng);
23 let ckey = ssh_key::private::Ed25519Keypair {
24 public: secret_key.public().public().into(),
25 private: secret_key.secret().into(),
26 };
27 let ser_key =
28 ssh_key::private::PrivateKey::from(ckey).to_openssh(ssh_key::LineEnding::default())?;
29
30 let key_path = key_path.canonicalize().unwrap_or(key_path);
32 let key_path_parent = key_path.parent().ok_or_else(|| {
33 anyhow::anyhow!("no parent directory found for '{}'", key_path.display())
34 })?;
35 tokio::fs::create_dir_all(&key_path_parent).await?;
36
37 let (file, temp_file_path) = tempfile::NamedTempFile::new_in(key_path_parent)
39 .context("unable to create tempfile")?
40 .into_parts();
41 let mut file = tokio::fs::File::from_std(file);
42 file.write_all(ser_key.as_bytes())
43 .await
44 .context("unable to write keyfile")?;
45 file.flush().await?;
46 drop(file);
47
48 tokio::fs::rename(temp_file_path, key_path)
50 .await
51 .context("failed to rename keyfile")?;
52
53 Ok(secret_key)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::load_secret_key;
60 #[tokio::test]
61 async fn secret_key_roundtrip() {
62 let dir = tempfile::tempdir().unwrap();
63 let path = dir.path().join("key");
64 let key1 = load_secret_key(path.clone()).await.unwrap();
65 let key2 = load_secret_key(path).await.unwrap();
66 assert_eq!(key1.to_bytes(), key2.to_bytes());
67 }
68}