fix(ssh): Re-add uint32 prefix for shared secret K in exchange hash and key derivation
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:
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user