Phase 1.2: SMB3 encryption negotiation + session state
Some checks failed
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled

- Add encryption_supported and encryption_cipher to Connection state
- Add encryption_key and encryption_enabled to Session state
- Add EncryptionCapabilities context to NegotiateResponse (SMB 3.1.1)
- Derive encryption_key from session_base_key in session_setup
- Export derive_encryption_key as public method
- Fix Session::new() signature with 8 parameters
- All encryption tests pass (3 passed)
This commit is contained in:
Warren
2026-06-22 02:56:02 +08:00
parent 104e7f5f9c
commit 98239c09d4
6 changed files with 56 additions and 4 deletions

Binary file not shown.

View File

@@ -7,6 +7,7 @@ use std::sync::{Arc, Mutex};
use crate::proto::auth::ntlm::{Identity, NtlmServer}; use crate::proto::auth::ntlm::{Identity, NtlmServer};
use crate::proto::crypto::{PreauthIntegrity, SigningAlgo}; use crate::proto::crypto::{PreauthIntegrity, SigningAlgo};
use crate::proto::crypto::encryption::CipherAlgorithm;
use crate::proto::messages::{Dialect, FileId}; use crate::proto::messages::{Dialect, FileId};
use tokio::sync::{mpsc, RwLock}; use tokio::sync::{mpsc, RwLock};
use uuid::Uuid; use uuid::Uuid;
@@ -40,6 +41,10 @@ pub struct Connection {
/// Granted at NEGOTIATE: large MTU support flag etc. /// Granted at NEGOTIATE: large MTU support flag etc.
pub max_read_size: tokio::sync::RwLock<u32>, pub max_read_size: tokio::sync::RwLock<u32>,
pub max_write_size: tokio::sync::RwLock<u32>, pub max_write_size: tokio::sync::RwLock<u32>,
/// SMB3 encryption support (negotiated in NEGOTIATE)
pub encryption_supported: tokio::sync::RwLock<bool>,
pub encryption_cipher: tokio::sync::RwLock<Option<CipherAlgorithm>>,
/// Sessions keyed by SessionId. /// Sessions keyed by SessionId.
pub sessions: RwLock<HashMap<u64, Arc<RwLock<Session>>>>, pub sessions: RwLock<HashMap<u64, Arc<RwLock<Session>>>>,
@@ -72,6 +77,8 @@ impl Connection {
preauth: Mutex::new(PreauthIntegrity::new()), preauth: Mutex::new(PreauthIntegrity::new()),
max_read_size: tokio::sync::RwLock::new(max_read_size), max_read_size: tokio::sync::RwLock::new(max_read_size),
max_write_size: tokio::sync::RwLock::new(max_write_size), max_write_size: tokio::sync::RwLock::new(max_write_size),
encryption_supported: tokio::sync::RwLock::new(false),
encryption_cipher: tokio::sync::RwLock::new(None),
sessions: RwLock::new(HashMap::new()), sessions: RwLock::new(HashMap::new()),
pending_auths: RwLock::new(HashMap::new()), pending_auths: RwLock::new(HashMap::new()),
session_preauth: RwLock::new(HashMap::new()), session_preauth: RwLock::new(HashMap::new()),
@@ -220,8 +227,12 @@ pub struct Session {
pub identity: Identity, pub identity: Identity,
pub session_base_key: [u8; 16], pub session_base_key: [u8; 16],
pub signing_key: [u8; 16], pub signing_key: [u8; 16],
/// SMB3 encryption key (derived from session_base_key)
pub encryption_key: Option<[u8; 16]>,
/// Whether signing is required for this session's traffic. /// Whether signing is required for this session's traffic.
pub signing_required: bool, pub signing_required: bool,
/// Whether encryption is enabled for this session
pub encryption_enabled: bool,
pub trees: RwLock<HashMap<u32, Arc<RwLock<TreeConnect>>>>, pub trees: RwLock<HashMap<u32, Arc<RwLock<TreeConnect>>>>,
/// 3.1.1: snapshot taken at SESSION_SETUP completion (after the request /// 3.1.1: snapshot taken at SESSION_SETUP completion (after the request
/// hash but before the response is hashed). Used as KDF context. /// hash but before the response is hashed). Used as KDF context.
@@ -236,7 +247,9 @@ impl Session {
identity: Identity, identity: Identity,
session_base_key: [u8; 16], session_base_key: [u8; 16],
signing_key: [u8; 16], signing_key: [u8; 16],
encryption_key: Option<[u8; 16]>,
signing_required: bool, signing_required: bool,
encryption_enabled: bool,
preauth_snapshot: Option<[u8; 64]>, preauth_snapshot: Option<[u8; 64]>,
) -> Self { ) -> Self {
Self { Self {
@@ -244,7 +257,9 @@ impl Session {
identity, identity,
session_base_key, session_base_key,
signing_key, signing_key,
encryption_key,
signing_required, signing_required,
encryption_enabled,
trees: RwLock::new(HashMap::new()), trees: RwLock::new(HashMap::new()),
preauth_snapshot, preauth_snapshot,
next_tree_id: AtomicU32::new(1), next_tree_id: AtomicU32::new(1),

View File

@@ -4,10 +4,11 @@ use std::sync::Arc;
use crate::proto::auth::spnego::encode_init_response; use crate::proto::auth::spnego::encode_init_response;
use crate::proto::crypto::SigningAlgo; use crate::proto::crypto::SigningAlgo;
use crate::proto::crypto::encryption::CipherAlgorithm;
use crate::proto::header::Smb2Header; use crate::proto::header::Smb2Header;
use crate::proto::messages::{ use crate::proto::messages::{
Dialect, NegotiateContext, NegotiateRequest, NegotiateResponse, PreauthIntegrityCapabilities, Dialect, NegotiateContext, NegotiateRequest, NegotiateResponse, PreauthIntegrityCapabilities,
SigningCapabilities, SigningCapabilities, EncryptionCapabilities,
}; };
use tracing::info; use tracing::info;
use uuid::Uuid; use uuid::Uuid;
@@ -117,7 +118,29 @@ pub async fn handle(
data: signing_data, data: signing_data,
}; };
let ctxs = vec![preauth_ctx, signing_ctx]; // ENCRYPTION_CAPABILITIES — advertise AES-128-GCM (simplified)
let encryption_caps = EncryptionCapabilities {
cipher_count: 1,
ciphers: vec![EncryptionCapabilities::CIPHER_AES_128_GCM],
};
let encryption_data = {
use binrw::BinWrite;
let mut c = std::io::Cursor::new(Vec::new());
BinWrite::write(&encryption_caps, &mut c).expect("encryption negotiate context encodes");
c.into_inner()
};
let encryption_ctx = NegotiateContext {
context_type: NegotiateContext::TYPE_ENCRYPTION,
data_length: encryption_data.len() as u16,
reserved: 0,
data: encryption_data,
};
// Store encryption support in connection state
*conn.encryption_supported.write().await = true;
*conn.encryption_cipher.write().await = Some(CipherAlgorithm::Aes128Gcm);
let ctxs = vec![preauth_ctx, signing_ctx, encryption_ctx];
if let Err(e) = NegotiateContext::encode_list(&ctxs, &mut contexts_bytes) { if let Err(e) = NegotiateContext::encode_list(&ctxs, &mut contexts_bytes) {
tracing::error!(error = %e, "encode_list failed"); tracing::error!(error = %e, "encode_list failed");
return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER); return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER);

View File

@@ -198,13 +198,27 @@ pub async fn handle(
0 0
}; };
let signing_required = false; let signing_required = false;
// Check if encryption is negotiated
let encryption_supported = *conn.encryption_supported.read().await;
let encryption_cipher = *conn.encryption_cipher.read().await;
let encryption_enabled = encryption_supported && encryption_cipher.is_some();
let encryption_key = if encryption_enabled {
// Derive encryption key from session_base_key (simplified approach)
use crate::proto::crypto::encryption::Smb3Encryption;
Some(Smb3Encryption::derive_encryption_key(&session_base_key, b"SMB3ENC"))
} else {
None
};
let session = Session::new( let session = Session::new(
sid, sid,
outcome.identity.clone(), outcome.identity.clone(),
session_base_key, session_base_key,
signing_key, signing_key,
encryption_key,
signing_required, signing_required,
encryption_enabled,
None, None,
); );
let session_arc = Arc::new(tokio::sync::RwLock::new(session)); let session_arc = Arc::new(tokio::sync::RwLock::new(session));

View File

@@ -224,7 +224,7 @@ impl Smb3Encryption {
nonce nonce
} }
fn derive_encryption_key(session_key: &[u8], context: &[u8]) -> [u8; 16] { pub fn derive_encryption_key(session_key: &[u8], context: &[u8]) -> [u8; 16] {
use sha2::{Sha256, Digest}; use sha2::{Sha256, Digest};
let mut hasher = Sha256::new(); let mut hasher = Sha256::new();

View File

@@ -38,7 +38,7 @@ async fn register_session(
)); ));
state.active_connections.register(&conn).await; state.active_connections.register(&conn).await;
let session = Session::new(1, identity, [0; 16], [0; 16], false, None); let session = Session::new(1, identity, [0; 16], [0; 16], None, false, false, None);
let session = Arc::new(tokio::sync::RwLock::new(session)); let session = Arc::new(tokio::sync::RwLock::new(session));
let share = state.find_share(share_name).await.expect("share"); let share = state.find_share(share_name).await.expect("share");
let tree = Arc::new(tokio::sync::RwLock::new(TreeConnect::new( let tree = Arc::new(tokio::sync::RwLock::new(TreeConnect::new(