tap_agent/key_store.rs
1//! Key Storage Abstraction Layer
2//!
3//! This module defines the [`KeyStore`] trait for future integration with
4//! external key management systems.
5//!
6//! # Current Implementation
7//!
8//! Currently, keys are stored as plaintext JSON in `~/.tap/keys.json`.
9//! This is suitable for development and testing but **NOT recommended for production**
10//! deployments with high-value keys.
11//!
12//! The current storage flow:
13//! 1. Keys are generated or imported into [`AgentKeyManager`](crate::AgentKeyManager)
14//! 2. Keys are serialized to [`StoredKey`](crate::StoredKey) format (base64-encoded key material)
15//! 3. [`KeyStorage`](crate::KeyStorage) writes JSON to disk at the configured path
16//!
17//! # Security Considerations
18//!
19//! The plaintext storage has the following security properties:
20//! - **No encryption at rest**: Key material is base64-encoded but not encrypted
21//! - **File permissions**: Relies on OS file permissions for access control
22//! - **Portable**: Keys can be easily backed up or transferred
23//!
24//! For production deployments, consider implementing a [`KeyStore`] backend that provides:
25//! - Encryption at rest (e.g., envelope encryption with master key)
26//! - Hardware security module (HSM) integration
27//! - Cloud key management service (KMS) integration
28//! - Platform keychain integration
29//!
30//! # Future External Key Management
31//!
32//! The [`KeyStore`] trait provides an abstraction that can be implemented for various backends:
33//!
34//! ## Hardware Security Modules (HSMs)
35//! - AWS CloudHSM
36//! - Azure Dedicated HSM
37//! - Thales Luna HSM
38//!
39//! ## Cloud Key Management Services
40//! - AWS KMS
41//! - Google Cloud KMS
42//! - Azure Key Vault
43//! - HashiCorp Vault
44//!
45//! ## Platform Keychains
46//! - macOS Keychain (Security.framework)
47//! - Windows DPAPI / Credential Manager
48//! - Linux Secret Service (libsecret)
49//!
50//! # Implementation Guide
51//!
52//! To implement a custom key store backend:
53//!
54//! 1. Implement the [`KeyStore`] trait for your backend
55//! 2. Handle key material serialization appropriate for your backend
56//! 3. Implement proper error handling for network/hardware failures
57//! 4. Consider caching strategies for performance
58//!
59//! ## Example: HashiCorp Vault Integration
60//!
61//! ```rust,ignore
62//! use tap_agent::key_store::{KeyStore, KeyStoreError};
63//! use async_trait::async_trait;
64//!
65//! pub struct VaultKeyStore {
66//! client: vault::Client,
67//! mount_path: String,
68//! }
69//!
70//! impl VaultKeyStore {
71//! pub fn new(addr: &str, token: &str) -> Result<Self, Box<dyn std::error::Error>> {
72//! let client = vault::Client::new(addr, token)?;
73//! Ok(Self {
74//! client,
75//! mount_path: "secret/tap-keys".to_string(),
76//! })
77//! }
78//! }
79//!
80//! #[async_trait]
81//! impl KeyStore for VaultKeyStore {
82//! async fn store_key(&self, id: &str, material: &[u8]) -> Result<(), KeyStoreError> {
83//! let path = format!("{}/{}", self.mount_path, id);
84//! let data = base64::encode(material);
85//! self.client.secrets().kv2().set(&path, &[("key", &data)]).await
86//! .map_err(|e| KeyStoreError::Storage(e.to_string()))?;
87//! Ok(())
88//! }
89//!
90//! async fn load_key(&self, id: &str) -> Result<Vec<u8>, KeyStoreError> {
91//! let path = format!("{}/{}", self.mount_path, id);
92//! let secret = self.client.secrets().kv2().get(&path).await
93//! .map_err(|e| KeyStoreError::NotFound(id.to_string()))?;
94//! let data = secret.data.get("key")
95//! .ok_or_else(|| KeyStoreError::InvalidFormat("Missing key field".to_string()))?;
96//! base64::decode(data)
97//! .map_err(|e| KeyStoreError::InvalidFormat(e.to_string()))
98//! }
99//!
100//! async fn delete_key(&self, id: &str) -> Result<(), KeyStoreError> {
101//! let path = format!("{}/{}", self.mount_path, id);
102//! self.client.secrets().kv2().delete(&path).await
103//! .map_err(|e| KeyStoreError::Storage(e.to_string()))?;
104//! Ok(())
105//! }
106//!
107//! async fn key_exists(&self, id: &str) -> Result<bool, KeyStoreError> {
108//! let path = format!("{}/{}", self.mount_path, id);
109//! match self.client.secrets().kv2().get(&path).await {
110//! Ok(_) => Ok(true),
111//! Err(_) => Ok(false),
112//! }
113//! }
114//!
115//! async fn list_keys(&self) -> Result<Vec<String>, KeyStoreError> {
116//! self.client.secrets().kv2().list(&self.mount_path).await
117//! .map_err(|e| KeyStoreError::Storage(e.to_string()))
118//! }
119//! }
120//! ```
121//!
122//! ## Integration with AgentKeyManager
123//!
124//! Future versions will support passing a custom [`KeyStore`] to the
125//! [`AgentKeyManagerBuilder`](crate::AgentKeyManagerBuilder):
126//!
127//! ```rust,ignore
128//! let vault_store = VaultKeyStore::new("https://vault.example.com", token)?;
129//!
130//! let key_manager = AgentKeyManagerBuilder::new()
131//! .with_key_store(Box::new(vault_store))
132//! .build()?;
133//! ```
134
135use async_trait::async_trait;
136
137/// Error types for key storage operations
138#[derive(Debug, thiserror::Error)]
139pub enum KeyStoreError {
140 /// The requested key was not found
141 #[error("Key not found: {0}")]
142 NotFound(String),
143
144 /// A storage backend error occurred
145 #[error("Storage error: {0}")]
146 Storage(String),
147
148 /// Access to the key was denied
149 #[error("Access denied: {0}")]
150 AccessDenied(String),
151
152 /// The key material format is invalid
153 #[error("Invalid key format: {0}")]
154 InvalidFormat(String),
155
156 /// The storage backend is unavailable
157 #[error("Backend unavailable: {0}")]
158 Unavailable(String),
159}
160
161/// Trait for key storage backends
162///
163/// Implement this trait to integrate with external key management systems.
164/// All operations are async to support network-based backends (HSMs, cloud KMS, etc.).
165///
166/// # Thread Safety
167///
168/// Implementations must be `Send + Sync` to allow use across async tasks.
169///
170/// # Error Handling
171///
172/// Implementations should:
173/// - Return `KeyStoreError::NotFound` for missing keys (not a general error)
174/// - Return `KeyStoreError::AccessDenied` for permission issues
175/// - Return `KeyStoreError::Unavailable` for transient failures (enable retry logic)
176/// - Return `KeyStoreError::Storage` for other backend errors
177#[async_trait]
178pub trait KeyStore: Send + Sync {
179 /// Store key material with the given identifier
180 ///
181 /// If a key with the same ID already exists, it should be overwritten.
182 ///
183 /// # Arguments
184 /// * `id` - Unique identifier for the key (typically a DID or key ID)
185 /// * `material` - Raw key material (private key bytes)
186 async fn store_key(&self, id: &str, material: &[u8]) -> Result<(), KeyStoreError>;
187
188 /// Load key material by identifier
189 ///
190 /// # Arguments
191 /// * `id` - The key identifier
192 ///
193 /// # Returns
194 /// The raw key material, or `KeyStoreError::NotFound` if the key doesn't exist
195 async fn load_key(&self, id: &str) -> Result<Vec<u8>, KeyStoreError>;
196
197 /// Delete a key by identifier
198 ///
199 /// # Arguments
200 /// * `id` - The key identifier
201 ///
202 /// # Returns
203 /// `Ok(())` if the key was deleted or didn't exist
204 async fn delete_key(&self, id: &str) -> Result<(), KeyStoreError>;
205
206 /// Check if a key exists
207 ///
208 /// # Arguments
209 /// * `id` - The key identifier
210 ///
211 /// # Returns
212 /// `true` if the key exists, `false` otherwise
213 async fn key_exists(&self, id: &str) -> Result<bool, KeyStoreError>;
214
215 /// List all key identifiers
216 ///
217 /// # Returns
218 /// A vector of all key IDs in the store
219 async fn list_keys(&self) -> Result<Vec<String>, KeyStoreError>;
220}
221
222/// Plaintext file-based key store
223///
224/// **WARNING**: This implementation stores keys as plaintext JSON.
225/// It wraps the existing [`KeyStorage`](crate::KeyStorage) implementation
226/// for backwards compatibility.
227///
228/// Use only for development and testing. For production deployments,
229/// implement a secure [`KeyStore`] backend with encryption at rest.
230#[derive(Debug, Default)]
231pub struct PlaintextFileKeyStore {
232 #[allow(dead_code)]
233 path: Option<std::path::PathBuf>,
234}
235
236impl PlaintextFileKeyStore {
237 /// Create a new plaintext key store using the default path (~/.tap/keys.json)
238 pub fn new() -> Self {
239 Self { path: None }
240 }
241
242 /// Create a new plaintext key store at a specific path
243 pub fn with_path(path: std::path::PathBuf) -> Self {
244 Self { path: Some(path) }
245 }
246}