_hope_core/auditor.rs
1//! # Hope Genome v1.4.0 - Proof Auditor
2//!
3//! **Hardened Security Edition - Pluggable Architecture**
4//!
5//! ## Major Changes in v1.4.0
6//!
7//! ### 1. Persistent Nonce Store Integration
8//! - **Replay Attack Protection**: Survives process restarts
9//! - **Pluggable Backends**: Memory, RocksDB, Redis
10//! - **Production Ready**: No more lost nonces on crash!
11//!
12//! ### 2. KeyStore Trait Integration
13//! - **Flexible Cryptography**: Software or HSM
14//! - **Ed25519 Support**: Fast, secure signatures
15//! - **Future-Proof**: Easy to add new key backends
16//!
17//! ## Example (New API)
18//!
19//! ```rust
20//! use _hope_core::auditor::ProofAuditor;
21//! use _hope_core::crypto::SoftwareKeyStore;
22//! use _hope_core::nonce_store::MemoryNonceStore;
23//!
24//! // Create with pluggable backends
25//! let key_store = SoftwareKeyStore::generate().unwrap();
26//! let nonce_store = MemoryNonceStore::new();
27//!
28//! let auditor = ProofAuditor::new(
29//! Box::new(key_store),
30//! Box::new(nonce_store),
31//! );
32//! ```
33//!
34//! ---
35//!
36//! **Date**: 2025-12-30
37//! **Version**: 1.4.0 (Hardened Security Edition)
38//! **Author**: Máté Róbert <stratosoiteam@gmail.com>
39
40use crate::crypto::{CryptoError, KeyStore};
41use crate::nonce_store::{NonceStore, NonceStoreError};
42use crate::proof::IntegrityProof;
43use thiserror::Error;
44
45// ============================================================================
46// ERROR TYPES
47// ============================================================================
48
49#[derive(Debug, Error)]
50pub enum AuditorError {
51 #[error("Invalid signature")]
52 InvalidSignature,
53
54 #[error("Proof expired: issued at {issued}, now {now}, TTL {ttl}s")]
55 ProofExpired { issued: u64, now: u64, ttl: u64 },
56
57 #[error("Nonce already used (replay attack detected): {0}")]
58 NonceReused(String),
59
60 #[error("Nonce store error: {0}")]
61 NonceStoreError(#[from] NonceStoreError),
62
63 #[error("Crypto error: {0}")]
64 CryptoError(String),
65}
66
67impl From<CryptoError> for AuditorError {
68 fn from(err: CryptoError) -> Self {
69 match err {
70 CryptoError::InvalidSignature => AuditorError::InvalidSignature,
71 other => AuditorError::CryptoError(other.to_string()),
72 }
73 }
74}
75
76pub type Result<T> = std::result::Result<T, AuditorError>;
77
78// ============================================================================
79// PROOF AUDITOR (v1.4.0 - Trait-Based Architecture)
80// ============================================================================
81
82/// Proof verification engine with pluggable backends
83///
84/// The auditor performs cryptographic verification of `IntegrityProof` objects
85/// with multi-layer security:
86///
87/// 1. **Signature Verification**: Ed25519 cryptographic signatures (KeyStore)
88/// 2. **TTL Enforcement**: Time-based proof expiry
89/// 3. **Replay Attack Prevention**: Persistent nonce tracking (NonceStore)
90///
91/// ## Architecture (v1.4.0)
92///
93/// ```text
94/// ┌─────────────────┐
95/// │ ProofAuditor │
96/// └────────┬────────┘
97/// │
98/// ├─── KeyStore ───────> SoftwareKeyStore (Ed25519)
99/// │ HsmKeyStore (PKCS#11)
100/// │
101/// └─── NonceStore ─────> MemoryNonceStore (testing)
102/// RocksDbNonceStore (production)
103/// RedisNonceStore (distributed)
104/// ```
105///
106/// ## Example (Production Setup)
107///
108/// ```no_run
109/// use _hope_core::auditor::ProofAuditor;
110/// use _hope_core::crypto::SoftwareKeyStore;
111/// # #[cfg(feature = "rocksdb-nonce-store")]
112/// use _hope_core::nonce_store::RocksDbNonceStore;
113///
114/// # #[cfg(feature = "rocksdb-nonce-store")]
115/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
116/// // Production setup: persistent nonce store
117/// let key_store = SoftwareKeyStore::generate()?;
118/// let nonce_store = RocksDbNonceStore::new("./hope_nonces.db")?;
119///
120/// let mut auditor = ProofAuditor::new(
121/// Box::new(key_store),
122/// Box::new(nonce_store),
123/// );
124///
125/// // Verify proofs - nonces persist across restarts!
126/// // auditor.verify_proof(&proof)?;
127/// # Ok(())
128/// # }
129/// # #[cfg(not(feature = "rocksdb-nonce-store"))]
130/// # fn main() {}
131/// ```
132pub struct ProofAuditor {
133 /// Cryptographic key store (pluggable: software or HSM)
134 key_store: Box<dyn KeyStore>,
135
136 /// Nonce store for replay attack prevention (pluggable: memory, RocksDB, Redis)
137 nonce_store: Box<dyn NonceStore>,
138}
139
140impl ProofAuditor {
141 /// Create a new proof auditor with custom backends
142 ///
143 /// # Arguments
144 /// * `key_store` - Cryptographic key storage (SoftwareKeyStore, HsmKeyStore, etc.)
145 /// * `nonce_store` - Nonce tracking storage (MemoryNonceStore, RocksDbNonceStore, etc.)
146 ///
147 /// # Example
148 /// ```rust
149 /// use _hope_core::auditor::ProofAuditor;
150 /// use _hope_core::crypto::SoftwareKeyStore;
151 /// use _hope_core::nonce_store::MemoryNonceStore;
152 ///
153 /// let key_store = SoftwareKeyStore::generate().unwrap();
154 /// let nonce_store = MemoryNonceStore::new();
155 ///
156 /// let auditor = ProofAuditor::new(
157 /// Box::new(key_store),
158 /// Box::new(nonce_store),
159 /// );
160 /// ```
161 pub fn new(key_store: Box<dyn KeyStore>, nonce_store: Box<dyn NonceStore>) -> Self {
162 ProofAuditor {
163 key_store,
164 nonce_store,
165 }
166 }
167
168 /// Verify a cryptographic proof
169 ///
170 /// This performs comprehensive multi-layer verification:
171 ///
172 /// 1. **Signature Verification**: Validates Ed25519 signature
173 /// 2. **TTL Check**: Ensures proof hasn't expired
174 /// 3. **Nonce Check**: Prevents replay attacks (atomic check-and-insert)
175 ///
176 /// # Arguments
177 /// * `proof` - The `IntegrityProof` to verify
178 ///
179 /// # Returns
180 /// - `Ok(())` if proof is valid and not replayed
181 /// - `Err(InvalidSignature)` if signature verification fails
182 /// - `Err(ProofExpired)` if proof TTL has expired
183 /// - `Err(NonceReused)` if nonce was already used (replay attack)
184 ///
185 /// # Security Guarantees
186 ///
187 /// - **Constant-time**: Ed25519 verification prevents timing attacks
188 /// - **Atomic nonce check**: Race-condition free (even in distributed setup)
189 /// - **Persistent protection**: With RocksDB/Redis, survives restarts
190 ///
191 /// # Example
192 /// ```rust
193 /// use _hope_core::auditor::ProofAuditor;
194 /// use _hope_core::crypto::{KeyStore, SoftwareKeyStore}; // KeyStore trait needed for .sign()
195 /// use _hope_core::nonce_store::MemoryNonceStore;
196 /// use _hope_core::proof::{Action, IntegrityProof};
197 ///
198 /// let key_store = SoftwareKeyStore::generate().unwrap();
199 /// let nonce_store = MemoryNonceStore::new();
200 /// let mut auditor = ProofAuditor::new(
201 /// Box::new(key_store.clone()),
202 /// Box::new(nonce_store),
203 /// );
204 ///
205 /// // Create and sign a proof
206 /// let action = Action::delete("test.txt");
207 /// let mut proof = IntegrityProof::new(&action, "capsule123".into(), 3600);
208 /// proof.signature = key_store.sign(&proof.signing_data()).unwrap();
209 ///
210 /// // Verify proof
211 /// assert!(auditor.verify_proof(&proof).is_ok());
212 ///
213 /// // Replay attack: blocked!
214 /// assert!(auditor.verify_proof(&proof).is_err());
215 /// ```
216 pub fn verify_proof(&mut self, proof: &IntegrityProof) -> Result<()> {
217 // Step 1: Verify signature (most critical - fail fast)
218 self.verify_signature(proof)?;
219
220 // Step 2: Check TTL (time-to-live)
221 // v1.6.0: Fixed H-1 - Integer underflow protection using saturating_sub()
222 let now = chrono::Utc::now().timestamp() as u64;
223
224 // Reject proofs from the future (clock skew protection)
225 const MAX_CLOCK_SKEW: u64 = 300; // 5 minutes tolerance
226 if proof.timestamp > now + MAX_CLOCK_SKEW {
227 return Err(AuditorError::ProofExpired {
228 issued: proof.timestamp,
229 now,
230 ttl: proof.ttl,
231 });
232 }
233
234 // Safe subtraction prevents underflow attack
235 let elapsed = now.saturating_sub(proof.timestamp);
236 if elapsed > proof.ttl {
237 return Err(AuditorError::ProofExpired {
238 issued: proof.timestamp,
239 now,
240 ttl: proof.ttl,
241 });
242 }
243
244 // Step 3: Check and insert nonce (atomic operation)
245 // This prevents replay attacks even across restarts (with persistent store)
246 self.nonce_store
247 .check_and_insert(proof.nonce, proof.ttl)
248 .map_err(|e| match e {
249 NonceStoreError::NonceReused(hex) => AuditorError::NonceReused(hex),
250 other => AuditorError::NonceStoreError(other),
251 })?;
252
253 Ok(())
254 }
255
256 /// Verify signature only (without nonce/TTL checks)
257 ///
258 /// Use this for read-only verification without state changes.
259 /// Does NOT mark nonce as used.
260 ///
261 /// # Example
262 /// ```rust
263 /// use _hope_core::auditor::ProofAuditor;
264 /// use _hope_core::crypto::{KeyStore, SoftwareKeyStore}; // KeyStore trait needed for .sign()
265 /// use _hope_core::nonce_store::MemoryNonceStore;
266 /// use _hope_core::proof::{Action, IntegrityProof};
267 ///
268 /// let key_store = SoftwareKeyStore::generate().unwrap();
269 /// let nonce_store = MemoryNonceStore::new();
270 /// let auditor = ProofAuditor::new(
271 /// Box::new(key_store.clone()),
272 /// Box::new(nonce_store),
273 /// );
274 ///
275 /// let action = Action::delete("test.txt");
276 /// let mut proof = IntegrityProof::new(&action, "capsule123".into(), 3600);
277 /// proof.signature = key_store.sign(&proof.signing_data()).unwrap();
278 ///
279 /// // Verify signature multiple times (no nonce consumption)
280 /// assert!(auditor.verify_signature(&proof).is_ok());
281 /// assert!(auditor.verify_signature(&proof).is_ok());
282 /// ```
283 pub fn verify_signature(&self, proof: &IntegrityProof) -> Result<()> {
284 let message = proof.signing_data();
285 self.key_store.verify(&message, &proof.signature)?;
286 Ok(())
287 }
288
289 /// Check if a nonce has been used
290 ///
291 /// Read-only operation - does not modify state.
292 pub fn is_nonce_used(&self, nonce: &[u8; 32]) -> bool {
293 self.nonce_store.contains(nonce)
294 }
295
296 /// Get count of used nonces
297 ///
298 /// Useful for monitoring and debugging.
299 pub fn used_nonce_count(&self) -> usize {
300 self.nonce_store.count()
301 }
302
303 /// Clear all nonces (DANGEROUS - use only for testing!)
304 ///
305 /// # Security Warning
306 /// This allows replay attacks! Only use for:
307 /// - Unit tests
308 /// - Development reset
309 /// - Maintenance with full system shutdown
310 pub fn clear_nonces(&mut self) -> Result<()> {
311 self.nonce_store.clear()?;
312 Ok(())
313 }
314
315 /// Cleanup expired nonces (optional maintenance)
316 ///
317 /// Most backends handle this automatically (e.g., Redis TTL),
318 /// but RocksDB may benefit from periodic cleanup.
319 ///
320 /// # Returns
321 /// Number of nonces removed
322 pub fn cleanup_expired_nonces(&mut self) -> Result<usize> {
323 Ok(self.nonce_store.cleanup_expired()?)
324 }
325
326 /// Get key store identifier (for logging/debugging)
327 pub fn key_store_info(&self) -> String {
328 self.key_store.identifier()
329 }
330}
331
332// ============================================================================
333// TESTS
334// ============================================================================
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use crate::crypto::SoftwareKeyStore;
340 use crate::nonce_store::MemoryNonceStore;
341 use crate::proof::Action;
342
343 fn create_test_auditor() -> (ProofAuditor, SoftwareKeyStore) {
344 let key_store = SoftwareKeyStore::generate().unwrap();
345 let key_store_clone = key_store.clone();
346 let nonce_store = MemoryNonceStore::new();
347
348 let auditor = ProofAuditor::new(Box::new(key_store), Box::new(nonce_store));
349
350 (auditor, key_store_clone)
351 }
352
353 fn create_test_proof(key_store: &SoftwareKeyStore) -> IntegrityProof {
354 let action = Action::delete("test.txt");
355 let mut proof = IntegrityProof::new(&action, "test_capsule".into(), 3600);
356
357 // Sign the proof
358 let signing_data = proof.signing_data();
359 proof.signature = key_store.sign(&signing_data).unwrap();
360
361 proof
362 }
363
364 #[test]
365 fn test_verify_valid_proof() {
366 let (mut auditor, key_store) = create_test_auditor();
367 let proof = create_test_proof(&key_store);
368
369 // First verification: should succeed
370 assert!(auditor.verify_proof(&proof).is_ok());
371 }
372
373 #[test]
374 fn test_replay_attack_prevention() {
375 let (mut auditor, key_store) = create_test_auditor();
376 let proof = create_test_proof(&key_store);
377
378 // First use: should succeed
379 assert!(auditor.verify_proof(&proof).is_ok());
380 assert_eq!(auditor.used_nonce_count(), 1);
381
382 // Second use: should FAIL (nonce reused)
383 let result = auditor.verify_proof(&proof);
384 assert!(result.is_err());
385 assert!(matches!(result, Err(AuditorError::NonceReused(_))));
386 }
387
388 #[test]
389 fn test_ttl_expiration() {
390 let (mut auditor, key_store) = create_test_auditor();
391
392 // Create proof with very short TTL
393 let action = Action::delete("test.txt");
394 let mut proof = IntegrityProof::new(&action, "test_capsule".into(), 0);
395
396 // Set timestamp to 10 seconds ago
397 proof.timestamp = chrono::Utc::now().timestamp() as u64 - 10;
398
399 // Sign it
400 let signing_data = proof.signing_data();
401 proof.signature = key_store.sign(&signing_data).unwrap();
402
403 // Should fail (expired)
404 let result = auditor.verify_proof(&proof);
405 assert!(result.is_err());
406 assert!(matches!(result, Err(AuditorError::ProofExpired { .. })));
407 }
408
409 #[test]
410 fn test_invalid_signature() {
411 let (mut auditor, key_store) = create_test_auditor();
412 let mut proof = create_test_proof(&key_store);
413
414 // Tamper with signature
415 proof.signature[0] ^= 0xFF;
416
417 // Should fail (invalid signature)
418 let result = auditor.verify_proof(&proof);
419 assert!(result.is_err());
420 assert!(matches!(result, Err(AuditorError::InvalidSignature)));
421 }
422
423 #[test]
424 fn test_verify_signature_readonly() {
425 let (auditor, key_store) = create_test_auditor();
426 let proof = create_test_proof(&key_store);
427
428 // Verify signature multiple times (should not consume nonce)
429 assert!(auditor.verify_signature(&proof).is_ok());
430 assert!(auditor.verify_signature(&proof).is_ok());
431 assert!(auditor.verify_signature(&proof).is_ok());
432
433 assert_eq!(auditor.used_nonce_count(), 0); // No nonce consumed
434 }
435
436 #[test]
437 fn test_nonce_tracking() {
438 let (mut auditor, key_store) = create_test_auditor();
439
440 // Create two different proofs
441 let proof1 = create_test_proof(&key_store);
442 let proof2 = create_test_proof(&key_store);
443
444 assert_eq!(auditor.used_nonce_count(), 0);
445
446 auditor.verify_proof(&proof1).unwrap();
447 assert_eq!(auditor.used_nonce_count(), 1);
448
449 auditor.verify_proof(&proof2).unwrap();
450 assert_eq!(auditor.used_nonce_count(), 2);
451 }
452
453 #[test]
454 fn test_clear_nonces() {
455 let (mut auditor, key_store) = create_test_auditor();
456
457 // Add some nonces
458 for _ in 0..5 {
459 let proof = create_test_proof(&key_store);
460 auditor.verify_proof(&proof).unwrap();
461 }
462
463 assert_eq!(auditor.used_nonce_count(), 5);
464
465 // Clear all nonces
466 auditor.clear_nonces().unwrap();
467 assert_eq!(auditor.used_nonce_count(), 0);
468 }
469
470 #[test]
471 fn test_different_actions_different_proofs() {
472 let (mut auditor, key_store) = create_test_auditor();
473
474 let action1 = Action::delete("file1.txt");
475 let mut proof1 = IntegrityProof::new(&action1, "capsule1".into(), 3600);
476 proof1.signature = key_store.sign(&proof1.signing_data()).unwrap();
477
478 let action2 = Action::delete("file2.txt");
479 let mut proof2 = IntegrityProof::new(&action2, "capsule2".into(), 3600);
480 proof2.signature = key_store.sign(&proof2.signing_data()).unwrap();
481
482 // Both should verify successfully (different nonces)
483 assert!(auditor.verify_proof(&proof1).is_ok());
484 assert!(auditor.verify_proof(&proof2).is_ok());
485 }
486
487 #[test]
488 fn test_is_nonce_used() {
489 let (mut auditor, key_store) = create_test_auditor();
490 let proof = create_test_proof(&key_store);
491
492 assert!(!auditor.is_nonce_used(&proof.nonce));
493
494 auditor.verify_proof(&proof).unwrap();
495
496 assert!(auditor.is_nonce_used(&proof.nonce));
497 }
498
499 #[test]
500 fn test_key_store_info() {
501 let (auditor, _) = create_test_auditor();
502 let info = auditor.key_store_info();
503
504 assert!(info.contains("SoftwareKeyStore"));
505 assert!(info.contains("Ed25519"));
506 }
507}