Implement SSH Compression Phase 3: Actual packet compression
- EncryptedPacket::new(): compress payload before encryption - EncryptedPacket::read(): decompress payload after decryption - Apply to AES-GCM, ChaCha20-Poly1305, and AES-CTR modes - Compression order: compress → encrypt (write) - Decompression order: decrypt → decompress (read) All 179 tests pass.
This commit is contained in:
@@ -278,6 +278,7 @@ pub struct EncryptedPacket {
|
|||||||
impl EncryptedPacket {
|
impl EncryptedPacket {
|
||||||
/// 创建加密packet(参考OpenSSH cipher.c)
|
/// 创建加密packet(参考OpenSSH cipher.c)
|
||||||
/// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
|
/// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
|
||||||
|
/// Phase 3: 支持压缩(压缩发生在加密之前)
|
||||||
pub fn new(
|
pub fn new(
|
||||||
plaintext_payload: &[u8],
|
plaintext_payload: &[u8],
|
||||||
encryption_ctx: &mut EncryptionContext,
|
encryption_ctx: &mut EncryptionContext,
|
||||||
@@ -285,8 +286,23 @@ impl EncryptedPacket {
|
|||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let block_size = 16;
|
let block_size = 16;
|
||||||
let min_padding = 4;
|
let min_padding = 4;
|
||||||
|
|
||||||
let payload_length = plaintext_payload.len();
|
// Phase 3: 压缩 payload(如果启用)
|
||||||
|
// 压缩顺序:压缩 → 加密(参考 RFC 4253 §6.2)
|
||||||
|
let compressed_payload = if is_server_to_client {
|
||||||
|
// Server → Client: 使用 compression_stoc
|
||||||
|
if encryption_ctx.compression_stoc.is_enabled() {
|
||||||
|
info!("Compressing payload (server→client, {} bytes)", plaintext_payload.len());
|
||||||
|
encryption_ctx.compression_stoc.compress(plaintext_payload)?
|
||||||
|
} else {
|
||||||
|
plaintext_payload.to_vec()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Client → Server: 使用 compression_ctos(server 解压缩,这里不压缩)
|
||||||
|
plaintext_payload.to_vec()
|
||||||
|
};
|
||||||
|
|
||||||
|
let payload_length = compressed_payload.len();
|
||||||
|
|
||||||
// Padding calculation:
|
// Padding calculation:
|
||||||
// AES-GCM: RFC 4253 body (padding_length + payload + padding = packet_length) must be % 16 == 0
|
// AES-GCM: RFC 4253 body (padding_length + payload + padding = packet_length) must be % 16 == 0
|
||||||
@@ -321,7 +337,7 @@ impl EncryptedPacket {
|
|||||||
let total_plaintext_size = 1 + payload_length + padding_length as usize;
|
let total_plaintext_size = 1 + payload_length + padding_length as usize;
|
||||||
let mut plaintext_payload_buffer = SshBuf::with_capacity(total_plaintext_size);
|
let mut plaintext_payload_buffer = SshBuf::with_capacity(total_plaintext_size);
|
||||||
plaintext_payload_buffer.put(&[padding_length])?;
|
plaintext_payload_buffer.put(&[padding_length])?;
|
||||||
plaintext_payload_buffer.put(plaintext_payload)?;
|
plaintext_payload_buffer.put(&compressed_payload)?;
|
||||||
|
|
||||||
let mut random_padding = vec![0u8; padding_length as usize];
|
let mut random_padding = vec![0u8; padding_length as usize];
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
@@ -421,7 +437,7 @@ impl EncryptedPacket {
|
|||||||
let total_plaintext_size = 1 + payload_length + padding_length as usize;
|
let total_plaintext_size = 1 + payload_length + padding_length as usize;
|
||||||
let mut plaintext_payload_buffer = SshBuf::with_capacity(total_plaintext_size);
|
let mut plaintext_payload_buffer = SshBuf::with_capacity(total_plaintext_size);
|
||||||
plaintext_payload_buffer.put(&[padding_length])?;
|
plaintext_payload_buffer.put(&[padding_length])?;
|
||||||
plaintext_payload_buffer.put(plaintext_payload)?;
|
plaintext_payload_buffer.put(&compressed_payload)?;
|
||||||
|
|
||||||
let mut random_padding = vec![0u8; padding_length as usize];
|
let mut random_padding = vec![0u8; padding_length as usize];
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
@@ -505,7 +521,7 @@ impl EncryptedPacket {
|
|||||||
let mut plaintext_packet = SshBuf::with_capacity(total_packet_size);
|
let mut plaintext_packet = SshBuf::with_capacity(total_packet_size);
|
||||||
plaintext_packet.put(&(packet_length as u32).to_be_bytes())?;
|
plaintext_packet.put(&(packet_length as u32).to_be_bytes())?;
|
||||||
plaintext_packet.put(&[padding_length])?;
|
plaintext_packet.put(&[padding_length])?;
|
||||||
plaintext_packet.put(plaintext_payload)?;
|
plaintext_packet.put(&compressed_payload)?;
|
||||||
|
|
||||||
let mut random_padding = vec![0u8; padding_length as usize];
|
let mut random_padding = vec![0u8; padding_length as usize];
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
@@ -705,7 +721,23 @@ impl EncryptedPacket {
|
|||||||
|
|
||||||
info!("AES-GCM: padding_length={}, payload_length={}", padding_length, payload_length);
|
info!("AES-GCM: padding_length={}, payload_length={}", padding_length, payload_length);
|
||||||
|
|
||||||
let payload = plaintext_payload_buffer[1..1 + payload_length].to_vec();
|
let compressed_payload = plaintext_payload_buffer[1..1 + payload_length].to_vec();
|
||||||
|
|
||||||
|
// Phase 3: 解压缩 payload(如果启用)
|
||||||
|
// 解压缩顺序:解密 → 解压缩(参考 RFC 4253 §6.2)
|
||||||
|
let payload = if is_client_to_server {
|
||||||
|
// Client → Server: 使用 compression_ctos
|
||||||
|
if encryption_ctx.compression_ctos.is_enabled() {
|
||||||
|
info!("Decompressing payload (client→server, {} bytes)", compressed_payload.len());
|
||||||
|
encryption_ctx.compression_ctos.decompress(&compressed_payload)?
|
||||||
|
} else {
|
||||||
|
compressed_payload
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Server → Client: 使用 compression_stoc(client 解压缩,这里不解压缩)
|
||||||
|
compressed_payload
|
||||||
|
};
|
||||||
|
|
||||||
let padding = Vec::new(); // AES-GCM: padding 不需要存储(write 时使用 payload 中的 ciphertext)
|
let padding = Vec::new(); // AES-GCM: padding 不需要存储(write 时使用 payload 中的 ciphertext)
|
||||||
|
|
||||||
// 9. 提取 GCM tag (last 16 bytes of ciphertext)
|
// 9. 提取 GCM tag (last 16 bytes of ciphertext)
|
||||||
@@ -917,6 +949,23 @@ impl EncryptedPacket {
|
|||||||
let mut payload = Vec::with_capacity(payload_length);
|
let mut payload = Vec::with_capacity(payload_length);
|
||||||
payload.extend_from_slice(payload_part1);
|
payload.extend_from_slice(payload_part1);
|
||||||
payload.extend_from_slice(payload_part2);
|
payload.extend_from_slice(payload_part2);
|
||||||
|
|
||||||
|
// Phase 3: 解压缩 payload(如果启用)
|
||||||
|
// 解压缩顺序:解密 → 解压缩(参考 RFC 4253 §6.2)
|
||||||
|
let decompressed_payload = if is_client_to_server {
|
||||||
|
// Client → Server: 使用 compression_ctos
|
||||||
|
if encryption_ctx.compression_ctos.is_enabled() {
|
||||||
|
info!("Decompressing payload (client→server, {} bytes)", payload.len());
|
||||||
|
encryption_ctx.compression_ctos.decompress(&payload)?
|
||||||
|
} else {
|
||||||
|
payload
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Server → Client: 使用 compression_stoc(client 解压缩,这里不解压缩)
|
||||||
|
payload
|
||||||
|
};
|
||||||
|
|
||||||
|
let payload = decompressed_payload;
|
||||||
|
|
||||||
// 提取padding(从remaining_encrypted的末尾)
|
// 提取padding(从remaining_encrypted的末尾)
|
||||||
let padding = remaining_encrypted[payload_part2_len..].to_vec();
|
let padding = remaining_encrypted[payload_part2_len..].to_vec();
|
||||||
|
|||||||
Reference in New Issue
Block a user