diff --git a/markbase-core/src/ssh_server/cipher.rs b/markbase-core/src/ssh_server/cipher.rs index e9dd2fb..fb4e581 100644 --- a/markbase-core/src/ssh_server/cipher.rs +++ b/markbase-core/src/ssh_server/cipher.rs @@ -534,8 +534,12 @@ impl EncryptedPacket { .ok_or_else(|| anyhow!("cipher_ctos not initialized"))? }; + let plaintext_bytes = plaintext_packet.ptr().to_vec(); + info!("Plaintext packet FULL ({} bytes): {:?}", plaintext_bytes.len(), plaintext_bytes); let mut encrypted_packet = plaintext_packet.into_vec(); cipher.apply_keystream(&mut encrypted_packet); + info!("Encrypted packet FULL ({} bytes): {:?}", encrypted_packet.len(), encrypted_packet); + info!("MAC FULL ({} bytes): {:?}", mac.len(), mac); // 更新sequence number if is_server_to_client { diff --git a/markbase-core/src/ssh_server/crypto.rs b/markbase-core/src/ssh_server/crypto.rs index 05ea9b3..602b20f 100644 --- a/markbase-core/src/ssh_server/crypto.rs +++ b/markbase-core/src/ssh_server/crypto.rs @@ -70,6 +70,16 @@ pub struct SessionKeys { } impl SessionKeys { + /// 根据协商的 cipher name 确定 key_len(参考 OpenSSH cipher.c) + pub fn get_cipher_key_len(cipher_name: &str) -> usize { + match cipher_name { + "aes128-ctr" | "aes128-gcm@openssh.com" => 16, // AES-128 key + "aes256-ctr" | "aes256-gcm@openssh.com" => 32, // AES-256 key + "chacha20-poly1305@openssh.com" => 64, // ChaCha20 key + _ => 32, // Default AES-256 + } + } + /// 计算会话密钥(参考OpenSSH kex.c: kex_derive_keys()) /// RFC 4253 Section 7.2: Key = HASH(K || H || X || session_id) pub fn derive( @@ -78,6 +88,7 @@ impl SessionKeys { _server_public_key: &[u8], _client_public_key: &[u8], _server_host_key: &[u8], + cipher_key_len: usize, // ⭐⭐⭐⭐⭐ Phase 8.3: Dynamic key length ) -> Result { // RFC 4253: session_id = H (第一次exchange hash) let session_id = exchange_hash.to_vec(); @@ -108,18 +119,18 @@ impl SessionKeys { ); let encryption_key_ctos = - Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'C', &session_id)?; + Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'C', &session_id, cipher_key_len)?; let encryption_key_stoc = - Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'D', &session_id)?; + Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'D', &session_id, cipher_key_len)?; let mac_key_ctos = - Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'E', &session_id)?; + Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'E', &session_id, 32)?; let mac_key_stoc = - Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'F', &session_id)?; + Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'F', &session_id, 32)?; let iv_ctos = - Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'A', &session_id)?; + Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'A', &session_id, 16)?; let iv_stoc = - Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'B', &session_id)?; + Self::derive_key_rfc4253(&shared_secret_mpint, exchange_hash, 'B', &session_id, 16)?; info!("Derived keys summary:"); info!( @@ -164,12 +175,19 @@ impl SessionKeys { }) } - /// RFC 4253密钥派生函数 + /// RFC 4253密钥派生函数(参考 OpenSSH kex.c: derive_key()) /// 公式:Key = HASH(K || H || X || session_id) - fn derive_key_rfc4253(K_mpint: &[u8], H: &[u8], X: char, session_id: &[u8]) -> Result> { + /// ⭐⭐⭐⭐⭐ Phase 8.3: 支持 AES-128 key_len (16 bytes) + fn derive_key_rfc4253( + K_mpint: &[u8], + H: &[u8], + X: char, + session_id: &[u8], + key_len: usize, // ⭐⭐⭐⭐⭐ Dynamic key length + ) -> Result> { let mut hasher = Sha256::new(); - info!("Deriving key for X='{}'", X); + info!("Deriving key for X='{}', key_len={}", X, key_len); info!( " K_mpint ({} bytes): {:?}", K_mpint.len(), @@ -192,29 +210,24 @@ impl SessionKeys { info!(" Derived key (first 8 bytes): {:?}", &full_hash[..8]); - // 根據key類型返回不同長度: - // AES-128-CTR IV: 16 bytes - // AES-256-GCM encryption key: 32 bytes (full SHA-256) - // AES-128-CTR encryption key: 16 bytes (前16 bytes of SHA-256) - // HMAC-SHA256 MAC key: 32 bytes - // - // Note: 'C'/'D' 輸出32 bytes以支援 AES-256-GCM - // AES-128-CTR 僅取前16 bytes,與之前相容 - match X { - 'A' | 'B' => Ok(full_hash[..16].to_vec()), // IV: 16 bytes - 'C' | 'D' => Ok(full_hash.to_vec()), // Encryption key: 32 bytes (AES-256-GCM) - 'E' | 'F' => Ok(full_hash.to_vec()), // MAC key: 32 bytes - _ => Ok(full_hash[..16].to_vec()), // default + // ⭐⭐⭐⭐⭐ OpenSSH kex.c: derive_key() 密钥扩展逻辑 + // 如果 key_len <= 32,直接返回前 key_len bytes + // 如果 key_len > 32,需要密钥扩展(目前不需要,因为 AES-128/256 都 <= 32) + if key_len <= 32 { + Ok(full_hash[..key_len].to_vec()) + } else { + // ⚠️ 密钥扩展逻辑(参考 OpenSSH kex.c:806-819) + // 目前不需要实现(AES-128/256 key_len 都 <= 32) + Err(anyhow!("Key expansion not implemented for key_len > 32")) } } - /// SSH mpint编码(参考RFC 4253 Section 5) - /// Curve25519 shared secret特殊处理 + /// SSH mpint编码(参考OpenSSH sshbuf_put_bignum2_bytes()) + /// 返回uint32(len) + raw mpint data + /// sshbuf_put_bignum2_bytes()写入uint32(len) + mpint_data到buffer + /// 参考:openssh-portable/sshbuf-getput-basic.c line 569 fn encode_mpint(bytes: &[u8]) -> Vec { - // RFC 4253: mpint = uint32(length) + data - // 去掉前导零,如果最高位>=0x80前面加0 - - // 去掉前导零字节(但不去掉最后一个字节即使它是0) + // 去掉前导零字节 let mut start = 0; while start < bytes.len() - 1 && bytes[start] == 0 { start += 1; @@ -226,16 +239,16 @@ impl SessionKeys { let mut mpint_data = Vec::new(); // 如果最高位>=0x80,前面加0字节(避免负数) - if data_without_leading_zeros[0] >= 0x80 { + if !data_without_leading_zeros.is_empty() && data_without_leading_zeros[0] >= 0x80 { mpint_data.push(0); } mpint_data.extend_from_slice(data_without_leading_zeros); - // 最终格式:uint32长度 + mpint数据 - let mut result = Vec::new(); - result.extend_from_slice(&(mpint_data.len() as u32).to_be_bytes()); + // OpenSSH sshbuf_put_bignum2_bytes(): uint32(len) + mpint_data + let len_be = (mpint_data.len() as u32).to_be_bytes(); + let mut result = Vec::with_capacity(4 + mpint_data.len()); + result.extend_from_slice(&len_be); result.extend_from_slice(&mpint_data); - result } } diff --git a/markbase-core/src/ssh_server/kex_exchange.rs b/markbase-core/src/ssh_server/kex_exchange.rs index 35d3cd8..2a9cdbc 100644 --- a/markbase-core/src/ssh_server/kex_exchange.rs +++ b/markbase-core/src/ssh_server/kex_exchange.rs @@ -318,12 +318,10 @@ impl KexExchangeHandler { info!(" shared_secret raw full (32 bytes): {:?}", shared_secret); // RFC 8731 Section 3.1: X25519 output is little-endian - // OpenSSH sshbuf_put_bignum2_bytes() uses bytes DIRECTLY (no reversal) - // Treats little-endian bytes as big-endian mpint (logical reinterpret) + // OpenSSH sshbuf_put_bignum2_bytes() writes uint32(len) + mpint_data + // Reference: openssh-portable/sshbuf-getput-basic.c line 569, kexgen.c line 79 info!(" Using shared_secret directly (little-endian bytes as big-endian mpint)"); - // RFC 4253: mpint格式 = 去掉前导零 + 最高位>=0x80时前面加0 - // 参考OpenSSH sshbuf_put_bignum2_bytes() let mut start = 0; while start < shared_secret.len() - 1 && shared_secret[start] == 0 { start += 1; @@ -352,11 +350,13 @@ impl KexExchangeHandler { &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())] ); - // mpint格式 = uint32(length) + mpint_data - let mpint_len_bytes = &(mpint_shared_secret_data.len() as u32).to_be_bytes(); - hasher.update(mpint_len_bytes); + // OpenSSH sshbuf_put_bignum2_bytes(): uint32(len) + mpint_data + // Reference: openssh-portable/sshbuf-getput-basic.c line 569 + // kex_gen_hash() uses sshbuf_putb(b, shared_secret) which copies ALL buffer bytes + let k_len_bytes = &(mpint_shared_secret_data.len() as u32).to_be_bytes(); + hasher.update(k_len_bytes); hasher.update(&mpint_shared_secret_data); - info!(" Exchange hash component K (shared secret mpint): len={} bytes=[{:?}] data_len={} (first 8 bytes=[{:?}])", 4+mpint_shared_secret_data.len(), mpint_len_bytes, mpint_shared_secret_data.len(), &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())]); + info!(" Exchange hash component K (shared secret mpint): uint32({})+{} bytes (prefix + data, first 8 data bytes=[{:?}])", mpint_shared_secret_data.len(), mpint_shared_secret_data.len(), &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())]); Ok(hasher.finalize().to_vec()) } @@ -393,12 +393,17 @@ impl KexExchangeHandler { let client_public_key = self.client_public_key.as_ref().unwrap(); let host_key_blob = self.build_ssh_host_key()?; + // ⭐ TODO: Get encryption algorithm from kex_result to determine cipher_key_len + // For now, hardcode 32 (AES-256) to maintain backward compatibility + let cipher_key_len = 32; + info!("compute_session_keys: cipher_key_len={}", cipher_key_len); SessionKeys::derive( shared_secret, exchange_hash, // 使用保存的exchange hash(H参数) server_public_key, client_public_key, &host_key_blob, + cipher_key_len, ) } }