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