1#[cfg(not(target_arch = "wasm32"))]
8use std::fs;
9use std::path::{Path, PathBuf};
10use std::time::{SystemTime, UNIX_EPOCH};
11
12use ed25519_dalek::{Keypair, PublicKey};
13use rand::rngs::OsRng;
14use thiserror::Error;
15
16use crate::{CoreError, Identity};
17
18#[derive(Debug, Error)]
19pub enum KeyStoreError {
20 #[error("io error: {0}")]
22 Io(#[from] std::io::Error),
23 #[error("invalid key length")]
25 InvalidKeyLength,
26 #[error("identity not found at {0}")]
28 IdentityMissing(String),
29 #[error("core error: {0}")]
31 Core(#[from] CoreError),
32}
33
34pub struct KeyStore {
35 path: PathBuf,
37 identity: Identity,
39}
40
41impl KeyStore {
42 #[cfg(not(target_arch = "wasm32"))]
43 pub fn load_or_generate(path: &Path) -> Result<Identity, KeyStoreError> {
45 match Identity::load(Some(path)) {
46 Ok(identity) => Ok(identity),
47 Err(CoreError::IdentityMissing(_)) => {
48 let mut rng = OsRng;
49 let keypair = Keypair::generate(&mut rng);
50 let identity = Identity::generate_from_keypair(keypair);
51 identity.save(path)?;
52 Ok(identity)
53 }
54 Err(err) => Err(KeyStoreError::Core(err)),
55 }
56 }
57
58 #[cfg(target_arch = "wasm32")]
59 pub fn load_or_generate(_path: &Path) -> Result<Identity, KeyStoreError> {
61 Ok(Identity::generate())
62 }
63
64 #[cfg(not(target_arch = "wasm32"))]
65 pub fn open(path: &Path) -> Result<Self, KeyStoreError> {
67 let identity = Identity::load(Some(path)).map_err(KeyStoreError::Core)?;
68 Ok(Self {
69 path: path.to_path_buf(),
70 identity,
71 })
72 }
73
74 #[cfg(target_arch = "wasm32")]
75 pub fn open(_path: &Path) -> Result<Self, KeyStoreError> {
77 Err(KeyStoreError::Io(std::io::Error::new(
78 std::io::ErrorKind::Unsupported,
79 "keystore unsupported on wasm",
80 )))
81 }
82
83 #[cfg(not(target_arch = "wasm32"))]
84 pub fn rotate(&mut self) -> Result<(), KeyStoreError> {
86 let old_dir = self.old_dir();
87 fs::create_dir_all(&old_dir)?;
88 let ts = SystemTime::now()
89 .duration_since(UNIX_EPOCH)
90 .unwrap_or_default()
91 .as_secs();
92 let archived = old_dir.join(format!("identity-{ts}.key"));
93 fs::rename(&self.path, archived)?;
94
95 let mut rng = OsRng;
96 let keypair = Keypair::generate(&mut rng);
97 let identity = Identity::generate_from_keypair(keypair);
98 identity.save(&self.path)?;
99 self.identity = identity;
100 Ok(())
101 }
102
103 #[cfg(target_arch = "wasm32")]
104 pub fn rotate(&mut self) -> Result<(), KeyStoreError> {
106 Err(KeyStoreError::Io(std::io::Error::new(
107 std::io::ErrorKind::Unsupported,
108 "keystore unsupported on wasm",
109 )))
110 }
111
112 #[cfg(not(target_arch = "wasm32"))]
113 pub fn list_public_keys(&self) -> Vec<PublicKey> {
115 let mut keys = Vec::new();
116 if let Ok(keypair) = read_keypair(&self.path) {
117 keys.push(keypair.public);
118 }
119 if let Ok(entries) = fs::read_dir(self.old_dir()) {
120 for entry in entries.flatten() {
121 if let Ok(keypair) = read_keypair(&entry.path()) {
122 keys.push(keypair.public);
123 }
124 }
125 }
126 keys
127 }
128
129 #[cfg(target_arch = "wasm32")]
130 pub fn list_public_keys(&self) -> Vec<PublicKey> {
132 vec![self.identity.keypair.public]
133 }
134
135 pub fn identity(&self) -> &Identity {
137 &self.identity
138 }
139
140 #[cfg(not(target_arch = "wasm32"))]
141 fn old_dir(&self) -> PathBuf {
143 self.path
144 .parent()
145 .unwrap_or_else(|| Path::new("."))
146 .join(".old")
147 }
148
149 #[cfg(target_arch = "wasm32")]
150 fn old_dir(&self) -> PathBuf {
152 PathBuf::new()
153 }
154}
155
156#[cfg(not(target_arch = "wasm32"))]
157fn read_keypair(path: &Path) -> Result<Keypair, KeyStoreError> {
159 let bytes = fs::read(path)?;
160 if bytes.len() != 64 {
161 return Err(KeyStoreError::InvalidKeyLength);
162 }
163 Keypair::from_bytes(&bytes).map_err(|_| KeyStoreError::InvalidKeyLength)
164}