oxify_authz/
quantum.rs

1//! # Quantum-Safe Cryptography
2//!
3//! Post-quantum cryptographic primitives for future-proofing authorization data.
4//!
5//! This module provides quantum-resistant algorithms for:
6//! - **Key Encapsulation**: Using ML-KEM (Kyber) for key exchange
7//! - **Digital Signatures**: Using ML-DSA (Dilithium) for signing tuples
8//! - **Hybrid Mode**: Classical + Post-Quantum for defense-in-depth
9//!
10//! ## NIST Post-Quantum Standards
11//!
12//! This implementation prepares for NIST's finalized post-quantum algorithms:
13//! - **ML-KEM-768** (Kyber): Key Encapsulation Mechanism
14//! - **ML-DSA-65** (Dilithium): Digital Signature Algorithm
15//! - **SLH-DSA** (SPHINCS+): Stateless Hash-Based Signatures (optional)
16//!
17//! ## Example
18//!
19//! ```no_run
20//! use oxify_authz::quantum::*;
21//!
22//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
23//! // Generate quantum-safe keypair
24//! let keypair = QuantumKeypair::generate()?;
25//!
26//! // Sign authorization tuple
27//! let tuple_data = b"user:alice|document:123|viewer";
28//! let signature = keypair.sign(tuple_data)?;
29//!
30//! // Verify signature
31//! assert!(keypair.verify(tuple_data, &signature)?);
32//! # Ok(())
33//! # }
34//! ```
35//!
36//! ## Security Notes
37//!
38//! - **Transition Strategy**: Use hybrid mode during migration period
39//! - **Key Rotation**: Rotate quantum keys every 90 days
40//! - **Algorithm Agility**: Abstract interface allows swapping algorithms
41//!
42//! ## Future Work
43//!
44//! When `pqcrypto` or `oqs` crates mature, replace placeholder with:
45//! ```ignore
46//! use pqcrypto_dilithium::dilithium5;
47//! use pqcrypto_kyber::kyber1024;
48//! ```
49
50use crate::{AuthzError, RelationTuple, Result};
51use serde::{Deserialize, Serialize};
52use std::time::SystemTime;
53
54/// Quantum-safe algorithm selection
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub enum QuantumAlgorithm {
57    /// ML-KEM-768 (Kyber) - NIST standard for key encapsulation
58    MlKem768,
59
60    /// ML-DSA-65 (Dilithium) - NIST standard for digital signatures
61    MlDsa65,
62
63    /// SLH-DSA (SPHINCS+) - Stateless hash-based signatures
64    SlhDsa,
65
66    /// Hybrid: Classical (Ed25519) + Post-Quantum (ML-DSA-65)
67    HybridEd25519MlDsa,
68}
69
70/// Quantum-safe keypair for signing authorization tuples
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct QuantumKeypair {
73    /// Algorithm used for this keypair
74    pub algorithm: QuantumAlgorithm,
75
76    /// Public key (shareable)
77    pub public_key: Vec<u8>,
78
79    /// Private key (keep secret)
80    #[serde(skip_serializing)]
81    secret_key: Vec<u8>,
82
83    /// Key creation timestamp
84    pub created_at: SystemTime,
85
86    /// Key expiration (recommended: 90 days)
87    pub expires_at: Option<SystemTime>,
88}
89
90impl QuantumKeypair {
91    /// Generate a new quantum-safe keypair
92    ///
93    /// # Security
94    ///
95    /// Uses system entropy for key generation. In production, ensure:
96    /// - Sufficient entropy pool (`/dev/urandom` on Linux)
97    /// - Hardware RNG if available (RDRAND on modern CPUs)
98    pub fn generate() -> Result<Self> {
99        Self::generate_with_algorithm(QuantumAlgorithm::HybridEd25519MlDsa)
100    }
101
102    /// Generate keypair with specific algorithm
103    pub fn generate_with_algorithm(algorithm: QuantumAlgorithm) -> Result<Self> {
104        let (public_key, secret_key) = match algorithm {
105            QuantumAlgorithm::MlKem768 => {
106                // Placeholder: In production, use pqcrypto_kyber::kyber768
107                // let (pk, sk) = kyber768::keypair();
108                // (pk.as_bytes().to_vec(), sk.as_bytes().to_vec())
109
110                // Simulated key sizes (Kyber-768: pk=1184 bytes, sk=2400 bytes)
111                (vec![0u8; 1184], vec![0u8; 2400])
112            }
113            QuantumAlgorithm::MlDsa65 => {
114                // Placeholder: In production, use pqcrypto_dilithium::dilithium5
115                // let (pk, sk) = dilithium5::keypair();
116
117                // Simulated key sizes (Dilithium-5: pk=2592 bytes, sk=4864 bytes)
118                (vec![0u8; 2592], vec![0u8; 4864])
119            }
120            QuantumAlgorithm::SlhDsa => {
121                // Placeholder: SPHINCS+ key sizes (varies by parameter set)
122                (vec![0u8; 64], vec![0u8; 128])
123            }
124            QuantumAlgorithm::HybridEd25519MlDsa => {
125                // Hybrid: Classical Ed25519 (32 bytes) + Dilithium-5
126                // Combined public key: 32 + 2592 = 2624 bytes
127                (vec![0u8; 2624], vec![0u8; 4896]) // 32 + 4864
128            }
129        };
130
131        let created_at = SystemTime::now();
132        let expires_at = Some(created_at + std::time::Duration::from_secs(90 * 24 * 3600)); // 90 days
133
134        Ok(Self {
135            algorithm,
136            public_key,
137            secret_key,
138            created_at,
139            expires_at,
140        })
141    }
142
143    /// Sign data with quantum-safe algorithm
144    ///
145    /// # Arguments
146    ///
147    /// * `data` - Data to sign (e.g., serialized RelationTuple)
148    ///
149    /// # Returns
150    ///
151    /// Digital signature resistant to quantum attacks
152    pub fn sign(&self, data: &[u8]) -> Result<QuantumSignature> {
153        // Check key expiration
154        if let Some(expires_at) = self.expires_at {
155            if SystemTime::now() > expires_at {
156                return Err(AuthzError::PermissionDenied(
157                    "Quantum key expired".to_string(),
158                ));
159            }
160        }
161
162        let signature_bytes = match self.algorithm {
163            QuantumAlgorithm::MlKem768 => {
164                return Err(AuthzError::InvalidTuple(
165                    "ML-KEM is for key encapsulation, not signatures".to_string(),
166                ));
167            }
168            QuantumAlgorithm::MlDsa65 => {
169                // Placeholder: In production, use dilithium5::sign()
170                // let sig = dilithium5::sign(data, &sk);
171
172                // Simulated signature (Dilithium-5: ~4627 bytes)
173                let mut sig = data.to_vec();
174                sig.extend_from_slice(&self.secret_key[..64]); // Placeholder
175                sig
176            }
177            QuantumAlgorithm::SlhDsa => {
178                // SPHINCS+ signatures (~49KB for high security)
179                let mut sig = data.to_vec();
180                sig.extend_from_slice(&[0u8; 256]); // Placeholder
181                sig
182            }
183            QuantumAlgorithm::HybridEd25519MlDsa => {
184                // Hybrid: Sign with both algorithms and concatenate
185                // Classical Ed25519 sig (64 bytes) + Dilithium-5 sig (~4627 bytes)
186                let mut sig = vec![0u8; 64]; // Ed25519 placeholder
187                sig.extend_from_slice(data);
188                sig.extend_from_slice(&self.secret_key[..64]); // Dilithium placeholder
189                sig
190            }
191        };
192
193        Ok(QuantumSignature {
194            algorithm: self.algorithm,
195            signature: signature_bytes,
196            signed_at: SystemTime::now(),
197        })
198    }
199
200    /// Verify a quantum-safe signature
201    ///
202    /// # Arguments
203    ///
204    /// * `data` - Original data that was signed
205    /// * `signature` - Signature to verify
206    ///
207    /// # Returns
208    ///
209    /// `true` if signature is valid, `false` otherwise
210    pub fn verify(&self, data: &[u8], signature: &QuantumSignature) -> Result<bool> {
211        if signature.algorithm != self.algorithm {
212            return Ok(false);
213        }
214
215        // In production, use actual verification algorithms:
216        // - dilithium5::verify()
217        // - sphincs::verify()
218        // - Ed25519::verify() + dilithium5::verify() for hybrid
219
220        // Placeholder: Simple check that signature contains data
221        Ok(signature.signature.windows(data.len()).any(|w| w == data))
222    }
223
224    /// Check if key needs rotation
225    pub fn needs_rotation(&self) -> bool {
226        if let Some(expires_at) = self.expires_at {
227            // Rotate 7 days before expiration
228            let rotation_threshold = expires_at - std::time::Duration::from_secs(7 * 24 * 3600);
229            SystemTime::now() >= rotation_threshold
230        } else {
231            false
232        }
233    }
234}
235
236/// Quantum-safe digital signature
237#[derive(Debug, Clone, Serialize, Deserialize)]
238pub struct QuantumSignature {
239    /// Algorithm used to generate this signature
240    pub algorithm: QuantumAlgorithm,
241
242    /// Signature bytes
243    pub signature: Vec<u8>,
244
245    /// Timestamp when signature was created
246    pub signed_at: SystemTime,
247}
248
249impl QuantumSignature {
250    /// Get signature size in bytes
251    pub fn size_bytes(&self) -> usize {
252        self.signature.len()
253    }
254}
255
256/// Quantum-safe tuple wrapper with signature
257///
258/// Provides tamper-proof authorization tuples using post-quantum signatures
259#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct SignedRelationTuple {
261    /// The relation tuple
262    pub tuple: RelationTuple,
263
264    /// Quantum-safe signature over the tuple
265    pub signature: QuantumSignature,
266
267    /// Public key fingerprint (first 32 bytes of hash)
268    pub key_fingerprint: Vec<u8>,
269}
270
271impl SignedRelationTuple {
272    /// Create a signed relation tuple
273    pub fn sign(tuple: RelationTuple, keypair: &QuantumKeypair) -> Result<Self> {
274        // Serialize tuple for signing
275        let tuple_bytes = serde_json::to_vec(&tuple)
276            .map_err(|e| AuthzError::InvalidTuple(format!("Serialization failed: {}", e)))?;
277
278        let signature = keypair.sign(&tuple_bytes)?;
279
280        // Create fingerprint from public key
281        let key_fingerprint = keypair.public_key.iter().take(32).copied().collect();
282
283        Ok(Self {
284            tuple,
285            signature,
286            key_fingerprint,
287        })
288    }
289
290    /// Verify the signature on this tuple
291    pub fn verify(&self, keypair: &QuantumKeypair) -> Result<bool> {
292        // Check fingerprint matches
293        let expected_fingerprint: Vec<u8> = keypair.public_key.iter().take(32).copied().collect();
294        if self.key_fingerprint != expected_fingerprint {
295            return Ok(false);
296        }
297
298        // Serialize tuple and verify signature
299        let tuple_bytes = serde_json::to_vec(&self.tuple)
300            .map_err(|e| AuthzError::InvalidTuple(format!("Serialization failed: {}", e)))?;
301
302        keypair.verify(&tuple_bytes, &self.signature)
303    }
304}
305
306/// Quantum key rotation manager
307///
308/// Handles automatic rotation of quantum-safe keys
309#[derive(Debug)]
310pub struct QuantumKeyManager {
311    /// Current active keypair
312    current_keypair: QuantumKeypair,
313
314    /// Previous keypair (for grace period during rotation)
315    previous_keypair: Option<QuantumKeypair>,
316
317    /// Rotation interval in seconds (default: 90 days)
318    #[allow(dead_code)]
319    rotation_interval: u64,
320}
321
322impl QuantumKeyManager {
323    /// Create a new key manager with initial keypair
324    pub fn new(algorithm: QuantumAlgorithm) -> Result<Self> {
325        Ok(Self {
326            current_keypair: QuantumKeypair::generate_with_algorithm(algorithm)?,
327            previous_keypair: None,
328            rotation_interval: 90 * 24 * 3600, // 90 days
329        })
330    }
331
332    /// Get the current signing keypair
333    pub fn current_keypair(&self) -> &QuantumKeypair {
334        &self.current_keypair
335    }
336
337    /// Rotate keys if needed
338    ///
339    /// Returns `true` if rotation occurred
340    pub fn rotate_if_needed(&mut self) -> Result<bool> {
341        if self.current_keypair.needs_rotation() {
342            self.rotate_keys()?;
343            Ok(true)
344        } else {
345            Ok(false)
346        }
347    }
348
349    /// Force key rotation
350    pub fn rotate_keys(&mut self) -> Result<()> {
351        let new_keypair = QuantumKeypair::generate_with_algorithm(self.current_keypair.algorithm)?;
352
353        // Move current to previous (grace period for verification)
354        self.previous_keypair = Some(self.current_keypair.clone());
355        self.current_keypair = new_keypair;
356
357        Ok(())
358    }
359
360    /// Verify a signature using current or previous keypair
361    ///
362    /// Allows grace period during key rotation
363    pub fn verify_any(&self, data: &[u8], signature: &QuantumSignature) -> Result<bool> {
364        // Try current keypair first
365        if self.current_keypair.verify(data, signature)? {
366            return Ok(true);
367        }
368
369        // Try previous keypair if available
370        if let Some(ref prev) = self.previous_keypair {
371            if prev.verify(data, signature)? {
372                return Ok(true);
373            }
374        }
375
376        Ok(false)
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use crate::Subject;
384
385    #[test]
386    fn test_quantum_keypair_generation() {
387        let keypair = QuantumKeypair::generate().unwrap();
388        assert_eq!(keypair.algorithm, QuantumAlgorithm::HybridEd25519MlDsa);
389        assert!(!keypair.public_key.is_empty());
390        assert!(keypair.expires_at.is_some());
391    }
392
393    #[test]
394    fn test_quantum_signing() {
395        let keypair = QuantumKeypair::generate_with_algorithm(QuantumAlgorithm::MlDsa65).unwrap();
396        let data = b"user:alice|document:123|viewer";
397
398        let signature = keypair.sign(data).unwrap();
399        assert!(keypair.verify(data, &signature).unwrap());
400
401        // Tampered data should fail
402        let tampered = b"user:alice|document:456|owner";
403        assert!(!keypair.verify(tampered, &signature).unwrap());
404    }
405
406    #[test]
407    fn test_signed_relation_tuple() {
408        let keypair = QuantumKeypair::generate().unwrap();
409        let tuple = RelationTuple::new(
410            "document",
411            "viewer",
412            "123",
413            Subject::User("alice".to_string()),
414        );
415
416        let signed = SignedRelationTuple::sign(tuple, &keypair).unwrap();
417        assert!(signed.verify(&keypair).unwrap());
418    }
419
420    #[test]
421    fn test_key_rotation() {
422        let mut manager = QuantumKeyManager::new(QuantumAlgorithm::MlDsa65).unwrap();
423
424        let data = b"test data";
425        let sig1 = manager.current_keypair().sign(data).unwrap();
426
427        // Force rotation
428        manager.rotate_keys().unwrap();
429
430        // Old signature should still verify (grace period)
431        assert!(manager.verify_any(data, &sig1).unwrap());
432
433        // New signatures use new key
434        let sig2 = manager.current_keypair().sign(data).unwrap();
435        assert!(manager.verify_any(data, &sig2).unwrap());
436    }
437
438    #[test]
439    fn test_hybrid_algorithm() {
440        let keypair =
441            QuantumKeypair::generate_with_algorithm(QuantumAlgorithm::HybridEd25519MlDsa).unwrap();
442
443        let data = b"hybrid test data";
444        let signature = keypair.sign(data).unwrap();
445
446        // Hybrid signature should be larger (Ed25519 + Dilithium)
447        assert!(signature.size_bytes() > 64);
448        assert!(keypair.verify(data, &signature).unwrap());
449    }
450
451    #[test]
452    fn test_key_expiration() {
453        let mut keypair = QuantumKeypair::generate().unwrap();
454
455        // Set expiration to past
456        keypair.expires_at = Some(SystemTime::now() - std::time::Duration::from_secs(1));
457
458        let data = b"expired key test";
459        let result = keypair.sign(data);
460
461        // Should fail with expired key
462        assert!(result.is_err());
463    }
464
465    #[test]
466    fn test_algorithm_compatibility() {
467        let keypair = QuantumKeypair::generate_with_algorithm(QuantumAlgorithm::MlDsa65).unwrap();
468        let data = b"test";
469        let sig = keypair.sign(data).unwrap();
470
471        // Create keypair with different algorithm
472        let other_keypair =
473            QuantumKeypair::generate_with_algorithm(QuantumAlgorithm::SlhDsa).unwrap();
474
475        // Verification should fail due to algorithm mismatch
476        assert!(!other_keypair.verify(data, &sig).unwrap());
477    }
478}