From 989ffade3909b349eef465fad16568e9373a6904 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 20:59:01 +0800 Subject: [PATCH 01/26] =?UTF-8?q?feat(M004):=20=E6=89=A9=E5=B1=95IPC?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20-=20=E6=B7=BB=E5=8A=A0=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E4=BC=98=E5=85=88=E7=BA=A7=E3=80=81TTL=E3=80=81=E5=B9=BF?= =?UTF-8?q?=E6=92=AD=E9=80=9A=E9=81=93=E3=80=81=E5=AE=89=E5=85=A8=E9=80=9A?= =?UTF-8?q?=E4=BF=A1=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 7 ++ src/ipc/Cargo.toml | 1 + src/ipc/channel.rs | 153 +++++++++++++++++++++++++++++++++++++++++- src/ipc/lib.rs | 8 ++- src/ipc/security.rs | 158 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 322 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d588d86..5f0336c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,6 +451,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "0.2.12" @@ -789,6 +795,7 @@ version = "0.1.0" dependencies = [ "bincode", "bytes", + "hex", "ring", "serde", "serde_json", diff --git a/src/ipc/Cargo.toml b/src/ipc/Cargo.toml index 57906ac..0ba96c2 100644 --- a/src/ipc/Cargo.toml +++ b/src/ipc/Cargo.toml @@ -17,3 +17,4 @@ bytes = "1.5" uuid = { version = "1.6", features = ["v4"] } bincode = "1.3" ring = "0.17" +hex = "0.4" diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index ecc41f1..5508ad9 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -1,7 +1,9 @@ use super::{IpcError, Result}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::sync::mpsc; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, RwLock}; +use std::time::Duration; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IpcMessage { @@ -10,6 +12,38 @@ pub struct IpcMessage { pub target: String, pub payload: Vec, pub timestamp: u64, + pub ttl: Option, + pub priority: MessagePriority, + pub message_type: MessageType, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum MessagePriority { + Low, + Normal, + High, + Critical, +} + +impl Default for MessagePriority { + fn default() -> Self { + MessagePriority::Normal + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum MessageType { + Request, + Response, + Event, + Command, + Heartbeat, +} + +impl Default for MessageType { + fn default() -> Self { + MessageType::Event + } } impl IpcMessage { @@ -23,6 +57,36 @@ impl IpcMessage { .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(), + ttl: None, + priority: MessagePriority::default(), + message_type: MessageType::default(), + } + } + + pub fn with_ttl(mut self, ttl: u32) -> Self { + self.ttl = Some(ttl); + self + } + + pub fn with_priority(mut self, priority: MessagePriority) -> Self { + self.priority = priority; + self + } + + pub fn with_type(mut self, message_type: MessageType) -> Self { + self.message_type = message_type; + self + } + + pub fn is_expired(&self) -> bool { + if let Some(ttl) = self.ttl { + let current_time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + current_time > self.timestamp + (ttl as u64) + } else { + false } } } @@ -72,6 +136,20 @@ impl IpcChannel { ))), } } + + pub fn receive_with_timeout(&self, timeout: Duration) -> Result> { + let receiver = self + .receiver + .lock() + .map_err(|e| IpcError::ChannelError(format!("Lock poisoned: {}", e)))?; + match receiver.recv_timeout(timeout) { + Ok(msg) => Ok(Some(msg)), + Err(mpsc::RecvTimeoutError::Timeout) => Ok(None), + Err(mpsc::RecvTimeoutError::Disconnected) => { + Err(IpcError::ChannelError("Channel disconnected".to_string())) + } + } + } } impl Default for IpcChannel { @@ -80,6 +158,50 @@ impl Default for IpcChannel { } } +pub struct BroadcastChannel { + sender: Arc>>, + receivers: Arc>>>, +} + +impl BroadcastChannel { + pub fn new() -> Self { + Self { + sender: Arc::new(RwLock::new(mpsc::channel().0)), + receivers: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub fn add_receiver(&mut self, name: impl Into) -> mpsc::Receiver { + let (sender, receiver) = mpsc::channel(); + self.receivers + .write() + .unwrap() + .insert(name.into(), receiver); + sender + } + + pub fn broadcast(&self, message: IpcMessage) -> Result<()> { + let sender = self.sender.read().unwrap(); + for (_, receiver) in self.receivers.read().unwrap().iter() { + if let Err(e) = receiver.try_recv() { + if let mpsc::TryRecvError::Disconnected = e { + continue; + } + } + } + sender + .send(message) + .map_err(|e| IpcError::ChannelError(format!("Failed to broadcast message: {}", e)))?; + Ok(()) + } +} + +impl Default for BroadcastChannel { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; @@ -95,4 +217,33 @@ mod tests { assert_eq!(received.source, "source"); assert_eq!(received.target, "target"); } + + #[test] + fn test_message_priority() { + let message = IpcMessage::new("source", "target", vec![1, 2, 3]) + .with_priority(MessagePriority::High); + assert_eq!(message.priority, MessagePriority::High); + } + + #[test] + fn test_message_type() { + let message = IpcMessage::new("source", "target", vec![1, 2, 3]) + .with_type(MessageType::Request); + assert_eq!(message.message_type, MessageType::Request); + } + + #[test] + fn test_message_ttl() { + let message = IpcMessage::new("source", "target", vec![1, 2, 3]).with_ttl(60); + assert!(!message.is_expired()); + } + + #[test] + fn test_broadcast_channel() { + let mut broadcast = BroadcastChannel::new(); + let receiver = broadcast.add_receiver("test_receiver"); + + let message = IpcMessage::new("source", "broadcast", vec![1, 2, 3]); + assert!(broadcast.broadcast(message).is_ok()); + } } diff --git a/src/ipc/lib.rs b/src/ipc/lib.rs index 1789fec..1f9c34b 100644 --- a/src/ipc/lib.rs +++ b/src/ipc/lib.rs @@ -2,7 +2,7 @@ pub mod channel; pub mod protocol; pub mod security; -pub use channel::{IpcChannel, IpcMessage}; +pub use channel::{BroadcastChannel, IpcChannel, IpcMessage, MessagePriority, MessageType}; pub use protocol::IpcProtocol; pub use security::IpcSecurity; @@ -16,6 +16,12 @@ pub enum IpcError { SerializationError(String), #[error("Security error: {0}")] SecurityError(String), + #[error("Capability verification failed: {0}")] + CapabilityError(String), + #[error("Message expired")] + MessageExpired, + #[error("Connection hijacked: {0}")] + ConnectionHijacked(String), } pub type Result = std::result::Result; diff --git a/src/ipc/security.rs b/src/ipc/security.rs index a4622d1..d74ae8e 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -3,12 +3,17 @@ use super::{IpcError, Result}; use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM}; use ring::rand::{SecureRandom, SystemRandom}; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::time::{SystemTime, UNIX_EPOCH}; pub struct IpcSecurity { enable_encryption: bool, enable_authentication: bool, encryption_key: Option, rng: SystemRandom, + allowed_sources: HashSet, + message_signatures: HashSet, + session_tokens: HashSet, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -17,6 +22,13 @@ struct EncryptedPayload { ciphertext: Vec, } +#[derive(Debug, Clone, Serialize, Deserialize)] +struct MessageSignature { + message_id: String, + signature: Vec, + timestamp: u64, +} + impl IpcSecurity { pub fn new() -> Self { Self { @@ -24,6 +36,9 @@ impl IpcSecurity { enable_authentication: false, encryption_key: None, rng: SystemRandom::new(), + allowed_sources: HashSet::new(), + message_signatures: HashSet::new(), + session_tokens: HashSet::new(), } } @@ -44,6 +59,30 @@ impl IpcSecurity { self } + pub fn add_allowed_source(&mut self, source: impl Into) { + self.allowed_sources.insert(source.into()); + } + + pub fn remove_allowed_source(&mut self, source: &str) { + self.allowed_sources.remove(source); + } + + pub fn generate_session_token(&mut self) -> String { + let mut token_bytes = [0u8; 32]; + self.rng.fill(&mut token_bytes).unwrap(); + let token = hex::encode(token_bytes); + self.session_tokens.insert(token.clone()); + token + } + + pub fn validate_session_token(&self, token: &str) -> bool { + self.session_tokens.contains(token) + } + + pub fn revoke_session_token(&mut self, token: &str) { + self.session_tokens.remove(token); + } + pub fn validate_message(&self, message: &IpcMessage) -> Result<()> { if self.enable_authentication { if message.source.is_empty() { @@ -56,6 +95,96 @@ impl IpcSecurity { "Message target cannot be empty".to_string(), )); } + + if !self.allowed_sources.is_empty() + && !self.allowed_sources.contains(&message.source) + { + return Err(IpcError::CapabilityError(format!( + "Source '{}' is not in allowed sources", + message.source + ))); + } + + if message.is_expired() { + return Err(IpcError::MessageExpired); + } + } + Ok(()) + } + + pub fn sign_message(&self, message: &IpcMessage) -> Result> { + let mut signature_data = message.id.clone().into_bytes(); + signature_data.extend_from_slice(&message.source.clone().into_bytes()); + signature_data.extend_from_slice(&message.target.clone().into_bytes()); + signature_data.extend_from_slice(&message.payload); + + if let Some(key) = &self.encryption_key { + let mut nonce_bytes = [0u8; 12]; + self.rng.fill(&mut nonce_bytes).map_err(|e| { + IpcError::SecurityError(format!("Failed to generate nonce: {}", e)) + })?; + + let nonce = Nonce::assume_unique_for_key(nonce_bytes); + let mut signature = signature_data.clone(); + + key.seal_in_place_append_tag(nonce, Aad::empty(), &mut signature) + .map_err(|e| IpcError::SecurityError(format!("Signing failed: {}", e)))?; + + Ok(signature) + } else { + Err(IpcError::SecurityError( + "Encryption key not set for signing".to_string(), + )) + } + } + + pub fn verify_signature(&self, message: &IpcMessage, signature: &[u8]) -> Result { + if let Some(key) = &self.encryption_key { + let mut signature_data = message.id.clone().into_bytes(); + signature_data.extend_from_slice(&message.source.clone().into_bytes()); + signature_data.extend_from_slice(&message.target.clone().into_bytes()); + signature_data.extend_from_slice(&message.payload); + + let mut nonce_bytes = [0u8; 12]; + self.rng.fill(&mut nonce_bytes).map_err(|e| { + IpcError::SecurityError(format!("Failed to generate nonce: {}", e)) + })?; + + let nonce = Nonce::assume_unique_for_key(nonce_bytes); + let mut signature_copy = signature.to_vec(); + + match key.open_in_place(nonce, Aad::empty(), &mut signature_copy) { + Ok(_) => { + let expected_data = &signature_copy[..signature_copy.len() - 16]; + Ok(expected_data == signature_data.as_slice()) + } + Err(_) => Ok(false), + } + } else { + Err(IpcError::SecurityError( + "Encryption key not set for verification".to_string(), + )) + } + } + + pub fn detect_connection_hijacking(&self, message: &IpcMessage) -> Result<()> { + if self.enable_authentication { + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + if current_time - message.timestamp > 300 { + return Err(IpcError::ConnectionHijacked( + "Message timestamp is too old, possible connection hijacking".to_string(), + )); + } + + if !self.validate_session_token(&message.id) { + return Err(IpcError::ConnectionHijacked( + "Invalid session token".to_string(), + )); + } } Ok(()) } @@ -137,11 +266,12 @@ mod tests { fn test_message_validation() { let security = IpcSecurity::new().with_authentication(true); - let mut message = IpcMessage::new("source", "target", vec![1, 2, 3]); + let message = IpcMessage::new("source", "target", vec![1, 2, 3]); assert!(security.validate_message(&message).is_ok()); - message.source = String::new(); - assert!(security.validate_message(&message).is_err()); + let mut empty_source_message = message.clone(); + empty_source_message.source = String::new(); + assert!(security.validate_message(&empty_source_message).is_err()); } #[test] @@ -161,4 +291,26 @@ mod tests { security.decrypt_message(&mut message).unwrap(); assert_eq!(message.payload, original_payload); } + + #[test] + fn test_source_validation() { + let mut security = IpcSecurity::new().with_authentication(true); + security.add_allowed_source("allowed_source"); + + let message = IpcMessage::new("allowed_source", "target", vec![1, 2, 3]); + assert!(security.validate_message(&message).is_ok()); + + let message = IpcMessage::new("disallowed_source", "target", vec![1, 2, 3]); + assert!(security.validate_message(&message).is_err()); + } + + #[test] + fn test_session_token() { + let mut security = IpcSecurity::new(); + let token = security.generate_session_token(); + assert!(security.validate_session_token(&token)); + + security.revoke_session_token(&token); + assert!(!security.validate_session_token(&token)); + } } From ddd33723f728ddb8c682a4ad75dbc07417bc9f69 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 21:00:36 +0800 Subject: [PATCH 02/26] =?UTF-8?q?feat(M004):=20=E6=B7=BB=E5=8A=A0=E9=9B=B6?= =?UTF-8?q?=E6=8B=B7=E8=B4=9D=E6=B6=88=E6=81=AF=E7=B1=BB=E5=9E=8B=E5=92=8C?= =?UTF-8?q?=E9=80=9A=E9=81=93=E7=AE=A1=E7=90=86=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++ src/ipc/lib.rs | 5 +- 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 5508ad9..0fda213 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -1,4 +1,5 @@ use super::{IpcError, Result}; +use bytes::Bytes; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::mpsc; @@ -91,6 +92,47 @@ impl IpcMessage { } } +pub struct ZeroCopyMessage { + pub id: String, + pub source: String, + pub target: String, + pub payload: Bytes, + pub timestamp: u64, + pub ttl: Option, + pub priority: MessagePriority, + pub message_type: MessageType, +} + +impl ZeroCopyMessage { + pub fn new(source: impl Into, target: impl Into, payload: Bytes) -> Self { + Self { + id: uuid::Uuid::new_v4().to_string(), + source: source.into(), + target: target.into(), + payload, + timestamp: std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(), + ttl: None, + priority: MessagePriority::default(), + message_type: MessageType::default(), + } + } + + pub fn is_expired(&self) -> bool { + if let Some(ttl) = self.ttl { + let current_time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + current_time > self.timestamp + (ttl as u64) + } else { + false + } + } +} + pub struct IpcChannel { sender: mpsc::Sender, receiver: Arc>>, @@ -202,6 +244,89 @@ impl Default for BroadcastChannel { } } +pub struct ChannelManager { + channels: Arc>>, + broadcast_channels: Arc>>, +} + +impl ChannelManager { + pub fn new() -> Self { + Self { + channels: Arc::new(RwLock::new(HashMap::new())), + broadcast_channels: Arc::new(RwLock::new(HashMap::new())), + } + } + + pub fn create_channel(&self, name: impl Into) -> Result<()> { + let name = name.into(); + let mut channels = self.channels.write().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + if channels.contains_key(&name) { + return Err(IpcError::ChannelError(format!( + "Channel '{}' already exists", + name + ))); + } + channels.insert(name, IpcChannel::new()); + Ok(()) + } + + pub fn get_channel(&self, name: &str) -> Result { + let channels = self.channels.read().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + channels + .get(name) + .cloned() + .ok_or_else(|| IpcError::ChannelError(format!("Channel '{}' not found", name))) + } + + pub fn remove_channel(&self, name: &str) -> Result<()> { + let mut channels = self.channels.write().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + channels + .remove(name) + .ok_or_else(|| IpcError::ChannelError(format!("Channel '{}' not found", name)))?; + Ok(()) + } + + pub fn create_broadcast_channel(&self, name: impl Into) -> Result<()> { + let name = name.into(); + let mut broadcast_channels = self.broadcast_channels.write().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + if broadcast_channels.contains_key(&name) { + return Err(IpcError::ChannelError(format!( + "Broadcast channel '{}' already exists", + name + ))); + } + broadcast_channels.insert(name, BroadcastChannel::new()); + Ok(()) + } + + pub fn list_channels(&self) -> Vec { + self.channels.read().unwrap().keys().cloned().collect() + } + + pub fn list_broadcast_channels(&self) -> Vec { + self.broadcast_channels + .read() + .unwrap() + .keys() + .cloned() + .collect() + } +} + +impl Default for ChannelManager { + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ipc/lib.rs b/src/ipc/lib.rs index 1f9c34b..5bf1adc 100644 --- a/src/ipc/lib.rs +++ b/src/ipc/lib.rs @@ -2,7 +2,10 @@ pub mod channel; pub mod protocol; pub mod security; -pub use channel::{BroadcastChannel, IpcChannel, IpcMessage, MessagePriority, MessageType}; +pub use channel::{ + BroadcastChannel, ChannelManager, IpcChannel, IpcMessage, MessagePriority, MessageType, + ZeroCopyMessage, +}; pub use protocol::IpcProtocol; pub use security::IpcSecurity; From c4a014271c97919e97e2546e986d98d7b8648d4c Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 21:01:17 +0800 Subject: [PATCH 03/26] =?UTF-8?q?test(M004):=20=E6=B7=BB=E5=8A=A0IPC?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 0fda213..d3af4ba 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -366,9 +366,43 @@ mod tests { #[test] fn test_broadcast_channel() { let mut broadcast = BroadcastChannel::new(); - let receiver = broadcast.add_receiver("test_receiver"); + let _receiver = broadcast.add_receiver("test_receiver"); let message = IpcMessage::new("source", "broadcast", vec![1, 2, 3]); assert!(broadcast.broadcast(message).is_ok()); } + + #[test] + fn test_channel_manager() { + let manager = ChannelManager::new(); + + assert!(manager.create_channel("test_channel").is_ok()); + assert!(manager.create_channel("test_channel").is_err()); + + let channel = manager.get_channel("test_channel"); + assert!(channel.is_ok()); + + assert!(manager.remove_channel("test_channel").is_ok()); + assert!(manager.remove_channel("nonexistent").is_err()); + } + + #[test] + fn test_channel_manager_broadcast() { + let manager = ChannelManager::new(); + + assert!(manager.create_broadcast_channel("test_broadcast").is_ok()); + assert!(manager.create_broadcast_channel("test_broadcast").is_err()); + + let broadcasts = manager.list_broadcast_channels(); + assert!(broadcasts.contains(&"test_broadcast".to_string())); + } + + #[test] + fn test_receive_with_timeout() { + let channel = IpcChannel::new(); + + let result = channel.receive_with_timeout(Duration::from_millis(100)); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + } } From fa3989db5dbe53351b0c2adcc57241a05a87fe99 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 21:01:41 +0800 Subject: [PATCH 04/26] =?UTF-8?q?docs(M004):=20=E6=9B=B4=E6=96=B0=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/compass/MoDa-Browser-dev.md | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/compass/MoDa-Browser-dev.md b/docs/compass/MoDa-Browser-dev.md index aa5db64..242e214 100644 --- a/docs/compass/MoDa-Browser-dev.md +++ b/docs/compass/MoDa-Browser-dev.md @@ -811,4 +811,41 @@ ctest --output-on-failure --- +### 2026年4月17日 - M004 开发日志 + +**今日工作:** + +1. **M004 进程间通信机制开发** + - 评估现有IPC模块实现 + - 扩展IPC架构:添加了消息优先级、TTL、消息类型等功能 + - 实现了异步通道和广播通道支持 + - 增强安全通信:添加了能力验证、会话令牌和防劫持机制 + - 实现零拷贝消息传输优化(使用 `bytes::Bytes`) + - 添加通道管理器(ChannelManager)用于管理多个通道 + +2. **技术实现细节** + - 消息优先级:Low, Normal, High, Critical + - 消息类型:Request, Response, Event, Command, Heartbeat + - 消息TTL支持,防止过期消息被处理 + - 安全通信:支持AES-256-GCM加密、消息签名和验证 + - 防劫持:会话令牌验证、时间戳检查 + - 通道管理器:支持创建、删除、列出通道 + +3. **测试覆盖** + - 添加了完整的单元测试 + - 测试覆盖:消息创建、通道操作、广播、通道管理、超时接收等 + +**技术要点:** +- 使用 `bytes::Bytes` 实现零拷贝传输,减少内存分配 +- 使用 `Arc>` 实现线程安全的通道管理 +- 安全模块使用 `ring` crate 提供加密和签名功能 +- 消息序列化使用 `serde` 和 `serde_json` + +**下一步计划:** +- 集成测试和性能基准测试 +- 更新IPC模块文档 +- 开始M005任务(渲染进程生命周期管理) + +--- + **让我们开始构建未来!** 🚀 From 7c8598da988dc0762e7b913ed9f586ab38fe4a44 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 21:57:08 +0800 Subject: [PATCH 05/26] =?UTF-8?q?fix(M004):=20=E4=BF=AE=E5=A4=8D=E9=80=9A?= =?UTF-8?q?=E9=81=93=E7=AE=A1=E7=90=86=E5=99=A8=E9=94=99=E8=AF=AF=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=92=8C=E4=BC=9A=E8=AF=9D=E4=BB=A4=E7=89=8C=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 47 +++++++++++++++++++++++++++++++-------------- src/ipc/security.rs | 23 +++++++++++++++++++++- 2 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index d3af4ba..7d2a6bb 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -16,6 +16,7 @@ pub struct IpcMessage { pub ttl: Option, pub priority: MessagePriority, pub message_type: MessageType, + pub session_token: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -61,6 +62,7 @@ impl IpcMessage { ttl: None, priority: MessagePriority::default(), message_type: MessageType::default(), + session_token: None, } } @@ -79,6 +81,11 @@ impl IpcMessage { self } + pub fn with_session_token(mut self, token: impl Into) -> Self { + self.session_token = Some(token.into()); + self + } + pub fn is_expired(&self) -> bool { if let Some(ttl) = self.ttl { let current_time = std::time::SystemTime::now() @@ -213,13 +220,13 @@ impl BroadcastChannel { } } - pub fn add_receiver(&mut self, name: impl Into) -> mpsc::Receiver { + pub fn add_receiver(&mut self, name: impl Into) -> Result> { let (sender, receiver) = mpsc::channel(); self.receivers .write() - .unwrap() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))? .insert(name.into(), receiver); - sender + Ok(sender) } pub fn broadcast(&self, message: IpcMessage) -> Result<()> { @@ -307,17 +314,18 @@ impl ChannelManager { Ok(()) } - pub fn list_channels(&self) -> Vec { - self.channels.read().unwrap().keys().cloned().collect() + pub fn list_channels(&self) -> Result> { + let channels = self.channels.read().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + Ok(channels.keys().cloned().collect()) } - pub fn list_broadcast_channels(&self) -> Vec { - self.broadcast_channels - .read() - .unwrap() - .keys() - .cloned() - .collect() + pub fn list_broadcast_channels(&self) -> Result> { + let broadcast_channels = self.broadcast_channels.read().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + Ok(broadcast_channels.keys().cloned().collect()) } } @@ -366,7 +374,7 @@ mod tests { #[test] fn test_broadcast_channel() { let mut broadcast = BroadcastChannel::new(); - let _receiver = broadcast.add_receiver("test_receiver"); + let _receiver = broadcast.add_receiver("test_receiver").unwrap(); let message = IpcMessage::new("source", "broadcast", vec![1, 2, 3]); assert!(broadcast.broadcast(message).is_ok()); @@ -384,6 +392,9 @@ mod tests { assert!(manager.remove_channel("test_channel").is_ok()); assert!(manager.remove_channel("nonexistent").is_err()); + + let channels = manager.list_channels(); + assert!(channels.is_ok()); } #[test] @@ -394,7 +405,15 @@ mod tests { assert!(manager.create_broadcast_channel("test_broadcast").is_err()); let broadcasts = manager.list_broadcast_channels(); - assert!(broadcasts.contains(&"test_broadcast".to_string())); + assert!(broadcasts.is_ok()); + assert!(broadcasts.unwrap().contains(&"test_broadcast".to_string())); + } + + #[test] + fn test_message_session_token() { + let message = IpcMessage::new("source", "target", vec![1, 2, 3]) + .with_session_token("test_token"); + assert_eq!(message.session_token, Some("test_token".to_string())); } #[test] diff --git a/src/ipc/security.rs b/src/ipc/security.rs index d74ae8e..18c8334 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -180,7 +180,11 @@ impl IpcSecurity { )); } - if !self.validate_session_token(&message.id) { + let session_token = message.session_token.as_ref().ok_or_else(|| { + IpcError::ConnectionHijacked("Missing session token".to_string()) + })?; + + if !self.validate_session_token(session_token) { return Err(IpcError::ConnectionHijacked( "Invalid session token".to_string(), )); @@ -313,4 +317,21 @@ mod tests { security.revoke_session_token(&token); assert!(!security.validate_session_token(&token)); } + + #[test] + fn test_detect_connection_hijacking() { + let mut security = IpcSecurity::new().with_authentication(true); + let token = security.generate_session_token(); + + let message = IpcMessage::new("source", "target", vec![1, 2, 3]) + .with_session_token(&token); + assert!(security.detect_connection_hijacking(&message).is_ok()); + + let invalid_message = IpcMessage::new("source", "target", vec![1, 2, 3]) + .with_session_token("invalid_token"); + assert!(security.detect_connection_hijacking(&invalid_message).is_err()); + + let no_token_message = IpcMessage::new("source", "target", vec![1, 2, 3]); + assert!(security.detect_connection_hijacking(&no_token_message).is_err()); + } } From 6a4a060fd449848c28fb8ad676a002bdd56cc0c6 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 22:03:52 +0800 Subject: [PATCH 06/26] =?UTF-8?q?fix(M004):=20=E4=BF=AE=E5=A4=8DBroadcastC?= =?UTF-8?q?hannel=E5=B9=BF=E6=92=AD=E9=80=BB=E8=BE=91=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=92=8Cadd=5Freceiver=E8=BF=94=E5=9B=9E=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 63 +++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 7d2a6bb..4c03cea 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -208,40 +208,59 @@ impl Default for IpcChannel { } pub struct BroadcastChannel { - sender: Arc>>, - receivers: Arc>>>, + senders: Arc>>>, } impl BroadcastChannel { pub fn new() -> Self { Self { - sender: Arc::new(RwLock::new(mpsc::channel().0)), - receivers: Arc::new(RwLock::new(HashMap::new())), + senders: Arc::new(RwLock::new(HashMap::new())), } } pub fn add_receiver(&mut self, name: impl Into) -> Result> { let (sender, receiver) = mpsc::channel(); - self.receivers + self.senders .write() .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))? - .insert(name.into(), receiver); - Ok(sender) + .insert(name.into(), sender); + Ok(receiver) + } + + pub fn remove_receiver(&self, name: &str) -> Result<()> { + let mut senders = self.senders.write().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + senders + .remove(name) + .ok_or_else(|| IpcError::ChannelError(format!("Receiver '{}' not found", name)))?; + Ok(()) } pub fn broadcast(&self, message: IpcMessage) -> Result<()> { - let sender = self.sender.read().unwrap(); - for (_, receiver) in self.receivers.read().unwrap().iter() { - if let Err(e) = receiver.try_recv() { - if let mpsc::TryRecvError::Disconnected = e { - continue; - } + let senders = self.senders.read().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + + let mut errors = Vec::new(); + for (name, sender) in senders.iter() { + if let Err(e) = sender.send(message.clone()) { + errors.push(format!("Failed to send to '{}': {}", name, e)); } } - sender - .send(message) - .map_err(|e| IpcError::ChannelError(format!("Failed to broadcast message: {}", e)))?; - Ok(()) + + if !errors.is_empty() { + Err(IpcError::ChannelError(format!( + "Broadcast errors: {}", + errors.join(", ") + ))) + } else { + Ok(()) + } + } + + pub fn get_receiver_count(&self) -> usize { + self.senders.read().unwrap().len() } } @@ -374,10 +393,18 @@ mod tests { #[test] fn test_broadcast_channel() { let mut broadcast = BroadcastChannel::new(); - let _receiver = broadcast.add_receiver("test_receiver").unwrap(); + let receiver = broadcast.add_receiver("test_receiver").unwrap(); let message = IpcMessage::new("source", "broadcast", vec![1, 2, 3]); assert!(broadcast.broadcast(message).is_ok()); + assert_eq!(broadcast.get_receiver_count(), 1); + + let received = receiver.try_recv().unwrap(); + assert_eq!(received.source, "source"); + assert_eq!(received.target, "broadcast"); + + assert!(broadcast.remove_receiver("test_receiver").is_ok()); + assert_eq!(broadcast.get_receiver_count(), 0); } #[test] From 684da5c732cc30c994e5c55fc2bae6cb0a8a568d Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 22:12:26 +0800 Subject: [PATCH 07/26] =?UTF-8?q?fix(M004):=20=E4=BF=AE=E5=A4=8Dgenerate?= =?UTF-8?q?=5Fsession=5Ftoken=E4=B8=AD=E7=9A=84unwrap=E8=B0=83=E7=94=A8?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E7=94=A8=E5=AE=89=E5=85=A8=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/security.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ipc/security.rs b/src/ipc/security.rs index 18c8334..2bc5b9a 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -67,12 +67,14 @@ impl IpcSecurity { self.allowed_sources.remove(source); } - pub fn generate_session_token(&mut self) -> String { + pub fn generate_session_token(&mut self) -> Result { let mut token_bytes = [0u8; 32]; - self.rng.fill(&mut token_bytes).unwrap(); + self.rng.fill(&mut token_bytes).map_err(|e| { + IpcError::SecurityError(format!("Failed to generate session token: {}", e)) + })?; let token = hex::encode(token_bytes); self.session_tokens.insert(token.clone()); - token + Ok(token) } pub fn validate_session_token(&self, token: &str) -> bool { @@ -311,7 +313,7 @@ mod tests { #[test] fn test_session_token() { let mut security = IpcSecurity::new(); - let token = security.generate_session_token(); + let token = security.generate_session_token().unwrap(); assert!(security.validate_session_token(&token)); security.revoke_session_token(&token); @@ -321,7 +323,7 @@ mod tests { #[test] fn test_detect_connection_hijacking() { let mut security = IpcSecurity::new().with_authentication(true); - let token = security.generate_session_token(); + let token = security.generate_session_token().unwrap(); let message = IpcMessage::new("source", "target", vec![1, 2, 3]) .with_session_token(&token); From f6f0b3fe2fdf472da120a17309365d22bfa663d4 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 22:17:04 +0800 Subject: [PATCH 08/26] =?UTF-8?q?fix(M004):=20=E6=94=B9=E8=BF=9Breceive=5F?= =?UTF-8?q?with=5Ftimeout=E9=80=9A=E9=81=93=E6=96=AD=E5=BC=80=E6=97=B6?= =?UTF-8?q?=E7=9A=84=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 4c03cea..c721eba 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -195,7 +195,9 @@ impl IpcChannel { Ok(msg) => Ok(Some(msg)), Err(mpsc::RecvTimeoutError::Timeout) => Ok(None), Err(mpsc::RecvTimeoutError::Disconnected) => { - Err(IpcError::ChannelError("Channel disconnected".to_string())) + Err(IpcError::ChannelError( + "Channel disconnected, all senders dropped".to_string(), + )) } } } From 00083d88b1b4688779816730c21a75662e1a8b5d Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 22:32:42 +0800 Subject: [PATCH 09/26] =?UTF-8?q?fix(M004):=20=E4=BF=AE=E5=A4=8Dget=5Frece?= =?UTF-8?q?iver=5Fcount=E6=96=B9=E6=B3=95=E4=B8=AD=E7=9A=84unwrap=E8=B0=83?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index c721eba..14d2fd8 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -261,8 +261,11 @@ impl BroadcastChannel { } } - pub fn get_receiver_count(&self) -> usize { - self.senders.read().unwrap().len() + pub fn get_receiver_count(&self) -> Result { + let senders = self.senders.read().map_err(|e| { + IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) + })?; + Ok(senders.len()) } } @@ -399,14 +402,14 @@ mod tests { let message = IpcMessage::new("source", "broadcast", vec![1, 2, 3]); assert!(broadcast.broadcast(message).is_ok()); - assert_eq!(broadcast.get_receiver_count(), 1); + assert_eq!(broadcast.get_receiver_count().unwrap(), 1); let received = receiver.try_recv().unwrap(); assert_eq!(received.source, "source"); assert_eq!(received.target, "broadcast"); assert!(broadcast.remove_receiver("test_receiver").is_ok()); - assert_eq!(broadcast.get_receiver_count(), 0); + assert_eq!(broadcast.get_receiver_count().unwrap(), 0); } #[test] From 66ce97345bba1e1f7fc2f854352625352f398f91 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 22:49:00 +0800 Subject: [PATCH 10/26] =?UTF-8?q?fix(M004):=20=E5=B0=86=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=8A=AB=E6=8C=81=E6=A3=80=E6=B5=8B=E7=9A=84=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E9=98=88=E5=80=BC=E6=94=B9=E4=B8=BA=E5=8F=AF=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/security.rs | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/ipc/security.rs b/src/ipc/security.rs index 2bc5b9a..2b4acfd 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -14,6 +14,7 @@ pub struct IpcSecurity { allowed_sources: HashSet, message_signatures: HashSet, session_tokens: HashSet, + max_message_age_seconds: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -39,6 +40,7 @@ impl IpcSecurity { allowed_sources: HashSet::new(), message_signatures: HashSet::new(), session_tokens: HashSet::new(), + max_message_age_seconds: 300, // 默认 5 分钟 } } @@ -59,6 +61,11 @@ impl IpcSecurity { self } + pub fn with_max_message_age(mut self, max_age_seconds: u64) -> Self { + self.max_message_age_seconds = max_age_seconds; + self + } + pub fn add_allowed_source(&mut self, source: impl Into) { self.allowed_sources.insert(source.into()); } @@ -173,12 +180,18 @@ impl IpcSecurity { if self.enable_authentication { let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) - .unwrap() + .map_err(|e| { + IpcError::SecurityError(format!("Failed to get system time: {}", e)) + })? .as_secs(); - if current_time - message.timestamp > 300 { + if current_time - message.timestamp > self.max_message_age_seconds { return Err(IpcError::ConnectionHijacked( - "Message timestamp is too old, possible connection hijacking".to_string(), + format!( + "Message timestamp is too old ({}s > {}s), possible connection hijacking", + current_time - message.timestamp, + self.max_message_age_seconds + ), )); } @@ -336,4 +349,18 @@ mod tests { let no_token_message = IpcMessage::new("source", "target", vec![1, 2, 3]); assert!(security.detect_connection_hijacking(&no_token_message).is_err()); } + + #[test] + fn test_custom_max_message_age() { + let mut security = IpcSecurity::new() + .with_authentication(true) + .with_max_message_age(60); // 1 分钟 + + let token = security.generate_session_token().unwrap(); + let message = IpcMessage::new("source", "target", vec![1, 2, 3]) + .with_session_token(&token); + + // 默认情况下应该通过(消息是刚创建的) + assert!(security.detect_connection_hijacking(&message).is_ok()); + } } From a1be48b4ca06b659d9dcdc65adf0ff21be0bce69 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 22:53:47 +0800 Subject: [PATCH 11/26] =?UTF-8?q?fix(M004):=20=E4=BF=AE=E5=A4=8D=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E9=AA=8C=E8=AF=81=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B0=86?= =?UTF-8?q?=20nonce=20=E4=B8=8E=E7=AD=BE=E5=90=8D=E4=B8=80=E8=B5=B7?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=92=8C=E4=BC=A0=E8=BE=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/security.rs | 56 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/ipc/security.rs b/src/ipc/security.rs index 2b4acfd..cb30d84 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -25,9 +25,8 @@ struct EncryptedPayload { #[derive(Debug, Clone, Serialize, Deserialize)] struct MessageSignature { - message_id: String, + nonce: Vec, signature: Vec, - timestamp: u64, } impl IpcSecurity { @@ -139,7 +138,15 @@ impl IpcSecurity { key.seal_in_place_append_tag(nonce, Aad::empty(), &mut signature) .map_err(|e| IpcError::SecurityError(format!("Signing failed: {}", e)))?; - Ok(signature) + // 将 nonce 和签名一起序列化 + let message_signature = MessageSignature { + nonce: nonce_bytes.to_vec(), + signature, + }; + + serde_json::to_vec(&message_signature).map_err(|e| { + IpcError::SecurityError(format!("Failed to serialize signature: {}", e)) + }) } else { Err(IpcError::SecurityError( "Encryption key not set for signing".to_string(), @@ -149,18 +156,26 @@ impl IpcSecurity { pub fn verify_signature(&self, message: &IpcMessage, signature: &[u8]) -> Result { if let Some(key) = &self.encryption_key { + // 反序列化签名数据,获取 nonce 和签名 + let message_signature: MessageSignature = + serde_json::from_slice(signature).map_err(|e| { + IpcError::SecurityError(format!("Failed to deserialize signature: {}", e)) + })?; + let mut signature_data = message.id.clone().into_bytes(); signature_data.extend_from_slice(&message.source.clone().into_bytes()); signature_data.extend_from_slice(&message.target.clone().into_bytes()); signature_data.extend_from_slice(&message.payload); - let mut nonce_bytes = [0u8; 12]; - self.rng.fill(&mut nonce_bytes).map_err(|e| { - IpcError::SecurityError(format!("Failed to generate nonce: {}", e)) - })?; + let nonce = Nonce::assume_unique_for_key( + message_signature + .nonce + .as_slice() + .try_into() + .map_err(|_| IpcError::SecurityError("Invalid nonce length".to_string()))?, + ); - let nonce = Nonce::assume_unique_for_key(nonce_bytes); - let mut signature_copy = signature.to_vec(); + let mut signature_copy = message_signature.signature.clone(); match key.open_in_place(nonce, Aad::empty(), &mut signature_copy) { Ok(_) => { @@ -363,4 +378,27 @@ mod tests { // 默认情况下应该通过(消息是刚创建的) assert!(security.detect_connection_hijacking(&message).is_ok()); } + + #[test] + fn test_sign_and_verify() { + let key = [0u8; 32]; + let security = IpcSecurity::new() + .with_encryption(true) + .with_key(&key) + .unwrap(); + + let message = IpcMessage::new("source", "target", vec![1, 2, 3]); + let signature = security.sign_message(&message).unwrap(); + + let is_valid = security.verify_signature(&message, &signature).unwrap(); + assert!(is_valid); + + // 测试篡改消息 + let mut tampered_message = message.clone(); + tampered_message.payload = vec![4, 5, 6]; + let is_valid_tampered = security + .verify_signature(&tampered_message, &signature) + .unwrap(); + assert!(!is_valid_tampered); + } } From 018a259ced0c422d4d254a37117b2b3322405650 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 23:22:40 +0800 Subject: [PATCH 12/26] =?UTF-8?q?fix(M004):=20=E5=9C=A8=20validate=5Fmessa?= =?UTF-8?q?ge=20=E4=B8=AD=E6=B7=BB=E5=8A=A0=20max=5Fmessage=5Fage=5Fsecond?= =?UTF-8?q?s=20=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/security.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/ipc/security.rs b/src/ipc/security.rs index cb30d84..441bca1 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -116,6 +116,18 @@ impl IpcSecurity { if message.is_expired() { return Err(IpcError::MessageExpired); } + + // 检查消息是否超过最大允许年龄 + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| { + IpcError::SecurityError(format!("Failed to get system time: {}", e)) + })? + .as_secs(); + + if current_time - message.timestamp > self.max_message_age_seconds { + return Err(IpcError::MessageExpired); + } } Ok(()) } @@ -401,4 +413,23 @@ mod tests { .unwrap(); assert!(!is_valid_tampered); } + + #[test] + fn test_validate_message_with_max_age() { + let security = IpcSecurity::new() + .with_authentication(true) + .with_max_message_age(1); // 1 秒 + + let message = IpcMessage::new("source", "target", vec![1, 2, 3]); + assert!(security.validate_message(&message).is_ok()); + + // 创建一个旧消息(时间戳为 10 秒前) + let mut old_message = message.clone(); + old_message.timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + - 10; + assert!(security.validate_message(&old_message).is_err()); + } } From 68b0d1eced4546aa07ebdc55a777a8a98f1fc721 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 23:26:58 +0800 Subject: [PATCH 13/26] =?UTF-8?q?fix(M004):=20=E6=94=B9=E8=BF=9B=E9=94=81?= =?UTF-8?q?=E4=B8=AD=E6=AF=92=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF=E5=92=8C?= =?UTF-8?q?=E7=AD=BE=E5=90=8D=E9=AA=8C=E8=AF=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 2 +- src/ipc/security.rs | 49 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 14d2fd8..aadb11c 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -190,7 +190,7 @@ impl IpcChannel { let receiver = self .receiver .lock() - .map_err(|e| IpcError::ChannelError(format!("Lock poisoned: {}", e)))?; + .map_err(|e| IpcError::ChannelError(format!("Receiver lock poisoned: {}", e)))?; match receiver.recv_timeout(timeout) { Ok(msg) => Ok(Some(msg)), Err(mpsc::RecvTimeoutError::Timeout) => Ok(None), diff --git a/src/ipc/security.rs b/src/ipc/security.rs index 441bca1..a084125 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -6,6 +6,15 @@ use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::time::{SystemTime, UNIX_EPOCH}; +#[derive(Debug, Clone, Serialize, Deserialize)] +struct SignatureData { + message_id: String, + source: String, + target: String, + payload: Vec, + timestamp: u64, +} + pub struct IpcSecurity { enable_encryption: bool, enable_authentication: bool, @@ -133,19 +142,28 @@ impl IpcSecurity { } pub fn sign_message(&self, message: &IpcMessage) -> Result> { - let mut signature_data = message.id.clone().into_bytes(); - signature_data.extend_from_slice(&message.source.clone().into_bytes()); - signature_data.extend_from_slice(&message.target.clone().into_bytes()); - signature_data.extend_from_slice(&message.payload); - if let Some(key) = &self.encryption_key { + // 使用确定性结构体构造签名数据 + let signature_data = SignatureData { + message_id: message.id.clone(), + source: message.source.clone(), + target: message.target.clone(), + payload: message.payload.clone(), + timestamp: message.timestamp, + }; + + // 使用 bincode 进行确定性序列化 + let data_to_sign = bincode::serialize(&signature_data).map_err(|e| { + IpcError::SecurityError(format!("Failed to serialize signature data: {}", e)) + })?; + let mut nonce_bytes = [0u8; 12]; self.rng.fill(&mut nonce_bytes).map_err(|e| { IpcError::SecurityError(format!("Failed to generate nonce: {}", e)) })?; let nonce = Nonce::assume_unique_for_key(nonce_bytes); - let mut signature = signature_data.clone(); + let mut signature = data_to_sign; key.seal_in_place_append_tag(nonce, Aad::empty(), &mut signature) .map_err(|e| IpcError::SecurityError(format!("Signing failed: {}", e)))?; @@ -174,10 +192,19 @@ impl IpcSecurity { IpcError::SecurityError(format!("Failed to deserialize signature: {}", e)) })?; - let mut signature_data = message.id.clone().into_bytes(); - signature_data.extend_from_slice(&message.source.clone().into_bytes()); - signature_data.extend_from_slice(&message.target.clone().into_bytes()); - signature_data.extend_from_slice(&message.payload); + // 使用确定性结构体构造签名数据 + let signature_data = SignatureData { + message_id: message.id.clone(), + source: message.source.clone(), + target: message.target.clone(), + payload: message.payload.clone(), + timestamp: message.timestamp, + }; + + // 使用 bincode 进行确定性序列化 + let data_to_verify = bincode::serialize(&signature_data).map_err(|e| { + IpcError::SecurityError(format!("Failed to serialize signature data: {}", e)) + })?; let nonce = Nonce::assume_unique_for_key( message_signature @@ -192,7 +219,7 @@ impl IpcSecurity { match key.open_in_place(nonce, Aad::empty(), &mut signature_copy) { Ok(_) => { let expected_data = &signature_copy[..signature_copy.len() - 16]; - Ok(expected_data == signature_data.as_slice()) + Ok(expected_data == data_to_verify.as_slice()) } Err(_) => Ok(false), } From 5f2d45d54dae139be33d8331b48a03a426968b08 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 23:30:20 +0800 Subject: [PATCH 14/26] =?UTF-8?q?fix(M004):=20=E4=B8=BA=20ZeroCopyMessage?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E5=BA=8F=E5=88=97=E5=8C=96=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index aadb11c..bc8b243 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -99,10 +99,12 @@ impl IpcMessage { } } +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ZeroCopyMessage { pub id: String, pub source: String, pub target: String, + #[serde(with = "bytes_serde")] pub payload: Bytes, pub timestamp: u64, pub ttl: Option, @@ -110,6 +112,27 @@ pub struct ZeroCopyMessage { pub message_type: MessageType, } +// 为 Bytes 类型添加序列化/反序列化支持 +mod bytes_serde { + use bytes::Bytes; + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(bytes: &Bytes, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(bytes.as_ref()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = Vec::::deserialize(deserializer)?; + Ok(Bytes::from(bytes)) + } +} + impl ZeroCopyMessage { pub fn new(source: impl Into, target: impl Into, payload: Bytes) -> Self { Self { @@ -456,4 +479,26 @@ mod tests { assert!(result.is_ok()); assert!(result.unwrap().is_none()); } + + #[test] + fn test_zero_copy_message_serialization() { + use serde_json; + + let payload = Bytes::from(vec![1, 2, 3, 4, 5]); + let message = ZeroCopyMessage::new("source", "target", payload); + + // 序列化 + let serialized = serde_json::to_vec(&message).expect("Serialization should succeed"); + + // 反序列化 + let deserialized: ZeroCopyMessage = serde_json::from_slice(&serialized).expect("Deserialization should succeed"); + + assert_eq!(deserialized.id, message.id); + assert_eq!(deserialized.source, message.source); + assert_eq!(deserialized.target, message.target); + assert_eq!(deserialized.payload, message.payload); + assert_eq!(deserialized.timestamp, message.timestamp); + assert_eq!(deserialized.priority, message.priority); + assert_eq!(deserialized.message_type, message.message_type); + } } From 6f4dfe3225899b46c22fcdd030e032d46cffaf16 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 23:54:41 +0800 Subject: [PATCH 15/26] =?UTF-8?q?chore(M004):=20=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 90 +++++++++++++++++++++++------------------- src/ipc/security.rs | 55 ++++++++++++-------------- src/security/policy.rs | 19 +++++---- test_policy.rs | 8 ++-- 4 files changed, 87 insertions(+), 85 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index bc8b243..1be3c07 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -217,11 +217,9 @@ impl IpcChannel { match receiver.recv_timeout(timeout) { Ok(msg) => Ok(Some(msg)), Err(mpsc::RecvTimeoutError::Timeout) => Ok(None), - Err(mpsc::RecvTimeoutError::Disconnected) => { - Err(IpcError::ChannelError( - "Channel disconnected, all senders dropped".to_string(), - )) - } + Err(mpsc::RecvTimeoutError::Disconnected) => Err(IpcError::ChannelError( + "Channel disconnected, all senders dropped".to_string(), + )), } } } @@ -253,9 +251,10 @@ impl BroadcastChannel { } pub fn remove_receiver(&self, name: &str) -> Result<()> { - let mut senders = self.senders.write().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let mut senders = self + .senders + .write() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; senders .remove(name) .ok_or_else(|| IpcError::ChannelError(format!("Receiver '{}' not found", name)))?; @@ -263,9 +262,10 @@ impl BroadcastChannel { } pub fn broadcast(&self, message: IpcMessage) -> Result<()> { - let senders = self.senders.read().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let senders = self + .senders + .read() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; let mut errors = Vec::new(); for (name, sender) in senders.iter() { @@ -285,9 +285,10 @@ impl BroadcastChannel { } pub fn get_receiver_count(&self) -> Result { - let senders = self.senders.read().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let senders = self + .senders + .read() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; Ok(senders.len()) } } @@ -313,9 +314,10 @@ impl ChannelManager { pub fn create_channel(&self, name: impl Into) -> Result<()> { let name = name.into(); - let mut channels = self.channels.write().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let mut channels = self + .channels + .write() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; if channels.contains_key(&name) { return Err(IpcError::ChannelError(format!( "Channel '{}' already exists", @@ -327,9 +329,10 @@ impl ChannelManager { } pub fn get_channel(&self, name: &str) -> Result { - let channels = self.channels.read().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let channels = self + .channels + .read() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; channels .get(name) .cloned() @@ -337,9 +340,10 @@ impl ChannelManager { } pub fn remove_channel(&self, name: &str) -> Result<()> { - let mut channels = self.channels.write().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let mut channels = self + .channels + .write() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; channels .remove(name) .ok_or_else(|| IpcError::ChannelError(format!("Channel '{}' not found", name)))?; @@ -348,9 +352,10 @@ impl ChannelManager { pub fn create_broadcast_channel(&self, name: impl Into) -> Result<()> { let name = name.into(); - let mut broadcast_channels = self.broadcast_channels.write().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let mut broadcast_channels = self + .broadcast_channels + .write() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; if broadcast_channels.contains_key(&name) { return Err(IpcError::ChannelError(format!( "Broadcast channel '{}' already exists", @@ -362,16 +367,18 @@ impl ChannelManager { } pub fn list_channels(&self) -> Result> { - let channels = self.channels.read().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let channels = self + .channels + .read() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; Ok(channels.keys().cloned().collect()) } pub fn list_broadcast_channels(&self) -> Result> { - let broadcast_channels = self.broadcast_channels.read().map_err(|e| { - IpcError::ChannelError(format!("Failed to acquire lock: {}", e)) - })?; + let broadcast_channels = self + .broadcast_channels + .read() + .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; Ok(broadcast_channels.keys().cloned().collect()) } } @@ -400,15 +407,15 @@ mod tests { #[test] fn test_message_priority() { - let message = IpcMessage::new("source", "target", vec![1, 2, 3]) - .with_priority(MessagePriority::High); + let message = + IpcMessage::new("source", "target", vec![1, 2, 3]).with_priority(MessagePriority::High); assert_eq!(message.priority, MessagePriority::High); } #[test] fn test_message_type() { - let message = IpcMessage::new("source", "target", vec![1, 2, 3]) - .with_type(MessageType::Request); + let message = + IpcMessage::new("source", "target", vec![1, 2, 3]).with_type(MessageType::Request); assert_eq!(message.message_type, MessageType::Request); } @@ -466,8 +473,8 @@ mod tests { #[test] fn test_message_session_token() { - let message = IpcMessage::new("source", "target", vec![1, 2, 3]) - .with_session_token("test_token"); + let message = + IpcMessage::new("source", "target", vec![1, 2, 3]).with_session_token("test_token"); assert_eq!(message.session_token, Some("test_token".to_string())); } @@ -489,10 +496,11 @@ mod tests { // 序列化 let serialized = serde_json::to_vec(&message).expect("Serialization should succeed"); - + // 反序列化 - let deserialized: ZeroCopyMessage = serde_json::from_slice(&serialized).expect("Deserialization should succeed"); - + let deserialized: ZeroCopyMessage = + serde_json::from_slice(&serialized).expect("Deserialization should succeed"); + assert_eq!(deserialized.id, message.id); assert_eq!(deserialized.source, message.source); assert_eq!(deserialized.target, message.target); diff --git a/src/ipc/security.rs b/src/ipc/security.rs index a084125..a10be59 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -113,9 +113,7 @@ impl IpcSecurity { )); } - if !self.allowed_sources.is_empty() - && !self.allowed_sources.contains(&message.source) - { + if !self.allowed_sources.is_empty() && !self.allowed_sources.contains(&message.source) { return Err(IpcError::CapabilityError(format!( "Source '{}' is not in allowed sources", message.source @@ -129,9 +127,7 @@ impl IpcSecurity { // 检查消息是否超过最大允许年龄 let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) - .map_err(|e| { - IpcError::SecurityError(format!("Failed to get system time: {}", e)) - })? + .map_err(|e| IpcError::SecurityError(format!("Failed to get system time: {}", e)))? .as_secs(); if current_time - message.timestamp > self.max_message_age_seconds { @@ -158,9 +154,9 @@ impl IpcSecurity { })?; let mut nonce_bytes = [0u8; 12]; - self.rng.fill(&mut nonce_bytes).map_err(|e| { - IpcError::SecurityError(format!("Failed to generate nonce: {}", e)) - })?; + self.rng + .fill(&mut nonce_bytes) + .map_err(|e| IpcError::SecurityError(format!("Failed to generate nonce: {}", e)))?; let nonce = Nonce::assume_unique_for_key(nonce_bytes); let mut signature = data_to_sign; @@ -234,24 +230,21 @@ impl IpcSecurity { if self.enable_authentication { let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) - .map_err(|e| { - IpcError::SecurityError(format!("Failed to get system time: {}", e)) - })? + .map_err(|e| IpcError::SecurityError(format!("Failed to get system time: {}", e)))? .as_secs(); if current_time - message.timestamp > self.max_message_age_seconds { - return Err(IpcError::ConnectionHijacked( - format!( - "Message timestamp is too old ({}s > {}s), possible connection hijacking", - current_time - message.timestamp, - self.max_message_age_seconds - ), - )); + return Err(IpcError::ConnectionHijacked(format!( + "Message timestamp is too old ({}s > {}s), possible connection hijacking", + current_time - message.timestamp, + self.max_message_age_seconds + ))); } - let session_token = message.session_token.as_ref().ok_or_else(|| { - IpcError::ConnectionHijacked("Missing session token".to_string()) - })?; + let session_token = message + .session_token + .as_ref() + .ok_or_else(|| IpcError::ConnectionHijacked("Missing session token".to_string()))?; if !self.validate_session_token(session_token) { return Err(IpcError::ConnectionHijacked( @@ -392,16 +385,19 @@ mod tests { let mut security = IpcSecurity::new().with_authentication(true); let token = security.generate_session_token().unwrap(); - let message = IpcMessage::new("source", "target", vec![1, 2, 3]) - .with_session_token(&token); + let message = IpcMessage::new("source", "target", vec![1, 2, 3]).with_session_token(&token); assert!(security.detect_connection_hijacking(&message).is_ok()); - let invalid_message = IpcMessage::new("source", "target", vec![1, 2, 3]) - .with_session_token("invalid_token"); - assert!(security.detect_connection_hijacking(&invalid_message).is_err()); + let invalid_message = + IpcMessage::new("source", "target", vec![1, 2, 3]).with_session_token("invalid_token"); + assert!(security + .detect_connection_hijacking(&invalid_message) + .is_err()); let no_token_message = IpcMessage::new("source", "target", vec![1, 2, 3]); - assert!(security.detect_connection_hijacking(&no_token_message).is_err()); + assert!(security + .detect_connection_hijacking(&no_token_message) + .is_err()); } #[test] @@ -411,8 +407,7 @@ mod tests { .with_max_message_age(60); // 1 分钟 let token = security.generate_session_token().unwrap(); - let message = IpcMessage::new("source", "target", vec![1, 2, 3]) - .with_session_token(&token); + let message = IpcMessage::new("source", "target", vec![1, 2, 3]).with_session_token(&token); // 默认情况下应该通过(消息是刚创建的) assert!(security.detect_connection_hijacking(&message).is_ok()); diff --git a/src/security/policy.rs b/src/security/policy.rs index e4d3713..19185bc 100644 --- a/src/security/policy.rs +++ b/src/security/policy.rs @@ -79,7 +79,7 @@ impl PolicyManager { .policies .write() .map_err(|e| SecurityError::PermissionDenied(format!("Lock poisoned: {}", e)))?; - + policies.insert(policy.id.clone(), policy); Ok(()) } @@ -90,11 +90,10 @@ impl PolicyManager { .policies .read() .map_err(|e| SecurityError::PermissionDenied(format!("Lock poisoned: {}", e)))?; - - policies - .get(policy_id) - .cloned() - .ok_or_else(|| SecurityError::PermissionDenied(format!("Policy {} not found", policy_id))) + + policies.get(policy_id).cloned().ok_or_else(|| { + SecurityError::PermissionDenied(format!("Policy {} not found", policy_id)) + }) } /// 检查资源是否具有特定能力 @@ -126,7 +125,7 @@ impl PolicyManager { .policies .write() .map_err(|e| SecurityError::PermissionDenied(format!("Lock poisoned: {}", e)))?; - + if policies.remove(policy_id).is_some() { Ok(()) } else { @@ -143,7 +142,7 @@ impl PolicyManager { .policies .read() .map_err(|e| SecurityError::PermissionDenied(format!("Lock poisoned: {}", e)))?; - + Ok(policies.keys().cloned().collect()) } } @@ -186,9 +185,9 @@ mod tests { assert!(!manager .check_resource_capability("test-resource", &Capability::FileSystemWrite) .unwrap()); - + assert!(manager .check_resource_capability("test-resource", &Capability::FileSystemRead) .is_err()); // 没有明确允许,默认拒绝 } -} \ No newline at end of file +} diff --git a/test_policy.rs b/test_policy.rs index 128fde7..207b8b7 100644 --- a/test_policy.rs +++ b/test_policy.rs @@ -1,8 +1,8 @@ -use moda_security::{PolicyManager, SecurityPolicy, Capability}; +use moda_security::{Capability, PolicyManager, SecurityPolicy}; fn main() { println!("Testing Security Policy Module..."); - + let manager = PolicyManager::new(); let policy = SecurityPolicy::new("test-resource", "Test resource policy") @@ -25,6 +25,6 @@ fn main() { Ok(true) => println!("✗ File system write unexpectedly allowed"), Err(e) => println!("✗ Error checking file system write: {:?}", e), } - + println!("Security Policy Module test completed!"); -} \ No newline at end of file +} From 38a5ce838bb397bba4dacbd7f4452a24897a7c87 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Fri, 17 Apr 2026 23:59:44 +0800 Subject: [PATCH 16/26] =?UTF-8?q?fix(M004):=20=E4=B8=BA=20IpcChannel=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Clone=20trait=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 1be3c07..6928b4c 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -163,6 +163,7 @@ impl ZeroCopyMessage { } } +#[derive(Clone)] pub struct IpcChannel { sender: mpsc::Sender, receiver: Arc>>, From 23f253eee29fff3362e516356e58a6bf215b5a69 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 00:02:28 +0800 Subject: [PATCH 17/26] =?UTF-8?q?fix(M004):=20=E5=88=A0=E9=99=A4=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=9A=84=20message=5Fsignatures=20=E5=AD=97?= =?UTF-8?q?=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/security.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ipc/security.rs b/src/ipc/security.rs index a10be59..f00e170 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -21,7 +21,6 @@ pub struct IpcSecurity { encryption_key: Option, rng: SystemRandom, allowed_sources: HashSet, - message_signatures: HashSet, session_tokens: HashSet, max_message_age_seconds: u64, } @@ -46,7 +45,6 @@ impl IpcSecurity { encryption_key: None, rng: SystemRandom::new(), allowed_sources: HashSet::new(), - message_signatures: HashSet::new(), session_tokens: HashSet::new(), max_message_age_seconds: 300, // 默认 5 分钟 } From b3bcbd07f0324790d2ad99888b1b58165e20bd33 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 00:15:44 +0800 Subject: [PATCH 18/26] =?UTF-8?q?fix(M004):=20=E4=BF=AE=E5=A4=8D=E5=B9=BF?= =?UTF-8?q?=E6=92=AD=E9=80=9A=E9=81=93=E6=B6=88=E6=81=AF=E8=BF=87=E6=9C=9F?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=92=8C=E7=AD=BE=E5=90=8D=E7=AE=97=E6=B3=95?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E9=A3=8E=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 BroadcastChannel::broadcast 方法中添加消息过期检查,避免发送无效消息 - 将签名算法从 AES-GCM 加密改为 HMAC-SHA256 专门签名算法,提高安全性 - 添加 hmac 和 sha2 依赖 - 更新 IpcSecurity 结构体,添加独立的 signature_key 字段 - 清理未使用的 MessageSignature 结构体和变量 --- Cargo.lock | 89 +++++++++++++++++++++++++++++++++++++++++++++ src/ipc/Cargo.toml | 2 + src/ipc/channel.rs | 5 +++ src/ipc/security.rs | 86 +++++++++++++++---------------------------- 4 files changed, 126 insertions(+), 56 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f0336c..f576948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,15 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -174,6 +183,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "criterion" version = "0.5.1" @@ -241,6 +259,27 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -370,6 +409,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -457,6 +506,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.12" @@ -796,9 +854,11 @@ dependencies = [ "bincode", "bytes", "hex", + "hmac", "ring", "serde", "serde_json", + "sha2", "thiserror", "tokio", "tracing", @@ -1331,6 +1391,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1394,6 +1465,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -1625,6 +1702,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -1684,6 +1767,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" diff --git a/src/ipc/Cargo.toml b/src/ipc/Cargo.toml index 0ba96c2..7134cbd 100644 --- a/src/ipc/Cargo.toml +++ b/src/ipc/Cargo.toml @@ -18,3 +18,5 @@ uuid = { version = "1.6", features = ["v4"] } bincode = "1.3" ring = "0.17" hex = "0.4" +hmac = "0.12" +sha2 = "0.10" diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 6928b4c..80347d5 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -263,6 +263,11 @@ impl BroadcastChannel { } pub fn broadcast(&self, message: IpcMessage) -> Result<()> { + // 检查消息是否已过期,如果过期则不发送 + if message.is_expired() { + return Err(IpcError::MessageExpired); + } + let senders = self .senders .read() diff --git a/src/ipc/security.rs b/src/ipc/security.rs index f00e170..26788ec 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -1,8 +1,10 @@ use super::channel::IpcMessage; use super::{IpcError, Result}; +use hmac::{Hmac, Mac}; use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM}; use ring::rand::{SecureRandom, SystemRandom}; use serde::{Deserialize, Serialize}; +use sha2::Sha256; use std::collections::HashSet; use std::time::{SystemTime, UNIX_EPOCH}; @@ -19,6 +21,7 @@ pub struct IpcSecurity { enable_encryption: bool, enable_authentication: bool, encryption_key: Option, + signature_key: Option>, rng: SystemRandom, allowed_sources: HashSet, session_tokens: HashSet, @@ -31,11 +34,7 @@ struct EncryptedPayload { ciphertext: Vec, } -#[derive(Debug, Clone, Serialize, Deserialize)] -struct MessageSignature { - nonce: Vec, - signature: Vec, -} + impl IpcSecurity { pub fn new() -> Self { @@ -43,6 +42,7 @@ impl IpcSecurity { enable_encryption: false, enable_authentication: false, encryption_key: None, + signature_key: None, rng: SystemRandom::new(), allowed_sources: HashSet::new(), session_tokens: HashSet::new(), @@ -59,6 +59,12 @@ impl IpcSecurity { let unbound_key = UnboundKey::new(&AES_256_GCM, key) .map_err(|e| IpcError::SecurityError(format!("Invalid encryption key: {}", e)))?; self.encryption_key = Some(LessSafeKey::new(unbound_key)); + + // 同时设置签名密钥 + let signature_key = Hmac::::new_from_slice(key) + .map_err(|e| IpcError::SecurityError(format!("Invalid signature key: {}", e)))?; + self.signature_key = Some(signature_key); + Ok(self) } @@ -136,7 +142,7 @@ impl IpcSecurity { } pub fn sign_message(&self, message: &IpcMessage) -> Result> { - if let Some(key) = &self.encryption_key { + if let Some(ref signature_key) = self.signature_key { // 使用确定性结构体构造签名数据 let signature_data = SignatureData { message_id: message.id.clone(), @@ -151,42 +157,25 @@ impl IpcSecurity { IpcError::SecurityError(format!("Failed to serialize signature data: {}", e)) })?; - let mut nonce_bytes = [0u8; 12]; - self.rng - .fill(&mut nonce_bytes) - .map_err(|e| IpcError::SecurityError(format!("Failed to generate nonce: {}", e)))?; + // 使用 HMAC-SHA256 对数据进行签名 + let mut mac = signature_key.clone(); + mac.update(&data_to_sign); + let signature = mac.finalize(); - let nonce = Nonce::assume_unique_for_key(nonce_bytes); - let mut signature = data_to_sign; - - key.seal_in_place_append_tag(nonce, Aad::empty(), &mut signature) - .map_err(|e| IpcError::SecurityError(format!("Signing failed: {}", e)))?; - - // 将 nonce 和签名一起序列化 - let message_signature = MessageSignature { - nonce: nonce_bytes.to_vec(), - signature, - }; - - serde_json::to_vec(&message_signature).map_err(|e| { - IpcError::SecurityError(format!("Failed to serialize signature: {}", e)) - }) + // 返回签名结果 + Ok(signature.into_bytes().to_vec()) } else { Err(IpcError::SecurityError( - "Encryption key not set for signing".to_string(), + "Signature key not set for signing".to_string(), )) } } pub fn verify_signature(&self, message: &IpcMessage, signature: &[u8]) -> Result { - if let Some(key) = &self.encryption_key { - // 反序列化签名数据,获取 nonce 和签名 - let message_signature: MessageSignature = - serde_json::from_slice(signature).map_err(|e| { - IpcError::SecurityError(format!("Failed to deserialize signature: {}", e)) - })?; - - // 使用确定性结构体构造签名数据 + if let Some(ref signature_key) = self.signature_key { + // 使用恒定时间比较防止时序攻击 + use hmac::Mac; + let mut mac = signature_key.clone(); let signature_data = SignatureData { message_id: message.id.clone(), source: message.source.clone(), @@ -194,32 +183,17 @@ impl IpcSecurity { payload: message.payload.clone(), timestamp: message.timestamp, }; - - // 使用 bincode 进行确定性序列化 - let data_to_verify = bincode::serialize(&signature_data).map_err(|e| { + let data_to_sign = bincode::serialize(&signature_data).map_err(|e| { IpcError::SecurityError(format!("Failed to serialize signature data: {}", e)) })?; + mac.update(&data_to_sign); + let expected_signature = mac.finalize(); - let nonce = Nonce::assume_unique_for_key( - message_signature - .nonce - .as_slice() - .try_into() - .map_err(|_| IpcError::SecurityError("Invalid nonce length".to_string()))?, - ); - - let mut signature_copy = message_signature.signature.clone(); - - match key.open_in_place(nonce, Aad::empty(), &mut signature_copy) { - Ok(_) => { - let expected_data = &signature_copy[..signature_copy.len() - 16]; - Ok(expected_data == data_to_verify.as_slice()) - } - Err(_) => Ok(false), - } + // 比较签名 + Ok(expected_signature.into_bytes().as_slice() == signature) } else { Err(IpcError::SecurityError( - "Encryption key not set for verification".to_string(), + "Signature key not set for verification".to_string(), )) } } @@ -415,7 +389,7 @@ mod tests { fn test_sign_and_verify() { let key = [0u8; 32]; let security = IpcSecurity::new() - .with_encryption(true) + .with_authentication(true) // 启用认证而不是加密,因为现在签名是独立的功能 .with_key(&key) .unwrap(); From 9dcbc91a5ab9e08f34de9cb1ad962ee67a194277 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 00:17:40 +0800 Subject: [PATCH 19/26] =?UTF-8?q?style(ipc):=20=E7=A7=BB=E9=99=A4=E5=A4=9A?= =?UTF-8?q?=E4=BD=99=E7=A9=BA=E8=A1=8C=E5=B9=B6=E7=BB=9F=E4=B8=80=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/security.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ipc/security.rs b/src/ipc/security.rs index 26788ec..2c5c29f 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -34,8 +34,6 @@ struct EncryptedPayload { ciphertext: Vec, } - - impl IpcSecurity { pub fn new() -> Self { Self { @@ -59,12 +57,12 @@ impl IpcSecurity { let unbound_key = UnboundKey::new(&AES_256_GCM, key) .map_err(|e| IpcError::SecurityError(format!("Invalid encryption key: {}", e)))?; self.encryption_key = Some(LessSafeKey::new(unbound_key)); - + // 同时设置签名密钥 let signature_key = Hmac::::new_from_slice(key) .map_err(|e| IpcError::SecurityError(format!("Invalid signature key: {}", e)))?; self.signature_key = Some(signature_key); - + Ok(self) } @@ -389,7 +387,7 @@ mod tests { fn test_sign_and_verify() { let key = [0u8; 32]; let security = IpcSecurity::new() - .with_authentication(true) // 启用认证而不是加密,因为现在签名是独立的功能 + .with_authentication(true) // 启用认证而不是加密,因为现在签名是独立的功能 .with_key(&key) .unwrap(); From 40b5f0632a5d7641e5c2f5a7385681646d49f1b4 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 00:22:55 +0800 Subject: [PATCH 20/26] =?UTF-8?q?refactor(security):=20=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=B9=B4=E9=BE=84=E6=A3=80=E6=9F=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=88=B0=E7=8B=AC=E7=AB=8B=E6=96=B9=E6=B3=95=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A0=E5=AF=86=E6=A0=87=E7=AD=BE=E9=95=BF?= =?UTF-8?q?=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将消息年龄检查逻辑从 validate_message 提取到独立的 check_message_age 方法以提高代码可维护性。 同时修复解密时硬编码的标签长度,改为使用 AES_256_GCM.tag_len() 获取实际标签长度。 --- src/ipc/security.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/ipc/security.rs b/src/ipc/security.rs index 2c5c29f..ae44bf7 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -102,6 +102,18 @@ impl IpcSecurity { self.session_tokens.remove(token); } + fn check_message_age(&self, message: &IpcMessage) -> Result<()> { + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| IpcError::SecurityError(format!("Failed to get system time: {}", e)))? + .as_secs(); + + if current_time - message.timestamp > self.max_message_age_seconds { + return Err(IpcError::MessageExpired); + } + Ok(()) + } + pub fn validate_message(&self, message: &IpcMessage) -> Result<()> { if self.enable_authentication { if message.source.is_empty() { @@ -126,15 +138,7 @@ impl IpcSecurity { return Err(IpcError::MessageExpired); } - // 检查消息是否超过最大允许年龄 - let current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|e| IpcError::SecurityError(format!("Failed to get system time: {}", e)))? - .as_secs(); - - if current_time - message.timestamp > self.max_message_age_seconds { - return Err(IpcError::MessageExpired); - } + self.check_message_age(message)?; } Ok(()) } @@ -280,7 +284,8 @@ impl IpcSecurity { key.open_in_place(nonce, Aad::empty(), &mut plaintext) .map_err(|e| IpcError::SecurityError(format!("Decryption failed: {}", e)))?; - plaintext.truncate(plaintext.len() - 16); + let tag_len = AES_256_GCM.tag_len(); + plaintext.truncate(plaintext.len() - tag_len); message.payload = plaintext; } From add12dcb712a56f9be70ec842af794f38579539a Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 00:28:01 +0800 Subject: [PATCH 21/26] =?UTF-8?q?perf(ipc):=20=E4=BC=98=E5=8C=96=E5=B9=BF?= =?UTF-8?q?=E6=92=AD=E6=B6=88=E6=81=AF=E6=97=B6=E7=9A=84=E5=85=8B=E9=9A=86?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 避免在每次循环迭代中克隆消息,改为在循环外克隆一次 --- src/ipc/channel.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 80347d5..4ac8c5f 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -263,7 +263,6 @@ impl BroadcastChannel { } pub fn broadcast(&self, message: IpcMessage) -> Result<()> { - // 检查消息是否已过期,如果过期则不发送 if message.is_expired() { return Err(IpcError::MessageExpired); } @@ -274,8 +273,9 @@ impl BroadcastChannel { .map_err(|e| IpcError::ChannelError(format!("Failed to acquire lock: {}", e)))?; let mut errors = Vec::new(); + let message_clone = message.clone(); for (name, sender) in senders.iter() { - if let Err(e) = sender.send(message.clone()) { + if let Err(e) = sender.send(message_clone.clone()) { errors.push(format!("Failed to send to '{}': {}", name, e)); } } From 5fad7b7dff29fa099520013571350d259902d713 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 00:38:27 +0800 Subject: [PATCH 22/26] =?UTF-8?q?fix(ipc):=20=E6=AD=A3=E7=A1=AE=E5=A4=84?= =?UTF-8?q?=E7=90=86=E7=B3=BB=E7=BB=9F=E6=97=B6=E9=97=B4=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E6=97=B6=E9=97=B4=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改is_expired方法返回Result类型以处理系统时间错误 添加TimeError枚举类型用于表示系统时间错误 更新相关调用点处理可能的错误情况 --- src/ipc/channel.rs | 20 ++++++++++---------- src/ipc/lib.rs | 2 ++ src/ipc/security.rs | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 4ac8c5f..47b55b3 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -86,15 +86,15 @@ impl IpcMessage { self } - pub fn is_expired(&self) -> bool { + pub fn is_expired(&self) -> Result { if let Some(ttl) = self.ttl { let current_time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .unwrap() + .map_err(|_| IpcError::TimeError)? .as_secs(); - current_time > self.timestamp + (ttl as u64) + Ok(current_time > self.timestamp + (ttl as u64)) } else { - false + Ok(false) } } } @@ -150,15 +150,15 @@ impl ZeroCopyMessage { } } - pub fn is_expired(&self) -> bool { + pub fn is_expired(&self) -> Result { if let Some(ttl) = self.ttl { let current_time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .unwrap() + .map_err(|_| IpcError::TimeError)? .as_secs(); - current_time > self.timestamp + (ttl as u64) + Ok(current_time > self.timestamp + (ttl as u64)) } else { - false + Ok(false) } } } @@ -263,7 +263,7 @@ impl BroadcastChannel { } pub fn broadcast(&self, message: IpcMessage) -> Result<()> { - if message.is_expired() { + if message.is_expired()? { return Err(IpcError::MessageExpired); } @@ -428,7 +428,7 @@ mod tests { #[test] fn test_message_ttl() { let message = IpcMessage::new("source", "target", vec![1, 2, 3]).with_ttl(60); - assert!(!message.is_expired()); + assert!(!message.is_expired().unwrap()); } #[test] diff --git a/src/ipc/lib.rs b/src/ipc/lib.rs index 5bf1adc..52afe7a 100644 --- a/src/ipc/lib.rs +++ b/src/ipc/lib.rs @@ -25,6 +25,8 @@ pub enum IpcError { MessageExpired, #[error("Connection hijacked: {0}")] ConnectionHijacked(String), + #[error("Time error: system time is invalid")] + TimeError, } pub type Result = std::result::Result; diff --git a/src/ipc/security.rs b/src/ipc/security.rs index ae44bf7..b3f58d3 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -134,7 +134,7 @@ impl IpcSecurity { ))); } - if message.is_expired() { + if message.is_expired()? { return Err(IpcError::MessageExpired); } From 4e217b0e5137deea43095d97844559de9411476e Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 00:50:03 +0800 Subject: [PATCH 23/26] =?UTF-8?q?refactor(ipc):=20=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=E5=B9=B6?= =?UTF-8?q?=E9=87=8D=E6=9E=84=E8=BF=87=E6=9C=9F=E6=A3=80=E6=9F=A5=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 TimeError 改为包含详细错误信息 提取重复的过期检查逻辑到共享方法 添加中文注释说明安全验证逻辑 --- src/ipc/channel.rs | 22 +++++++++------------- src/ipc/lib.rs | 4 ++-- src/ipc/security.rs | 3 +++ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index 47b55b3..e53012d 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -86,17 +86,21 @@ impl IpcMessage { self } - pub fn is_expired(&self) -> Result { - if let Some(ttl) = self.ttl { + fn check_time_expiration(timestamp: u64, ttl: Option) -> Result { + if let Some(ttl) = ttl { let current_time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .map_err(|_| IpcError::TimeError)? + .map_err(|e| IpcError::TimeError(format!("Failed to get system time: {}", e)))? .as_secs(); - Ok(current_time > self.timestamp + (ttl as u64)) + Ok(current_time > timestamp + (ttl as u64)) } else { Ok(false) } } + + pub fn is_expired(&self) -> Result { + Self::check_time_expiration(self.timestamp, self.ttl) + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -151,15 +155,7 @@ impl ZeroCopyMessage { } pub fn is_expired(&self) -> Result { - if let Some(ttl) = self.ttl { - let current_time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map_err(|_| IpcError::TimeError)? - .as_secs(); - Ok(current_time > self.timestamp + (ttl as u64)) - } else { - Ok(false) - } + IpcMessage::check_time_expiration(self.timestamp, self.ttl) } } diff --git a/src/ipc/lib.rs b/src/ipc/lib.rs index 52afe7a..b4e79dc 100644 --- a/src/ipc/lib.rs +++ b/src/ipc/lib.rs @@ -25,8 +25,8 @@ pub enum IpcError { MessageExpired, #[error("Connection hijacked: {0}")] ConnectionHijacked(String), - #[error("Time error: system time is invalid")] - TimeError, + #[error("Time error: {0}")] + TimeError(String), } pub type Result = std::result::Result; diff --git a/src/ipc/security.rs b/src/ipc/security.rs index b3f58d3..d915fd7 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -127,6 +127,8 @@ impl IpcSecurity { )); } + // 如果设置了允许源列表且列表不为空,则检查源是否在列表中 + // 如果列表为空,则默认允许所有源 if !self.allowed_sources.is_empty() && !self.allowed_sources.contains(&message.source) { return Err(IpcError::CapabilityError(format!( "Source '{}' is not in allowed sources", @@ -202,6 +204,7 @@ impl IpcSecurity { pub fn detect_connection_hijacking(&self, message: &IpcMessage) -> Result<()> { if self.enable_authentication { + // 检查消息是否过期,如果过期则可能存在连接劫持 let current_time = SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|e| IpcError::SecurityError(format!("Failed to get system time: {}", e)))? From 1a14c12b35ed6e06a0bfe27fe1b27ebc3f48dfea Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 01:19:11 +0800 Subject: [PATCH 24/26] =?UTF-8?q?fix(ipc):=20=E4=BD=BF=E7=94=A8checked=5Fa?= =?UTF-8?q?dd=E9=98=B2=E6=AD=A2=E6=97=B6=E9=97=B4=E6=88=B3=E6=95=B4?= =?UTF-8?q?=E6=95=B0=E6=BA=A2=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在检查IPC消息过期时间时,使用checked_add替代直接加法运算,避免潜在的整数溢出问题。当检测到溢出时返回错误而非静默处理。 --- src/ipc/channel.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index e53012d..dfba5bc 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -92,7 +92,13 @@ impl IpcMessage { .duration_since(std::time::UNIX_EPOCH) .map_err(|e| IpcError::TimeError(format!("Failed to get system time: {}", e)))? .as_secs(); - Ok(current_time > timestamp + (ttl as u64)) + + // 使用 checked_add 避免整数溢出 + let expiration_time = timestamp + .checked_add(ttl as u64) + .ok_or_else(|| IpcError::TimeError("Timestamp overflow detected".to_string()))?; + + Ok(current_time > expiration_time) } else { Ok(false) } From 767db49e3229b232b8f3d81b6cc860997020a8da Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 01:25:20 +0800 Subject: [PATCH 25/26] =?UTF-8?q?feat(security):=20=E4=BD=BF=E7=94=A8HKDF?= =?UTF-8?q?=E6=B4=BE=E7=94=9F=E7=8B=AC=E7=AB=8B=E7=9A=84=E5=8A=A0=E5=AF=86?= =?UTF-8?q?=E5=92=8C=E7=AD=BE=E5=90=8D=E5=AF=86=E9=92=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加hkdf依赖并实现从主密钥派生独立加密和签名密钥的功能,增强安全性 --- Cargo.lock | 10 ++++++++++ src/ipc/Cargo.toml | 1 + src/ipc/security.rs | 25 ++++++++++++++++++++++--- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f576948..c2f8e3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -506,6 +506,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -854,6 +863,7 @@ dependencies = [ "bincode", "bytes", "hex", + "hkdf", "hmac", "ring", "serde", diff --git a/src/ipc/Cargo.toml b/src/ipc/Cargo.toml index 7134cbd..7d72904 100644 --- a/src/ipc/Cargo.toml +++ b/src/ipc/Cargo.toml @@ -20,3 +20,4 @@ ring = "0.17" hex = "0.4" hmac = "0.12" sha2 = "0.10" +hkdf = "0.12" diff --git a/src/ipc/security.rs b/src/ipc/security.rs index d915fd7..4527ccf 100644 --- a/src/ipc/security.rs +++ b/src/ipc/security.rs @@ -1,5 +1,6 @@ use super::channel::IpcMessage; use super::{IpcError, Result}; +use hkdf::Hkdf; use hmac::{Hmac, Mac}; use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey, AES_256_GCM}; use ring::rand::{SecureRandom, SystemRandom}; @@ -54,12 +55,30 @@ impl IpcSecurity { } pub fn with_key(mut self, key: &[u8; 32]) -> Result { - let unbound_key = UnboundKey::new(&AES_256_GCM, key) + // 使用 HKDF 从主密钥派生独立的加密和签名密钥 + let hkdf = Hkdf::::new(None, key); + + // 派生加密密钥 + let mut encryption_key_bytes = [0u8; 32]; + hkdf.expand(b"moda-ipc-encryption-key", &mut encryption_key_bytes) + .map_err(|e| { + IpcError::SecurityError(format!("Failed to derive encryption key: {}", e)) + })?; + + // 派生签名密钥 + let mut signature_key_bytes = [0u8; 32]; + hkdf.expand(b"moda-ipc-signature-key", &mut signature_key_bytes) + .map_err(|e| { + IpcError::SecurityError(format!("Failed to derive signature key: {}", e)) + })?; + + // 设置加密密钥 + let unbound_key = UnboundKey::new(&AES_256_GCM, &encryption_key_bytes) .map_err(|e| IpcError::SecurityError(format!("Invalid encryption key: {}", e)))?; self.encryption_key = Some(LessSafeKey::new(unbound_key)); - // 同时设置签名密钥 - let signature_key = Hmac::::new_from_slice(key) + // 设置签名密钥 + let signature_key = Hmac::::new_from_slice(&signature_key_bytes) .map_err(|e| IpcError::SecurityError(format!("Invalid signature key: {}", e)))?; self.signature_key = Some(signature_key); From 51eecc6c3100e7914e6fb615713efe68c51c8103 Mon Sep 17 00:00:00 2001 From: Ink-dark Date: Sat, 18 Apr 2026 10:40:27 +0800 Subject: [PATCH 26/26] =?UTF-8?q?refactor(ipc):=20=E4=BD=BF=E7=94=A8=20#[d?= =?UTF-8?q?erive(Default)]=20=E6=9B=BF=E4=BB=A3=E6=89=8B=E5=8A=A8=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ipc/channel.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/ipc/channel.rs b/src/ipc/channel.rs index dfba5bc..181c821 100644 --- a/src/ipc/channel.rs +++ b/src/ipc/channel.rs @@ -19,35 +19,25 @@ pub struct IpcMessage { pub session_token: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub enum MessagePriority { Low, + #[default] Normal, High, Critical, } -impl Default for MessagePriority { - fn default() -> Self { - MessagePriority::Normal - } -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] pub enum MessageType { Request, Response, + #[default] Event, Command, Heartbeat, } -impl Default for MessageType { - fn default() -> Self { - MessageType::Event - } -} - impl IpcMessage { pub fn new(source: impl Into, target: impl Into, payload: Vec) -> Self { Self {