Skip to main content

smb_server_proto_smb2/
negotiate.rs

1//! NEGOTIATE ([MS-SMB2] §2.2.3).
2
3/// Dialect numbers (§2.2.3.1.1).
4/// SMB 2.0.2 dialect revision.
5pub const DIALECT_202: u16 = 0x0202;
6/// SMB 2.1.0 dialect revision.
7pub const DIALECT_210: u16 = 0x0210;
8/// SMB 3.0 dialect revision.
9pub const DIALECT_300: u16 = 0x0300;
10/// SMB 3.0.2 dialect revision.
11pub const DIALECT_302: u16 = 0x0302;
12/// SMB 3.1.1 dialect revision.
13pub const DIALECT_311: u16 = 0x0311;
14/// Wildcard revision returned to a multi-protocol "SMB 2.???" offer
15/// ([MS-SMB2] §2.2.4); the client then sends a real SMB2 NEGOTIATE.
16pub const DIALECT_WILDCARD: u16 = 0x02FF;
17
18/// SecurityMode bits (§2.2.3.1.1).
19/// Signing is supported but not required.
20pub const SIGNING_ENABLED: u16 = 0x0001;
21/// Signing is required by the server.
22pub const SIGNING_REQUIRED: u16 = 0x0002;
23
24/// Capability bits (§2.2.3.2.5).
25pub mod caps {
26    /// Leasing (file leases) — available from dialect 2.1.
27    pub const LEASING: u32 = 0x0000_0002;
28    /// Large MTU — multi-credit transfers.
29    pub const LARGE_MTU: u32 = 0x0000_0004;
30    /// Multiple channels for one session (multichannel).
31    pub const MULTI_CHANNEL: u32 = 0x0000_0008;
32    /// Persistent handles (continuous availability).
33    pub const PERSISTENT_HANDLES: u32 = 0x0000_0010;
34    /// Directory leasing.
35    pub const DIRECTORY_LEASING: u32 = 0x0000_0020;
36    /// Encryption (dialects 3.0/3.0.2; 3.1.1 negotiates via context).
37    pub const ENCRYPTION: u32 = 0x0000_0040;
38    /// Server-to-client notifications (dialect 3.1.1).
39    pub const NOTIFICATIONS: u32 = 0x0000_0080;
40}
41
42/// Negotiate context types (§2.2.3.1.2).
43pub mod ctx_type {
44    /// PREAUTH_INTEGRITY_CAPABILITIES.
45    pub const PREAUTH_INTEGRITY: u16 = 0x0001;
46    /// ENCRYPTION_CAPABILITIES.
47    pub const ENCRYPTION: u16 = 0x0002;
48    /// COMPRESSION_CAPABILITIES.
49    pub const COMPRESSION: u16 = 0x0003;
50    /// SIGNING_CAPABILITIES.
51    pub const SIGNING: u16 = 0x0008;
52    /// SIGNING_ALGORITHM: HMAC-SHA256 ([MS-SMB2] §2.2.3.1.7).
53    pub const SIGNING_HMAC_SHA256: u16 = 0x0000;
54    /// SIGNING_ALGORITHM: AES-128-CMAC.
55    pub const SIGNING_AES128_CMAC: u16 = 0x0001;
56    /// SIGNING_ALGORITHM: AES-128-GMAC.
57    pub const SIGNING_AES128_GMAC: u16 = 0x0002;
58    /// HASH_ALGORITHMS values: SHA-512.
59    pub const SHA512: u16 = 0x0001;
60    /// CIPHER values: AES-128-CCM.
61    pub const AES128_CCM: u16 = 0x0001;
62    /// CIPHER values: AES-128-GCM.
63    pub const AES128_GCM: u16 = 0x0002;
64    /// CIPHER values: AES-256-CCM.
65    pub const AES256_CCM: u16 = 0x0003;
66    /// CIPHER values: AES-256-GCM.
67    pub const AES256_GCM: u16 = 0x0004;
68}
69
70/// One parsed negotiate context.
71#[derive(Debug, Clone)]
72pub struct Context {
73    /// ContextType.
74    pub kind: u16,
75    /// Raw data payload (already stripped of padding).
76    pub data: Vec<u8>,
77}
78
79/// Parsed SMB2 NEGOTIATE request.
80#[derive(Debug)]
81pub struct Request {
82    /// Dialect numbers offered by the client.
83    pub dialects: Vec<u16>,
84    /// Client GUID.
85    pub client_guid: [u8; 16],
86    /// Negotiate contexts (3.1.1 only; empty otherwise).
87    pub contexts: Vec<Context>,
88}
89
90impl Request {
91    /// Parse from the request body (bytes after the 64-byte header).
92    ///
93    /// Two layouts exist ([MS-SMB2] §2.2.3.1): pre-3.1.1 clients place the
94    /// dialect array directly after the fixed part (offset 28); 3.1.1-aware
95    /// clients interpose NegotiateContextOffset/Count fields so dialects
96    /// begin at offset 36. We accept either by validating candidates
97    /// against the set of known dialect revisions.
98    pub fn parse(b: &[u8]) -> Option<Request> {
99        const KNOWN: &[u16] = &[0x0202, 0x0210, 0x0300, 0x0302, 0x0310, 0x0311];
100        if b.len() < 28 || u16::from_le_bytes([b[0], b[1]]) != 36 {
101            return None;
102        }
103        let dcount = u16::from_le_bytes([b[2], b[3]]) as usize;
104        if dcount == 0 || dcount > 64 {
105            return None;
106        }
107        let mut guid = [0u8; 16];
108        guid.copy_from_slice(b.get(12..28)?);
109
110        let read_dialects = |start: usize| -> Option<Vec<u16>> {
111            if b.len() < start + dcount * 2 {
112                return None;
113            }
114            let v: Vec<u16> = b[start..]
115                .chunks_exact(2)
116                .take(dcount)
117                .map(|c| u16::from_le_bytes([c[0], c[1]]))
118                .collect();
119            // Only accept when every entry is a real dialect revision.
120            (v.iter().all(|d| KNOWN.contains(d))).then_some(v)
121        };
122
123        let dialects = read_dialects(28)
124            .or_else(|| read_dialects(36))
125            .or_else(|| read_dialects(44))?;
126
127        // Contexts ride behind the dialect array when the 3.1.1 layout was
128        // used: NegotiateContextOffset/Count replace ClientStartTime.
129        let mut contexts = Vec::new();
130        if b.len() >= 36 {
131            let ctx_off = u32::from_le_bytes(b[28..32].try_into().unwrap()) as usize;
132            let ctx_count = u16::from_le_bytes([b[32], b[33]]) as usize;
133            if ctx_off >= 64 && ctx_count > 0 && ctx_off <= 64 + b.len() {
134                let base = ctx_off - 64; // body-relative
135                let mut p = base;
136                for _ in 0..ctx_count.min(64) {
137                    let Some(hdr) = b.get(p..p + 8) else { break };
138                    let kind = u16::from_le_bytes([hdr[0], hdr[1]]);
139                    let dlen = u16::from_le_bytes([hdr[2], hdr[3]]) as usize;
140                    let Some(data) = b.get(p + 8..p + 8 + dlen) else { break };
141                    contexts.push(Context { kind, data: data.to_vec() });
142                    // Contexts are 8-byte aligned.
143                    p += 8 + ((dlen + 7) & !7usize);
144                }
145            }
146        }
147
148        Some(Request { dialects, client_guid: guid, contexts })
149    }
150}
151
152/// Pick the highest supported dialect.
153pub fn pick(dialects: &[u16]) -> Option<u16> {
154    let mut best = None;
155    for &d in dialects {
156        if matches!(d, DIALECT_202 | DIALECT_210 | DIALECT_300 | DIALECT_302 | DIALECT_311) {
157            best = Some(best.map_or(d, |b: u16| b.max(d)));
158        }
159    }
160    best
161}
162
163/// Signing algorithms this server can compute, in fallback preference order.
164pub const SUPPORTED_SIGNING: &[u16] = &[
165    ctx_type::SIGNING_AES128_GMAC,
166    ctx_type::SIGNING_AES128_CMAC,
167    ctx_type::SIGNING_HMAC_SHA256,
168];
169
170/// Parse a client `SMB2_SIGNING_CAPABILITIES` context (§2.2.3.1.7) into its
171/// advertised SigningAlgorithms list.
172pub fn parse_signing_algos(data: &[u8]) -> Vec<u16> {
173    if data.len() < 2 {
174        return Vec::new();
175    }
176    let count = u16::from_le_bytes([data[0], data[1]]) as usize;
177    data.get(2..)
178        .map(|r| {
179            r.chunks_exact(2)
180                .take(count)
181                .map(|c| u16::from_le_bytes([c[0], c[1]]))
182                .collect()
183        })
184        .unwrap_or_default()
185}
186
187/// Select the signing algorithm: the client's first that the server supports
188/// ([MS-SMB2] §3.3.5.4).
189pub fn select_signing_algo(client: &[u16]) -> Option<u16> {
190    client.iter().copied().find(|a| SUPPORTED_SIGNING.contains(a))
191}
192
193/// Build the NEGOTIATE response body (§2.2.3.1) — no security blob
194/// (clients initiate NTLMSSP in SESSION_SETUP).
195///
196/// For dialect 3.1.1 the response carries negotiate contexts
197/// ([MS-SMB2] §2.2.3.1.1): PREAUTH_INTEGRITY_CAPABILITIES announcing
198/// SHA-512 with a random salt.
199#[allow(clippy::too_many_arguments)]
200pub fn build_response_full(
201    dialect: u16,
202    guid: &[u8; 16],
203    now: u64,
204    salt: &[u8; 32],
205    encryption: Option<u16>,
206    signing_algo: Option<u16>,
207    compression: &[u16],
208    compression_chained: bool,
209    require_signing: bool,
210    notifications: bool,
211) -> Vec<u8> {
212    let mut b = Vec::with_capacity(128);
213    b.extend_from_slice(&65u16.to_le_bytes()); // StructureSize
214    let sec_mode = SIGNING_ENABLED | if require_signing { SIGNING_REQUIRED } else { 0 };
215    b.extend_from_slice(&sec_mode.to_le_bytes()); // SecurityMode
216    b.extend_from_slice(&dialect.to_le_bytes());
217    if dialect == DIALECT_311 {
218        // Echo only the contexts the client offered ([MS-SMB2] §3.3.5.4);
219        // PREAUTH is mandatory, the rest are conditional.
220        let count = 1
221            + u16::from(signing_algo.is_some())
222            + u16::from(encryption.is_some())
223            + u16::from(!compression.is_empty());
224        b.extend_from_slice(&count.to_le_bytes()); // NegotiateContextCount
225    } else {
226        b.extend_from_slice(&0u16.to_le_bytes()); // Reserved
227    }
228    b.extend_from_slice(guid); // ServerGuid
229    let caps = if dialect >= DIALECT_300 {
230        caps::LARGE_MTU | caps::MULTI_CHANNEL | caps::LEASING | caps::DIRECTORY_LEASING | caps::PERSISTENT_HANDLES
231    } else if dialect >= DIALECT_210 {
232        caps::LARGE_MTU | caps::LEASING
233    } else {
234        0
235    };
236    // NOTIFICATIONS is set only when the client requested it and the server
237    // declares support ([MS-SMB2] §3.3.5.4); the caller has already applied
238    // both conditions plus the dialect==3.1.1 requirement.
239    let caps = caps | if notifications { caps::NOTIFICATIONS } else { 0 };
240    b.extend_from_slice(&caps.to_le_bytes()); // Capabilities
241    b.extend_from_slice(&(1024 * 1024u32).to_le_bytes()); // MaxTransactionSize
242    b.extend_from_slice(&(1024 * 1024u32).to_le_bytes()); // MaxReadSize
243    b.extend_from_slice(&(1024 * 1024u32).to_le_bytes()); // MaxWriteSize
244    b.extend_from_slice(&now.to_le_bytes()); // SystemTime
245    b.extend_from_slice(&now.to_le_bytes()); // ServerStartTime
246    // Empty security buffer: the offset points just past the fixed part
247    // (header 64 + padded body 64 = 128); clients validate this arithmetic.
248    b.extend_from_slice(&128u16.to_le_bytes()); // SecurityBufferOffset
249    b.extend_from_slice(&0u16.to_le_bytes()); // SecurityBufferLength
250    b.extend_from_slice(&0u32.to_le_bytes()); // NegotiateContextOffset slot
251
252    let mut ctx = Vec::new();
253    if dialect == DIALECT_311 {
254        let push_ctx = |ctx: &mut Vec<u8>, kind: u16, data: &[u8]| {
255            // Each context begins 8-byte aligned; pad BEFORE it, never after,
256            // so no trailing pad follows the final context. Clients (and the
257            // MS-SMB2 test SDK) recompute the pre-auth hash over the canonical
258            // unpadded tail, so a spurious trailing pad breaks 3.1.1 signing
259            // and encryption key derivation ([MS-SMB2] §3.3.4.1 / §3.3.5.4).
260            while !ctx.len().is_multiple_of(8) {
261                ctx.push(0);
262            }
263            ctx.extend_from_slice(&kind.to_le_bytes());
264            ctx.extend_from_slice(&(data.len() as u16).to_le_bytes());
265            ctx.extend_from_slice(&0u32.to_le_bytes()); // Reserved
266            ctx.extend_from_slice(data);
267        };
268
269        // PREAUTH_INTEGRITY_CAPABILITIES: SHA-512 + our salt.
270        let mut preauth = Vec::new();
271        preauth.extend_from_slice(&1u16.to_le_bytes()); // HashAlgorithmCount
272        preauth.extend_from_slice(&(salt.len() as u16).to_le_bytes());
273        preauth.extend_from_slice(&ctx_type::SHA512.to_le_bytes());
274        preauth.extend_from_slice(salt);
275        push_ctx(&mut ctx, ctx_type::PREAUTH_INTEGRITY, &preauth);
276
277        // SIGNING_CAPABILITIES: echo the signing algorithm selected from the
278        // client's SIGNING_CAPABILITIES context ([MS-SMB2] §3.3.5.4).
279        if let Some(algo) = signing_algo {
280            let signing = [1u16.to_le_bytes(), algo.to_le_bytes()].concat();
281            push_ctx(&mut ctx, ctx_type::SIGNING, &signing);
282        }
283
284        // ENCRYPTION_CAPABILITIES: exactly ONE selected cipher ([MS-SMB2]
285        // §2.2.3.1.2) — only when the client offered encryption.
286        if let Some(cipher) = encryption {
287            let enc = [1u16.to_le_bytes(), cipher.to_le_bytes()].concat();
288            push_ctx(&mut ctx, ctx_type::ENCRYPTION, &enc);
289        }
290
291        // COMPRESSION_CAPABILITIES: advertise the negotiated algorithm set.
292        // Echo the CHAINED flag only when the client requested it ([MS-SMB2]
293        // §3.3.5.4).
294        if !compression.is_empty() {
295            let comp = crate::compress::build_compression_caps(compression, compression_chained);
296            push_ctx(&mut ctx, ctx_type::COMPRESSION, &comp);
297        }
298
299        let ctx_off = BODY_START_FIXED; // absolute offset of contexts
300        b[60..64].copy_from_slice(&(ctx_off as u32).to_le_bytes()); // NegotiateContextOffset
301        b.extend_from_slice(&ctx);
302    }
303    b
304}
305
306/// Absolute frame offset where the negotiate context list starts for a
307/// response whose fixed body is 64 bytes.
308const BODY_START_FIXED: usize = 64 + 64;
309
310/// Build the NEGOTIATE response body (§2.2.3.1) — no security blob
311/// (clients initiate NTLMSSP in SESSION_SETUP).
312///
313/// The trailing 4-byte NegotiateContextOffset is emitted as 0 even for
314/// pre-3.1.1 dialects (where the spec marks it Reserved); every real-world
315/// parser expects the field's presence.
316pub fn build_response(dialect: u16, guid: &[u8; 16], now: u64) -> Vec<u8> {
317    build_response_full(dialect, guid, now, &[0u8; 32], None, None, &[], false, false, false)
318}