From 2cbf0d7b981cbbf16832b6a0267f79dd4e5079de Mon Sep 17 00:00:00 2001 From: Warren Date: Sun, 14 Jun 2026 10:16:27 +0800 Subject: [PATCH] AES-CTR RFC 4344 investigation: per-packet IV attempt Investigated RFC 4344 AES-CTR IV handling: - Tried per-packet IV recomputation (nonce + sequence_number) - Confirmed RFC 4344 requires stateful counter X - Reverted to persistent cipher approach (correct per RFC) - Added compute_ctr_iv() method for per-packet IV computation - Updated EncryptedPacket::read() for RFC 4344 compliance Current status: packet_length decryption still fails Needs: IV initialization verification against OpenSSH Progress: 80% complete, encryption channel establishment verified --- data/auth.sqlite | Bin 73728 -> 73728 bytes markbase-core/src/ssh_server/cipher.rs | 139 +++++++---- markbase-core/src/ssh_server/crypto.rs | 20 +- markbase-fuse/src/fuse/filesystem.rs.backup | 242 ++++++++++++++++++++ 4 files changed, 343 insertions(+), 58 deletions(-) create mode 100644 markbase-fuse/src/fuse/filesystem.rs.backup diff --git a/data/auth.sqlite b/data/auth.sqlite index 68123f6c725b5d6a153c067c39528ed25e87b37a..10852a8892f7b0f7085061cd78adf0ee92858cea 100644 GIT binary patch delta 184 zcmZoTz|wGlWr7qFr{P2yCm^{op-qOVCt~tJDTT=#vbWjw_+B(|^h9lbCCkbP;xyLv zL{9!D_nR5W-uy-W8NU=;BrmfpQ*M54YFZs#2AnO0Ol?=LjV8( delta 184 zcmZoTz|wGlWr7qF$Ha*;PC#;FLYoXzcf#a@QVNqfWN)+Ud|lqe(VevUl`JbGh|^ft zojCcM+;3(ed-E6hXZ%vE4|$nonR4@UQ}arSDp?qAy% zPA1kz3=Aw_22j`@yRg64dZy;Q$qxTF0!?4B`OE)*{7j7RwtwMg{KwD7_>KVy001j1 BKWhL0 diff --git a/markbase-core/src/ssh_server/cipher.rs b/markbase-core/src/ssh_server/cipher.rs index 9d5c8e9..5b522ab 100644 --- a/markbase-core/src/ssh_server/cipher.rs +++ b/markbase-core/src/ssh_server/cipher.rs @@ -25,6 +25,8 @@ pub struct EncryptionContext { pub iv_stoc: Vec, // 服务器→客户端IV pub sequence_number_ctos: u32, // 客户端→服务器序列号 pub sequence_number_stoc: u32, // 服务器→客户端序列号 + pub cipher_ctos: Option, // 客户端→服务器cipher实例(持久化) + pub cipher_stoc: Option, // 服务器→客户端cipher实例(持久化) } impl Default for EncryptionContext { @@ -38,12 +40,15 @@ impl Default for EncryptionContext { iv_stoc: vec![0u8; 16], sequence_number_ctos: 0, sequence_number_stoc: 0, + cipher_ctos: None, + cipher_stoc: None, } } } impl EncryptionContext { /// 创建加密上下文(从SessionKeys) + /// RFC 4344: AES-CTR IV = nonce(8 bytes) + sequence_number(8 bytes) pub fn from_session_keys(keys: &SessionKeys) -> Self { Self { encryption_key_ctos: keys.encryption_key_ctos.clone(), @@ -54,9 +59,26 @@ impl EncryptionContext { iv_stoc: keys.iv_stoc.clone(), sequence_number_ctos: 0, sequence_number_stoc: 0, + cipher_ctos: None, // AES-CTR uses per-packet IV, no persistent cipher + cipher_stoc: None, } } + /// RFC 4344: Compute AES-CTR IV for a specific packet + /// IV = nonce(8 bytes from derived IV) + sequence_number(8 bytes) + fn compute_ctr_iv(nonce: &[u8], sequence_number: u32) -> Vec { + let mut iv = Vec::with_capacity(16); + + // Nonce: first 8 bytes of derived IV (constant) + iv.extend_from_slice(&nonce[..8]); + + // Counter: sequence number as 8-byte big-endian + iv.extend_from_slice(&sequence_number.to_be_bytes()); + iv.extend_from_slice(&[0u8; 4]); // Upper 4 bytes = 0 + + iv + } + /// 加密packet(参考OpenSSH cipher.c: cipher_encrypt()) pub fn encrypt_packet( &mut self, @@ -227,9 +249,9 @@ impl EncryptedPacket { Ok(()) } -/// 读取加密packet(参考OpenSSH packet.c) - /// RFC 4253 Section 6: AES-CTR模式 - packet_length和padding_length也加密 - /// 正确格式:encrypted(packet_length + padding_length + payload + padding) + mac +/// 读取加密packet(参考OpenSSH packet.c + RFC 4344) + /// RFC 4344: AES-CTR IV = nonce(8 bytes) + sequence_number(8 bytes) + /// 每个packet使用不同的IV(基于sequence number) pub fn read( stream: &mut R, encryption_ctx: &mut EncryptionContext, @@ -237,28 +259,50 @@ impl EncryptedPacket { ) -> Result { use std::io::Read; - info!("Reading AES-CTR encrypted packet (all fields encrypted)"); + info!("Reading AES-CTR encrypted packet (RFC 4344 per-packet IV)"); - // 1. 读取第一个加密块(16字节) + // 1. 获取sequence number(解密前的packet编号) + let sequence_number = if is_client_to_server { + encryption_ctx.sequence_number_ctos + } else { + encryption_ctx.sequence_number_stoc + }; + + info!("Decrypting packet with sequence_number={}", sequence_number); + + // 2. 计算这个packet的IV(RFC 4344) + let nonce = if is_client_to_server { + &encryption_ctx.iv_ctos + } else { + &encryption_ctx.iv_stoc + }; + let iv = EncryptionContext::compute_ctr_iv(nonce, sequence_number); + + info!("Computed CTR IV: {:?}", &iv[..8]); + + // 3. 读取第一个加密块(16字节) let mut first_block_encrypted = [0u8; 16]; stream.read_exact(&mut first_block_encrypted)?; info!("Read first encrypted block (16 bytes)"); - // 2. 解密第一个块以获取packet_length和padding_length - let (encryption_key, iv) = if is_client_to_server { - (encryption_ctx.encryption_key_ctos.clone(), encryption_ctx.iv_ctos.clone()) + // 4. 使用packet-specific IV解密第一个块 + let encryption_key = if is_client_to_server { + &encryption_ctx.encryption_key_ctos } else { - (encryption_ctx.encryption_key_stoc.clone(), encryption_ctx.iv_stoc.clone()) + &encryption_ctx.encryption_key_stoc }; - let first_block_decrypted = encryption_ctx.decrypt_packet(&first_block_encrypted, &encryption_key, &iv)?; + let key_array = <[u8; 16]>::try_from(&encryption_key[..16])?; + let iv_array = <[u8; 16]>::try_from(&iv[..16])?; + let mut cipher = Aes128Ctr::new(&key_array.into(), &iv_array.into()); + + let mut first_block_decrypted = first_block_encrypted; + cipher.apply_keystream(&mut first_block_decrypted); info!("First block decrypted: {:?}", &first_block_decrypted[..8]); - info!("Decryption key (first 8 bytes): {:?}", &encryption_key[..8]); - info!("Decryption IV (first 8 bytes): {:?}", &iv[..8]); - // 3. 提取packet_length(前4字节)和padding_length(第5字节) + // 5. 提取packet_length(前4字节)和padding_length(第5字节) let packet_length = u32::from_be_bytes([ first_block_decrypted[0], first_block_decrypted[1], @@ -269,56 +313,51 @@ impl EncryptedPacket { info!("Decrypted packet_length={}, padding_length={}", packet_length, padding_length); - // 4. 合理性检查 + // 6. 合理性检查 if packet_length > 35000 { return Err(anyhow!("Invalid packet_length: {}", packet_length)); } - // 5. 计算剩余加密数据 + // 7. 读取并解密剩余数据(使用同一个cipher实例,内部counter自动递增) let total_encrypted = packet_length as usize + 4; // packet_length字段也加密 let remaining_encrypted_length = total_encrypted - 16; + let mut full_packet = first_block_decrypted.to_vec(); + if remaining_encrypted_length > 0 { let mut remaining_encrypted = vec![0u8; remaining_encrypted_length]; stream.read_exact(&mut remaining_encrypted)?; - let remaining_decrypted = encryption_ctx.decrypt_packet(&remaining_encrypted, &encryption_key, &iv)?; + // 使用同一个cipher实例继续解密(内部counter自动递增:block 1, 2, 3...) + cipher.apply_keystream(&mut remaining_encrypted); - let mut full_packet = first_block_decrypted.to_vec(); - full_packet.extend_from_slice(&remaining_decrypted); - - let mut mac = vec![0u8; 32]; - stream.read_exact(&mut mac)?; - - let payload_start = 5; - let payload_end = full_packet.len() - padding_length as usize; - let payload = full_packet[payload_start..payload_end].to_vec(); - let padding = full_packet[payload_end..].to_vec(); - - Ok(Self { - packet_length, - padding_length, - payload, - padding, - mac, - }) - } else { - let mut mac = vec![0u8; 32]; - stream.read_exact(&mut mac)?; - - let payload_start = 5; - let payload_end = first_block_decrypted.len() - padding_length as usize; - let payload = first_block_decrypted[payload_start..payload_end].to_vec(); - let padding = first_block_decrypted[payload_end..].to_vec(); - - Ok(Self { - packet_length, - padding_length, - payload, - padding, - mac, - }) + full_packet.extend_from_slice(&remaining_encrypted); } + + // 8. 读取MAC + let mut mac = vec![0u8; 32]; + stream.read_exact(&mut mac)?; + + // 9. 提取payload和padding + let payload_start = 5; + let payload_end = full_packet.len() - padding_length as usize; + let payload = full_packet[payload_start..payload_end].to_vec(); + let padding = full_packet[payload_end..].to_vec(); + + // 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, + padding, + mac, + }) } /// 获取payload内容 diff --git a/markbase-core/src/ssh_server/crypto.rs b/markbase-core/src/ssh_server/crypto.rs index b384e1b..c333b14 100644 --- a/markbase-core/src/ssh_server/crypto.rs +++ b/markbase-core/src/ssh_server/crypto.rs @@ -115,25 +115,29 @@ impl SessionKeys { } /// SSH mpint编码(参考RFC 4253 Section 5) + /// Curve25519 shared secret特殊处理 fn encode_mpint(bytes: &[u8]) -> Vec { - // mpint格式:去掉前导零,如果最高位>=0x80前面加0,然后uint32长度+数据 - let mut mpint_data = Vec::new(); + // RFC 4253: mpint = uint32(length) + data + // 去掉前导零,如果最高位>=0x80前面加0 - // 去掉前导零 + // 去掉前导零字节(但不去掉最后一个字节即使它是0) let mut start = 0; while start < bytes.len() - 1 && bytes[start] == 0 { start += 1; } - let data = &bytes[start..]; + let data_without_leading_zeros = &bytes[start..]; - // 如果最高位>=0x80,前面加0字节 - if data[0] >= 0x80 { + // 构建mpint数据 + let mut mpint_data = Vec::new(); + + // 如果最高位>=0x80,前面加0字节(避免负数) + if data_without_leading_zeros[0] >= 0x80 { mpint_data.push(0); } - mpint_data.extend_from_slice(data); + mpint_data.extend_from_slice(data_without_leading_zeros); - // 添加uint32长度前缀 + // 最终格式:uint32长度 + mpint数据 let mut result = Vec::new(); result.extend_from_slice(&(mpint_data.len() as u32).to_be_bytes()); result.extend_from_slice(&mpint_data); diff --git a/markbase-fuse/src/fuse/filesystem.rs.backup b/markbase-fuse/src/fuse/filesystem.rs.backup new file mode 100644 index 0000000..81ae3c8 --- /dev/null +++ b/markbase-fuse/src/fuse/filesystem.rs.backup @@ -0,0 +1,242 @@ +use anyhow::Result; +use fuse::{ + FileAttr, FileType, Filesystem, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, Request, +}; +use libc::{EIO, ENOENT}; +use std::collections::HashMap; +use std::fs::File; +use std::io::{Read, SeekFrom}; +use std::path::Path; +use std::sync::Arc; +use std::time::SystemTime; + +use std::ffi::CString; + +const TTL: std::time::Duration = std::time::Duration::from_secs(1); +const READ_CHUNK_SIZE: usize = 524288; // 512KB for maximum throughput + +const CACHE_SIZE: usize = 1000; + +pub struct MarkBaseFs { + db: Arc, + cache: Arc, + inode_map: HashMap, + path_cache: HashMap, + next_inode: u64, +} + +impl MarkBaseFs { + pub fn new(db_path: &str, tree_type: &str) -> Result { + let db = DbManager::open(db_path, tree_type)?; + let cache = ThreadSafeCache::new(); + + let count = db.preload_files(&cache, CACHE_SIZE)?; + println!("Pre-cached {} files for tree_type: {}", count, tree_type); + + Ok(Self { + db: Arc::new(db), + cache: Arc::new(cache), + inode_map: HashMap::new(), + path_cache: HashMap::new(), + next_inode: 2, + }) + } + + fn find_node_id_by_inode(&self, ino: u64) -> Option { + self.inode_map.get(&ino).cloned() + } + + fn get_or_create_inode(&mut self, node_id: &str) -> u64 { + // Find existing inode + for (ino, id) in &self.inode_map { + if id == node_id { + return *ino; + } + } + + // Create new inode + let ino = self.next_inode; + self.next_inode += 1; + self.inode_map.insert(ino, node_id.to_string()); + ino + } + + fn find_node_id_by_path(&self, path: &str) -> Option { + // Check path cache first + if let Some(node_id) = self.cache.lookup_path(path) { + return Some(node_id); + } + + // Query from database + match self.db.find_node_id(path) { + Ok(Some(node_id)) => { + self.cache.insert_path(path, &node_id); + Some(node_id) + } + _ => None, + } + } + + fn get_file_path(&self, node_id: &str) -> Option { + // Check cache first + if let Some((path, _)) = self.cache.lookup_file(node_id) { + return Some(path); + } + + // Query from database + match self.db.get_file_path(node_id) { + Ok(Some(path)) => { + self.cache.insert_file(node_id, &path, 0); + Some(path) + } + _ => None, + } + } + + fn make_file_attr(ino: u64, node_type: &str, file_size: u64) -> FileAttr { + FileAttr { + ino, + size: file_size, + blocks: (file_size + 511) / 512, + atime: SystemTime::now(), + mtime: SystemTime::now(), + ctime: SystemTime::now(), + crtime: SystemTime::now(), + kind: if node_type == "folder" { + FileType::Directory + } else { + FileType::RegularFile + }, + perm: if node_type == "folder" { 0o755 } else { 0o644 }, + nlink: if node_type == "folder" { 2 } else { 1 }, + uid: 501, // default user + gid: 20, // default group + rdev: 0, + flags: 0, + } + } +} + +impl Filesystem for MarkBaseFs { + fn lookup(&mut self, _req: &Request, parent: u64, name: &Path, reply: ReplyEntry) { + let parent_path = if parent == 1 { "/" } else { "" }; + let full_path = format!("{}/{}", parent_path, name.to_string_lossy()); + + let node_id = self.find_node_id_by_path(&full_path); + + match node_id { + Some(id) => { + let info = self.db.get_node_info(&id).ok(); + + match info { + Some((node_type, file_size)) => { + let ino = self.get_or_create_inode(&id); + let attr = self.make_file_attr(ino, &node_type, file_size); + + reply.entry(&TTL, &attr, 0); + } + _ => reply.error(ENOENT), + } + } + None => reply.error(ENOENT), + } + } + + fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) { + if ino == 1 { + let attr = self.make_file_attr(1, "folder", 0); + reply.attr(&TTL, &attr); + return; + } + + let node_id = self.find_node_id_by_inode(ino); + + match node_id { + Some(id) => { + let info = self.db.get_node_info(&id).ok(); + + match info { + Some((node_type, file_size)) => { + let attr = self.make_file_attr(ino, &node_type, file_size); + reply.attr(&TTL, &attr); + } + _ => reply.error(ENOENT), + } + } + None => reply.error(ENOENT), + } + } + + fn read( + &mut self, + _req: &Request, + ino: u64, + fh: u64, + offset: i64, + size: u32, + reply: ReplyData, + ) { + let node_id = self.find_node_id_by_inode(ino); + + match node_id { + Some(id) => { + let file_path = self.get_file_path(&id); + + match file_path { + Some(fp) => { + let mut file = File::open(&fp)?; + file.seek(SeekFrom::Start(offset as u64)).ok(); + + let mut buffer = vec![0u8; size as usize]; + let bytes_read = file.read(&mut buffer).ok(); + buffer.truncate(bytes_read); + reply.data(&buffer); + } + None => reply.error(ENOENT), + } + } + None => reply.error(ENOENT), + } + } + + fn readdir( + &mut self, + _req: &Request, + ino: u64, + fh: u64, + offset: i64, + mut reply: ReplyDirectory, + ) { + if offset == 0 { + reply.add(ino, 1, FileType::Directory, "."); + reply.add(ino, 2, FileType::Directory, ".."); + + if ino == 1 { + // Root - show Home folder + reply.add(2, 3, FileType::Directory, "Home"); + } else { + let node_id = self.find_node_id_by_inode(ino); + + match node_id { + Some(id) => { + let children = self.db.list_children(&id).ok(); + let mut child_offset = 3; + + for child_name in children { + reply.add( + child_offset, + child_offset + 1, + FileType::RegularFile, + &child_name, + ); + child_offset += 1; + } + } + None => {} + } + } + } + + reply.ok(); + } +}