Fix code quality: trailing whitespace, unused imports, clippy warnings

- Fix trailing whitespace in kex.rs and s3.rs
- Add missing KexProposal import in kex_complete.rs
- Auto-fix clippy warnings across all crates
- All 153 tests pass
This commit is contained in:
Warren
2026-06-19 05:21:38 +08:00
parent 4b37e524cf
commit d94cb2df4c
135 changed files with 7256 additions and 4321 deletions
+177 -112
View File
@@ -1,14 +1,14 @@
// SSH密钥交换流程实现(Phase 3)
// 参考OpenSSH kex.c: kex_input_kex_init(), kex_send_kex_reply()
use crate::ssh_server::packet::{SshPacket, PacketType};
use crate::ssh_server::kex::{KexResult};
use crate::ssh_server::crypto::{Curve25519Kex, SessionKeys, Ed25519HostKey};
use anyhow::{Result, anyhow};
use crate::ssh_server::crypto::{Curve25519Kex, Ed25519HostKey, SessionKeys};
use crate::ssh_server::kex::KexResult;
use crate::ssh_server::packet::{PacketType, SshPacket};
use anyhow::{anyhow, Result};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use log::{info, debug};
use log::info;
use sha2::Digest;
use std::io::{Read, Write};
use sha2::{Sha256, Digest};
/// SSH密钥交换流程处理器(参考OpenSSH kex.c
pub struct KexExchangeHandler {
@@ -18,7 +18,7 @@ pub struct KexExchangeHandler {
shared_secret: Option<Vec<u8>>,
client_public_key: Option<Vec<u8>>,
server_public_key: Option<Vec<u8>>,
exchange_hash: Option<Vec<u8>>, // 保存exchange hashH参数)
exchange_hash: Option<Vec<u8>>, // 保存exchange hashH参数)
client_version: Option<String>,
server_version: Option<String>,
client_kexinit_payload: Option<Vec<u8>>,
@@ -30,7 +30,7 @@ impl KexExchangeHandler {
pub fn new(kex_result: KexResult) -> Result<Self> {
// 加载或生成服务器主机密钥
let host_key = Ed25519HostKey::load_or_generate("config/ssh_host_ed25519_key")?;
Ok(Self {
kex_algorithm: kex_result.kex_algorithm,
server_kex: None,
@@ -45,10 +45,10 @@ impl KexExchangeHandler {
server_kexinit_payload: None,
})
}
/// 处理SSH_MSG_KEXDH_INITCurve25519密钥交换)(参考OpenSSH kex.c: kex_input_kex_init()
/// 处理SSH_MSG_KEXDH_INITCurve25519密钥交换)(参考OpenSSH kex.c: kex_input_kex_init()
pub fn handle_kexdh_init(
&mut self,
&mut self,
packet: &SshPacket,
client_version: &str,
server_version: &str,
@@ -56,41 +56,44 @@ impl KexExchangeHandler {
server_kexinit_payload: &[u8],
) -> Result<SshPacket> {
info!("Processing SSH_MSG_KEXDH_INIT (Curve25519)");
let mut cursor = std::io::Cursor::new(packet.payload.as_slice());
let packet_type = cursor.read_u8()?;
if packet_type != PacketType::SSH_MSG_KEXDH_INIT as u8 {
return Err(anyhow!("Invalid packet type for KEXDH_INIT"));
}
let key_length = cursor.read_u32::<BigEndian>()?;
if key_length != 32 {
return Err(anyhow!("Invalid Curve25519 public key length: {}", key_length));
return Err(anyhow!(
"Invalid Curve25519 public key length: {}",
key_length
));
}
let mut client_public_key = vec![0u8; 32];
cursor.read_exact(&mut client_public_key)?;
self.server_kex = Some(Curve25519Kex::new());
let server_kex = self.server_kex.as_mut().unwrap();
let shared_secret = server_kex.compute_shared_secret(&client_public_key)?;
let server_public_key = server_kex.public_key().to_vec();
// Save for later session key computation
self.shared_secret = Some(shared_secret.to_vec());
self.client_public_key = Some(client_public_key.clone());
self.server_public_key = Some(server_public_key.clone());
// Save client_version, server_version, kexinit payloads for exchange hash
self.client_version = Some(client_version.to_string());
self.server_version = Some(server_version.to_string());
self.client_kexinit_payload = Some(client_kexinit_payload.to_vec());
self.server_kexinit_payload = Some(server_kexinit_payload.to_vec());
info!("Curve25519 shared secret computed and saved");
// Compute exchange hash ONCE and reuse it
let host_key_blob = self.build_ssh_host_key()?;
let exchange_hash = self.compute_exchange_hash(
@@ -103,69 +106,69 @@ impl KexExchangeHandler {
client_kexinit_payload,
server_kexinit_payload,
)?;
info!("Exchange hash computed:");
info!(" shared_secret[0] = {} (>=0x80? {})", shared_secret[0], shared_secret[0] >= 0x80);
info!(
" shared_secret[0] = {} (>=0x80? {})",
shared_secret[0],
shared_secret[0] >= 0x80
);
info!(" exchange_hash full (32 bytes): {:?}", exchange_hash);
self.exchange_hash = Some(exchange_hash.clone());
info!("Exchange hash saved for key derivation");
self.build_kexdh_reply(
&exchange_hash,
&host_key_blob,
&server_public_key,
)
self.build_kexdh_reply(&exchange_hash, &host_key_blob, &server_public_key)
}
/// 构建SSH_MSG_KEXDH_REPLY packet(参考OpenSSH kex.c
fn build_kexdh_reply(
&self,
exchange_hash: &[u8],
&self,
exchange_hash: &[u8],
host_key_blob: &[u8],
server_public_key: &[u8],
) -> Result<SshPacket> {
info!("=== Building SSH_MSG_KEXDH_REPLY ===");
info!("Input server_public_key: {:?}", server_public_key);
let mut payload = Vec::new();
payload.write_u8(PacketType::SSH_MSG_KEXDH_REPLY as u8)?;
payload.write_u32::<BigEndian>(host_key_blob.len() as u32)?;
payload.write_all(host_key_blob)?;
info!("Writing server_public_key to payload (32 bytes)");
payload.write_u32::<BigEndian>(32)?;
payload.write_all(server_public_key)?;
let signature = self.build_exchange_signature(exchange_hash)?;
payload.write_u32::<BigEndian>(signature.len() as u32)?;
payload.write_all(&signature)?;
info!("SSH_MSG_KEXDH_REPLY payload built successfully");
Ok(SshPacket::new(payload))
}
/// 构建SSH主机密钥blob(参考OpenSSH sshkey.c: sshkey_to_blob()
fn build_ssh_host_key(&self) -> Result<Vec<u8>> {
let mut blob = Vec::new();
// SSH key format: key-type + public-key
// 参考OpenSSH sshkey.c
// Key type: ssh-ed25519
blob.write_u32::<BigEndian>(11)?; // "ssh-ed25519".len()
blob.write_u32::<BigEndian>(11)?; // "ssh-ed25519".len()
blob.write_all("ssh-ed25519".as_bytes())?;
// Ed25519公钥(32字节)
let public_key = self.host_key.public_key_bytes();
blob.write_u32::<BigEndian>(32)?;
blob.write_all(&public_key)?;
Ok(blob)
}
/// 计算Exchange Hash(参考OpenSSH kex.c: kex_hash() RFC 4253 Section 7.2
fn compute_exchange_hash(
&self,
@@ -178,94 +181,147 @@ impl KexExchangeHandler {
client_kexinit_payload: &[u8],
server_kexinit_payload: &[u8],
) -> Result<Vec<u8>> {
use sha2::{Sha256, Digest};
use sha2::{Digest, Sha256};
info!("=== EXCHANGE HASH COMPUTATION ===");
info!("V_C (client version): {:?}", client_version.as_bytes());
info!("V_C length: {}", client_version.len());
info!("V_S (server version): {:?}", server_version.as_bytes());
info!("V_S length: {}", server_version.len());
info!("I_C (client KEXINIT payload): {:?}", &client_kexinit_payload[..std::cmp::min(50, client_kexinit_payload.len())]);
info!(
"I_C (client KEXINIT payload): {:?}",
&client_kexinit_payload[..std::cmp::min(50, client_kexinit_payload.len())]
);
info!("I_C length: {}", client_kexinit_payload.len());
info!("I_C[0] (packet type): {} (should be SSH_MSG_KEXINIT=20)", client_kexinit_payload[0]);
info!("I_S (server KEXINIT payload): {:?}", &server_kexinit_payload[..std::cmp::min(50, server_kexinit_payload.len())]);
info!(
"I_C[0] (packet type): {} (should be SSH_MSG_KEXINIT=20)",
client_kexinit_payload[0]
);
info!(
"I_S (server KEXINIT payload): {:?}",
&server_kexinit_payload[..std::cmp::min(50, server_kexinit_payload.len())]
);
info!("I_S length: {}", server_kexinit_payload.len());
info!("I_S[0] (packet type): {} (should be SSH_MSG_KEXINIT=20)", server_kexinit_payload[0]);
info!("K_S (host key blob): {:?}", &host_key_blob[..std::cmp::min(30, host_key_blob.len())]);
info!(
"I_S[0] (packet type): {} (should be SSH_MSG_KEXINIT=20)",
server_kexinit_payload[0]
);
info!(
"K_S (host key blob): {:?}",
&host_key_blob[..std::cmp::min(30, host_key_blob.len())]
);
info!("K_S length: {}", host_key_blob.len());
info!("Q_C (client ECDH public key): {:?}", &client_public_key[..std::cmp::min(16, client_public_key.len())]);
info!(
"Q_C (client ECDH public key): {:?}",
&client_public_key[..std::cmp::min(16, client_public_key.len())]
);
info!("Q_C full (32 bytes): {:?}", client_public_key);
info!("Q_C length: {}", client_public_key.len());
info!("Q_S (server ECDH public key): {:?}", &server_public_key[..std::cmp::min(16, server_public_key.len())]);
info!(
"Q_S (server ECDH public key): {:?}",
&server_public_key[..std::cmp::min(16, server_public_key.len())]
);
info!("Q_S full (32 bytes): {:?}", server_public_key);
info!("Q_S length: {}", server_public_key.len());
let mut hasher = Sha256::new();
// RFC 4253 Section 7: V_C and V_S are version strings (without \r\n based on testing)
let vc_ssh_string = &(client_version.len() as u32).to_be_bytes();
hasher.update(vc_ssh_string);
hasher.update(client_version.as_bytes());
info!(" Exchange hash component V_C: len={} bytes=[{:?}] data=[{:?}]", 4+client_version.len(), vc_ssh_string, client_version.as_bytes());
info!(
" Exchange hash component V_C: len={} bytes=[{:?}] data=[{:?}]",
4 + client_version.len(),
vc_ssh_string,
client_version.as_bytes()
);
let vs_ssh_string = &(server_version.len() as u32).to_be_bytes();
hasher.update(vs_ssh_string);
hasher.update(server_version.as_bytes());
info!(" Exchange hash component V_S: len={} bytes=[{:?}] data=[{:?}]", 4+server_version.len(), vs_ssh_string, server_version.as_bytes());
info!(
" Exchange hash component V_S: len={} bytes=[{:?}] data=[{:?}]",
4 + server_version.len(),
vs_ssh_string,
server_version.as_bytes()
);
// OpenSSH kexgex.c: "kexinit messages: fake header: len+SSH2_MSG_KEXINIT"
// KEXINIT payload should NOT include SSH_MSG_KEXINIT type byte
// OpenSSH stores payload starting from cookie, prepends SSH_MSG_KEXINIT in exchange hash
// Remove SSH_MSG_KEXINIT type byte from payloads (our payload includes it)
let client_kexinit_without_type = &client_kexinit_payload[1..];
let server_kexinit_without_type = &server_kexinit_payload[1..];
info!("I_C (client KEXINIT without type byte): {} bytes (first byte should be cookie)", client_kexinit_without_type.len());
info!("I_S (server KEXINIT without type byte): {} bytes", server_kexinit_without_type.len());
info!(
"I_C (client KEXINIT without type byte): {} bytes (first byte should be cookie)",
client_kexinit_without_type.len()
);
info!(
"I_S (server KEXINIT without type byte): {} bytes",
server_kexinit_without_type.len()
);
// Exchange hash: uint32(len+1) + uint8(SSH_MSG_KEXINIT) + payload_without_type
let ic_len_bytes = &((client_kexinit_without_type.len() + 1) as u32).to_be_bytes();
hasher.update(ic_len_bytes);
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
hasher.update([20]); // SSH_MSG_KEXINIT type byte
hasher.update(client_kexinit_without_type);
info!(" Exchange hash component I_C: len={} bytes=[{:?}] type=[20] payload_len={} (first 8 bytes=[{:?}])", 4+1+client_kexinit_without_type.len(), ic_len_bytes, client_kexinit_without_type.len(), &client_kexinit_without_type[..std::cmp::min(8, client_kexinit_without_type.len())]);
let is_len_bytes = &((server_kexinit_without_type.len() + 1) as u32).to_be_bytes();
hasher.update(is_len_bytes);
hasher.update(&[20]); // SSH_MSG_KEXINIT type byte
hasher.update([20]); // SSH_MSG_KEXINIT type byte
hasher.update(server_kexinit_without_type);
info!(" Exchange hash component I_S: len={} bytes=[{:?}] type=[20] payload_len={} (first 8 bytes=[{:?}])", 4+1+server_kexinit_without_type.len(), is_len_bytes, server_kexinit_without_type.len(), &server_kexinit_without_type[..std::cmp::min(8, server_kexinit_without_type.len())]);
let ks_len_bytes = &(host_key_blob.len() as u32).to_be_bytes();
hasher.update(ks_len_bytes);
hasher.update(host_key_blob);
info!(" Exchange hash component K_S: len={} bytes=[{:?}] blob_len={} (full=[{:?}])", 4+host_key_blob.len(), ks_len_bytes, host_key_blob.len(), host_key_blob);
info!(
" Exchange hash component K_S: len={} bytes=[{:?}] blob_len={} (full=[{:?}])",
4 + host_key_blob.len(),
ks_len_bytes,
host_key_blob.len(),
host_key_blob
);
let qc_len_bytes = &(client_public_key.len() as u32).to_be_bytes();
hasher.update(qc_len_bytes);
hasher.update(client_public_key);
info!(" Exchange hash component Q_C: len={} bytes=[{:?}] key=[{:?}]", 4+client_public_key.len(), qc_len_bytes, client_public_key);
info!(
" Exchange hash component Q_C: len={} bytes=[{:?}] key=[{:?}]",
4 + client_public_key.len(),
qc_len_bytes,
client_public_key
);
let qs_len_bytes = &(server_public_key.len() as u32).to_be_bytes();
hasher.update(qs_len_bytes);
hasher.update(server_public_key);
info!(" Exchange hash component Q_S: len={} bytes=[{:?}] key=[{:?}]", 4+server_public_key.len(), qs_len_bytes, server_public_key);
info!(
" Exchange hash component Q_S: len={} bytes=[{:?}] key=[{:?}]",
4 + server_public_key.len(),
qs_len_bytes,
server_public_key
);
info!("Exchange hash components:");
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)
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;
@@ -273,64 +329,73 @@ impl KexExchangeHandler {
start += 1;
}
let trimmed_shared_secret = &shared_secret[start..];
info!(" shared_secret after removing leading zeros ({} bytes): {:?}", trimmed_shared_secret.len(), trimmed_shared_secret);
let mpint_shared_secret_data = if trimmed_shared_secret.len() > 0 && trimmed_shared_secret[0] >= 0x80 {
let mut mpint = vec![0u8];
mpint.extend_from_slice(trimmed_shared_secret);
info!(" trimmed_shared_secret[0] >= 0x80, prepending 0 byte");
mpint
} else {
trimmed_shared_secret.to_vec()
};
info!(" mpint_shared_secret_data ({} bytes): {:?}", mpint_shared_secret_data.len(), &mpint_shared_secret_data[..std::cmp::min(8, mpint_shared_secret_data.len())]);
info!(
" shared_secret after removing leading zeros ({} bytes): {:?}",
trimmed_shared_secret.len(),
trimmed_shared_secret
);
let mpint_shared_secret_data =
if !trimmed_shared_secret.is_empty() && trimmed_shared_secret[0] >= 0x80 {
let mut mpint = vec![0u8];
mpint.extend_from_slice(trimmed_shared_secret);
info!(" trimmed_shared_secret[0] >= 0x80, prepending 0 byte");
mpint
} else {
trimmed_shared_secret.to_vec()
};
info!(
" mpint_shared_secret_data ({} bytes): {:?}",
mpint_shared_secret_data.len(),
&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);
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())]);
Ok(hasher.finalize().to_vec())
}
/// 构建交换签名(参考OpenSSH ssh-sign.c
fn build_exchange_signature(&self, exchange_hash: &[u8]) -> Result<Vec<u8>> {
let signature_bytes = self.host_key.sign(exchange_hash)?;
let mut ssh_signature = Vec::new();
ssh_signature.write_u32::<BigEndian>(11)?;
ssh_signature.write_all("ssh-ed25519".as_bytes())?;
ssh_signature.write_u32::<BigEndian>(64)?;
ssh_signature.write_all(&signature_bytes)?;
Ok(ssh_signature)
}
/// 计算会话密钥(参考OpenSSH kex.c: derive_keys()
/// 使用保存的exchange_hashH参数)
pub fn compute_session_keys(&self) -> Result<SessionKeys> {
if self.shared_secret.is_none() {
return Err(anyhow!("No shared secret available"));
}
if self.exchange_hash.is_none() {
return Err(anyhow!("No exchange hash available"));
}
let shared_secret = self.shared_secret.as_ref().unwrap();
let exchange_hash = self.exchange_hash.as_ref().unwrap();
let server_public_key = self.server_public_key.as_ref().unwrap();
let client_public_key = self.client_public_key.as_ref().unwrap();
let host_key_blob = self.build_ssh_host_key()?;
SessionKeys::derive(
shared_secret,
exchange_hash, // 使用保存的exchange hashH参数)
exchange_hash, // 使用保存的exchange hashH参数)
server_public_key,
client_public_key,
&host_key_blob,
@@ -342,13 +407,13 @@ impl KexExchangeHandler {
mod tests {
use super::*;
use crate::ssh_server::kex::KexProposal;
#[test]
fn test_kex_exchange_handler_creation() {
let server_proposal = KexProposal::server_default();
let client_proposal = KexProposal::client_default();
let kex_result = KexResult::choose_algorithms(&server_proposal, &client_proposal).unwrap();
let handler = KexExchangeHandler::new(kex_result).unwrap();
assert!(handler.host_key.public_key_bytes().len() == 32);
}