nonce_auth/nonce/
credential_verifier.rs

1use crate::NonceCredential;
2use crate::nonce::error::NonceError;
3use crate::nonce::storage::NonceStorage;
4use crate::nonce::time_utils::{current_timestamp, is_outside_window};
5use base64::Engine;
6use hmac::{Hmac, Mac};
7use sha2::Sha256;
8use std::future::Future;
9use std::pin::Pin;
10use std::sync::Arc;
11use std::time::Duration;
12
13type HmacSha256 = Hmac<Sha256>;
14
15/// A function that provides secrets dynamically based on context.
16type SecretProviderFn = Box<
17    dyn for<'a> Fn(
18            Option<&'a str>,
19        )
20            -> Pin<Box<dyn Future<Output = Result<Vec<u8>, NonceError>> + Send + 'a>>
21        + Send
22        + Sync,
23>;
24
25/// Verifier for cryptographic credentials.
26///
27/// `CredentialVerifier` provides a fluent interface for configuring and verifying
28/// `NonceCredential` instances. It supports various verification methods including
29/// dynamic secret resolution and context isolation.
30///
31/// This verifier is `Send + Sync` and can be safely shared across threads using
32/// `Arc<CredentialVerifier>`, making it suitable for server environments with
33/// high concurrency requirements.
34pub struct CredentialVerifier {
35    storage: Arc<dyn NonceStorage>,
36    secret: Option<Vec<u8>>,
37    secret_provider: Option<SecretProviderFn>,
38    context: Option<String>,
39    storage_ttl: Duration,
40    time_window: Duration,
41}
42
43impl CredentialVerifier {
44    /// Creates a new `CredentialVerifier` with default settings.
45    ///
46    /// # Arguments
47    ///
48    /// * `storage` - The storage backend for nonce tracking
49    ///
50    /// # Default Settings
51    ///
52    /// - Storage TTL: 5 minutes
53    /// - Time window: 1 minute
54    /// - Signature algorithm: HMAC-SHA256
55    ///
56    /// # Example
57    ///
58    /// ```rust
59    /// use nonce_auth::{CredentialVerifier, storage::MemoryStorage};
60    /// use std::sync::Arc;
61    ///
62    /// # async fn example() -> Result<(), nonce_auth::NonceError> {
63    /// let storage = Arc::new(MemoryStorage::new());
64    /// let verifier = CredentialVerifier::new(storage);
65    /// # Ok(())
66    /// # }
67    /// ```
68    pub fn new(storage: Arc<dyn NonceStorage>) -> Self {
69        Self {
70            storage,
71            secret: None,
72            secret_provider: None,
73            context: None,
74            storage_ttl: Duration::from_secs(300), // 5 minutes
75            time_window: Duration::from_secs(60),  // 1 minute
76        }
77    }
78
79    /// Sets the shared secret for verification.
80    pub fn with_secret(mut self, secret: &[u8]) -> Self {
81        self.secret = Some(secret.to_vec());
82        self
83    }
84
85    /// Sets the context for nonce isolation.
86    ///
87    /// Contexts allow the same nonce to be used across different scopes
88    /// while still preventing replay attacks within each scope.
89    pub fn with_context(mut self, context: Option<&str>) -> Self {
90        self.context = context.map(|s| s.to_string());
91        self
92    }
93
94    /// Sets the storage TTL (time-to-live) for nonce records.
95    ///
96    /// This determines how long nonces are kept in storage before
97    /// they can be cleaned up. This should be longer than the
98    /// time window to ensure nonces aren't reused.
99    ///
100    /// # Arguments
101    ///
102    /// * `storage_ttl` - Duration for which nonces should be stored
103    ///
104    /// # Example
105    ///
106    /// ```rust
107    /// # use nonce_auth::{CredentialVerifier, storage::MemoryStorage};
108    /// # use std::sync::Arc;
109    /// # use std::time::Duration;
110    /// # async fn example() -> Result<(), nonce_auth::NonceError> {
111    /// # let storage = Arc::new(MemoryStorage::new());
112    /// # let credential = nonce_auth::CredentialBuilder::new(b"secret").sign(b"payload")?;
113    /// let verifier = CredentialVerifier::new(storage)
114    ///     .with_storage_ttl(Duration::from_secs(600)); // 10 minutes
115    /// # Ok(())
116    /// # }
117    /// ```
118    pub fn with_storage_ttl(mut self, storage_ttl: Duration) -> Self {
119        self.storage_ttl = storage_ttl;
120        self
121    }
122
123    /// Sets the time window for timestamp validation.
124    pub fn with_time_window(mut self, time_window: Duration) -> Self {
125        self.time_window = time_window;
126        self
127    }
128
129    /// Sets a dynamic secret provider for verification.
130    ///
131    /// This allows secrets to be resolved dynamically based on the context,
132    /// enabling multi-user scenarios where each user has a different secret.
133    ///
134    /// # Arguments
135    ///
136    /// * `provider` - Function that resolves the secret based on context
137    ///
138    /// # Example
139    ///
140    /// ```rust
141    /// # use nonce_auth::{CredentialVerifier, storage::MemoryStorage, NonceError};
142    /// # use std::sync::Arc;
143    /// # async fn example() -> Result<(), NonceError> {
144    /// # let storage = Arc::new(MemoryStorage::new());
145    /// # let credential = nonce_auth::CredentialBuilder::new(b"user123_secret").sign(b"payload")?;
146    /// let verifier = CredentialVerifier::new(storage)
147    ///     .with_context(Some("user123"))
148    ///     .with_secret_provider(|context| {
149    ///         let owned_context = context.map(|s| s.to_owned());
150    ///         async move {
151    ///             match owned_context.as_deref() {
152    ///                 Some("user123") => Ok(b"user123_secret".to_vec()),
153    ///                 Some("user456") => Ok(b"user456_secret".to_vec()),
154    ///                 _ => Err(NonceError::CryptoError("Unknown user".to_string())),
155    ///             }
156    ///         }
157    ///     });
158    /// # Ok(())
159    /// # }
160    /// ```
161    pub fn with_secret_provider<P, F>(mut self, provider: P) -> Self
162    where
163        P: for<'a> Fn(Option<&'a str>) -> F + Send + Sync + 'static,
164        F: Future<Output = Result<Vec<u8>, NonceError>> + Send + 'static,
165    {
166        self.secret_provider = Some(Box::new(move |context| Box::pin(provider(context))));
167        self
168    }
169
170    /// Verifies a credential with a standard payload.
171    ///
172    /// This method performs the complete verification process:
173    /// 1. Validates the timestamp is within the time window
174    /// 2. Checks that the nonce hasn't been used before
175    /// 3. Verifies the HMAC signature
176    /// 4. Stores the nonce to prevent replay attacks
177    ///
178    /// # Arguments
179    ///
180    /// * `credential` - The credential to verify
181    /// * `payload` - The data that was signed
182    ///
183    /// # Returns
184    ///
185    /// `Ok(())` if verification succeeds, or a `NonceError` if verification fails.
186    ///
187    /// # Example
188    ///
189    /// ```rust
190    /// # use nonce_auth::{CredentialVerifier, storage::MemoryStorage};
191    /// # use std::sync::Arc;
192    /// # async fn example() -> Result<(), nonce_auth::NonceError> {
193    /// # let storage = Arc::new(MemoryStorage::new());
194    /// # let credential = nonce_auth::CredentialBuilder::new(b"secret").sign(b"payload")?;
195    /// let result = CredentialVerifier::new(storage)
196    ///     .with_secret(b"shared_secret")
197    ///     .verify(&credential, b"payload")
198    ///     .await?;
199    /// # Ok(())
200    /// # }
201    /// ```
202    pub async fn verify(
203        self,
204        credential: &NonceCredential,
205        payload: &[u8],
206    ) -> Result<(), NonceError> {
207        let secret = if let Some(ref provider) = self.secret_provider {
208            provider(self.context.as_deref()).await?
209        } else if let Some(ref secret) = self.secret {
210            secret.clone()
211        } else {
212            return Err(NonceError::CryptoError(
213                "Either secret or secret_provider must be set before verification".to_string(),
214            ));
215        };
216
217        self.verify_internal(credential, &secret, |secret| {
218            self.verify_signature(
219                secret,
220                credential.timestamp,
221                &credential.nonce,
222                payload,
223                &credential.signature,
224            )
225        })
226        .await
227    }
228
229    /// Verifies a credential with structured data components.
230    ///
231    /// This method verifies credentials that were created using `sign_structured()`.
232    /// The components must be provided in the same order as during signing.
233    ///
234    /// # Arguments
235    ///
236    /// * `credential` - The credential to verify
237    /// * `components` - Array of data components in the same order as signing
238    ///
239    /// # Example
240    ///
241    /// ```rust
242    /// # use nonce_auth::{CredentialVerifier, storage::MemoryStorage};
243    /// # use std::sync::Arc;
244    /// # async fn example() -> Result<(), nonce_auth::NonceError> {
245    /// # let storage = Arc::new(MemoryStorage::new());
246    /// # let credential = nonce_auth::CredentialBuilder::new(b"secret")
247    /// #     .sign_structured(&[b"user123", b"action", b"data"])?;
248    /// let result = CredentialVerifier::new(storage)
249    ///     .with_secret(b"shared_secret")
250    ///     .verify_structured(&credential, &[b"user123", b"action", b"data"])
251    ///     .await?;
252    /// # Ok(())
253    /// # }
254    /// ```
255    pub async fn verify_structured(
256        self,
257        credential: &NonceCredential,
258        components: &[&[u8]],
259    ) -> Result<(), NonceError> {
260        let secret = if let Some(ref provider) = self.secret_provider {
261            provider(self.context.as_deref()).await?
262        } else if let Some(ref secret) = self.secret {
263            secret.clone()
264        } else {
265            return Err(NonceError::CryptoError(
266                "Either secret or secret_provider must be set before verification".to_string(),
267            ));
268        };
269
270        self.verify_internal(credential, &secret, |secret| {
271            self.verify_structured_signature(
272                secret,
273                credential.timestamp,
274                &credential.nonce,
275                components,
276                &credential.signature,
277            )
278        })
279        .await
280    }
281
282    /// Verifies a credential using a custom MAC construction function.
283    ///
284    /// This method provides maximum flexibility by allowing custom MAC verification
285    /// that matches the signing process used with `sign_with()`.
286    ///
287    /// # Arguments
288    ///
289    /// * `credential` - The credential to verify
290    /// * `mac_fn` - Function that constructs the MAC for verification
291    ///
292    /// # Example
293    ///
294    /// ```rust
295    /// # use nonce_auth::{CredentialVerifier, storage::MemoryStorage};
296    /// # use std::sync::Arc;
297    /// # use hmac::Mac;
298    /// # async fn example() -> Result<(), nonce_auth::NonceError> {
299    /// # let storage = Arc::new(MemoryStorage::new());
300    /// # let credential = nonce_auth::CredentialBuilder::new(b"secret")
301    /// #     .sign_with(|mac, timestamp, nonce| {
302    /// #         mac.update(b"prefix:");
303    /// #         mac.update(timestamp.as_bytes());
304    /// #         mac.update(b":nonce:");
305    /// #         mac.update(nonce.as_bytes());
306    /// #         mac.update(b":custom_data");
307    /// #     })?;
308    /// let result = CredentialVerifier::new(storage)
309    ///     .with_secret(b"shared_secret")
310    ///     .verify_with(&credential, |mac| {
311    ///         mac.update(b"prefix:");
312    ///         mac.update(credential.timestamp.to_string().as_bytes());
313    ///         mac.update(b":nonce:");
314    ///         mac.update(credential.nonce.as_bytes());
315    ///         mac.update(b":custom_data");
316    ///     })
317    ///     .await?;
318    /// # Ok(())
319    /// # }
320    /// ```
321    pub async fn verify_with<F>(
322        self,
323        credential: &NonceCredential,
324        mac_fn: F,
325    ) -> Result<(), NonceError>
326    where
327        F: FnOnce(&mut HmacSha256),
328    {
329        let secret = if let Some(ref provider) = self.secret_provider {
330            provider(self.context.as_deref()).await?
331        } else if let Some(ref secret) = self.secret {
332            secret.clone()
333        } else {
334            return Err(NonceError::CryptoError(
335                "Either secret or secret_provider must be set before verification".to_string(),
336            ));
337        };
338
339        self.verify_internal(credential, &secret, |secret| {
340            self.verify_custom_signature(
341                secret,
342                credential.timestamp,
343                &credential.nonce,
344                &credential.signature,
345                mac_fn,
346            )
347        })
348        .await
349    }
350
351    /// Verifies a standard HMAC signature for timestamp, nonce, and payload.
352    fn verify_signature(
353        &self,
354        secret: &[u8],
355        timestamp: u64,
356        nonce: &str,
357        payload: &[u8],
358        signature: &str,
359    ) -> Result<bool, NonceError> {
360        let mut mac = HmacSha256::new_from_slice(secret)
361            .map_err(|e| NonceError::CryptoError(format!("Invalid secret key: {e}")))?;
362
363        mac.update(timestamp.to_string().as_bytes());
364        mac.update(nonce.as_bytes());
365        mac.update(payload);
366
367        let expected_signature =
368            base64::engine::general_purpose::STANDARD.encode(mac.finalize().into_bytes());
369        Ok(expected_signature == signature)
370    }
371
372    /// Verifies a structured signature for multiple data components.
373    fn verify_structured_signature(
374        &self,
375        secret: &[u8],
376        timestamp: u64,
377        nonce: &str,
378        components: &[&[u8]],
379        signature: &str,
380    ) -> Result<bool, NonceError> {
381        let mut mac = HmacSha256::new_from_slice(secret)
382            .map_err(|e| NonceError::CryptoError(format!("Invalid secret key: {e}")))?;
383
384        mac.update(timestamp.to_string().as_bytes());
385        mac.update(nonce.as_bytes());
386        for component in components {
387            mac.update(component);
388        }
389
390        let expected_signature =
391            base64::engine::general_purpose::STANDARD.encode(mac.finalize().into_bytes());
392        Ok(expected_signature == signature)
393    }
394
395    /// Verifies a custom signature using a user-provided MAC construction function.
396    fn verify_custom_signature<F>(
397        &self,
398        secret: &[u8],
399        _timestamp: u64,
400        _nonce: &str,
401        signature: &str,
402        mac_fn: F,
403    ) -> Result<bool, NonceError>
404    where
405        F: FnOnce(&mut HmacSha256),
406    {
407        let mut mac = HmacSha256::new_from_slice(secret)
408            .map_err(|e| NonceError::CryptoError(format!("Invalid secret key: {e}")))?;
409
410        mac_fn(&mut mac);
411
412        let expected_signature =
413            base64::engine::general_purpose::STANDARD.encode(mac.finalize().into_bytes());
414        Ok(expected_signature == signature)
415    }
416
417    /// Internal verification logic shared by all verification methods.
418    async fn verify_internal<F>(
419        &self,
420        credential: &NonceCredential,
421        secret: &[u8],
422        verify_signature: F,
423    ) -> Result<(), NonceError>
424    where
425        F: FnOnce(&[u8]) -> Result<bool, NonceError>,
426    {
427        // 1. Validate timestamp is within time window
428        let current_time = current_timestamp()?;
429        if is_outside_window(credential.timestamp, current_time, self.time_window) {
430            return Err(NonceError::TimestampOutOfWindow);
431        }
432
433        // 2. Verify signature FIRST (before consuming the nonce)
434        let signature_valid = verify_signature(secret)?;
435        if !signature_valid {
436            return Err(NonceError::InvalidSignature);
437        }
438
439        // 3. Check nonce uniqueness and store if unique (only after signature is verified)
440        let context_str = self.context.as_deref();
441
442        // Check if nonce already exists - if it does, it's a duplicate regardless of TTL
443        if self.storage.exists(&credential.nonce, context_str).await? {
444            return Err(NonceError::DuplicateNonce);
445        }
446
447        // Store the nonce (only if it doesn't exist)
448        self.storage
449            .set(&credential.nonce, context_str, self.storage_ttl)
450            .await?;
451
452        Ok(())
453    }
454}
455
456#[cfg(test)]
457mod tests {
458    use super::*;
459    use crate::CredentialBuilder;
460    use crate::nonce::storage::MemoryStorage;
461    use std::time::Duration;
462
463    #[tokio::test]
464    async fn test_basic_verification() {
465        let storage = Arc::new(MemoryStorage::new());
466        let secret = b"test_secret";
467        let payload = b"test_payload";
468
469        let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
470
471        let result = CredentialVerifier::new(storage)
472            .with_secret(secret)
473            .verify(&credential, payload)
474            .await;
475
476        assert!(result.is_ok());
477    }
478
479    #[tokio::test]
480    async fn test_duplicate_nonce_rejection() {
481        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
482        let secret = b"test_secret";
483        let payload = b"test_payload";
484
485        let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
486
487        // First verification should succeed
488        CredentialVerifier::new(Arc::clone(&storage))
489            .with_secret(secret)
490            .verify(&credential, payload)
491            .await
492            .unwrap();
493
494        // Second verification should fail
495        let result = CredentialVerifier::new(storage)
496            .with_secret(secret)
497            .verify(&credential, payload)
498            .await;
499
500        assert!(matches!(result, Err(NonceError::DuplicateNonce)));
501    }
502
503    #[tokio::test]
504    async fn test_invalid_signature() {
505        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
506        let payload = b"test_payload";
507
508        let credential = CredentialBuilder::new(b"secret1").sign(payload).unwrap();
509
510        let result = CredentialVerifier::new(storage)
511            .with_secret(b"secret2") // Different secret
512            .verify(&credential, payload)
513            .await;
514
515        assert!(matches!(result, Err(NonceError::InvalidSignature)));
516    }
517
518    #[tokio::test]
519    async fn test_timestamp_out_of_window() {
520        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
521        let secret = b"test_secret";
522        let payload = b"test_payload";
523
524        // Create credential with old timestamp
525        let old_timestamp = (current_timestamp().unwrap() - 3600) as u64; // 1 hour ago
526        let credential = CredentialBuilder::new(secret)
527            .with_time_provider(move || Ok(old_timestamp))
528            .sign(payload)
529            .unwrap();
530
531        let result = CredentialVerifier::new(storage)
532            .with_secret(secret)
533            .with_time_window(Duration::from_secs(60)) // 1 minute window
534            .verify(&credential, payload)
535            .await;
536
537        assert!(matches!(result, Err(NonceError::TimestampOutOfWindow)));
538    }
539
540    #[tokio::test]
541    async fn test_context_isolation() {
542        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
543        let secret = b"test_secret";
544        let payload = b"test_payload";
545
546        let credential = CredentialBuilder::new(secret).sign(payload).unwrap();
547
548        // Use in context1
549        CredentialVerifier::new(Arc::clone(&storage))
550            .with_secret(secret)
551            .with_context(Some("context1"))
552            .verify(&credential, payload)
553            .await
554            .unwrap();
555
556        // Should work in context2 (different context)
557        let result = CredentialVerifier::new(Arc::clone(&storage))
558            .with_secret(secret)
559            .with_context(Some("context2"))
560            .verify(&credential, payload)
561            .await;
562
563        assert!(result.is_ok());
564
565        // Should fail in context1 again (same context)
566        let result = CredentialVerifier::new(storage)
567            .with_secret(secret)
568            .with_context(Some("context1"))
569            .verify(&credential, payload)
570            .await;
571
572        assert!(matches!(result, Err(NonceError::DuplicateNonce)));
573    }
574
575    #[tokio::test]
576    async fn test_structured_verification() {
577        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
578        let secret = b"test_secret";
579        let components = [b"part1".as_slice(), b"part2", b"part3"];
580
581        let credential = CredentialBuilder::new(secret)
582            .sign_structured(&components)
583            .unwrap();
584
585        let result = CredentialVerifier::new(storage)
586            .with_secret(secret)
587            .verify_structured(&credential, &components)
588            .await;
589
590        assert!(result.is_ok());
591    }
592
593    #[tokio::test]
594    async fn test_custom_verification() {
595        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
596        let secret = b"test_secret";
597
598        let credential = CredentialBuilder::new(secret)
599            .sign_with(|mac, timestamp, nonce| {
600                mac.update(b"prefix:");
601                mac.update(timestamp.as_bytes());
602                mac.update(b":nonce:");
603                mac.update(nonce.as_bytes());
604                mac.update(b":custom");
605            })
606            .unwrap();
607
608        let result = CredentialVerifier::new(storage)
609            .with_secret(secret)
610            .verify_with(&credential, |mac| {
611                mac.update(b"prefix:");
612                mac.update(credential.timestamp.to_string().as_bytes());
613                mac.update(b":nonce:");
614                mac.update(credential.nonce.as_bytes());
615                mac.update(b":custom");
616            })
617            .await;
618
619        assert!(result.is_ok());
620    }
621
622    #[tokio::test]
623    async fn test_secret_provider() {
624        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
625        let payload = b"test_payload";
626
627        let credential = CredentialBuilder::new(b"user123_secret")
628            .sign(payload)
629            .unwrap();
630
631        let result = CredentialVerifier::new(storage)
632            .with_context(Some("user123"))
633            .with_secret_provider(|context| {
634                let owned_context = context.map(|s| s.to_owned());
635                async move {
636                    match owned_context.as_deref() {
637                        Some("user123") => Ok(b"user123_secret".to_vec()),
638                        Some("user456") => Ok(b"user456_secret".to_vec()),
639                        _ => Err(NonceError::CryptoError("Unknown user".to_string())),
640                    }
641                }
642            })
643            .verify(&credential, payload)
644            .await;
645
646        assert!(result.is_ok());
647    }
648
649    #[tokio::test]
650    async fn test_secret_provider_error() {
651        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
652        let payload = b"test_payload";
653
654        let credential = CredentialBuilder::new(b"secret").sign(payload).unwrap();
655
656        let result = CredentialVerifier::new(storage)
657            .with_context(Some("unknown_user"))
658            .with_secret_provider(|_context| async {
659                Err(NonceError::from_storage_message("User not found"))
660            })
661            .verify(&credential, payload)
662            .await;
663
664        assert!(matches!(result, Err(NonceError::StorageError(_))));
665    }
666
667    #[tokio::test]
668    async fn test_verification_without_secret() {
669        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
670        let payload = b"test_payload";
671
672        let credential = CredentialBuilder::new(b"secret").sign(payload).unwrap();
673
674        let result = CredentialVerifier::new(storage)
675            .verify(&credential, payload) // No secret set
676            .await;
677
678        assert!(matches!(result, Err(NonceError::CryptoError(_))));
679    }
680
681    #[test]
682    fn test_credential_verifier_implements_sync() {
683        // This test will only compile if CredentialVerifier implements Sync
684        fn assert_sync<T: Sync>() {}
685        assert_sync::<CredentialVerifier>();
686
687        // Test that it can actually be shared across threads
688        let storage: Arc<dyn NonceStorage> = Arc::new(MemoryStorage::new());
689        let verifier = Arc::new(CredentialVerifier::new(storage).with_secret(b"test"));
690
691        let verifier_clone = Arc::clone(&verifier);
692        let handle = std::thread::spawn(move || {
693            // This verifies the verifier can be moved to another thread
694            drop(verifier_clone);
695        });
696
697        handle.join().unwrap();
698        println!("CredentialVerifier successfully implements Sync!");
699    }
700}