1use core::fmt;
4use std::fs::{self, OpenOptions};
5use std::io::{self, Write as _};
6use std::path::{Path, PathBuf};
7use std::sync::{PoisonError, RwLock};
8
9use indexmap::IndexMap;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13use super::local::{self, Entry};
14use super::{KeyId, KeySpec, KeyStore, KeyStoreCapabilities, KeyStoreError};
15use crate::jose::{Jwk, JwsAlgorithm};
16
17#[derive(Serialize, Deserialize)]
19struct JwkSet {
20 keys: Vec<Jwk>,
21 #[serde(flatten)]
22 additional: IndexMap<String, Value>,
23}
24
25#[derive(Debug, Clone, Default, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct SoftwareKeyStoreOptions {
29 pub allow_insecure_permissions: bool,
34 pub create_if_missing: bool,
36}
37
38impl SoftwareKeyStoreOptions {
39 pub fn new() -> Self {
41 Self::default()
42 }
43
44 #[must_use]
46 pub fn allow_insecure_permissions(mut self, allow: bool) -> Self {
47 self.allow_insecure_permissions = allow;
48 self
49 }
50
51 #[must_use]
53 pub fn create_if_missing(mut self, create: bool) -> Self {
54 self.create_if_missing = create;
55 self
56 }
57}
58
59pub struct SoftwareKeyStore {
104 path: PathBuf,
105 keys: RwLock<IndexMap<KeyId, Entry>>,
106 additional: IndexMap<String, Value>,
107}
108
109impl SoftwareKeyStore {
110 pub fn open(path: impl Into<PathBuf>) -> Result<Self, KeyStoreError> {
118 Self::open_with(path, SoftwareKeyStoreOptions::default())
119 }
120
121 pub fn open_with(
127 path: impl Into<PathBuf>,
128 options: SoftwareKeyStoreOptions,
129 ) -> Result<Self, KeyStoreError> {
130 let path = path.into();
131 if options.create_if_missing && !path.exists() {
132 return Self::create(path);
133 }
134 let data = fs::read(&path).map_err(|source| KeyStoreError::Io {
135 path: path.clone(),
136 source,
137 })?;
138 if !options.allow_insecure_permissions {
139 check_permissions(&path)?;
140 }
141 let set: JwkSet =
142 serde_json::from_slice(&data).map_err(|e| KeyStoreError::InvalidKeySet {
143 path: path.clone(),
144 reason: e.to_string(),
145 })?;
146 let mut keys = IndexMap::with_capacity(set.keys.len());
147 for jwk in set.keys {
148 let (id, entry) =
149 Entry::from_jwk(jwk).map_err(|reason| KeyStoreError::InvalidKeySet {
150 path: path.clone(),
151 reason,
152 })?;
153 if keys.insert(id.clone(), entry).is_some() {
154 return Err(KeyStoreError::InvalidKeySet {
155 path,
156 reason: format!("key `{id}` is defined twice"),
157 });
158 }
159 }
160 Ok(Self {
161 path,
162 keys: RwLock::new(keys),
163 additional: set.additional,
164 })
165 }
166
167 pub fn create(path: impl Into<PathBuf>) -> Result<Self, KeyStoreError> {
173 let path = path.into();
174 if path.exists() {
175 return Err(KeyStoreError::Io {
176 path,
177 source: io::Error::new(io::ErrorKind::AlreadyExists, "file exists"),
178 });
179 }
180 let store = Self {
181 path,
182 keys: RwLock::new(IndexMap::new()),
183 additional: IndexMap::new(),
184 };
185 store.save(&IndexMap::new())?;
186 Ok(store)
187 }
188
189 pub fn path(&self) -> &Path {
191 &self.path
192 }
193
194 pub fn key_ids(&self) -> Vec<KeyId> {
196 self.keys
197 .read()
198 .unwrap_or_else(PoisonError::into_inner)
199 .keys()
200 .cloned()
201 .collect()
202 }
203
204 pub fn import(&self, jwk: Jwk) -> Result<KeyId, KeyStoreError> {
215 let (id, entry) =
216 Entry::from_jwk(jwk.clone()).map_err(|reason| KeyStoreError::InvalidKey {
217 key: KeyId::new(jwk.kid().unwrap_or_default()),
218 reason,
219 })?;
220 self.insert(id, entry)
221 }
222
223 fn insert(&self, id: KeyId, entry: Entry) -> Result<KeyId, KeyStoreError> {
225 let mut keys = self.keys.write().unwrap_or_else(PoisonError::into_inner);
226 if keys.contains_key(&id) {
227 return Err(KeyStoreError::AlreadyExists(id));
228 }
229 keys.insert(id.clone(), entry);
230 if let Err(e) = self.save(&keys) {
231 keys.shift_remove(&id);
232 return Err(e);
233 }
234 Ok(id)
235 }
236
237 fn save(&self, keys: &IndexMap<KeyId, Entry>) -> Result<(), KeyStoreError> {
239 let set = JwkSet {
240 keys: keys.values().map(|entry| entry.jwk.clone()).collect(),
241 additional: self.additional.clone(),
242 };
243 let mut json = serde_json::to_vec_pretty(&set).map_err(|e| KeyStoreError::Io {
245 path: self.path.clone(),
246 source: io::Error::other(e),
247 })?;
248 json.push(b'\n');
249 write_atomically(&self.path, &json).map_err(|source| KeyStoreError::Io {
250 path: self.path.clone(),
251 source,
252 })
253 }
254}
255
256impl fmt::Debug for SoftwareKeyStore {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 f.debug_struct("SoftwareKeyStore")
259 .field("path", &self.path)
260 .field("keys", &self.key_ids())
261 .finish()
262 }
263}
264
265#[async_trait::async_trait]
266impl KeyStore for SoftwareKeyStore {
267 fn capabilities(&self) -> KeyStoreCapabilities {
268 KeyStoreCapabilities::new(local::ALGORITHMS.iter().copied())
269 .with_generate()
270 .with_delete()
271 }
272
273 async fn public_jwk(&self, key: &KeyId) -> Result<Jwk, KeyStoreError> {
274 self.keys
275 .read()
276 .unwrap_or_else(PoisonError::into_inner)
277 .get(key)
278 .map(Entry::public_jwk)
279 .ok_or_else(|| KeyStoreError::NotFound(key.clone()))
280 }
281
282 async fn sign(
283 &self,
284 key: &KeyId,
285 alg: JwsAlgorithm,
286 data: &[u8],
287 ) -> Result<Vec<u8>, KeyStoreError> {
288 self.keys
289 .read()
290 .unwrap_or_else(PoisonError::into_inner)
291 .get(key)
292 .ok_or_else(|| KeyStoreError::NotFound(key.clone()))?
293 .sign(key, alg, data)
294 }
295
296 async fn exists(&self, key: &KeyId) -> Result<bool, KeyStoreError> {
297 Ok(self
298 .keys
299 .read()
300 .unwrap_or_else(PoisonError::into_inner)
301 .contains_key(key))
302 }
303
304 async fn generate(&self, spec: KeySpec) -> Result<KeyId, KeyStoreError> {
305 let (id, key) = local::generate(&spec, &mut local::os_random)?;
306 let entry = Entry::new(&id, key, spec.alg);
307 self.insert(id, entry)
308 }
309
310 async fn delete(&self, key: &KeyId) -> Result<(), KeyStoreError> {
311 let mut keys = self.keys.write().unwrap_or_else(PoisonError::into_inner);
312 let (index, id, entry) = keys
313 .shift_remove_full(key)
314 .ok_or_else(|| KeyStoreError::NotFound(key.clone()))?;
315 if let Err(e) = self.save(&keys) {
316 keys.shift_insert(index, id, entry);
317 return Err(e);
318 }
319 Ok(())
320 }
321}
322
323#[cfg(unix)]
325fn check_permissions(path: &Path) -> Result<(), KeyStoreError> {
326 use std::os::unix::fs::PermissionsExt as _;
327 let mode = fs::metadata(path)
328 .map_err(|source| KeyStoreError::Io {
329 path: path.to_owned(),
330 source,
331 })?
332 .permissions()
333 .mode()
334 & 0o7777;
335 if mode & 0o077 != 0 {
336 return Err(KeyStoreError::InsecurePermissions {
337 path: path.to_owned(),
338 mode,
339 });
340 }
341 Ok(())
342}
343
344#[cfg(not(unix))]
345fn check_permissions(_path: &Path) -> Result<(), KeyStoreError> {
346 Ok(())
347}
348
349fn write_atomically(path: &Path, data: &[u8]) -> io::Result<()> {
352 let dir = match path.parent() {
353 Some(dir) if !dir.as_os_str().is_empty() => dir,
354 _ => Path::new("."),
355 };
356 let file_name = path
357 .file_name()
358 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "path has no file name"))?;
359 let mut nonce = [0u8; 8];
360 getrandom::fill(&mut nonce).map_err(io::Error::other)?;
361 let mut tmp_name = std::ffi::OsString::from(".");
362 tmp_name.push(file_name);
363 tmp_name.push(format!(".{:016x}.tmp", u64::from_be_bytes(nonce)));
364 let tmp = dir.join(tmp_name);
365
366 let mut options = OpenOptions::new();
367 options.write(true).create_new(true);
368 #[cfg(unix)]
369 {
370 use std::os::unix::fs::OpenOptionsExt as _;
371 options.mode(0o600);
372 }
373 let result = options.open(&tmp).and_then(|mut file| {
374 file.write_all(data)?;
375 file.sync_all()?;
376 drop(file);
377 fs::rename(&tmp, path)
378 });
379 if result.is_err() {
380 let _ = fs::remove_file(&tmp);
381 }
382 result
383}