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}