ant_quic/
auth.rs

1//! Authentication module for P2P connections using Ed25519 keys
2//!
3//! This module provides authentication functionality for P2P connections,
4//! including peer identity verification, challenge-response authentication,
5//! and secure session establishment.
6
7use std::{
8    collections::HashMap,
9    sync::Arc,
10    time::{Duration, Instant, SystemTime},
11};
12
13use ed25519_dalek::{
14    Signature, Signer, SigningKey as Ed25519SecretKey, Verifier, VerifyingKey as Ed25519PublicKey,
15};
16
17use serde::{Deserialize, Serialize};
18use tokio::sync::RwLock;
19use tracing::{debug, error, info, warn};
20
21use crate::{
22    crypto::raw_public_keys::key_utils::{
23        derive_peer_id_from_public_key, public_key_from_bytes, public_key_to_bytes, verify_peer_id,
24    },
25    nat_traversal_api::PeerId,
26};
27
28/// Constant-time equality comparison for byte arrays
29fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
30    // For constant-time execution, we must not return early based on length
31    let len_equal = a.len() == b.len();
32
33    // Process up to the shorter length to avoid bounds issues
34    let min_len = a.len().min(b.len());
35    let mut result = 0u8;
36
37    // Compare bytes up to min length
38    for i in 0..min_len {
39        result |= a[i] ^ b[i];
40    }
41
42    // If lengths differ, ensure result is non-zero
43    if !len_equal {
44        result |= 1;
45    }
46
47    // Constant-time conversion to bool
48    result == 0
49}
50
51/// Authentication error types
52#[derive(Debug, thiserror::Error)]
53pub enum AuthError {
54    #[error("Invalid signature")]
55    InvalidSignature,
56    #[error("Challenge expired")]
57    ChallengeExpired,
58    #[error("Peer not found")]
59    PeerNotFound,
60    #[error("Authentication timeout")]
61    Timeout,
62    #[error("Invalid peer ID")]
63    InvalidPeerId,
64    #[error("Signature error: {0}")]
65    SignatureError(String),
66    #[error("Serialization error: {0}")]
67    SerializationError(String),
68    #[error("Key error: {0}")]
69    KeyError(String),
70}
71
72/// Authentication configuration
73#[derive(Debug, Clone)]
74pub struct AuthConfig {
75    /// Timeout for authentication handshake
76    pub auth_timeout: Duration,
77    /// Challenge validity duration
78    pub challenge_validity: Duration,
79    /// Whether to require authentication for all connections
80    pub require_authentication: bool,
81    /// Maximum number of authentication attempts
82    pub max_auth_attempts: u32,
83}
84
85impl Default for AuthConfig {
86    fn default() -> Self {
87        Self {
88            auth_timeout: Duration::from_secs(10),
89            challenge_validity: Duration::from_secs(60),
90            require_authentication: true,
91            max_auth_attempts: 3,
92        }
93    }
94}
95
96/// Authentication message types
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub enum AuthMessage {
99    /// Initial authentication request with public key
100    AuthRequest {
101        peer_id: PeerId,
102        public_key: [u8; 32],
103        timestamp: SystemTime,
104    },
105    /// Challenge to verify key ownership
106    Challenge {
107        nonce: [u8; 32],
108        timestamp: SystemTime,
109    },
110    /// Response to challenge with signature
111    ChallengeResponse {
112        nonce: [u8; 32],
113        signature: Vec<u8>,
114        timestamp: SystemTime,
115    },
116    /// Authentication successful
117    AuthSuccess {
118        session_id: [u8; 32],
119        timestamp: SystemTime,
120    },
121    /// Authentication failed
122    AuthFailure { reason: String },
123}
124
125/// Authenticated peer information
126#[derive(Debug, Clone)]
127pub struct AuthenticatedPeer {
128    /// Peer ID derived from public key
129    pub peer_id: PeerId,
130    /// Ed25519 public key
131    pub public_key: Ed25519PublicKey,
132    /// When authentication was completed
133    pub authenticated_at: Instant,
134    /// Session ID for this connection
135    pub session_id: [u8; 32],
136}
137
138/// Authentication manager for handling peer authentication
139pub struct AuthManager {
140    /// Our Ed25519 secret key
141    secret_key: Ed25519SecretKey,
142    /// Our public key
143    public_key: Ed25519PublicKey,
144    /// Our peer ID
145    peer_id: PeerId,
146    /// Configuration
147    config: AuthConfig,
148    /// Authenticated peers
149    authenticated_peers: Arc<RwLock<HashMap<PeerId, AuthenticatedPeer>>>,
150    /// Pending challenges
151    pending_challenges: Arc<RwLock<HashMap<PeerId, PendingChallenge>>>,
152}
153
154/// Pending authentication challenge
155#[derive(Debug)]
156struct PendingChallenge {
157    nonce: [u8; 32],
158    created_at: Instant,
159    attempts: u32,
160}
161
162impl AuthManager {
163    /// Create a new authentication manager
164    pub fn new(secret_key: Ed25519SecretKey, config: AuthConfig) -> Self {
165        let public_key = secret_key.verifying_key();
166        let peer_id = derive_peer_id_from_public_key(&public_key);
167
168        info!("Initialized AuthManager with peer ID: {:?}", peer_id);
169
170        Self {
171            secret_key,
172            public_key,
173            peer_id,
174            config,
175            authenticated_peers: Arc::new(RwLock::new(HashMap::new())),
176            pending_challenges: Arc::new(RwLock::new(HashMap::new())),
177        }
178    }
179
180    /// Get our peer ID
181    pub fn peer_id(&self) -> PeerId {
182        self.peer_id
183    }
184
185    /// Get our public key bytes
186    pub fn public_key_bytes(&self) -> [u8; 32] {
187        public_key_to_bytes(&self.public_key)
188    }
189
190    /// Get authentication configuration
191    pub fn config(&self) -> &AuthConfig {
192        &self.config
193    }
194
195    /// Create an authentication request
196    pub fn create_auth_request(&self) -> AuthMessage {
197        AuthMessage::AuthRequest {
198            peer_id: self.peer_id,
199            public_key: self.public_key_bytes(),
200            timestamp: SystemTime::now(),
201        }
202    }
203
204    /// Handle incoming authentication request
205    pub async fn handle_auth_request(
206        &self,
207        peer_id: PeerId,
208        public_key_bytes: [u8; 32],
209    ) -> Result<AuthMessage, AuthError> {
210        // Verify that the peer ID matches the public key
211        let public_key = public_key_from_bytes(&public_key_bytes)
212            .map_err(|e| AuthError::KeyError(e.to_string()))?;
213
214        if !verify_peer_id(&peer_id, &public_key) {
215            return Err(AuthError::InvalidPeerId);
216        }
217
218        // Generate a challenge nonce
219        let nonce = {
220            use rand::Rng;
221            let mut nonce = [0u8; 32];
222            rand::thread_rng().fill(&mut nonce);
223            nonce
224        };
225
226        // Store the pending challenge
227        let mut challenges = self.pending_challenges.write().await;
228        challenges.insert(
229            peer_id,
230            PendingChallenge {
231                nonce,
232                created_at: Instant::now(),
233                attempts: 0,
234            },
235        );
236
237        debug!("Created challenge for peer {:?}", peer_id);
238
239        Ok(AuthMessage::Challenge {
240            nonce,
241            timestamp: SystemTime::now(),
242        })
243    }
244
245    /// Create a challenge response
246    pub fn create_challenge_response(&self, nonce: [u8; 32]) -> Result<AuthMessage, AuthError> {
247        // Sign the nonce with our private key
248        let signature = self.secret_key.sign(&nonce);
249
250        Ok(AuthMessage::ChallengeResponse {
251            nonce,
252            signature: signature.to_vec(),
253            timestamp: SystemTime::now(),
254        })
255    }
256
257    /// Verify a challenge response
258    pub async fn verify_challenge_response(
259        &self,
260        peer_id: PeerId,
261        public_key_bytes: [u8; 32],
262        nonce: [u8; 32],
263        signature_bytes: &[u8],
264    ) -> Result<AuthMessage, AuthError> {
265        // Perform all operations to ensure constant timing
266
267        // Step 1: Gather all data and perform checks without early returns
268        let mut challenges = self.pending_challenges.write().await;
269        let challenge_exists = challenges.contains_key(&peer_id);
270        let stored_nonce = challenges
271            .get(&peer_id)
272            .map(|c| c.nonce)
273            .unwrap_or([0u8; 32]);
274        let created_at = challenges
275            .get(&peer_id)
276            .map(|c| c.created_at)
277            .unwrap_or(Instant::now());
278        let attempts = challenges
279            .get_mut(&peer_id)
280            .map(|c| {
281                c.attempts += 1;
282                c.attempts
283            })
284            .unwrap_or(0);
285
286        // Check conditions (but don't return early)
287        let nonce_matches = constant_time_eq(&stored_nonce, &nonce);
288        let not_expired = created_at.elapsed() <= self.config.challenge_validity;
289        let attempts_ok = attempts < self.config.max_auth_attempts;
290
291        // Step 2: Parse keys and signature (always do this)
292        let public_key_result = public_key_from_bytes(&public_key_bytes);
293        let signature_result = Signature::from_slice(signature_bytes);
294
295        // Step 3: Verify signature (always attempt this)
296        let verification_result = match (public_key_result, signature_result) {
297            (Ok(pk), Ok(sig)) => pk.verify(&nonce, &sig).is_ok(),
298            _ => false,
299        };
300
301        // Step 4: Generate session data (always do this to maintain constant timing)
302        let session_id = {
303            use rand::Rng;
304            let mut id = [0u8; 32];
305            rand::thread_rng().fill(&mut id);
306            id
307        };
308
309        // Step 5: Determine final result based on all checks
310        let all_valid =
311            challenge_exists && nonce_matches && not_expired && attempts_ok && verification_result;
312
313        debug!(
314            "Verification results - exists: {}, nonce_matches: {}, not_expired: {}, attempts_ok: {}, verification: {}",
315            challenge_exists, nonce_matches, not_expired, attempts_ok, verification_result
316        );
317
318        // Step 6: Clean up and store results based on validity
319        if all_valid {
320            // Remove the challenge
321            challenges.remove(&peer_id);
322            drop(challenges); // Release lock before acquiring peers lock
323
324            // Store authenticated peer
325            if let Ok(public_key) = public_key_from_bytes(&public_key_bytes) {
326                let mut peers = self.authenticated_peers.write().await;
327                peers.insert(
328                    peer_id,
329                    AuthenticatedPeer {
330                        peer_id,
331                        public_key,
332                        authenticated_at: Instant::now(),
333                        session_id,
334                    },
335                );
336
337                info!("Successfully authenticated peer {:?}", peer_id);
338            }
339
340            Ok(AuthMessage::AuthSuccess {
341                session_id,
342                timestamp: SystemTime::now(),
343            })
344        } else {
345            // Determine specific error (but after all operations complete)
346            let error = if !challenge_exists {
347                AuthError::PeerNotFound
348            } else if !not_expired {
349                challenges.remove(&peer_id);
350                AuthError::ChallengeExpired
351            } else if !attempts_ok {
352                challenges.remove(&peer_id);
353                AuthError::InvalidSignature
354            } else if !nonce_matches {
355                AuthError::InvalidSignature
356            } else {
357                AuthError::InvalidSignature
358            };
359
360            Err(error)
361        }
362    }
363
364    /// Check if a peer is authenticated
365    pub async fn is_authenticated(&self, peer_id: &PeerId) -> bool {
366        let peers = self.authenticated_peers.read().await;
367        peers.contains_key(peer_id)
368    }
369
370    /// Get authenticated peer information
371    pub async fn get_authenticated_peer(&self, peer_id: &PeerId) -> Option<AuthenticatedPeer> {
372        let peers = self.authenticated_peers.read().await;
373        peers.get(peer_id).cloned()
374    }
375
376    /// Handle successful authentication from responder
377    pub async fn handle_auth_success(
378        &self,
379        peer_id: PeerId,
380        public_key_bytes: [u8; 32],
381        session_id: [u8; 32],
382    ) -> Result<(), AuthError> {
383        // Parse the public key
384        let public_key = public_key_from_bytes(&public_key_bytes)
385            .map_err(|e| AuthError::KeyError(e.to_string()))?;
386
387        // Store the authenticated peer
388        let mut peers = self.authenticated_peers.write().await;
389        peers.insert(
390            peer_id,
391            AuthenticatedPeer {
392                peer_id,
393                public_key,
394                authenticated_at: Instant::now(),
395                session_id,
396            },
397        );
398
399        info!(
400            "Marked peer {:?} as authenticated after receiving AuthSuccess",
401            peer_id
402        );
403        Ok(())
404    }
405
406    /// Remove an authenticated peer
407    pub async fn remove_peer(&self, peer_id: &PeerId) {
408        let mut peers = self.authenticated_peers.write().await;
409        if peers.remove(peer_id).is_some() {
410            info!("Removed authenticated peer {:?}", peer_id);
411        }
412    }
413
414    /// Clean up expired challenges
415    pub async fn cleanup_expired_challenges(&self) {
416        let mut challenges = self.pending_challenges.write().await;
417        let now = Instant::now();
418
419        challenges.retain(|peer_id, challenge| {
420            let expired =
421                now.duration_since(challenge.created_at) <= self.config.challenge_validity;
422            if !expired {
423                debug!("Removing expired challenge for peer {:?}", peer_id);
424            }
425            expired
426        });
427    }
428
429    /// Get list of authenticated peers
430    pub async fn list_authenticated_peers(&self) -> Vec<PeerId> {
431        let peers = self.authenticated_peers.read().await;
432        peers.keys().cloned().collect()
433    }
434
435    /// Serialize an auth message
436    pub fn serialize_message(msg: &AuthMessage) -> Result<Vec<u8>, AuthError> {
437        serde_json::to_vec(msg).map_err(|e| AuthError::SerializationError(e.to_string()))
438    }
439
440    /// Deserialize an auth message
441    pub fn deserialize_message(data: &[u8]) -> Result<AuthMessage, AuthError> {
442        serde_json::from_slice(data).map_err(|e| AuthError::SerializationError(e.to_string()))
443    }
444}
445
446/// Authentication protocol handler for integration with QuicP2PNode
447pub struct AuthProtocol {
448    auth_manager: Arc<AuthManager>,
449    /// Temporary storage for public keys during authentication
450    pending_auth: Arc<tokio::sync::RwLock<HashMap<PeerId, [u8; 32]>>>,
451}
452
453impl AuthProtocol {
454    /// Create a new authentication protocol handler
455    pub fn new(auth_manager: Arc<AuthManager>) -> Self {
456        Self {
457            auth_manager,
458            pending_auth: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
459        }
460    }
461
462    /// Handle incoming authentication message
463    pub async fn handle_message(
464        &self,
465        peer_id: PeerId,
466        message: AuthMessage,
467    ) -> Result<Option<AuthMessage>, AuthError> {
468        match message {
469            AuthMessage::AuthRequest {
470                peer_id: req_peer_id,
471                public_key,
472                ..
473            } => {
474                if req_peer_id != peer_id {
475                    return Err(AuthError::InvalidPeerId);
476                }
477                // Store the public key for later verification
478                self.pending_auth.write().await.insert(peer_id, public_key);
479                let response = self
480                    .auth_manager
481                    .handle_auth_request(peer_id, public_key)
482                    .await?;
483                Ok(Some(response))
484            }
485            AuthMessage::Challenge { nonce, .. } => {
486                let response = self.auth_manager.create_challenge_response(nonce)?;
487                Ok(Some(response))
488            }
489            AuthMessage::ChallengeResponse {
490                nonce, signature, ..
491            } => {
492                // Get the public key from the initial auth request
493                let public_key_bytes = match self.pending_auth.read().await.get(&peer_id) {
494                    Some(key) => *key,
495                    None => return Err(AuthError::PeerNotFound),
496                };
497
498                let response = self
499                    .auth_manager
500                    .verify_challenge_response(peer_id, public_key_bytes, nonce, &signature)
501                    .await?;
502
503                // Remove the pending auth entry on success
504                if matches!(response, AuthMessage::AuthSuccess { .. }) {
505                    self.pending_auth.write().await.remove(&peer_id);
506                }
507
508                Ok(Some(response))
509            }
510            AuthMessage::AuthSuccess { session_id, .. } => {
511                info!(
512                    "Authentication successful with peer {:?}, session: {:?}",
513                    peer_id,
514                    hex::encode(&session_id)
515                );
516                Ok(None)
517            }
518            AuthMessage::AuthFailure { reason } => {
519                warn!("Authentication failed with peer {:?}: {}", peer_id, reason);
520                Err(AuthError::InvalidSignature)
521            }
522        }
523    }
524
525    /// Initiate authentication with a peer
526    pub async fn initiate_auth(&self) -> AuthMessage {
527        self.auth_manager.create_auth_request()
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534    use crate::crypto::raw_public_keys::key_utils::generate_ed25519_keypair;
535
536    #[tokio::test]
537    async fn test_auth_manager_creation() {
538        let (secret_key, _) = generate_ed25519_keypair();
539        let config = AuthConfig::default();
540        let auth_manager = AuthManager::new(secret_key, config);
541
542        // Verify peer ID is derived correctly
543        let peer_id = auth_manager.peer_id();
544        assert_eq!(peer_id.0.len(), 32);
545    }
546
547    #[tokio::test]
548    async fn test_authentication_flow() {
549        // Create two auth managers (simulating two peers)
550        let (secret_key1, public_key1) = generate_ed25519_keypair();
551        let (secret_key2, _) = generate_ed25519_keypair();
552
553        let auth1 = AuthManager::new(secret_key1, AuthConfig::default());
554        let auth2 = AuthManager::new(secret_key2, AuthConfig::default());
555
556        // Peer 1 creates auth request
557        let auth_request = auth1.create_auth_request();
558
559        // Peer 2 handles the request and creates a challenge
560        let challenge = match &auth_request {
561            AuthMessage::AuthRequest {
562                peer_id,
563                public_key,
564                ..
565            } => auth2
566                .handle_auth_request(*peer_id, *public_key)
567                .await
568                .unwrap(),
569            _ => panic!("Expected AuthRequest"),
570        };
571
572        // Peer 1 responds to the challenge
573        let response = match &challenge {
574            AuthMessage::Challenge { nonce, .. } => {
575                auth1.create_challenge_response(*nonce).unwrap()
576            }
577            _ => panic!("Expected Challenge"),
578        };
579
580        // Peer 2 verifies the response
581        let result = match &response {
582            AuthMessage::ChallengeResponse {
583                nonce, signature, ..
584            } => {
585                auth2
586                    .verify_challenge_response(
587                        auth1.peer_id(),
588                        public_key_to_bytes(&public_key1),
589                        *nonce,
590                        signature,
591                    )
592                    .await
593            }
594            _ => panic!("Expected ChallengeResponse"),
595        };
596
597        // Should be successful
598        assert!(matches!(result, Ok(AuthMessage::AuthSuccess { .. })));
599
600        // Peer 1 should now be authenticated by peer 2
601        assert!(auth2.is_authenticated(&auth1.peer_id()).await);
602    }
603
604    #[tokio::test]
605    async fn test_invalid_signature() {
606        let (secret_key1, _) = generate_ed25519_keypair();
607        let (secret_key2, public_key2) = generate_ed25519_keypair();
608
609        let auth1 = AuthManager::new(secret_key1, AuthConfig::default());
610        let _auth2 = AuthManager::new(secret_key2, AuthConfig::default());
611
612        // Create a challenge
613        let peer_id2 = derive_peer_id_from_public_key(&public_key2);
614        let challenge = auth1
615            .handle_auth_request(peer_id2, public_key_to_bytes(&public_key2))
616            .await
617            .unwrap();
618
619        // Create an invalid response (wrong signature)
620        let invalid_signature = vec![0u8; 64];
621        let nonce = match &challenge {
622            AuthMessage::Challenge { nonce, .. } => *nonce,
623            _ => panic!("Expected Challenge"),
624        };
625
626        // Verification should fail
627        let result = auth1
628            .verify_challenge_response(
629                peer_id2,
630                public_key_to_bytes(&public_key2),
631                nonce,
632                &invalid_signature,
633            )
634            .await;
635
636        assert!(matches!(result, Err(AuthError::InvalidSignature)));
637    }
638
639    #[tokio::test]
640    async fn test_challenge_expiry() {
641        let (secret_key, public_key) = generate_ed25519_keypair();
642        let mut config = AuthConfig::default();
643        config.challenge_validity = Duration::from_millis(100); // Very short for testing
644
645        let auth = AuthManager::new(secret_key, config);
646        let peer_id = derive_peer_id_from_public_key(&public_key);
647
648        // Create a challenge
649        let _challenge = auth
650            .handle_auth_request(peer_id, public_key_to_bytes(&public_key))
651            .await
652            .unwrap();
653
654        // Wait for it to expire
655        tokio::time::sleep(Duration::from_millis(200)).await;
656
657        // Try to verify - should fail due to expiry
658        let result = auth
659            .verify_challenge_response(
660                peer_id,
661                public_key_to_bytes(&public_key),
662                [0u8; 32],  // dummy nonce
663                &[0u8; 64], // dummy signature
664            )
665            .await;
666
667        assert!(matches!(result, Err(AuthError::ChallengeExpired)));
668    }
669
670    #[tokio::test]
671    async fn test_message_serialization() {
672        let (_, public_key) = generate_ed25519_keypair();
673        let peer_id = derive_peer_id_from_public_key(&public_key);
674
675        let msg = AuthMessage::AuthRequest {
676            peer_id,
677            public_key: public_key_to_bytes(&public_key),
678            timestamp: SystemTime::now(),
679        };
680
681        // Serialize and deserialize
682        let serialized = AuthManager::serialize_message(&msg).unwrap();
683        let deserialized = AuthManager::deserialize_message(&serialized).unwrap();
684
685        match (msg, deserialized) {
686            (
687                AuthMessage::AuthRequest {
688                    peer_id: p1,
689                    public_key: k1,
690                    ..
691                },
692                AuthMessage::AuthRequest {
693                    peer_id: p2,
694                    public_key: k2,
695                    ..
696                },
697            ) => {
698                assert_eq!(p1, p2);
699                assert_eq!(k1, k2);
700            }
701            _ => panic!("Message mismatch"),
702        }
703    }
704}