diff --git a/Cargo.lock b/Cargo.lock index 10caf37..77bcae8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -678,6 +678,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.44" @@ -700,6 +713,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", "inout 0.1.4", + "zeroize", ] [[package]] @@ -2678,6 +2692,8 @@ dependencies = [ "bcrypt", "byteorder", "bytes", + "chacha20 0.9.1", + "chacha20poly1305", "chrono", "cipher 0.4.4", "clap", @@ -2694,9 +2710,11 @@ dependencies = [ "log", "md5 0.8.0", "nix 0.29.0", + "poly1305 0.8.0", "postgres", "pulldown-cmark", "rand 0.8.6", + "rayon", "regex", "rusqlite", "russh", diff --git a/data/auth.sqlite b/data/auth.sqlite index 1712087..6797d40 100644 Binary files a/data/auth.sqlite and b/data/auth.sqlite differ diff --git a/markbase-core/Cargo.toml b/markbase-core/Cargo.toml index fdfe203..ff5c0c5 100644 --- a/markbase-core/Cargo.toml +++ b/markbase-core/Cargo.toml @@ -59,6 +59,9 @@ aes = "0.8" ctr = "0.9" cipher = "0.4" aes-gcm = "0.10" # Phase 1: AES-256-GCM AEAD(性能优化) +chacha20 = "0.9" # Phase 5: ChaCha20 stream cipher(OpenSSH chacha20-poly1305) +poly1305 = "0.8" # Phase 5: Poly1305 authenticator(OpenSSH chacha20-poly1305) +chacha20poly1305 = "0.10" # Phase 5: ChaCha20-Poly1305 AEAD(备用) nix = { version = "0.29", features = ["poll", "fs"] } # Phase 14: OpenSSH风格的poll()和非阻塞I/O(fs feature包含fcntl) rusty-s3 = "0.10" # S3 API 签名(AWS Signature V4) ureq = "2.12" # 輕量同步 HTTP 客戶端 diff --git a/markbase-core/src/ssh_server/cipher.rs b/markbase-core/src/ssh_server/cipher.rs index e3fa05d..af5d632 100644 --- a/markbase-core/src/ssh_server/cipher.rs +++ b/markbase-core/src/ssh_server/cipher.rs @@ -8,6 +8,10 @@ use aes_gcm::{ aead::{Aead, KeyInit, Payload}, Aes256Gcm, Nonce, // Phase 1: AES-256-GCM AEAD(性能优化) }; +use chacha20poly1305::{ + aead::{Aead as ChaAead, KeyInit as ChaKeyInit, Payload as ChaPayload}, + ChaCha20Poly1305, Key as ChaKey, Nonce as ChaNonce, // Phase 5: ChaCha20-Poly1305 AEAD +}; use anyhow::{anyhow, Result}; use byteorder::{BigEndian, WriteBytesExt}; use cipher::{KeyIvInit, StreamCipher}; @@ -41,8 +45,9 @@ pub struct EncryptionContext { /// Phase 1: 加密模式选择(AES-CTR vs AES-GCM) #[derive(Debug, Clone, PartialEq)] pub enum CipherMode { - AesCtr, // AES-128-CTR + HMAC-SHA256(MtE模式,兼容性) - AesGcm, // AES-256-GCM(AEAD模式,性能优化) + AesCtr, // AES-128-CTR + HMAC-SHA256(MtE模式,兼容性) + AesGcm, // AES-256-GCM(AEAD模式,性能优化) + ChaChaPoly, // ChaCha20-Poly1305(AEAD模式,OpenSSH默认) } impl Default for EncryptionContext { @@ -389,6 +394,87 @@ impl EncryptedPacket { padding: random_padding, mac: ciphertext[ciphertext.len()-16..].to_vec(), // AES-GCM tag (last 16 bytes) }) + } else if encryption_ctx.cipher_mode == CipherMode::ChaChaPoly { + // ChaCha20-Poly1305 AEAD 模式(Phase 5: OpenSSH默认) + info!("Creating ChaCha20-Poly1305 encrypted packet: payload_len={}", payload_length); + + // ChaCha20-Poly1305: packet_length 不加密(作为 AAD) + // 构建plaintext payload(padding_length + payload + padding) + 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)?; + + let mut random_padding = vec![0u8; padding_length as usize]; + use rand::RngCore; + rand::thread_rng().fill_bytes(&mut random_padding); + plaintext_payload_buffer.put(&random_padding)?; + + // ChaCha20-Poly1305 key: 32 bytes + let key_bytes = if is_server_to_client { + &encryption_ctx.encryption_key_stoc + } else { + &encryption_ctx.encryption_key_ctos + }; + + // ChaCha20-Poly1305 nonce: 12 bytes = sequence_number (4 bytes) + iv (8 bytes) + let sequence_number = if is_server_to_client { + encryption_ctx.sequence_number_stoc + } else { + encryption_ctx.sequence_number_ctos + }; + let iv_bytes = if is_server_to_client { + &encryption_ctx.iv_stoc + } else { + &encryption_ctx.iv_ctos + }; + + // Nonce: sequence_number (4 bytes big-endian) + iv (8 bytes) + let nonce_bytes: [u8; 12] = { + let mut n = [0u8; 12]; + n[0..4].copy_from_slice(&sequence_number.to_be_bytes()); + n[4..12].copy_from_slice(&iv_bytes[..8]); + n + }; + + info!("ChaCha20-Poly1305 encrypt: nonce={:?}, key_len={}", nonce_bytes, key_bytes.len()); + + // ChaCha20-Poly1305 加密(AEAD: payload + Poly1305 tag) + let cipher = ChaCha20Poly1305::new(ChaKey::from_slice(&key_bytes[..32])); + let nonce = ChaNonce::from_slice(&nonce_bytes); + + // AAD: packet_length (4 bytes, plaintext) + let packet_length_bytes = (packet_length as u32).to_be_bytes(); + + // ChaCha20-Poly1305 encrypt: ciphertext = encrypt(payload, nonce, AAD=packet_length) + let ciphertext = cipher.encrypt(nonce, ChaPayload { + msg: plaintext_payload_buffer.ptr(), + aad: &packet_length_bytes, + }).map_err(|e| anyhow!("ChaCha20-Poly1305 encryption failed: {}", e))?; + + info!("ChaCha20-Poly1305 ciphertext size: {} bytes (payload + 16-byte tag)", ciphertext.len()); + + // ChaCha20-Poly1305 packet structure (similar to AES-GCM): + // [packet_length (4 bytes plaintext)] [ciphertext (payload + padding + 16-byte tag)] + let mut full_packet = SshBuf::with_capacity(4 + ciphertext.len()); + full_packet.put(&(packet_length as u32).to_be_bytes())?; + full_packet.put(&ciphertext)?; + let full_packet_vec = full_packet.into_vec(); + + // 更新sequence number + if is_server_to_client { + encryption_ctx.sequence_number_stoc += 1; + } else { + encryption_ctx.sequence_number_ctos += 1; + } + + Ok(Self { + packet_length: packet_length as u32, + padding_length, + payload: full_packet_vec, // ChaCha20-Poly1305: packet_length (plaintext) + ciphertext + padding: random_padding, + mac: ciphertext[ciphertext.len()-16..].to_vec(), // Poly1305 tag (last 16 bytes) + }) } else { // AES-CTR MtE 模式(原有逻辑) info!( @@ -615,6 +701,104 @@ impl EncryptedPacket { padding, mac, // AES-GCM tag }) + } else if encryption_ctx.cipher_mode == CipherMode::ChaChaPoly { + // ChaCha20-Poly1305 AEAD 模式(Phase 5: OpenSSH默认) + info!("Reading ChaCha20-Poly1305 AEAD packet (packet_length plaintext)"); + + // 1. 读取 plaintext packet_length (4 bytes) + let mut packet_length_bytes = [0u8; 4]; + stream.read_exact(&mut packet_length_bytes)?; + let packet_length = u32::from_be_bytes(packet_length_bytes); + + info!("Read plaintext packet_length: {}", packet_length); + + // 2. 合理性检查 + if packet_length > 35000 { + return Err(anyhow!("Invalid packet_length: {}", packet_length)); + } + + // 3. 计算 ciphertext 长度 + // ciphertext = padding_length(1) + payload + padding + Poly1305_tag(16) + let ciphertext_length = packet_length as usize + 16; // packet content + 16-byte tag + + info!("Ciphertext length: {} bytes (payload + 16-byte tag)", ciphertext_length); + + // 4. 读取 ciphertext + let mut ciphertext = vec![0u8; ciphertext_length]; + stream.read_exact(&mut ciphertext)?; + + info!("Read ciphertext: {} bytes", ciphertext.len()); + + // 5. ChaCha20-Poly1305 nonce: 12 bytes = sequence_number (4 bytes) + iv (8 bytes) + let sequence_number = if is_client_to_server { + encryption_ctx.sequence_number_ctos + } else { + encryption_ctx.sequence_number_stoc + }; + + let iv_bytes = if is_client_to_server { + &encryption_ctx.iv_ctos + } else { + &encryption_ctx.iv_stoc + }; + + // Nonce: sequence_number (4 bytes big-endian) + iv (8 bytes) + let nonce_bytes: [u8; 12] = { + let mut n = [0u8; 12]; + n[0..4].copy_from_slice(&sequence_number.to_be_bytes()); + n[4..12].copy_from_slice(&iv_bytes[..8]); + n + }; + + info!("ChaCha20-Poly1305 nonce: seq={}, iv[:8]={:?}, nonce={:?}", sequence_number, &iv_bytes[..8], nonce_bytes); + + // 6. ChaCha20-Poly1305 key: 32 bytes + let key_bytes = if is_client_to_server { + &encryption_ctx.encryption_key_ctos + } else { + &encryption_ctx.encryption_key_stoc + }; + + // 7. ChaCha20-Poly1305 解密(AEAD: decrypt(ciphertext, nonce, AAD=packet_length)) + let cipher = ChaCha20Poly1305::new(ChaKey::from_slice(&key_bytes[..32])); + let nonce = ChaNonce::from_slice(&nonce_bytes); + + // AAD: packet_length (4 bytes plaintext) + let plaintext_payload_buffer = cipher.decrypt(nonce, ChaPayload { + msg: ciphertext.as_slice(), + aad: &packet_length_bytes, + }).map_err(|e| anyhow!("ChaCha20-Poly1305 decryption failed: {}", e))?; + + info!("ChaCha20-Poly1305 decrypted payload: {} bytes", plaintext_payload_buffer.len()); + + // 8. 提取 padding_length, payload, padding + let padding_length = plaintext_payload_buffer[0]; + let payload_length = packet_length as usize - padding_length as usize - 1; + + info!("ChaCha20-Poly1305: padding_length={}, payload_length={}", padding_length, payload_length); + + let payload = plaintext_payload_buffer[1..1 + payload_length].to_vec(); + let padding = Vec::new(); // ChaCha20-Poly1305: padding 不需要存储 + + // 9. 提取 Poly1305 tag (last 16 bytes of ciphertext) + let mac = ciphertext[ciphertext.len()-16..].to_vec(); + + info!("ChaCha20-Poly1305 tag (16 bytes): {:?}", &mac); + + // 10. 更新sequence number + if is_client_to_server { + encryption_ctx.sequence_number_ctos += 1; + } else { + encryption_ctx.sequence_number_stoc += 1; + } + + Ok(Self { + packet_length, + padding_length, + payload, // Just the SSH payload (not full packet) + padding, + mac, // Poly1305 tag + }) } else { // AES-CTR MtE 模式(原有逻辑) info!("Reading AES-CTR encrypted packet (packet_length encrypted)"); diff --git a/markbase-core/src/ssh_server/server.rs b/markbase-core/src/ssh_server/server.rs index 8cdb0b4..2a169a9 100644 --- a/markbase-core/src/ssh_server/server.rs +++ b/markbase-core/src/ssh_server/server.rs @@ -306,12 +306,15 @@ fn perform_complete_kex_exchange( let session_keys = kex_state.exchange_handler.compute_session_keys()?; let mut encryption_ctx = EncryptionContext::from_session_keys(&session_keys); - // Phase 1: 根据 KEX 协商结果设置加密模式(AES-GCM vs AES-CTR) + // Phase 1+5: 根据 KEX 协商结果设置加密模式(ChaCha20-Poly1305 / AES-GCM / AES-CTR) let encryption_algorithm = &kex_result.encryption_stoc; info!("KEX negotiated encryption algorithm: {}", encryption_algorithm); use crate::ssh_server::cipher::CipherMode; - if encryption_algorithm.contains("gcm") { + if encryption_algorithm.contains("chacha20") { + info!("Setting cipher mode to ChaCha20-Poly1305 (AEAD)"); + encryption_ctx.set_cipher_mode(CipherMode::ChaChaPoly)?; + } else if encryption_algorithm.contains("gcm") { info!("Setting cipher mode to AES-GCM (AEAD)"); encryption_ctx.set_cipher_mode(CipherMode::AesGcm)?; } else {