nonce_auth/
lib.rs

1//! A lightweight, secure nonce-based authentication library for Rust.
2//!
3//! This library provides a simple, robust solution for preventing replay attacks
4//! in APIs and other network services. It uses a combination of nonces, timestamps,
5//! and HMAC signatures to ensure that each request is unique and authentic.
6//!
7//! # Core Components
8//!
9//! - [`CredentialBuilder`] - Creates cryptographic credentials
10//! - [`CredentialVerifier`] - Verifies cryptographic credentials
11//! - [`NonceStorage`] - Pluggable storage backends
12//!
13//! # Quick Example
14//!
15//! ```rust
16//! use nonce_auth::{CredentialBuilder, CredentialVerifier, storage::MemoryStorage};
17//! use std::sync::Arc;
18//!
19//! # async fn example() -> Result<(), nonce_auth::NonceError> {
20//! // Create a credential
21//! let credential = CredentialBuilder::new(b"shared_secret")
22//!     .sign(b"important_data")?;
23//!
24//! // Verify the credential
25//! let storage = Arc::new(MemoryStorage::new());
26//! CredentialVerifier::new(storage)
27//!     .with_secret(b"shared_secret")
28//!     .verify(&credential, b"important_data")
29//!     .await?;
30//! # Ok(())
31//! # }
32//! ```
33//!
34//! For detailed configuration options, see [CONFIGURATION.md](https://github.com/kookyleo/nonce-auth/blob/main/docs/CONFIGURATION.md).
35
36use serde::{Deserialize, Serialize};
37
38pub mod nonce;
39pub mod signature {
40    //! Pluggable signature algorithms for cryptographic operations.
41    pub use crate::nonce::signature::*;
42}
43pub mod storage {
44    //! Pluggable storage backends for nonce persistence.
45    pub use crate::nonce::storage::*;
46}
47
48// Re-export key types for easy access.
49pub use nonce::{
50    BoxedCleanupStrategy,
51    CleanupStrategy,
52    // Configuration
53    ConfigPreset,
54    // Core architecture
55    CredentialBuilder,
56    CredentialVerifier,
57    CustomCleanupStrategy,
58
59    HybridCleanupStrategy,
60    MacLike,
61    // Storage and cleanup systems
62    MemoryStorage,
63    NonceConfig,
64
65    NonceEntry,
66    NonceError,
67    NonceGeneratorFn,
68    NonceStorage,
69    // Signature algorithms
70    SignatureAlgorithm,
71    StorageStats,
72    TimeProviderFn,
73};
74
75// Conditional exports based on features
76#[cfg(feature = "algo-hmac-sha256")]
77pub use nonce::{DefaultSignatureAlgorithm, create_default_algorithm};
78
79#[cfg(feature = "metrics")]
80pub use nonce::{
81    ErrorMetrics, InMemoryMetricsCollector, MetricEvent, MetricsCollector, MetricsTimer,
82    NoOpMetricsCollector, NonceMetrics, PerformanceMetrics,
83};
84
85/// A self-contained cryptographic credential used to authenticate a request.
86///
87/// This structure is generated by a [`CredentialBuilder`] and verified by a [`CredentialVerifier`].
88/// It is designed to be serialized and sent alongside your application's request data.
89///
90/// # Fields
91///
92/// - `timestamp`: Unix timestamp indicating when the credential was created.
93/// - `nonce`: A unique, single-use value to prevent replay attacks.
94/// - `signature`: An HMAC-SHA256 signature covering the timestamp, nonce, and user-defined payload.
95///
96/// # Example
97///
98/// ```rust
99/// use nonce_auth::CredentialBuilder;
100///
101/// let credential = CredentialBuilder::new(b"secret")
102///     .sign(b"data")?;
103///
104/// // Serialize for transmission
105/// let json = serde_json::to_string(&credential)?;
106/// # Ok::<(), Box<dyn std::error::Error>>(())
107/// ```
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct NonceCredential {
110    pub timestamp: u64,
111    pub nonce: String,
112    pub signature: String,
113}
114
115#[cfg(test)]
116mod tests {
117    use crate::{CredentialBuilder, CredentialVerifier, NonceError, storage::MemoryStorage};
118    use hmac::Mac;
119    use std::sync::Arc;
120
121    #[tokio::test]
122    async fn test_basic_credential_workflow() {
123        let secret = b"test_secret";
124        let payload = b"test_payload";
125        let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
126
127        // Create a credential
128        let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
129
130        // Verify the credential
131        let result = CredentialVerifier::new(storage)
132            .with_secret(secret)
133            .verify(&credential, payload)
134            .await;
135
136        assert!(result.is_ok());
137    }
138
139    #[tokio::test]
140    async fn test_nonce_replay_protection() {
141        let secret = b"test_secret";
142        let payload = b"test_payload";
143        let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
144
145        // Create a credential
146        let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
147
148        // First verification should succeed
149        CredentialVerifier::new(Arc::clone(&storage))
150            .with_secret(secret)
151            .verify(&credential, payload)
152            .await
153            .unwrap();
154
155        // Second verification should fail (replay attack)
156        let result = CredentialVerifier::new(storage)
157            .with_secret(secret)
158            .verify(&credential, payload)
159            .await;
160
161        assert!(matches!(result, Err(NonceError::DuplicateNonce)));
162    }
163
164    #[tokio::test]
165    async fn test_context_isolation() {
166        let secret = b"test_secret";
167        let payload = b"test_payload";
168        let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
169
170        // Create a credential
171        let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
172
173        // Use in context1
174        CredentialVerifier::new(Arc::clone(&storage))
175            .with_secret(secret)
176            .with_context(Some("context1"))
177            .verify(&credential, payload)
178            .await
179            .unwrap();
180
181        // Should work in context2 (different context)
182        let result = CredentialVerifier::new(Arc::clone(&storage))
183            .with_secret(secret)
184            .with_context(Some("context2"))
185            .verify(&credential, payload)
186            .await;
187
188        assert!(result.is_ok());
189
190        // Should fail in context1 again (same context)
191        let result = CredentialVerifier::new(storage)
192            .with_secret(secret)
193            .with_context(Some("context1"))
194            .verify(&credential, payload)
195            .await;
196
197        assert!(matches!(result, Err(NonceError::DuplicateNonce)));
198    }
199
200    #[tokio::test]
201    async fn test_invalid_signature() {
202        let payload = b"test_payload";
203        let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
204
205        // Create credential with one secret
206        let credential = CredentialBuilder::new(b"secret1").sign(payload).unwrap();
207
208        // Verify with different secret
209        let result = CredentialVerifier::new(storage)
210            .with_secret(b"secret2")
211            .verify(&credential, payload)
212            .await;
213
214        assert!(matches!(result, Err(NonceError::InvalidSignature)));
215    }
216
217    #[tokio::test]
218    async fn test_structured_signing() {
219        let secret = b"test_secret";
220        let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
221        let components = [b"part1".as_slice(), b"part2", b"part3"];
222
223        // Create credential with structured signing
224        let credential = CredentialBuilder::new(secret)
225            .sign_structured(&components)
226            .unwrap();
227
228        // Verify with structured verification
229        let result = CredentialVerifier::new(storage)
230            .with_secret(secret)
231            .verify_structured(&credential, &components)
232            .await;
233
234        assert!(result.is_ok());
235    }
236
237    #[tokio::test]
238    async fn test_custom_signing() {
239        let secret = b"test_secret";
240        let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
241
242        // Create credential with custom signing
243        let credential = CredentialBuilder::new(secret)
244            .sign_with(|mac, timestamp, nonce| {
245                mac.update(b"prefix:");
246                mac.update(timestamp.as_bytes());
247                mac.update(b":nonce:");
248                mac.update(nonce.as_bytes());
249                mac.update(b":custom");
250            })
251            .unwrap();
252
253        // Verify with matching custom verification
254        let result = CredentialVerifier::new(storage)
255            .with_secret(secret)
256            .verify_with(&credential, |mac| {
257                mac.update(b"prefix:");
258                mac.update(credential.timestamp.to_string().as_bytes());
259                mac.update(b":nonce:");
260                mac.update(credential.nonce.as_bytes());
261                mac.update(b":custom");
262            })
263            .await;
264
265        assert!(result.is_ok());
266    }
267
268    #[tokio::test]
269    async fn test_serialization() {
270        let secret = b"test_secret";
271        let payload = b"test_payload";
272
273        // Create credential
274        let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
275
276        // Test JSON serialization
277        let json = serde_json::to_string(&credential).unwrap();
278        let deserialized: super::NonceCredential = serde_json::from_str(&json).unwrap();
279
280        assert_eq!(credential.timestamp, deserialized.timestamp);
281        assert_eq!(credential.nonce, deserialized.nonce);
282        assert_eq!(credential.signature, deserialized.signature);
283    }
284
285    #[tokio::test]
286    async fn test_secret_provider() {
287        let payload = b"test_payload";
288        let storage: Arc<dyn crate::storage::NonceStorage> = Arc::new(MemoryStorage::new());
289
290        // Create credential with known secret
291        let credential = CredentialBuilder::new(b"user123_secret")
292            .sign(payload)
293            .unwrap();
294
295        // Verify using secret provider
296        let result = CredentialVerifier::new(storage)
297            .with_context(Some("user123"))
298            .with_secret_provider(|context| {
299                let owned_context = context.map(|s| s.to_owned());
300                async move {
301                    match owned_context.as_deref() {
302                        Some("user123") => Ok(b"user123_secret".to_vec()),
303                        Some("user456") => Ok(b"user456_secret".to_vec()),
304                        _ => Err(NonceError::CryptoError("Unknown user".to_string())),
305                    }
306                }
307            })
308            .verify(&credential, payload)
309            .await;
310
311        assert!(result.is_ok());
312    }
313}