Phase 1.2: SMB3 encryption negotiation + session state
- 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:
BIN
data/auth.sqlite
BIN
data/auth.sqlite
Binary file not shown.
15
vendor/smb-server/src/conn/state.rs
vendored
15
vendor/smb-server/src/conn/state.rs
vendored
@@ -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;
|
||||||
@@ -41,6 +42,10 @@ pub struct Connection {
|
|||||||
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),
|
||||||
|
|||||||
27
vendor/smb-server/src/handlers/negotiate.rs
vendored
27
vendor/smb-server/src/handlers/negotiate.rs
vendored
@@ -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);
|
||||||
|
|||||||
14
vendor/smb-server/src/handlers/session_setup.rs
vendored
14
vendor/smb-server/src/handlers/session_setup.rs
vendored
@@ -199,12 +199,26 @@ pub async fn handle(
|
|||||||
};
|
};
|
||||||
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));
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user