ant_quic/
auth.rs

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