fix(ssh): Re-add uint32 prefix for shared secret K in exchange hash and key derivation
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

OpenSSH sshbuf_put_bignum2_bytes() writes uint32(len) + mpint_data
to the buffer (confirmed from sshbuf-getput-basic.c line 569). Both
kex_gen_hash() via sshbuf_putb() and kex_derive_keys() via
ssh_digest_update_buffer() consume the full buffer including the uint32
prefix.

Fixes 'incorrect signature' error on OpenSSH 10.2.
This commit is contained in:
Warren
2026-06-20 15:41:43 +08:00
parent 6ef1537c1b
commit e0e145e277
3 changed files with 63 additions and 41 deletions
+13 -8
View File
@@ -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 hashH参数)
server_public_key,
client_public_key,
&host_key_blob,
cipher_key_len,
)
}
}