Skip to main content

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}