1use super::*;
24use anyhow::Result;
25use serde::{Deserialize, Serialize};
26use sha2::{Digest, Sha256};
27use std::{
28 collections::{HashMap, HashSet, VecDeque},
29 net::IpAddr,
30 sync::Arc,
31 time::{Duration, Instant, SystemTime},
32};
33use tokio::sync::{Mutex, RwLock};
34
35#[derive(Debug, Clone, Default)]
37pub struct SecurityConfig {
38 pub rate_limit: RateLimitConfig,
40
41 pub blacklist: BlacklistConfig,
43
44 pub eclipse_detection: EclipseDetectionConfig,
46
47 pub integrity: IntegrityConfig,
49
50 pub audit: AuditConfig,
52}
53
54#[derive(Debug, Clone)]
56pub struct RateLimitConfig {
57 pub node_requests_per_window: u32,
59
60 pub ip_requests_per_window: u32,
62
63 pub window_duration: Duration,
65
66 pub max_connections_per_node: u32,
68
69 pub max_joins_per_hour: u32,
71}
72
73impl Default for RateLimitConfig {
74 fn default() -> Self {
75 Self {
76 node_requests_per_window: 100,
77 ip_requests_per_window: 500,
78 window_duration: Duration::from_secs(60),
79 max_connections_per_node: 10,
80 max_joins_per_hour: 20,
81 }
82 }
83}
84
85#[derive(Debug, Clone)]
87pub struct BlacklistConfig {
88 pub entry_ttl: Duration,
90
91 pub max_entries: usize,
93
94 pub violation_threshold: u32,
96}
97
98impl Default for BlacklistConfig {
99 fn default() -> Self {
100 Self {
101 entry_ttl: Duration::from_secs(86400), max_entries: 10000,
103 violation_threshold: 3,
104 }
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct EclipseDetectionConfig {
111 pub min_diversity_score: f64,
113
114 pub max_subnet_ratio: f64,
116
117 pub pattern_threshold: f64,
119}
120
121impl Default for EclipseDetectionConfig {
122 fn default() -> Self {
123 Self {
124 min_diversity_score: 0.5,
125 max_subnet_ratio: 0.2,
126 pattern_threshold: 0.7,
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct IntegrityConfig {
134 pub verify_content_hash: bool,
136
137 pub require_signatures: bool,
139
140 pub max_message_size: usize,
142}
143
144impl Default for IntegrityConfig {
145 fn default() -> Self {
146 Self {
147 verify_content_hash: true,
148 require_signatures: true,
149 max_message_size: 10 * 1024 * 1024, }
151 }
152}
153
154#[derive(Debug, Clone)]
156pub struct AuditConfig {
157 pub enabled: bool,
159
160 pub log_security_events: bool,
162
163 pub log_rate_limits: bool,
165
166 pub retention_days: u32,
168}
169
170impl Default for AuditConfig {
171 fn default() -> Self {
172 Self {
173 enabled: true,
174 log_security_events: true,
175 log_rate_limits: true,
176 retention_days: 30,
177 }
178 }
179}
180
181pub struct SecurityManager {
183 config: SecurityConfig,
185
186 rate_limiter: Arc<RateLimiter>,
188
189 blacklist: Arc<BlacklistManager>,
191
192 eclipse_detector: Arc<EclipseDetector>,
194
195 integrity_verifier: Arc<IntegrityVerifier>,
197
198 auditor: Arc<SecurityAuditor>,
200
201 _identity: crate::peer_record::UserId,
203}
204
205pub struct RateLimiter {
207 config: RateLimitConfig,
209
210 node_requests: Arc<RwLock<HashMap<NodeId, RequestWindow>>>,
212
213 ip_requests: Arc<RwLock<HashMap<IpAddr, RequestWindow>>>,
215
216 _connections: Arc<RwLock<HashMap<NodeId, u32>>>,
218
219 join_requests: Arc<RwLock<VecDeque<Instant>>>,
221}
222
223#[derive(Debug, Clone)]
225struct RequestWindow {
226 count: u32,
228
229 window_start: Instant,
231}
232
233pub struct BlacklistManager {
235 config: BlacklistConfig,
237
238 blacklist: Arc<RwLock<HashMap<NodeId, BlacklistEntry>>>,
240
241 violations: Arc<RwLock<HashMap<NodeId, u32>>>,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct BlacklistEntry {
248 pub node_id: NodeId,
250
251 pub reason: BlacklistReason,
253
254 pub timestamp: SystemTime,
256
257 pub reporter: Option<NodeId>,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub enum BlacklistReason {
264 RateLimitViolation,
266
267 MaliciousBehavior(String),
269
270 EclipseAttack,
272
273 DataCorruption,
275
276 InvalidCrypto,
278
279 Manual(String),
281}
282
283pub struct EclipseDetector {
285 config: EclipseDetectionConfig,
287
288 patterns: Arc<RwLock<AnomalyPatterns>>,
290}
291
292#[derive(Debug, Default)]
294struct AnomalyPatterns {
295 _rapid_connections: HashMap<NodeId, Vec<Instant>>,
297
298 subnet_distribution: HashMap<String, u32>,
300
301 routing_anomalies: Vec<RoutingAnomaly>,
303}
304
305#[derive(Debug, Clone)]
307struct RoutingAnomaly {
308 _node_id: NodeId,
310
311 _anomaly_type: AnomalyType,
313
314 timestamp: Instant,
316}
317
318#[derive(Debug, Clone)]
320pub enum AnomalyType {
321 SubnetConcentration,
323
324 RapidChurn,
326
327 ConnectionPattern,
329
330 CoordinatedActivity,
332}
333
334pub struct IntegrityVerifier {
336 _config: IntegrityConfig,
338
339 stats: Arc<RwLock<VerificationStats>>,
341}
342
343#[derive(Debug, Default)]
345struct VerificationStats {
346 total_verified: u64,
348
349 failed_verifications: u64,
351
352 invalid_hashes: u64,
354
355 _invalid_signatures: u64,
357}
358
359pub struct SecurityAuditor {
361 config: AuditConfig,
363
364 audit_log: Arc<Mutex<VecDeque<AuditEntry>>>,
366
367 event_counts: Arc<RwLock<HashMap<String, u64>>>,
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct AuditEntry {
374 pub timestamp: SystemTime,
376
377 pub event_type: SecurityEvent,
379
380 pub node_id: Option<NodeId>,
382
383 pub details: String,
385
386 pub severity: Severity,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
392pub enum SecurityEvent {
393 RateLimitExceeded,
395
396 NodeBlacklisted,
398
399 EclipseAttackDetected,
401
402 IntegrityFailure,
404
405 AuthenticationFailure,
407
408 SuspiciousActivity,
410
411 ConfigurationChange,
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
417pub enum Severity {
418 Debug,
419 Info,
420 Warning,
421 Error,
422 Critical,
423}
424
425#[derive(Debug, thiserror::Error)]
427pub enum SecurityError {
428 #[error("Rate limit exceeded")]
429 RateLimitExceeded,
430
431 #[error("Node is blacklisted")]
432 Blacklisted,
433
434 #[error("Invalid cryptographic identity")]
435 InvalidIdentity,
436
437 #[error("Data integrity check failed")]
438 IntegrityCheckFailed,
439
440 #[error("Eclipse attack detected")]
441 EclipseAttackDetected,
442
443 #[error("Message too large")]
444 MessageTooLarge,
445
446 #[error("Invalid signature")]
447 InvalidSignature,
448}
449
450impl SecurityManager {
451 pub fn new(config: SecurityConfig, identity: &NodeIdentity) -> Self {
453 let rate_limiter = Arc::new(RateLimiter::new(config.rate_limit.clone()));
454 let blacklist = Arc::new(BlacklistManager::new(config.blacklist.clone()));
455 let eclipse_detector = Arc::new(EclipseDetector::new(config.eclipse_detection.clone()));
456 let integrity_verifier = Arc::new(IntegrityVerifier::new(config.integrity.clone()));
457 let auditor = Arc::new(SecurityAuditor::new(config.audit.clone()));
458
459 Self {
460 config,
461 rate_limiter,
462 blacklist,
463 eclipse_detector,
464 integrity_verifier,
465 auditor,
466 _identity: identity.to_user_id(),
467 }
468 }
469
470 pub async fn validate_node_join(&self, node: &NodeDescriptor) -> Result<(), SecurityError> {
472 if self.blacklist.is_blacklisted(&node.id).await {
474 self.auditor
475 .log_event(
476 SecurityEvent::NodeBlacklisted,
477 Some(node.id.clone()),
478 "Node attempted to join while blacklisted".to_string(),
479 Severity::Warning,
480 )
481 .await;
482 return Err(SecurityError::Blacklisted);
483 }
484
485 if !self.rate_limiter.check_join_rate().await {
487 self.auditor
488 .log_event(
489 SecurityEvent::RateLimitExceeded,
490 Some(node.id.clone()),
491 "Join rate limit exceeded".to_string(),
492 Severity::Warning,
493 )
494 .await;
495 return Err(SecurityError::RateLimitExceeded);
496 }
497
498 if !self.verify_identity(node).await {
500 return Err(SecurityError::InvalidIdentity);
501 }
502
503 Ok(())
504 }
505
506 pub async fn check_rate_limit(
508 &self,
509 node_id: &NodeId,
510 ip: Option<IpAddr>,
511 ) -> Result<(), SecurityError> {
512 if !self.rate_limiter.check_node_rate(node_id).await {
514 self.blacklist
515 .record_violation(node_id, BlacklistReason::RateLimitViolation)
516 .await;
517 self.auditor
518 .log_event(
519 SecurityEvent::RateLimitExceeded,
520 Some(node_id.clone()),
521 "Node request rate limit exceeded".to_string(),
522 Severity::Warning,
523 )
524 .await;
525 return Err(SecurityError::RateLimitExceeded);
526 }
527
528 if let Some(ip_addr) = ip
530 && !self.rate_limiter.check_ip_rate(&ip_addr).await
531 {
532 self.auditor
533 .log_event(
534 SecurityEvent::RateLimitExceeded,
535 None,
536 format!("IP {ip_addr} rate limit exceeded"),
537 Severity::Warning,
538 )
539 .await;
540 return Err(SecurityError::RateLimitExceeded);
541 }
542
543 Ok(())
544 }
545
546 pub async fn detect_eclipse_attack(
548 &self,
549 routing_table: &[NodeId],
550 ) -> Result<(), SecurityError> {
551 let diversity_score = self
552 .eclipse_detector
553 .calculate_diversity_score(routing_table)
554 .await;
555
556 if diversity_score < self.config.eclipse_detection.min_diversity_score {
557 self.auditor
558 .log_event(
559 SecurityEvent::EclipseAttackDetected,
560 None,
561 format!("Low routing table diversity: {diversity_score:.2}"),
562 Severity::Critical,
563 )
564 .await;
565 return Err(SecurityError::EclipseAttackDetected);
566 }
567
568 if self
569 .eclipse_detector
570 .detect_suspicious_patterns(routing_table)
571 .await
572 {
573 self.auditor
574 .log_event(
575 SecurityEvent::EclipseAttackDetected,
576 None,
577 "Suspicious routing patterns detected".to_string(),
578 Severity::Critical,
579 )
580 .await;
581 return Err(SecurityError::EclipseAttackDetected);
582 }
583
584 Ok(())
585 }
586
587 pub async fn verify_message_integrity(
589 &self,
590 message: &[u8],
591 hash: &[u8],
592 signature: Option<&[u8]>,
593 ) -> Result<(), SecurityError> {
594 if message.len() >= 8 {
596 let mut ts_bytes = [0u8; 8];
597 ts_bytes.copy_from_slice(&message[..8]);
598 let msg_ts = u64::from_be_bytes(ts_bytes);
599 let now = SystemTime::now()
600 .duration_since(SystemTime::UNIX_EPOCH)
601 .unwrap_or_default()
602 .as_secs();
603 if msg_ts > now + 300 {
605 return Err(SecurityError::IntegrityCheckFailed);
606 }
607 }
608 if message.len() > self.config.integrity.max_message_size {
610 return Err(SecurityError::MessageTooLarge);
611 }
612
613 if self.config.integrity.verify_content_hash
615 && !self.integrity_verifier.verify_hash(message, hash).await
616 {
617 self.auditor
618 .log_event(
619 SecurityEvent::IntegrityFailure,
620 None,
621 "Content hash verification failed".to_string(),
622 Severity::Error,
623 )
624 .await;
625 return Err(SecurityError::IntegrityCheckFailed);
626 }
627
628 if self.config.integrity.require_signatures {
630 if let Some(sig) = signature {
631 if !self.integrity_verifier.verify_signature(message, sig).await {
632 self.auditor
633 .log_event(
634 SecurityEvent::IntegrityFailure,
635 None,
636 "Message signature verification failed".to_string(),
637 Severity::Error,
638 )
639 .await;
640 return Err(SecurityError::InvalidSignature);
641 }
642 } else {
643 return Err(SecurityError::InvalidSignature);
644 }
645 }
646
647 Ok(())
648 }
649
650 pub async fn blacklist_node(&self, node_id: NodeId, reason: BlacklistReason) {
652 self.blacklist
653 .add_entry(node_id.clone(), reason.clone())
654 .await;
655
656 self.auditor
657 .log_event(
658 SecurityEvent::NodeBlacklisted,
659 Some(node_id),
660 format!("Node blacklisted: {reason:?}"),
661 Severity::Warning,
662 )
663 .await;
664 }
665
666 pub async fn get_metrics(&self) -> SecurityMetrics {
668 SecurityMetrics {
669 rate_limit_violations: self.rate_limiter.get_violation_count().await,
670 blacklisted_nodes: self.blacklist.get_blacklist_size().await,
671 verification_failures: self.integrity_verifier.get_failure_count().await,
672 eclipse_detections: self.eclipse_detector.get_detection_count().await,
673 audit_entries: self.auditor.get_entry_count().await,
674 }
675 }
676
677 async fn verify_identity(&self, node: &NodeDescriptor) -> bool {
679 let bytes = node.public_key.as_bytes();
681 let hash = blake3::hash(bytes);
682 node.id.as_bytes() == hash.as_bytes()
683 }
684}
685
686impl RateLimiter {
687 pub fn new(config: RateLimitConfig) -> Self {
689 Self {
690 config,
691 node_requests: Arc::new(RwLock::new(HashMap::new())),
692 ip_requests: Arc::new(RwLock::new(HashMap::new())),
693 _connections: Arc::new(RwLock::new(HashMap::new())),
694 join_requests: Arc::new(RwLock::new(VecDeque::new())),
695 }
696 }
697
698 pub async fn check_node_rate(&self, node_id: &NodeId) -> bool {
700 let mut requests = self.node_requests.write().await;
701 let now = Instant::now();
702
703 let window = requests.entry(node_id.clone()).or_insert(RequestWindow {
704 count: 0,
705 window_start: now,
706 });
707
708 if now.duration_since(window.window_start) > self.config.window_duration {
710 window.count = 0;
711 window.window_start = now;
712 }
713
714 if window.count < self.config.node_requests_per_window {
716 window.count += 1;
717 true
718 } else {
719 false
720 }
721 }
722
723 pub async fn check_ip_rate(&self, ip: &IpAddr) -> bool {
725 let mut requests = self.ip_requests.write().await;
726 let now = Instant::now();
727
728 let window = requests.entry(*ip).or_insert(RequestWindow {
729 count: 0,
730 window_start: now,
731 });
732
733 if now.duration_since(window.window_start) > self.config.window_duration {
735 window.count = 0;
736 window.window_start = now;
737 }
738
739 if window.count < self.config.ip_requests_per_window {
741 window.count += 1;
742 true
743 } else {
744 false
745 }
746 }
747
748 pub async fn check_join_rate(&self) -> bool {
750 let mut join_requests = self.join_requests.write().await;
751 let now = Instant::now();
752 let hour_ago = now - Duration::from_secs(3600);
753
754 while let Some(front) = join_requests.front() {
756 if *front < hour_ago {
757 join_requests.pop_front();
758 } else {
759 break;
760 }
761 }
762
763 if join_requests.len() < self.config.max_joins_per_hour as usize {
765 join_requests.push_back(now);
766 true
767 } else {
768 false
769 }
770 }
771
772 pub async fn get_violation_count(&self) -> u64 {
774 0
776 }
777}
778
779impl BlacklistManager {
780 pub fn new(config: BlacklistConfig) -> Self {
782 Self {
783 config,
784 blacklist: Arc::new(RwLock::new(HashMap::new())),
785 violations: Arc::new(RwLock::new(HashMap::new())),
786 }
787 }
788
789 pub async fn is_blacklisted(&self, node_id: &NodeId) -> bool {
791 let blacklist = self.blacklist.read().await;
792
793 if let Some(entry) = blacklist.get(node_id) {
794 let now = SystemTime::now();
796 let elapsed = now
797 .duration_since(entry.timestamp)
798 .unwrap_or(Duration::ZERO);
799
800 elapsed < self.config.entry_ttl
801 } else {
802 false
803 }
804 }
805
806 pub async fn add_entry(&self, node_id: NodeId, reason: BlacklistReason) {
808 let mut blacklist = self.blacklist.write().await;
809
810 if blacklist.len() >= self.config.max_entries {
812 if let Some(oldest) = blacklist
814 .iter()
815 .min_by_key(|(_, entry)| entry.timestamp)
816 .map(|(id, _)| id.clone())
817 {
818 blacklist.remove(&oldest);
819 }
820 }
821
822 blacklist.insert(
823 node_id.clone(),
824 BlacklistEntry {
825 node_id,
826 reason,
827 timestamp: SystemTime::now(),
828 reporter: None,
829 },
830 );
831 }
832
833 pub async fn record_violation(&self, node_id: &NodeId, reason: BlacklistReason) {
835 let mut violations = self.violations.write().await;
836 let count = violations.entry(node_id.clone()).or_insert(0);
837 *count += 1;
838
839 if *count >= self.config.violation_threshold {
841 drop(violations);
842 self.add_entry(node_id.clone(), reason).await;
843 }
844 }
845
846 pub async fn get_blacklist_size(&self) -> usize {
848 self.blacklist.read().await.len()
849 }
850
851 pub async fn export_blacklist(&self) -> Vec<BlacklistEntry> {
853 let blacklist = self.blacklist.read().await;
854 let now = SystemTime::now();
855
856 blacklist
857 .values()
858 .filter(|entry| {
859 let elapsed = now
860 .duration_since(entry.timestamp)
861 .unwrap_or(Duration::ZERO);
862 elapsed < self.config.entry_ttl
863 })
864 .cloned()
865 .collect()
866 }
867
868 pub async fn import_blacklist(&self, entries: Vec<BlacklistEntry>) {
870 let mut blacklist = self.blacklist.write().await;
871
872 for entry in entries {
873 match blacklist.get(&entry.node_id) {
875 Some(existing) if existing.timestamp >= entry.timestamp => continue,
876 _ => {
877 blacklist.insert(entry.node_id.clone(), entry);
878 }
879 }
880 }
881 }
882}
883
884impl EclipseDetector {
885 pub fn new(config: EclipseDetectionConfig) -> Self {
887 Self {
888 config,
889 patterns: Arc::new(RwLock::new(AnomalyPatterns::default())),
890 }
891 }
892
893 pub async fn calculate_diversity_score(&self, routing_table: &[NodeId]) -> f64 {
895 if routing_table.is_empty() {
896 return 0.0;
897 }
898
899 let mut prefixes = HashSet::new();
901 for node_id in routing_table {
902 let prefix = &node_id.hash[..4];
904 prefixes.insert(prefix.to_vec());
905 }
906
907 prefixes.len() as f64 / routing_table.len() as f64
909 }
910
911 pub async fn detect_suspicious_patterns(&self, routing_table: &[NodeId]) -> bool {
913 let mut patterns = self.patterns.write().await;
914
915 patterns.subnet_distribution.clear();
919
920 for node_id in routing_table {
921 let subnet = format!("{:02x}{:02x}", node_id.hash[0], node_id.hash[1]);
922 *patterns.subnet_distribution.entry(subnet).or_insert(0) += 1;
923 }
924
925 let max_allowed = (routing_table.len() as f64 * self.config.max_subnet_ratio) as u32;
927 for count in patterns.subnet_distribution.values() {
928 if *count > max_allowed {
929 return true;
930 }
931 }
932
933 false
934 }
935
936 pub async fn record_anomaly(&self, node_id: NodeId, anomaly_type: AnomalyType) {
938 let mut patterns = self.patterns.write().await;
939
940 patterns.routing_anomalies.push(RoutingAnomaly {
941 _node_id: node_id,
942 _anomaly_type: anomaly_type,
943 timestamp: Instant::now(),
944 });
945
946 let cutoff = Instant::now() - Duration::from_secs(3600);
948 patterns.routing_anomalies.retain(|a| a.timestamp > cutoff);
949 }
950
951 pub async fn get_detection_count(&self) -> u64 {
953 self.patterns.read().await.routing_anomalies.len() as u64
954 }
955}
956
957impl IntegrityVerifier {
958 pub fn new(config: IntegrityConfig) -> Self {
960 Self {
961 _config: config,
962 stats: Arc::new(RwLock::new(VerificationStats::default())),
963 }
964 }
965
966 pub async fn verify_hash(&self, content: &[u8], expected_hash: &[u8]) -> bool {
968 let mut stats = self.stats.write().await;
969 stats.total_verified += 1;
970
971 let mut hasher = Sha256::new();
972 hasher.update(content);
973 let computed_hash = hasher.finalize();
974
975 if computed_hash.as_slice() == expected_hash {
976 true
977 } else {
978 stats.failed_verifications += 1;
979 stats.invalid_hashes += 1;
980 false
981 }
982 }
983
984 pub async fn verify_signature(&self, _message: &[u8], _signature: &[u8]) -> bool {
986 true
989 }
990
991 pub async fn get_failure_count(&self) -> u64 {
993 self.stats.read().await.failed_verifications
994 }
995}
996
997impl SecurityAuditor {
998 pub fn new(config: AuditConfig) -> Self {
1000 Self {
1001 config,
1002 audit_log: Arc::new(Mutex::new(VecDeque::new())),
1003 event_counts: Arc::new(RwLock::new(HashMap::new())),
1004 }
1005 }
1006
1007 pub async fn log_event(
1009 &self,
1010 event_type: SecurityEvent,
1011 node_id: Option<NodeId>,
1012 details: String,
1013 severity: Severity,
1014 ) {
1015 if !self.config.enabled {
1016 return;
1017 }
1018
1019 let entry = AuditEntry {
1020 timestamp: SystemTime::now(),
1021 event_type: event_type.clone(),
1022 node_id,
1023 details,
1024 severity,
1025 };
1026
1027 let mut log = self.audit_log.lock().await;
1029 log.push_back(entry);
1030
1031 let retention_duration = Duration::from_secs(self.config.retention_days as u64 * 86400);
1033 let cutoff = SystemTime::now() - retention_duration;
1034
1035 while let Some(front) = log.front() {
1036 if front.timestamp < cutoff {
1037 log.pop_front();
1038 } else {
1039 break;
1040 }
1041 }
1042
1043 let event_name = format!("{event_type:?}");
1045 let mut counts = self.event_counts.write().await;
1046 *counts.entry(event_name).or_insert(0) += 1;
1047 }
1048
1049 pub async fn get_entries(
1051 &self,
1052 since: Option<SystemTime>,
1053 severity_filter: Option<Severity>,
1054 ) -> Vec<AuditEntry> {
1055 let log = self.audit_log.lock().await;
1056
1057 log.iter()
1058 .filter(|entry| {
1059 if let Some(min_time) = since
1060 && entry.timestamp < min_time
1061 {
1062 return false;
1063 }
1064 if let Some(min_severity) = severity_filter
1065 && (entry.severity as u8) < (min_severity as u8)
1066 {
1067 return false;
1068 }
1069 true
1070 })
1071 .cloned()
1072 .collect()
1073 }
1074
1075 pub async fn get_entry_count(&self) -> u64 {
1077 self.audit_log.lock().await.len() as u64
1078 }
1079
1080 pub async fn export_report(&self) -> AuditReport {
1082 let entries = self.get_entries(None, None).await;
1083 let event_counts = self.event_counts.read().await.clone();
1084
1085 AuditReport {
1086 generated_at: SystemTime::now(),
1087 total_entries: entries.len(),
1088 event_counts,
1089 severity_breakdown: self.calculate_severity_breakdown(&entries),
1090 recent_critical_events: entries
1091 .iter()
1092 .filter(|e| e.severity == Severity::Critical)
1093 .take(10)
1094 .cloned()
1095 .collect(),
1096 }
1097 }
1098
1099 fn calculate_severity_breakdown(&self, entries: &[AuditEntry]) -> HashMap<Severity, u64> {
1101 let mut breakdown = HashMap::new();
1102
1103 for entry in entries {
1104 *breakdown.entry(entry.severity).or_insert(0) += 1;
1105 }
1106
1107 breakdown
1108 }
1109}
1110
1111#[derive(Debug, Clone, Default)]
1113pub struct SecurityMetrics {
1114 pub rate_limit_violations: u64,
1115 pub blacklisted_nodes: usize,
1116 pub verification_failures: u64,
1117 pub eclipse_detections: u64,
1118 pub audit_entries: u64,
1119}
1120
1121#[derive(Debug, Clone, Serialize, Deserialize)]
1123pub struct AuditReport {
1124 pub generated_at: SystemTime,
1125 pub total_entries: usize,
1126 pub event_counts: HashMap<String, u64>,
1127 pub severity_breakdown: HashMap<Severity, u64>,
1128 pub recent_critical_events: Vec<AuditEntry>,
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133 use super::*;
1134
1135 #[tokio::test]
1136 async fn test_rate_limiter_node_limits() {
1137 let config = RateLimitConfig {
1138 node_requests_per_window: 5,
1139 window_duration: Duration::from_secs(1),
1140 ..Default::default()
1141 };
1142
1143 let limiter = RateLimiter::new(config);
1144 let node_id = NodeId { hash: [1u8; 32] };
1145
1146 for _ in 0..5 {
1148 assert!(limiter.check_node_rate(&node_id).await);
1149 }
1150
1151 assert!(!limiter.check_node_rate(&node_id).await);
1153
1154 tokio::time::sleep(Duration::from_secs(2)).await;
1156
1157 assert!(limiter.check_node_rate(&node_id).await);
1159 }
1160
1161 #[tokio::test]
1162 async fn test_blacklist_management() {
1163 let config = BlacklistConfig::default();
1164 let blacklist = BlacklistManager::new(config);
1165
1166 let node_id = NodeId { hash: [2u8; 32] };
1167
1168 assert!(!blacklist.is_blacklisted(&node_id).await);
1170
1171 blacklist
1173 .add_entry(
1174 node_id.clone(),
1175 BlacklistReason::MaliciousBehavior("Test".to_string()),
1176 )
1177 .await;
1178
1179 assert!(blacklist.is_blacklisted(&node_id).await);
1181
1182 assert_eq!(blacklist.get_blacklist_size().await, 1);
1184 }
1185
1186 #[tokio::test]
1187 async fn test_eclipse_detection() {
1188 let config = EclipseDetectionConfig {
1189 min_diversity_score: 0.5,
1190 max_subnet_ratio: 0.3,
1191 pattern_threshold: 0.7,
1192 };
1193
1194 let detector = EclipseDetector::new(config);
1195
1196 let mut routing_table = vec![];
1198 for i in 0..10 {
1199 let mut hash = [0u8; 32];
1200 hash[0] = 1; hash[31] = i;
1202 routing_table.push(NodeId { hash });
1203 }
1204
1205 let score = detector.calculate_diversity_score(&routing_table).await;
1207 assert!(score < 0.5);
1208
1209 let mut diverse_table = vec![];
1211 for i in 0..10 {
1212 let mut hash = [0u8; 32];
1213 hash[0] = i * 25; diverse_table.push(NodeId { hash });
1215 }
1216
1217 let diverse_score = detector.calculate_diversity_score(&diverse_table).await;
1219 assert!(diverse_score > 0.8);
1220 }
1221
1222 #[tokio::test]
1223 async fn test_integrity_verification() {
1224 let config = IntegrityConfig::default();
1225 let verifier = IntegrityVerifier::new(config);
1226
1227 let content = b"Test content";
1228 let mut hasher = Sha256::new();
1229 hasher.update(content);
1230 let correct_hash = hasher.finalize();
1231
1232 assert!(verifier.verify_hash(content, &correct_hash).await);
1234
1235 let wrong_hash = [0u8; 32];
1237 assert!(!verifier.verify_hash(content, &wrong_hash).await);
1238
1239 assert_eq!(verifier.get_failure_count().await, 1);
1241 }
1242
1243 #[tokio::test]
1244 async fn test_security_auditor() {
1245 let config = AuditConfig::default();
1246 let auditor = SecurityAuditor::new(config);
1247
1248 auditor
1250 .log_event(
1251 SecurityEvent::RateLimitExceeded,
1252 None,
1253 "Test rate limit".to_string(),
1254 Severity::Warning,
1255 )
1256 .await;
1257
1258 auditor
1259 .log_event(
1260 SecurityEvent::EclipseAttackDetected,
1261 None,
1262 "Test eclipse attack".to_string(),
1263 Severity::Critical,
1264 )
1265 .await;
1266
1267 assert_eq!(auditor.get_entry_count().await, 2);
1269
1270 let entries = auditor.get_entries(None, Some(Severity::Critical)).await;
1272 assert_eq!(entries.len(), 1);
1273 assert_eq!(entries[0].severity, Severity::Critical);
1274
1275 let report = auditor.export_report().await;
1277 assert_eq!(report.total_entries, 2);
1278 assert_eq!(report.recent_critical_events.len(), 1);
1279 }
1280
1281 #[tokio::test]
1282 async fn test_security_manager_integration() {
1283 let config = SecurityConfig::default();
1284 let identity = NodeIdentity::generate().unwrap();
1285 let manager = SecurityManager::new(config, &identity);
1286
1287 let (ml_pub, _ml_sec) = crate::quantum_crypto::generate_ml_dsa_keypair().unwrap();
1290 let derived_hash = blake3::hash(ml_pub.as_bytes());
1291 let derived_id = crate::peer_record::UserId::from_bytes(*derived_hash.as_bytes());
1292 let node = NodeDescriptor {
1293 id: derived_id,
1294 public_key: ml_pub,
1295 addresses: vec![],
1296 hyperbolic: None,
1297 som_position: None,
1298 trust: 0.5,
1299 capabilities: NodeCapabilities {
1300 storage: 100,
1301 compute: 50,
1302 bandwidth: 10,
1303 },
1304 };
1305
1306 assert!(manager.validate_node_join(&node).await.is_ok());
1308
1309 manager
1311 .blacklist_node(node.id.clone(), BlacklistReason::Manual("Test".to_string()))
1312 .await;
1313
1314 assert!(matches!(
1316 manager.validate_node_join(&node).await,
1317 Err(SecurityError::Blacklisted)
1318 ));
1319
1320 let metrics = manager.get_metrics().await;
1322 assert_eq!(metrics.blacklisted_nodes, 1);
1323 assert!(metrics.audit_entries > 0);
1324 }
1325}