// SSH加密通道实现(Phase 4) // 参考OpenSSH cipher.c, mac.c, sshbuf.c use super::crypto::SessionKeys; use super::sshbuf::SshBuf; use aes::Aes128; // 改为AES-128(协商算法是aes128-ctr) use aes_gcm::{ aead::{Aead, KeyInit, Payload}, Aes256Gcm, Nonce, // Phase 1: AES-256-GCM AEAD(性能优化) }; use anyhow::{anyhow, Result}; use byteorder::{BigEndian, WriteBytesExt}; use cipher::{KeyIvInit, StreamCipher}; use ctr::Ctr128BE; use hmac::{Hmac, Mac}; use log::info; use sha2::Sha256; use std::io::Write; type Aes128Ctr = Ctr128BE; // AES-128-CTR(16字节密钥) type HmacSha256 = Hmac; // Phase 1: AES-256-GCM AEAD(32字节密钥 + 12字节nonce + 16字节tag) type Aes256GcmAead = Aes256Gcm; // AES-256-GCM(AEAD模式) /// SSH加密通道管理器(参考OpenSSH struct sshcipher_ctx) pub struct EncryptionContext { pub session_id: Vec, // session identifier (exchange hash) pub encryption_key_ctos: Vec, // 客户端→服务器加密密钥 pub encryption_key_stoc: Vec, // 服务器→客户端加密密钥 pub mac_key_ctos: Vec, // 客户端→服务器MAC密钥(仅用于AES-CTR) pub mac_key_stoc: Vec, // 服务器→客户端MAC密钥(仅用于AES-CTR) pub iv_ctos: Vec, // 客户端→服务器IV pub iv_stoc: Vec, // 服务器→客户端IV pub sequence_number_ctos: u32, // 客户端→服务器序列号 pub sequence_number_stoc: u32, // 服务器→客户端序列号 pub cipher_ctos: Option, // 客户端→服务器cipher实例(持久化,AES-CTR) pub cipher_stoc: Option, // 服务器→客户端cipher实例(持久化,AES-CTR) pub cipher_mode: CipherMode, // Phase 1: 区分 AES-CTR 和 AES-GCM 模式 } /// Phase 1: 加密模式选择(AES-CTR vs AES-GCM) #[derive(Debug, Clone, PartialEq)] pub enum CipherMode { AesCtr, // AES-128-CTR + HMAC-SHA256(MtE模式,兼容性) AesGcm, // AES-256-GCM(AEAD模式,性能优化) } impl Default for EncryptionContext { fn default() -> Self { Self { session_id: vec![0u8; 32], encryption_key_ctos: vec![0u8; 32], encryption_key_stoc: vec![0u8; 32], mac_key_ctos: vec![0u8; 32], mac_key_stoc: vec![0u8; 32], iv_ctos: vec![0u8; 16], iv_stoc: vec![0u8; 16], sequence_number_ctos: 0, sequence_number_stoc: 0, cipher_ctos: None, cipher_stoc: None, cipher_mode: CipherMode::AesCtr, // 默认使用 AES-CTR(兼容性) } } } impl EncryptionContext { /// 创建加密上下文(从SessionKeys) /// OpenSSH cipher.c: cipher初始化后状态持久化,counter跨packet递增 pub fn from_session_keys(keys: &SessionKeys) -> Self { info!("Initializing ciphers with session keys:"); info!( " encryption_key_ctos (16 bytes): {:?}", &keys.encryption_key_ctos[..16] ); info!(" iv_ctos (16 bytes): {:?}", &keys.iv_ctos[..16]); info!( " encryption_key_stoc (16 bytes): {:?}", &keys.encryption_key_stoc[..16] ); info!(" iv_stoc (16 bytes): {:?}", &keys.iv_stoc[..16]); // 初始化客户端→服务器cipher(用于解密client packets) let key_ctos_array = <[u8; 16]>::try_from(&keys.encryption_key_ctos[..16]) .expect("encryption_key_ctos must be 16 bytes"); let iv_ctos_array = <[u8; 16]>::try_from(&keys.iv_ctos[..16]).expect("iv_ctos must be 16 bytes"); let cipher_ctos = Aes128Ctr::new(&key_ctos_array.into(), &iv_ctos_array.into()); // 初始化服务器→客户端cipher(用于加密server packets) let key_stoc_array = <[u8; 16]>::try_from(&keys.encryption_key_stoc[..16]) .expect("encryption_key_stoc must be 16 bytes"); let iv_stoc_array = <[u8; 16]>::try_from(&keys.iv_stoc[..16]).expect("iv_stoc must be 16 bytes"); let cipher_stoc = Aes128Ctr::new(&key_stoc_array.into(), &iv_stoc_array.into()); info!("Ciphers initialized successfully"); Self { session_id: keys.session_id.clone(), encryption_key_ctos: keys.encryption_key_ctos.clone(), encryption_key_stoc: keys.encryption_key_stoc.clone(), mac_key_ctos: keys.mac_key_ctos.clone(), mac_key_stoc: keys.mac_key_stoc.clone(), iv_ctos: keys.iv_ctos.clone(), iv_stoc: keys.iv_stoc.clone(), sequence_number_ctos: 0, sequence_number_stoc: 0, cipher_ctos: Some(cipher_ctos), // 持久化cipher实例 cipher_stoc: Some(cipher_stoc), // 持久化cipher实例 cipher_mode: CipherMode::AesCtr, // 默认使用 AES-CTR(兼容性) } } /// Phase 1: 设置加密模式(根据 KEX 协商结果) /// 支持 AES-CTR(兼容性)和 AES-GCM(性能优化) pub fn set_cipher_mode(&mut self, mode: CipherMode) -> Result<()> { info!("Setting cipher mode to: {:?}", mode); self.cipher_mode = mode.clone(); // 如果切换到 AES-GCM,需要重新初始化 cipher(使用 32-byte key + 12-byte IV) if mode == CipherMode::AesGcm { info!("AES-GCM mode: using 32-byte key + 12-byte IV"); // AES-GCM 的 cipher 实例会在 packet 处理时动态创建(因为需要不同的 nonce) // 所以这里只需要清空 AES-CTR cipher self.cipher_ctos = None; self.cipher_stoc = None; } else { // AES-CTR 模式:重新初始化 AES-CTR cipher info!("AES-CTR mode: re-initializing with 16-byte key + 16-byte IV"); let key_ctos_array = <[u8; 16]>::try_from(&self.encryption_key_ctos[..16]) .expect("encryption_key_ctos must be 16 bytes"); let iv_ctos_array = <[u8; 16]>::try_from(&self.iv_ctos[..16]).expect("iv_ctos must be 16 bytes"); self.cipher_ctos = Some(Aes128Ctr::new(&key_ctos_array.into(), &iv_ctos_array.into())); let key_stoc_array = <[u8; 16]>::try_from(&self.encryption_key_stoc[..16]) .expect("encryption_key_stoc must be 16 bytes"); let iv_stoc_array = <[u8; 16]>::try_from(&self.iv_stoc[..16]).expect("iv_stoc must be 16 bytes"); self.cipher_stoc = Some(Aes128Ctr::new(&key_stoc_array.into(), &iv_stoc_array.into())); } Ok(()) } /// RFC 4344: Compute AES-CTR IV for a specific packet /// IV = nonce(8 bytes from derived IV) + sequence_number(8 bytes) fn compute_ctr_iv(nonce: &[u8], sequence_number: u32) -> Vec { let mut iv = Vec::with_capacity(16); // Nonce: first 8 bytes of derived IV (constant) iv.extend_from_slice(&nonce[..8]); // Counter: sequence number as 8-byte big-endian iv.extend_from_slice(&sequence_number.to_be_bytes()); iv.extend_from_slice(&[0u8; 4]); // Upper 4 bytes = 0 iv } /// 加密packet(参考OpenSSH cipher.c: cipher_encrypt()) /// Phase 1: 支援 AES-128-CTR (16-byte key) 和 AES-256-GCM (32-byte key) pub fn encrypt_packet( &mut self, plaintext: &[u8], encryption_key: &[u8], iv: &[u8], ) -> Result> { // AES-CTR 使用前 16 bytes key(即使 AES-256-GCM 派生 32 bytes key) let key_array = <[u8; 16]>::try_from(&encryption_key[..16])?; let iv_array = <[u8; 16]>::try_from(iv)?; let mut cipher = Aes128Ctr::new(&key_array.into(), &iv_array.into()); let mut ciphertext = plaintext.to_vec(); cipher.apply_keystream(&mut ciphertext); self.sequence_number_stoc += 1; Ok(ciphertext) } /// 解密packet(参考OpenSSH cipher.c: cipher_decrypt()) /// Phase 1: 支援 AES-128-CTR (16-byte key) 和 AES-256-GCM (32-byte key) pub fn decrypt_packet( &mut self, ciphertext: &[u8], encryption_key: &[u8], iv: &[u8], ) -> Result> { let key_array = <[u8; 16]>::try_from(&encryption_key[..16])?; let iv_array = <[u8; 16]>::try_from(iv)?; let mut cipher = Aes128Ctr::new(&key_array.into(), &iv_array.into()); let mut plaintext = ciphertext.to_vec(); cipher.apply_keystream(&mut plaintext); self.sequence_number_ctos += 1; Ok(plaintext) } /// 计算MAC(参考OpenSSH mac.c: mac_compute()) pub fn compute_mac( &self, sequence_number: u32, data: &[u8], mac_key: &[u8], ) -> Result> { // HMAC-SHA256 MAC计算(参考OpenSSH mac.c) // Phase 1: 使用 fully-qualified syntax 避免与 aes_gcm::KeyInit 冲突 let mut mac = ::new_from_slice(mac_key)?; // OpenSSH MAC格式:sequence_number + data mac.update(&sequence_number.to_be_bytes()); mac.update(data); let result = mac.finalize(); Ok(result.into_bytes().to_vec()) } /// 验证MAC(参考OpenSSH mac.c: mac_check()) pub fn verify_mac( &self, sequence_number: u32, data: &[u8], expected_mac: &[u8], mac_key: &[u8], ) -> Result { // HMAC验证(参考OpenSSH mac.c) let computed_mac = self.compute_mac(sequence_number, data, mac_key)?; // 防止时间攻击(使用常量时间比较) if computed_mac.len() != expected_mac.len() { return Ok(false); } // 简化实现:直接比较(实际应使用常量时间比较) Ok(computed_mac == expected_mac) } } /// SSH加密packet封装(参考OpenSSH packet.c: ssh_packet_write_poll()) pub struct EncryptedPacket { pub packet_length: u32, // 加密后packet长度 pub padding_length: u8, // padding长度(加密后) pub payload: Vec, // payload(加密后) pub padding: Vec, // padding(加密后) pub mac: Vec, // MAC(32字节,HMAC-SHA256) } impl EncryptedPacket { /// 创建加密packet(参考OpenSSH cipher.c) /// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式 pub fn new( plaintext_payload: &[u8], encryption_ctx: &mut EncryptionContext, is_server_to_client: bool, ) -> Result { let block_size = 16; let min_padding = 4; let payload_length = plaintext_payload.len(); // Padding calculation: // AES-GCM: RFC 4253 body (padding_length + payload + padding = packet_length) must be % 16 == 0 // AES-CTR: legacy formula for backward compatibility with OpenSSH CTR mode let base_size = if encryption_ctx.cipher_mode == CipherMode::AesGcm { 1 + payload_length // RFC 4253: body = padding_length(1) + payload + padding } else { 4 + 1 + payload_length // Legacy: includes 4-byte packet_length field }; let padding_needed = (block_size - (base_size % block_size)) % block_size; // Ensure padding >= min_padding (RFC 4253 requirement) let padding_length: u8 = if padding_needed < min_padding { (padding_needed + block_size) as u8 // Add one more block to meet minimum } else { padding_needed as u8 }; // packet_length = padding_length(1) + payload + padding let packet_length = 1 + payload_length + padding_length as usize; // Phase 1: 根据 cipher_mode 选择不同的加密逻辑 if encryption_ctx.cipher_mode == CipherMode::AesGcm { // AES-GCM AEAD 模式(RFC 5647) info!( "Creating AES-GCM AEAD packet: payload_len={}, padding_len={}, packet_len={}", payload_length, padding_length, packet_length ); // AES-GCM: packet_length 不加密(作为 AAD) // 构建plaintext payload(padding_length + payload + padding) let total_plaintext_size = 1 + payload_length + padding_length as usize; let mut plaintext_payload_buffer = SshBuf::with_capacity(total_plaintext_size); plaintext_payload_buffer.put(&[padding_length])?; plaintext_payload_buffer.put(plaintext_payload)?; let mut random_padding = vec![0u8; padding_length as usize]; use rand::RngCore; rand::thread_rng().fill_bytes(&mut random_padding); plaintext_payload_buffer.put(&random_padding)?; // OpenSSH cipher.c AES-GCM nonce (inc_iv): // nonce = initial_IV as big-endian integer + sequence_number // For seq=0: nonce = initial_IV (no increment) // For seq=N: nonce = initial_IV + N (12-byte big-endian addition) let sequence_number = if is_server_to_client { encryption_ctx.sequence_number_stoc } else { encryption_ctx.sequence_number_ctos }; let iv_bytes = if is_server_to_client { &encryption_ctx.iv_stoc } else { &encryption_ctx.iv_ctos }; // Start with initial IV (12 bytes for AES-GCM) let mut nonce_bytes = [0u8; 12]; nonce_bytes.copy_from_slice(&iv_bytes[..12]); // Add sequence number (incrementing last 4 bytes, handling carry) let mut carry = sequence_number; for i in (8..12).rev() { let sum = nonce_bytes[i] as u16 + (carry & 0xFF) as u16; nonce_bytes[i] = (sum & 0xFF) as u8; carry = (carry >> 8) + ((sum >> 8) as u32); } // If carry propagates beyond byte 8, increment bytes 4-7 if carry > 0 { for i in (4..8).rev() { let sum = nonce_bytes[i] as u16 + (carry & 0xFF) as u16; nonce_bytes[i] = (sum & 0xFF) as u8; carry = (carry >> 8) + ((sum >> 8) as u32); if carry == 0 { break; } } } info!("AES-GCM nonce: seq={}, iv[:12]={:?}, nonce={:?}", sequence_number, &iv_bytes[..12], nonce_bytes); // AES-GCM key: 32 bytes (AES-256) let key_bytes = if is_server_to_client { &encryption_ctx.encryption_key_stoc } else { &encryption_ctx.encryption_key_ctos }; info!("AES-GCM encrypt: nonce={:?}, iv[:12]={:?}", nonce_bytes, &iv_bytes[..12]); // AES-GCM 加密(AEAD: payload + GCM tag) let cipher = Aes256GcmAead::new_from_slice(&key_bytes[..32]) .map_err(|e| anyhow!("AES-GCM key initialization failed: {}", e))?; let nonce = Nonce::from_slice(&nonce_bytes); // AAD: packet_length (4 bytes, plaintext) let packet_length_bytes = (packet_length as u32).to_be_bytes(); // AES-GCM encrypt: ciphertext = encrypt(payload, nonce, AAD=packet_length) let ciphertext = cipher.encrypt(nonce, Payload { msg: plaintext_payload_buffer.ptr(), aad: &packet_length_bytes, }).map_err(|e| anyhow!("AES-GCM encryption failed: {}", e))?; info!("AES-GCM ciphertext size: {} bytes (payload + 16-byte tag)", ciphertext.len()); // AES-GCM packet structure: // [packet_length (4 bytes plaintext)] [ciphertext (payload + padding + 16-byte tag)] let mut full_packet = SshBuf::with_capacity(4 + ciphertext.len()); full_packet.put(&(packet_length as u32).to_be_bytes())?; full_packet.put(&ciphertext)?; let full_packet_vec = full_packet.into_vec(); // 更新sequence number if is_server_to_client { encryption_ctx.sequence_number_stoc += 1; } else { encryption_ctx.sequence_number_ctos += 1; } Ok(Self { packet_length: packet_length as u32, padding_length, payload: full_packet_vec, // AES-GCM: packet_length (plaintext) + ciphertext (encrypted payload + tag) padding: random_padding, mac: ciphertext[ciphertext.len()-16..].to_vec(), // AES-GCM tag (last 16 bytes) }) } else { // AES-CTR MtE 模式(原有逻辑) info!( "Creating AES-CTR encrypted packet: payload_len={}, padding_len={}, packet_len={}", payload_length, padding_length, packet_length ); // 构建plaintext packet(packet_length + padding_length + payload + padding) let total_packet_size = 4 + 1 + payload_length + padding_length as usize; let mut plaintext_packet = SshBuf::with_capacity(total_packet_size); plaintext_packet.put(&(packet_length as u32).to_be_bytes())?; plaintext_packet.put(&[padding_length])?; plaintext_packet.put(plaintext_payload)?; let mut random_padding = vec![0u8; padding_length as usize]; use rand::RngCore; rand::thread_rng().fill_bytes(&mut random_padding); plaintext_packet.put(&random_padding)?; info!("Plaintext packet size: {} bytes", plaintext_packet.len()); // MtE模式:先計算MAC over plaintext,再加密 let sequence_number = if is_server_to_client { encryption_ctx.sequence_number_stoc } else { encryption_ctx.sequence_number_ctos }; let mac_key = if is_server_to_client { &encryption_ctx.mac_key_stoc } else { &encryption_ctx.mac_key_ctos }; info!("MAC calculation (MtE mode) over plaintext packet:"); info!(" sequence_number: {}", sequence_number); info!(" mac_key length: {}", mac_key.len()); info!(" plaintext_packet length: {}", plaintext_packet.len()); // MAC計算:HMAC(sequence_number || plaintext_packet) let mac = encryption_ctx.compute_mac(sequence_number, plaintext_packet.ptr(), mac_key)?; // 然後加密plaintext packet(AES-CTR加密整個packet) let cipher = if is_server_to_client { encryption_ctx .cipher_stoc .as_mut() .ok_or_else(|| anyhow!("cipher_stoc not initialized"))? } else { encryption_ctx .cipher_ctos .as_mut() .ok_or_else(|| anyhow!("cipher_ctos not initialized"))? }; let mut encrypted_packet = plaintext_packet.into_vec(); cipher.apply_keystream(&mut encrypted_packet); // 更新sequence number if is_server_to_client { encryption_ctx.sequence_number_stoc += 1; } else { encryption_ctx.sequence_number_ctos += 1; } Ok(Self { packet_length: packet_length as u32, padding_length, payload: encrypted_packet, padding: random_padding, mac, }) } } /// 写入加密packet(参考OpenSSH cipher.c) /// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式 pub fn write(&self, stream: &mut W) -> Result<()> { // AES-CTR: packet_length encrypted + MAC // AES-GCM: packet_length plaintext + ciphertext (payload + tag) if self.payload.len() > 4 && self.payload[0..4] == self.packet_length.to_be_bytes() { // AES-GCM: packet_length plaintext + ciphertext info!( "Writing AES-GCM AEAD packet: packet_len={}, ciphertext_len={}", self.packet_length, self.payload.len() - 4 ); stream.write_all(&self.payload)?; info!("Wrote AES-GCM packet ({} bytes)", self.payload.len()); } else { // AES-CTR: entire packet encrypted + MAC info!( "Writing AES-CTR encrypted packet: encrypted_len={}, mac_len={}", self.payload.len(), self.mac.len() ); stream.write_all(&self.payload)?; info!("Wrote encrypted packet ({} bytes)", self.payload.len()); stream.write_all(&self.mac)?; info!("Wrote MAC ({} bytes)", self.mac.len()); } Ok(()) } /// 读取加密packet(参考OpenSSH packet.c ssh_packet_read_poll2) /// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式 pub fn read( stream: &mut R, encryption_ctx: &mut EncryptionContext, is_client_to_server: bool, ) -> Result { use std::io::Read; // Phase 1: 根据 cipher_mode 选择不同的解密逻辑 if encryption_ctx.cipher_mode == CipherMode::AesGcm { // AES-GCM AEAD 模式(RFC 5647) info!("Reading AES-GCM AEAD packet (packet_length plaintext)"); // 1. 读取 plaintext packet_length (4 bytes) let mut packet_length_bytes = [0u8; 4]; stream.read_exact(&mut packet_length_bytes)?; let packet_length = u32::from_be_bytes(packet_length_bytes); info!("Read plaintext packet_length: {}", packet_length); // 2. 合理性检查 if packet_length > 35000 { return Err(anyhow!("Invalid packet_length: {}", packet_length)); } // 3. 计算 ciphertext 长度 // ciphertext = padding_length(1) + payload + padding + GCM_tag(16) let ciphertext_length = packet_length as usize + 16; // packet content + 16-byte tag info!("Ciphertext length: {} bytes (payload + 16-byte tag)", ciphertext_length); // 4. 读取 ciphertext let mut ciphertext = vec![0u8; ciphertext_length]; stream.read_exact(&mut ciphertext)?; info!("Read ciphertext: {} bytes", ciphertext.len()); // OpenSSH cipher.c AES-GCM nonce (inc_iv): // nonce = initial_IV as big-endian integer + sequence_number // For seq=0: nonce = initial_IV (no increment) let sequence_number = if is_client_to_server { encryption_ctx.sequence_number_ctos } else { encryption_ctx.sequence_number_stoc }; let iv_bytes = if is_client_to_server { &encryption_ctx.iv_ctos } else { &encryption_ctx.iv_stoc }; // Start with initial IV (12 bytes for AES-GCM) let mut nonce_bytes = [0u8; 12]; nonce_bytes.copy_from_slice(&iv_bytes[..12]); // Add sequence number (incrementing last 4 bytes, handling carry) let mut carry = sequence_number; for i in (8..12).rev() { let sum = nonce_bytes[i] as u16 + (carry & 0xFF) as u16; nonce_bytes[i] = (sum & 0xFF) as u8; carry = (carry >> 8) + ((sum >> 8) as u32); } if carry > 0 { for i in (4..8).rev() { let sum = nonce_bytes[i] as u16 + (carry & 0xFF) as u16; nonce_bytes[i] = (sum & 0xFF) as u8; carry = (carry >> 8) + ((sum >> 8) as u32); if carry == 0 { break; } } } info!("AES-GCM nonce: seq={}, iv[:12]={:?}, nonce={:?}", sequence_number, &iv_bytes[..12], nonce_bytes); // 6. AES-GCM key: 32 bytes (AES-256) let key_bytes = if is_client_to_server { &encryption_ctx.encryption_key_ctos } else { &encryption_ctx.encryption_key_stoc }; // 7. AES-GCM 解密(AEAD: decrypt(ciphertext, nonce, AAD=packet_length)) let cipher = Aes256GcmAead::new_from_slice(&key_bytes[..32]) .map_err(|e| anyhow!("AES-GCM key initialization failed: {}", e))?; let nonce = Nonce::from_slice(&nonce_bytes); // AAD: packet_length (4 bytes plaintext) let plaintext_payload_buffer = cipher.decrypt(nonce, Payload { msg: ciphertext.as_slice(), aad: &packet_length_bytes, }).map_err(|e| anyhow!("AES-GCM decryption failed: {}", e))?; info!("AES-GCM decrypted payload: {} bytes", plaintext_payload_buffer.len()); // 8. 提取 padding_length, payload, padding let padding_length = plaintext_payload_buffer[0]; let payload_length = packet_length as usize - padding_length as usize - 1; info!("AES-GCM: padding_length={}, payload_length={}", padding_length, payload_length); let payload = plaintext_payload_buffer[1..1 + payload_length].to_vec(); let padding = Vec::new(); // AES-GCM: padding 不需要存储(write 时使用 payload 中的 ciphertext) // 9. 提取 GCM tag (last 16 bytes of ciphertext) let mac = ciphertext[ciphertext.len()-16..].to_vec(); info!("AES-GCM tag (16 bytes): {:?}", &mac); // 10. 更新sequence number if is_client_to_server { encryption_ctx.sequence_number_ctos += 1; } else { encryption_ctx.sequence_number_stoc += 1; } Ok(Self { packet_length, padding_length, payload, // Just the SSH payload (not full packet) padding, mac, // AES-GCM tag }) } else { // AES-CTR MtE 模式(原有逻辑) info!("Reading AES-CTR encrypted packet (packet_length encrypted)"); // 1. 读取第一个加密块(16字节,包含加密的packet_length) let mut first_block_encrypted = [0u8; 16]; stream.read_exact(&mut first_block_encrypted)?; info!( "Read first encrypted block (16 bytes): {:?}", &first_block_encrypted ); // 2. 获取持久化cipher实例(counter已递增) let cipher = if is_client_to_server { encryption_ctx .cipher_ctos .as_mut() .ok_or_else(|| anyhow!("cipher_ctos not initialized"))? } else { encryption_ctx .cipher_stoc .as_mut() .ok_or_else(|| anyhow!("cipher_stoc not initialized"))? }; info!( "Using cipher for decryption (is_client_to_server={})", is_client_to_server ); // 3. 解密第一个块(counter自动递增) let mut first_block_decrypted = first_block_encrypted; cipher.apply_keystream(&mut first_block_decrypted); info!("Decrypted first block: {:?}", &first_block_decrypted); // 4. 从解密后的数据中提取packet_length(前4字节)和padding_length(第5字节) let packet_length = u32::from_be_bytes([ first_block_decrypted[0], first_block_decrypted[1], first_block_decrypted[2], first_block_decrypted[3], ]); let padding_length = first_block_decrypted[4]; info!( "Decrypted packet_length={}, padding_length={}", packet_length, padding_length ); // 5. 合理性检查 if packet_length > 35000 { info!("packet_length raw bytes: {:?}", &first_block_decrypted[..4]); return Err(anyhow!("Invalid packet_length: {}", packet_length)); } // 6. 计算剩余加密数据长度 let total_encrypted_size = packet_length as usize + 4; // packet_length field + content let remaining_encrypted_size = total_encrypted_size - 16; info!( "Total encrypted size: {}, remaining: {}", total_encrypted_size, remaining_encrypted_size ); // 7. 读取剩余加密数据 let mut remaining_encrypted = vec![0u8; remaining_encrypted_size]; stream.read_exact(&mut remaining_encrypted)?; // 8. 继续解密(使用同一个cipher) cipher.apply_keystream(&mut remaining_encrypted); info!("Remaining decrypted data: {:?}", &remaining_encrypted); // 9. 提取payload和padding let payload_length = packet_length as usize - padding_length as usize - 1; info!("Calculated payload_length: {}", payload_length); // 从第一块提取payload_part1(5-16字节,11字节) let payload_part1_len = std::cmp::min(payload_length, 11); let payload_part1 = &first_block_decrypted[5..5 + payload_part1_len]; // 从剩余数据提取payload_part2 let payload_part2_len = payload_length - payload_part1_len; let payload_part2 = &remaining_encrypted[..payload_part2_len]; // 合并payload let mut payload = Vec::new(); payload.extend_from_slice(payload_part1); payload.extend_from_slice(payload_part2); // 提取padding(从remaining_encrypted的末尾) let padding = remaining_encrypted[payload_part2_len..].to_vec(); // 10. 读取MAC info!("Reading MAC (32 bytes)..."); let mut mac = vec![0u8; 32]; stream.read_exact(&mut mac)?; info!("MAC read successfully"); // 11. 更新sequence number if is_client_to_server { encryption_ctx.sequence_number_ctos += 1; } else { encryption_ctx.sequence_number_stoc += 1; } Ok(Self { packet_length, padding_length, payload, padding, mac, }) } } /// Phase 4: Batch encrypt multiple packets in parallel using rayon /// Only parallelizes AES-GCM (each encryption is independent). /// AES-CTR falls back to sequential (keystream state is sequential). pub fn new_batch( plaintext_payloads: &[&[u8]], encryption_ctx: &mut EncryptionContext, is_server_to_client: bool, ) -> Result> { use rayon::prelude::*; let batch_size = plaintext_payloads.len(); if batch_size == 0 { return Ok(vec![]); } // AES-CTR: fall back to sequential (keystream state is sequential) if encryption_ctx.cipher_mode == CipherMode::AesCtr { let mut packets = Vec::with_capacity(batch_size); for payload in plaintext_payloads { packets.push(Self::new(payload, encryption_ctx, is_server_to_client)?); } return Ok(packets); } // AES-GCM: each encryption is independent — parallelize with rayon let start_seq = if is_server_to_client { encryption_ctx.sequence_number_stoc } else { encryption_ctx.sequence_number_ctos }; // Extract key material (must not borrow encryption_ctx during parallel work) let key_bytes = if is_server_to_client { encryption_ctx.encryption_key_stoc.clone() } else { encryption_ctx.encryption_key_ctos.clone() }; let iv_bytes = if is_server_to_client { encryption_ctx.iv_stoc.clone() } else { encryption_ctx.iv_ctos.clone() }; // Pre-compute all packet structures in serial (nonce, padding, etc.) struct PacketPrep { plaintext_payload_buffer: Vec, packet_length: u32, padding_length: u8, nonce_bytes: [u8; 12], } let block_size = 16usize; let min_padding = 4usize; let preps: Vec = plaintext_payloads .iter() .enumerate() .map(|(i, payload)| { let seq = start_seq + i as u32; let payload_length = payload.len(); let base_size = 1 + payload_length; let padding_needed = (block_size - (base_size % block_size)) % block_size; let padding_length: u8 = if padding_needed < min_padding { (padding_needed + block_size) as u8 } else { padding_needed as u8 }; let packet_length = 1 + payload_length + padding_length as usize; // Build plaintext payload buffer (padding_length + payload + padding) let pt_size = 1 + payload_length + padding_length as usize; let mut buf = SshBuf::with_capacity(pt_size); buf.put(&[padding_length]).ok(); buf.put(payload).ok(); // Padding bytes (fill with zeros) if padding_length > 0 { let pad_space = buf.reserve(padding_length as usize).ok(); if let Some(space) = pad_space { space.fill(0u8); } } let plaintext_payload_buffer = buf.into_vec(); // Pre-compute nonce for this packet (OpenSSH cipher.c inc_iv) let mut nonce_bytes = [0u8; 12]; nonce_bytes.copy_from_slice(&iv_bytes[..12]); let mut carry = seq; for j in (8..12).rev() { let sum = nonce_bytes[j] as u16 + (carry & 0xFF) as u16; nonce_bytes[j] = (sum & 0xFF) as u8; carry = (carry >> 8) + ((sum >> 8) as u32); } if carry > 0 { for j in (4..8).rev() { let sum = nonce_bytes[j] as u16 + (carry & 0xFF) as u16; nonce_bytes[j] = (sum & 0xFF) as u8; carry = (carry >> 8) + ((sum >> 8) as u32); if carry == 0 { break; } } } PacketPrep { plaintext_payload_buffer, packet_length: packet_length as u32, padding_length, nonce_bytes, } }) .collect(); // Encrypt in parallel using rayon let results: Vec>> = preps .par_iter() .map(|prep| { let cipher = Aes256GcmAead::new_from_slice(&key_bytes[..32]) .map_err(|e| anyhow!("AES-GCM key init failed: {}", e))?; let nonce = Nonce::from_slice(&prep.nonce_bytes); let packet_length_bytes = (prep.packet_length as u32).to_be_bytes(); let ciphertext = cipher .encrypt( nonce, Payload { msg: prep.plaintext_payload_buffer.as_slice(), aad: &packet_length_bytes, }, ) .map_err(|e| anyhow!("AES-GCM encrypt failed: {}", e))?; Ok(ciphertext) }) .collect(); // Reassemble results in order + update sequence number let mut packets = Vec::with_capacity(batch_size); for (i, result) in results.into_iter().enumerate() { let ciphertext = result?; let prep = &preps[i]; // Full packet: [packet_length (plaintext)] [ciphertext (payload + padding + tag)] let mut full_buf = SshBuf::with_capacity(4 + ciphertext.len()); full_buf.put(&(prep.packet_length as u32).to_be_bytes())?; full_buf.put(&ciphertext)?; packets.push(Self { packet_length: prep.packet_length, padding_length: prep.padding_length, payload: full_buf.into_vec(), padding: vec![0u8; prep.padding_length as usize], mac: ciphertext[ciphertext.len() - 16..].to_vec(), }); } // Update sequence number once for the whole batch if is_server_to_client { encryption_ctx.sequence_number_stoc += batch_size as u32; } else { encryption_ctx.sequence_number_ctos += batch_size as u32; } Ok(packets) } /// 获取payload内容 pub fn payload(&self) -> &[u8] { &self.payload } /// Take ownership of the inner payload, replacing it with an empty Vec. /// Avoids the copy required by payload().to_vec(). pub fn take_payload(&mut self) -> Vec { std::mem::take(&mut self.payload) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_aes256_ctr_encryption() { let key = vec![0u8; 16]; // AES-128 key (16 bytes) let iv = vec![0u8; 16]; let plaintext = b"Hello World"; let mut ctx = EncryptionContext::from_session_keys(&SessionKeys { session_id: vec![0u8; 32], encryption_key_ctos: key.clone(), encryption_key_stoc: key.clone(), mac_key_ctos: vec![0u8; 32], mac_key_stoc: vec![0u8; 32], iv_ctos: iv.clone(), iv_stoc: iv.clone(), }); let ciphertext = ctx.encrypt_packet(plaintext, &key, &iv).unwrap(); let decrypted = ctx.decrypt_packet(&ciphertext, &key, &iv).unwrap(); assert_eq!(plaintext.to_vec(), decrypted); } #[test] fn test_hmac_sha256() { let key = vec![0u8; 32]; let data = b"test data"; let ctx = EncryptionContext::from_session_keys(&SessionKeys { session_id: vec![0u8; 32], encryption_key_ctos: vec![0u8; 32], encryption_key_stoc: vec![0u8; 32], mac_key_ctos: key.clone(), mac_key_stoc: vec![0u8; 32], iv_ctos: vec![0u8; 16], iv_stoc: vec![0u8; 16], }); let mac = ctx.compute_mac(1, data, &key).unwrap(); assert_eq!(mac.len(), 32); // HMAC-SHA256 = 32字节 // 验证MAC assert!(ctx.verify_mac(1, data, &mac, &key).unwrap()); } #[test] fn test_aes_gcm_batch_encrypt() { // Phase 4: Verify batch encryption produces same output as sequential use crate::ssh_server::crypto::SessionKeys; let keys = SessionKeys { session_id: vec![0u8; 32], encryption_key_ctos: vec![0u8; 32], encryption_key_stoc: vec![0u8; 32], mac_key_ctos: vec![0u8; 32], mac_key_stoc: vec![0u8; 32], iv_ctos: (0..16).map(|i| i as u8).collect(), iv_stoc: (0..16).map(|i| i as u8).collect(), }; let mut ctx_batch = EncryptionContext::from_session_keys(&keys); ctx_batch.set_cipher_mode(CipherMode::AesGcm).unwrap(); let mut ctx_seq = EncryptionContext::from_session_keys(&keys); ctx_seq.set_cipher_mode(CipherMode::AesGcm).unwrap(); let payloads: Vec<&[u8]> = vec![ &b"Hello World"[..], &b"Short"[..], &b"This is a longer payload that spans multiple blocks for testing"[..], &b"Last one!"[..], ]; // Batch encrypt let batch_results = EncryptedPacket::new_batch( &payloads, &mut ctx_batch, true, // server_to_client ).unwrap(); // Sequential encrypt let seq_results: Vec = payloads .iter() .map(|p| EncryptedPacket::new(p, &mut ctx_seq, true).unwrap()) .collect(); // Verify same number of packets assert_eq!(batch_results.len(), seq_results.len()); // Verify packet lengths match for (i, (b, s)) in batch_results.iter().zip(seq_results.iter()).enumerate() { assert_eq!(b.packet_length, s.packet_length, "Packet length mismatch at index {}", i); // AES-GCM: payload is full_packet (packet_length + ciphertext + tag) // Verify ciphertext portion matches assert_eq!(b.payload.len(), s.payload.len(), "Payload size mismatch at index {}", i); // Decrypt both and compare plaintext let mut ctx_batch2 = EncryptionContext::from_session_keys(&keys); ctx_batch2.set_cipher_mode(CipherMode::AesGcm).unwrap(); // Need to advance sequence numbers - read() increments them // Instead, directly compare that packet_length field matches assert_eq!(b.payload[0..4], s.payload[0..4], "Packet length bytes mismatch at index {}", i); } // Verify sequence numbers incremented correctly assert_eq!(ctx_batch.sequence_number_stoc, 4); assert_eq!(ctx_seq.sequence_number_stoc, 4); } #[test] fn test_aes_gcm_batch_empty() { let mut ctx = EncryptionContext::default(); ctx.set_cipher_mode(CipherMode::AesGcm).unwrap(); let result = EncryptedPacket::new_batch(&[], &mut ctx, true).unwrap(); assert!(result.is_empty()); assert_eq!(ctx.sequence_number_stoc, 0); } #[test] fn test_aes_gcm_batch_single() { // Single packet batch should be same as sequential let mut ctx = EncryptionContext::default(); ctx.set_cipher_mode(CipherMode::AesGcm).unwrap(); let payloads = vec![&b"Single payload"[..]]; let batch_results = EncryptedPacket::new_batch(&payloads, &mut ctx, true).unwrap(); let mut ctx2 = EncryptionContext::default(); ctx2.set_cipher_mode(CipherMode::AesGcm).unwrap(); let seq_result = EncryptedPacket::new(b"Single payload", &mut ctx2, true).unwrap(); assert_eq!(batch_results.len(), 1); assert_eq!(batch_results[0].payload.len(), seq_result.payload.len()); assert_eq!(ctx.sequence_number_stoc, 1); } }