Implement SSH Compression Phase 3: Actual packet compression
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

- 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:
Warren
2026-06-21 02:07:35 +08:00
parent 93e33b04a7
commit 913296fe96

View File

@@ -278,6 +278,7 @@ pub struct EncryptedPacket {
impl EncryptedPacket {
/// 创建加密packet参考OpenSSH cipher.c
/// Phase 1: 支持 AES-CTR (MtE) 和 AES-GCM (AEAD) 两种模式
/// Phase 3: 支持压缩(压缩发生在加密之前)
pub fn new(
plaintext_payload: &[u8],
encryption_ctx: &mut EncryptionContext,
@@ -285,8 +286,23 @@ impl EncryptedPacket {
) -> Result<Self> {
let block_size = 16;
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_ctosserver 解压缩,这里不压缩)
plaintext_payload.to_vec()
};
let payload_length = compressed_payload.len();
// Padding calculation:
// 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 mut plaintext_payload_buffer = SshBuf::with_capacity(total_plaintext_size);
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];
use rand::RngCore;
@@ -421,7 +437,7 @@ impl EncryptedPacket {
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)?;
plaintext_payload_buffer.put(&compressed_payload)?;
let mut random_padding = vec![0u8; padding_length as usize];
use rand::RngCore;
@@ -505,7 +521,7 @@ impl EncryptedPacket {
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)?;
plaintext_packet.put(&compressed_payload)?;
let mut random_padding = vec![0u8; padding_length as usize];
use rand::RngCore;
@@ -705,7 +721,23 @@ impl EncryptedPacket {
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_stocclient 解压缩,这里不解压缩)
compressed_payload
};
let padding = Vec::new(); // AES-GCM: padding 不需要存储write 时使用 payload 中的 ciphertext
// 9. 提取 GCM tag (last 16 bytes of ciphertext)
@@ -917,6 +949,23 @@ impl EncryptedPacket {
let mut payload = Vec::with_capacity(payload_length);
payload.extend_from_slice(payload_part1);
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_stocclient 解压缩,这里不解压缩)
payload
};
let payload = decompressed_payload;
// 提取padding从remaining_encrypted的末尾
let padding = remaining_encrypted[payload_part2_len..].to_vec();