Skip to main content

cyphr_cli/
keystore.rs

1//! Keystore abstraction for private key storage.
2//!
3//! This module provides a trait for key storage backends and an MVP
4//! plaintext JSON implementation.
5
6use std::collections::HashMap;
7use std::fs;
8use std::io;
9use std::path::{Path, PathBuf};
10
11use serde::{Deserialize, Serialize};
12
13/// Error type for keystore operations.
14#[derive(Debug, thiserror::Error)]
15pub enum Error {
16    /// I/O error reading or writing keystore file.
17    #[error("keystore I/O error: {0}")]
18    Io(#[from] io::Error),
19
20    /// JSON serialization/deserialization error.
21    #[error("keystore JSON error: {0}")]
22    Json(#[from] serde_json::Error),
23
24    /// Key not found in keystore.
25    #[error("key not found: {0}")]
26    NotFound(String),
27
28    /// Key already exists in keystore.
29    #[error("key already exists: {0}")]
30    AlreadyExists(String),
31
32    /// Unknown algorithm.
33    #[error("unknown algorithm: {0}")]
34    UnknownAlgorithm(String),
35}
36
37/// A stored key entry with algorithm, public, and private key material.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct StoredKey {
40    /// Algorithm identifier (ES256, ES384, ES512, Ed25519).
41    pub alg: String,
42
43    /// Public key bytes (base64url encoded in JSON).
44    #[serde(with = "base64url_bytes")]
45    pub pub_key: Vec<u8>,
46
47    /// Private key bytes (base64url encoded in JSON).
48    #[serde(with = "base64url_bytes")]
49    pub prv_key: Vec<u8>,
50
51    /// Optional human-readable tag.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub tag: Option<String>,
54}
55
56/// Keystore trait for private key storage.
57///
58/// Implementations may use files, OS keychains, HSMs, etc.
59pub trait KeyStore {
60    /// Store a key by its thumbprint.
61    fn store(&mut self, tmb: &str, key: StoredKey) -> Result<(), Error>;
62
63    /// Retrieve a key by its thumbprint.
64    fn get(&self, tmb: &str) -> Result<&StoredKey, Error>;
65
66    /// List all stored key thumbprints.
67    fn list(&self) -> Vec<&str>;
68
69    /// Persist changes to backing storage.
70    fn save(&self) -> Result<(), Error>;
71}
72
73/// Plaintext JSON keystore (MVP implementation).
74///
75/// Stores keys in a JSON file on disk. **NOT SECURE** for production use.
76/// Keys are stored in plaintext. Use only for development and testing.
77pub struct JsonKeyStore {
78    path: PathBuf,
79    keys: HashMap<String, StoredKey>,
80}
81
82impl JsonKeyStore {
83    /// Create or load a keystore from the given path.
84    pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
85        let path = path.as_ref().to_path_buf();
86
87        let keys = if path.exists() {
88            let content = fs::read_to_string(&path)?;
89            serde_json::from_str(&content)?
90        } else {
91            HashMap::new()
92        };
93
94        Ok(Self { path, keys })
95    }
96
97    /// Get the path to the keystore file.
98    pub fn path(&self) -> &Path {
99        &self.path
100    }
101}
102
103impl KeyStore for JsonKeyStore {
104    fn store(&mut self, tmb: &str, key: StoredKey) -> Result<(), Error> {
105        if self.keys.contains_key(tmb) {
106            return Err(Error::AlreadyExists(tmb.to_string()));
107        }
108        self.keys.insert(tmb.to_string(), key);
109        Ok(())
110    }
111
112    fn get(&self, tmb: &str) -> Result<&StoredKey, Error> {
113        self.keys
114            .get(tmb)
115            .ok_or_else(|| Error::NotFound(tmb.to_string()))
116    }
117
118    fn list(&self) -> Vec<&str> {
119        self.keys.keys().map(String::as_str).collect()
120    }
121
122    fn save(&self) -> Result<(), Error> {
123        let content = serde_json::to_string_pretty(&self.keys)?;
124        fs::write(&self.path, content)?;
125        Ok(())
126    }
127}
128
129/// Serde helper for base64url encoding/decoding of byte vectors.
130mod base64url_bytes {
131    use base64ct::{Base64UrlUnpadded, Encoding};
132    use serde::{Deserialize, Deserializer, Serializer, de};
133
134    pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
135    where
136        S: Serializer,
137    {
138        let encoded = Base64UrlUnpadded::encode_string(bytes);
139        serializer.serialize_str(&encoded)
140    }
141
142    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
143    where
144        D: Deserializer<'de>,
145    {
146        let s = String::deserialize(deserializer)?;
147        Base64UrlUnpadded::decode_vec(&s).map_err(de::Error::custom)
148    }
149}