ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
smb2.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file smb2.h
6 * @brief SMB2 client wire codec (MS-SMB2), PC_ENABLE_SMB - increment 1: the transport
7 * frame, the 64-byte sync packet header, and the NEGOTIATE exchange.
8 *
9 * Windows-share program storage is a common CNC file path (Fanuc / Haas / Mazak / Heidenhain
10 * expose one), so a device can read/write `.nc` files over SMB2. This is the pure wire layer:
11 * build the little-endian SMB2 messages and parse the responses; the TCP socket is the
12 * application's. All fields are little-endian (SMB2 is a little-endian protocol).
13 *
14 * A client speaks SMB2 over Direct TCP (port 445): each message is prefixed by a 4-byte transport
15 * header (`0x00` + a 24-bit big-endian length), then the 64-byte SMB2 sync header (MS-SMB2
16 * §2.2.1.2), then the per-command body. The exchange begins with NEGOTIATE (§2.2.3 request /
17 * §2.2.4 response): the client offers a dialect list, the server picks one and returns the SPNEGO
18 * security token that seeds authentication.
19 *
20 * Shipped: the NEGOTIATE exchange; the NTLM crypto (smb_md / ntlm / ntlmssp); the SPNEGO wrapping
21 * (spnego); the SESSION_SETUP request/response framing that carries those tokens; and the
22 * TREE_CONNECT / CREATE / CLOSE / READ / WRITE file commands - the full read/write-a-file-on-a-share
23 * client; **SMB 2.x message signing** (pc_smb2_sign / pc_smb2_verify, HMAC-SHA256) wired into the
24 * client's SigningRequired path; and the **SMB 3.1.1 negotiate-context codec** (pc_smb2_build_negotiate_311
25 * / pc_smb2_parse_negotiate_contexts - preauth-integrity SHA-512, signing, and encryption capabilities);
26 * and the **SP800-108 counter-mode KDF** (pc_kdf_ctr_hmac_sha256 in src/crypto/kdf, NIST-CAVP-verified) that
27 * SMB 3.x uses to derive its keys. **SMB 3.1.1 runs end to end:** the client offers 2.0.2 .. 3.1.1, chains
28 * the preauth-integrity hash (pc_smb_preauth_*) across NEGOTIATE + both SESSION_SETUP rounds, derives the
29 * signing key (pc_smb3_derive_signing_key), and signs the session with AES-128-CMAC (pc_smb2_sign_cmac /
30 * _verify_cmac; crypto/aes_cmac) - the KDF assembly + CMAC cross-checked byte-for-byte against impacket.
31 *
32 * @author Douglas Quigg (dstroy0)
33 * @date 2026
34 */
35
36#ifndef PROTOCORE_SMB2_H
37#define PROTOCORE_SMB2_H
38
39#include "protocore_config.h"
40
41#if PC_ENABLE_SMB
42
43#include <stddef.h>
44#include <stdint.h>
45
46/** @brief SMB2 command codes (MS-SMB2 §2.2.1.2). */
47enum class Smb2Command : uint16_t
48{
49 SMB2_NEGOTIATE = 0x0000,
50 SMB2_SESSION_SETUP = 0x0001,
51 SMB2_LOGOFF = 0x0002,
52 SMB2_TREE_CONNECT = 0x0003,
53 SMB2_TREE_DISCONNECT = 0x0004,
54 SMB2_CREATE = 0x0005,
55 SMB2_CLOSE = 0x0006,
56 SMB2_READ = 0x0008,
57 SMB2_WRITE = 0x0009,
58};
59
60/** @brief SMB2 dialect revision numbers (MS-SMB2 §2.2.4). */
61enum class Smb2Dialect : uint16_t
62{
63 SMB2_DIALECT_0202 = 0x0202, ///< SMB 2.0.2
64 SMB2_DIALECT_0210 = 0x0210, ///< SMB 2.1
65 SMB2_DIALECT_0300 = 0x0300, ///< SMB 3.0
66 SMB2_DIALECT_0302 = 0x0302, ///< SMB 3.0.2
67 SMB2_DIALECT_0311 = 0x0311, ///< SMB 3.1.1
68};
69
70// These are SMB2 wire constants: flag words that get OR'd/AND'd and field values compared against the
71// uint8/16/32 wire fields of a parsed header. They live in namespacing structs of static constexpr
72// members - not enum class, which would force a cast at every bitwise op and every wire compare.
73
74/** @brief Fixed SMB2 sync header size (MS-SMB2 §2.2.1). */
75#define PC_SMB2_HEADER_SIZE 64
76
77/** @brief NEGOTIATE / SESSION_SETUP SecurityMode flags (MS-SMB2 §2.2.3). */
78struct Smb2SecurityMode
79{
80 static constexpr uint16_t SMB2_NEGOTIATE_SIGNING_ENABLED = 0x0001;
81 static constexpr uint16_t SMB2_NEGOTIATE_SIGNING_REQUIRED = 0x0002;
82};
83
84/** @brief SMB2 header Flags field (MS-SMB2 §2.2.1.2). */
85struct Smb2HeaderFlags
86{
87 static constexpr uint32_t SMB2_FLAGS_SERVER_TO_REDIR = 0x00000001; ///< set on a response (server -> client)
88 static constexpr uint32_t SMB2_FLAGS_SIGNED = 0x00000008; ///< the message carries an HMAC signature
89};
90
91/** @brief SESSION_SETUP response SessionFlags (MS-SMB2 §2.2.6). */
92struct Smb2SessionFlags
93{
94 static constexpr uint16_t SMB2_SESSION_FLAG_IS_GUEST = 0x0001;
95 static constexpr uint16_t SMB2_SESSION_FLAG_IS_NULL = 0x0002;
96 static constexpr uint16_t SMB2_SESSION_FLAG_ENCRYPT_DATA = 0x0004;
97};
98
99/** @brief NT status values seen in the SMB2 header during the SESSION_SETUP exchange. */
100struct Smb2Status
101{
102 static constexpr uint32_t SMB2_STATUS_SUCCESS = 0x00000000;
103 static constexpr uint32_t SMB2_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016; ///< server wants the next round
104 static constexpr uint32_t SMB2_STATUS_END_OF_FILE = 0xC0000011; ///< a READ at/past end of file
105};
106
107/** @brief TREE_CONNECT response ShareType (MS-SMB2 §2.2.10). */
108struct Smb2ShareType
109{
110 static constexpr uint8_t SMB2_SHARE_TYPE_DISK = 0x01;
111 static constexpr uint8_t SMB2_SHARE_TYPE_PIPE = 0x02;
112 static constexpr uint8_t SMB2_SHARE_TYPE_PRINT = 0x03;
113};
114
115/** @brief CREATE DesiredAccess masks (MS-DTYP ACCESS_MASK; the common file rights). */
116struct Smb2Access
117{
118 static constexpr uint32_t SMB2_FILE_READ_DATA = 0x00000001;
119 static constexpr uint32_t SMB2_FILE_WRITE_DATA = 0x00000002;
120 static constexpr uint32_t SMB2_FILE_APPEND_DATA = 0x00000004;
121 static constexpr uint32_t SMB2_FILE_READ_ATTRIBUTES = 0x00000080;
122 static constexpr uint32_t SMB2_FILE_GENERIC_READ = 0x00120089; ///< RC|SYNC|READ_ATTR|READ_EA|READ_DATA
123 static constexpr uint32_t SMB2_FILE_GENERIC_WRITE = 0x00120116; ///< RC|SYNC|WRITE_ATTR|WRITE_EA|APPEND|WRITE
124};
125
126/** @brief CREATE ShareAccess (MS-SMB2 §2.2.13). */
127struct Smb2ShareAccess
128{
129 static constexpr uint32_t SMB2_FILE_SHARE_READ = 0x01;
130 static constexpr uint32_t SMB2_FILE_SHARE_WRITE = 0x02;
131 static constexpr uint32_t SMB2_FILE_SHARE_DELETE = 0x04;
132};
133
134/** @brief CREATE CreateDisposition (MS-SMB2 §2.2.13). */
135struct Smb2Disposition
136{
137 static constexpr uint32_t SMB2_FILE_SUPERSEDE = 0;
138 static constexpr uint32_t SMB2_FILE_OPEN = 1; ///< open an existing file, fail if absent
139 static constexpr uint32_t SMB2_FILE_CREATE = 2; ///< create, fail if it exists
140 static constexpr uint32_t SMB2_FILE_OPEN_IF = 3; ///< open, create if absent
141 static constexpr uint32_t SMB2_FILE_OVERWRITE = 4; ///< open + truncate, fail if absent
142 static constexpr uint32_t SMB2_FILE_OVERWRITE_IF = 5;
143};
144
145/** @brief CREATE CreateOptions (MS-SMB2 §2.2.13; the two we set). */
146struct Smb2CreateOptions
147{
148 static constexpr uint32_t SMB2_FILE_DIRECTORY_FILE = 0x00000001;
149 static constexpr uint32_t SMB2_FILE_NON_DIRECTORY_FILE = 0x00000040;
150};
151
152/** @brief Parsed SMB2 sync header. */
153struct Smb2Header
154{
155 Smb2Command command;
156 uint32_t status; ///< NT status (response); 0 = STATUS_SUCCESS
157 uint32_t flags;
158 uint64_t message_id;
159 uint32_t tree_id;
160 uint64_t session_id;
161 uint16_t credit_response;
162};
163
164/** @brief SMB 3.1.1 negotiate-context types (MS-SMB2 §2.2.3.1). */
165struct Smb2NegotiateContextType
166{
167 static constexpr uint16_t SMB2_PREAUTH_INTEGRITY_CAPABILITIES = 0x0001;
168 static constexpr uint16_t SMB2_ENCRYPTION_CAPABILITIES = 0x0002;
169 static constexpr uint16_t SMB2_COMPRESSION_CAPABILITIES = 0x0003;
170 static constexpr uint16_t SMB2_NETNAME_NEGOTIATE_CONTEXT_ID = 0x0005;
171 static constexpr uint16_t SMB2_TRANSPORT_CAPABILITIES = 0x0006;
172 static constexpr uint16_t SMB2_RDMA_TRANSFORM_CAPABILITIES = 0x0007;
173 static constexpr uint16_t SMB2_SIGNING_CAPABILITIES = 0x0008;
174};
175
176/** @brief Preauth-integrity hash algorithm IDs (MS-SMB2 §2.2.3.1.1). */
177struct Smb2HashAlgorithm
178{
179 static constexpr uint16_t SMB2_PREAUTH_INTEGRITY_SHA512 = 0x0001;
180};
181
182/** @brief Signing algorithm IDs (MS-SMB2 §2.2.3.1.7). */
183struct Smb2SigningAlgorithm
184{
185 static constexpr uint16_t SMB2_SIGNING_HMAC_SHA256 = 0x0000;
186 static constexpr uint16_t SMB2_SIGNING_AES_CMAC = 0x0001;
187 static constexpr uint16_t SMB2_SIGNING_AES_GMAC = 0x0002;
188};
189
190/** @brief NEGOTIATE request/response Capabilities flags (MS-SMB2 §2.2.3 / §2.2.4). A client that supports
191 * transport encryption MUST advertise SMB2_GLOBAL_CAP_ENCRYPTION here, or a server (e.g. Samba with
192 * `smb encrypt = required`) will not negotiate a cipher and will reject the unencrypted session (§3.2.4.2.2). */
193struct Smb2GlobalCap
194{
195 static constexpr uint32_t SMB2_GLOBAL_CAP_ENCRYPTION = 0x00000040;
196};
197
198/** @brief Encryption cipher IDs (MS-SMB2 §2.2.3.1.2). */
199struct Smb2Cipher
200{
201 static constexpr uint16_t SMB2_ENCRYPTION_AES128_CCM = 0x0001;
202 static constexpr uint16_t SMB2_ENCRYPTION_AES128_GCM = 0x0002;
203 static constexpr uint16_t SMB2_ENCRYPTION_AES256_CCM = 0x0003;
204 static constexpr uint16_t SMB2_ENCRYPTION_AES256_GCM = 0x0004;
205};
206
207/** @brief AES key length in bytes for an SMB2 cipher id: 16 for the -128 ciphers, 32 for the -256 ciphers,
208 * 0 if @p cipher is not a recognized cipher id. */
209inline size_t pc_smb2_cipher_key_len(uint16_t cipher)
210{
211 switch (cipher)
212 {
213 case Smb2Cipher::SMB2_ENCRYPTION_AES128_CCM:
214 case Smb2Cipher::SMB2_ENCRYPTION_AES128_GCM:
215 return 16;
216 case Smb2Cipher::SMB2_ENCRYPTION_AES256_CCM:
217 case Smb2Cipher::SMB2_ENCRYPTION_AES256_GCM:
218 return 32;
219 default:
220 return 0;
221 }
222}
223
224/** @brief AEAD nonce length in bytes for an SMB2 cipher id: 12 for the GCM ciphers, 11 for the CCM ciphers
225 * (MS-SMB2 §3.1.4.3), 0 if unrecognized. Both are written into the 16-byte TRANSFORM_HEADER Nonce
226 * field with the remaining bytes zero. */
227inline size_t pc_smb2_cipher_nonce_len(uint16_t cipher)
228{
229 switch (cipher)
230 {
231 case Smb2Cipher::SMB2_ENCRYPTION_AES128_GCM:
232 case Smb2Cipher::SMB2_ENCRYPTION_AES256_GCM:
233 return 12;
234 case Smb2Cipher::SMB2_ENCRYPTION_AES128_CCM:
235 case Smb2Cipher::SMB2_ENCRYPTION_AES256_CCM:
236 return 11;
237 default:
238 return 0;
239 }
240}
241
242/** @brief Parsed SMB 3.1.1 NEGOTIATE-response negotiate contexts (MS-SMB2 §2.2.4 / §2.2.3.1). */
243struct Smb2NegotiateContexts
244{
245 bool have_preauth; ///< a PREAUTH_INTEGRITY_CAPABILITIES context was present
246 uint16_t hash_algorithm; ///< the server's chosen preauth hash (expect SMB2_PREAUTH_INTEGRITY_SHA512)
247 const uint8_t *salt; ///< the preauth-integrity salt (points into msg), or nullptr
248 uint16_t salt_len; ///< length of @ref salt
249 bool have_signing; ///< a SIGNING_CAPABILITIES context was present
250 uint16_t signing_algorithm; ///< the server's chosen signing algorithm
251 bool have_encryption; ///< an ENCRYPTION_CAPABILITIES context was present
252 uint16_t cipher; ///< the server's chosen cipher
253};
254
255/** @brief Parsed NEGOTIATE response (MS-SMB2 §2.2.4). */
256struct Smb2NegotiateResp
257{
258 uint16_t security_mode;
259 uint16_t dialect; ///< the DialectRevision the server chose
260 uint8_t server_guid[16];
261 uint32_t capabilities;
262 uint32_t max_transact;
263 uint32_t max_read;
264 uint32_t max_write;
265 const uint8_t *sec_buf; ///< SPNEGO/NTLM security token (points into @p msg), or nullptr
266 uint16_t sec_buf_len;
267};
268
269/**
270 * @brief Prefix an SMB2 message with the 4-byte Direct-TCP transport header (`0x00` + a 24-bit
271 * big-endian length) into @p out.
272 * @return total bytes written (4 + @p msg_len), or 0 on overflow / a length that does not fit 24 bits.
273 */
274size_t pc_smb2_transport_frame(uint8_t *out, size_t cap, const uint8_t *msg, size_t msg_len);
275
276/**
277 * @brief Read the Direct-TCP transport length prefix.
278 * @return the SMB2 message length that follows the 4-byte prefix, or 0 if @p len < 4 or the first
279 * byte is non-zero (an invalid Direct-TCP frame).
280 */
281uint32_t pc_smb2_transport_len(const uint8_t *buf, size_t len);
282
283/**
284 * @brief Build a 64-byte SMB2 sync header into @p buf.
285 * @return PC_SMB2_HEADER_SIZE, or 0 if @p cap < 64.
286 */
287size_t pc_smb2_build_header(uint8_t *buf, size_t cap, Smb2Command command, uint16_t credit_request, uint64_t message_id,
288 uint32_t tree_id, uint64_t session_id);
289
290/**
291 * @brief Parse a 64-byte SMB2 sync header (validates ProtocolId + StructureSize).
292 * @return true on a valid header; false if @p len < 64, ProtocolId != `FE 53 4D 42`, or
293 * StructureSize != 64.
294 */
295bool pc_smb2_parse_header(const uint8_t *buf, size_t len, Smb2Header *out);
296
297/**
298 * @brief Build a NEGOTIATE request (header + body) offering SMB 2.0.2 / 2.1 / 3.0 / 3.0.2.
299 * @param client_guid the 16-byte client GUID.
300 * @param security_mode SMB2_NEGOTIATE_SIGNING_ENABLED and/or _REQUIRED.
301 * @return total message bytes (no transport prefix), or 0 on overflow.
302 */
303size_t pc_smb2_build_negotiate(uint8_t *buf, size_t cap, const uint8_t client_guid[16], uint16_t security_mode);
304
305/**
306 * @brief Parse a NEGOTIATE response message (the SMB2 header + §2.2.4 body).
307 *
308 * @param msg the SMB2 message (starting at the sync header, transport prefix already stripped).
309 * @return true on a well-formed response (header valid, command == NEGOTIATE, StructureSize == 65,
310 * and the security buffer within bounds); false otherwise. On success @p out->sec_buf points
311 * into @p msg (or is nullptr when SecurityBufferLength is 0).
312 */
313bool pc_smb2_parse_negotiate_response(const uint8_t *msg, size_t len, Smb2NegotiateResp *out);
314
315/** @brief Max encryption ciphers a NEGOTIATE request can advertise (the four SMB 3.1.1 ciphers). */
316#define PC_SMB2_MAX_OFFER_CIPHERS 4
317
318/**
319 * @brief Build an SMB 3.1.1 NEGOTIATE request: the dialect list SMB 2.0.2 .. 3.1.1 followed by the
320 * mandatory PREAUTH_INTEGRITY_CAPABILITIES negotiate context (SHA-512 + @p salt), a
321 * SIGNING_CAPABILITIES context advertising AES-CMAC, and an ENCRYPTION_CAPABILITIES context listing
322 * @p ciphers in preference order (MS-SMB2 §2.2.3 / §2.2.3.1.1 / §2.2.3.1.2 / §2.2.3.1.7). The
323 * Capabilities field advertises SMB2_GLOBAL_CAP_ENCRYPTION so a server that requires encryption will
324 * negotiate a cipher (§3.2.4.2.2.2).
325 *
326 * Offering 0x0311 obliges the client to send the preauth-integrity context, so this is a distinct
327 * builder from ::pc_smb2_build_negotiate (which stops at 3.0.2). The NegotiateContextOffset /
328 * NegotiateContextCount fields overlay the pre-3.1.1 ClientStartTime, and each context is 8-byte
329 * aligned per §2.2.3.1.
330 *
331 * @param salt the preauth-integrity salt (a fresh random blob the client keeps for the hash chain).
332 * @param salt_len salt length in bytes (>= 1); a common choice is 32.
333 * @param ciphers cipher ids to offer, most-preferred first (a server picks the first it supports, in
334 * this order). May be null when @p cipher_count is 0 (offer no encryption).
335 * @param cipher_count number of entries in @p ciphers (0 .. PC_SMB2_MAX_OFFER_CIPHERS).
336 * @return total message bytes (no transport prefix), or 0 on overflow / bad args.
337 */
338size_t pc_smb2_build_negotiate_311(uint8_t *buf, size_t cap, const uint8_t client_guid[16], uint16_t security_mode,
339 const uint8_t *salt, size_t salt_len, const uint16_t *ciphers, size_t cipher_count);
340
341/**
342 * @brief Walk the negotiate-context list of a 3.1.1 NEGOTIATE response (located by NegotiateContextOffset
343 * / NegotiateContextCount in the §2.2.4 body) and extract the preauth hash + salt, the signing
344 * algorithm, and the cipher. Every context header and its data are bounds-checked against @p len.
345 * @return true if the list parsed cleanly (all contexts within bounds); false on a malformed / truncated
346 * list. Absent context types leave their `have_*` flag false.
347 */
348bool pc_smb2_parse_negotiate_contexts(const uint8_t *msg, size_t len, Smb2NegotiateContexts *out);
349
350/** @brief Length of the SMB 3.1.1 preauth-integrity hash (SHA-512 digest size). */
351#define PC_SMB2_PREAUTH_HASH_LEN 64
352
353/**
354 * @brief The SMB 3.1.1 preauth-integrity hash value (MS-SMB2 §3.1.5.2): a running SHA-512 chained over
355 * every NEGOTIATE and SESSION_SETUP message of the handshake. Its final value binds the whole
356 * pre-authentication exchange and feeds the 3.1.1 signing / encryption key derivation.
357 */
358struct SmbPreauth
359{
360 uint8_t hash[PC_SMB2_PREAUTH_HASH_LEN];
361};
362
363/** @brief Seed the preauth-integrity hash with 64 zero bytes (the initial value, MS-SMB2 §3.1.5.2). */
364void pc_smb_preauth_init(SmbPreauth *p);
365
366/**
367 * @brief Fold one handshake message into the preauth-integrity hash: hash = SHA-512(hash || msg).
368 * Call once per NEGOTIATE / SESSION_SETUP message (request and response), in wire order, passing
369 * the SMB2 message (header + body) without the Direct-TCP transport prefix.
370 */
371void pc_smb_preauth_update(SmbPreauth *p, const uint8_t *msg, size_t len);
372
373/** @brief Parsed SESSION_SETUP response (MS-SMB2 §2.2.6). */
374struct Smb2SessionSetupResp
375{
376 uint16_t session_flags;
377 const uint8_t *sec_buf; ///< the server's SPNEGO/NTLM token (points into @p msg), or nullptr
378 uint16_t sec_buf_len;
379};
380
381/**
382 * @brief Build a SESSION_SETUP request (header + §2.2.5 body) carrying a security token.
383 *
384 * The token is the SPNEGO/NTLMSSP blob for this round of the handshake: the InitialContextToken on
385 * the first request, and (echoing the SessionId the server returned) the AUTHENTICATE NegTokenResp
386 * on the second. Capabilities / Channel / PreviousSessionId are 0 (a plain new session).
387 *
388 * @param message_id the SMB2 MessageId (increments across the exchange).
389 * @param session_id 0 on the first round; the server-assigned SessionId on the second.
390 * @param security_mode SMB2_NEGOTIATE_SIGNING_ENABLED and/or _REQUIRED (one byte on the wire).
391 * @return total message bytes (no transport prefix), or 0 on overflow / empty token.
392 */
393size_t pc_smb2_build_session_setup(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id,
394 uint8_t security_mode, const uint8_t *sec_buf, size_t sec_len);
395
396/**
397 * @brief Parse a SESSION_SETUP response message (the SMB2 header + §2.2.6 body).
398 *
399 * The caller reads the SessionId (to echo on the next round) and the NT status
400 * (SMB2_STATUS_MORE_PROCESSING_REQUIRED vs SMB2_STATUS_SUCCESS) from pc_smb2_parse_header on the same
401 * @p msg; this extracts the SessionFlags and the server security buffer.
402 *
403 * @param msg the SMB2 message (starting at the sync header, transport prefix already stripped).
404 * @return true on a well-formed response (header valid, command == SESSION_SETUP, StructureSize == 9,
405 * security buffer within bounds); false otherwise. On success @p out->sec_buf points into
406 * @p msg (or is nullptr when SecurityBufferLength is 0).
407 */
408bool pc_smb2_parse_session_setup_response(const uint8_t *msg, size_t len, Smb2SessionSetupResp *out);
409
410/**
411 * @brief Build a TREE_CONNECT request (header + §2.2.9 body) for a share path.
412 * @param path_utf16 the UNC path `\\server\share` in UTF-16LE (no NUL); @p path_len its byte length.
413 * @return total message bytes (no transport prefix), or 0 on overflow / empty path.
414 */
415size_t pc_smb2_build_tree_connect(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id,
416 const uint8_t *path_utf16, size_t path_len);
417
418/** @brief TREE_CONNECT response ShareFlags of interest (MS-SMB2 §2.2.10). */
419struct Smb2ShareFlags
420{
421 static constexpr uint32_t SMB2_SHAREFLAG_ENCRYPT_DATA = 0x00008000; ///< the share mandates SMB3 encryption
422};
423
424/** @brief Parsed TREE_CONNECT response (MS-SMB2 §2.2.10). The TreeId is in the response header. */
425struct Smb2TreeConnectResp
426{
427 uint8_t share_type;
428 uint32_t share_flags;
429 uint32_t capabilities;
430 uint32_t maximal_access;
431};
432
433/**
434 * @brief Parse a TREE_CONNECT response message (validates command + StructureSize 16).
435 * @return true on a well-formed response; the caller reads the TreeId from pc_smb2_parse_header.
436 */
437bool pc_smb2_parse_tree_connect_response(const uint8_t *msg, size_t len, Smb2TreeConnectResp *out);
438
439/**
440 * @brief Build a CREATE request (header + §2.2.13 body) to open/create a file on the tree.
441 * @param name_utf16 the file name relative to the share root in UTF-16LE (no leading backslash, no
442 * NUL); @p name_len its byte length (must be > 0).
443 * @param desired_access e.g. SMB2_FILE_GENERIC_READ / _WRITE.
444 * @param share_access SMB2_FILE_SHARE_* bitmask.
445 * @param create_disposition SMB2_FILE_OPEN / _CREATE / _OPEN_IF / ...
446 * @param create_options SMB2_FILE_NON_DIRECTORY_FILE for a regular file.
447 * @return total message bytes (no transport prefix), or 0 on overflow.
448 */
449size_t pc_smb2_build_create(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
450 uint32_t desired_access, uint32_t share_access, uint32_t create_disposition,
451 uint32_t create_options, const uint8_t *name_utf16, size_t name_len);
452
453/** @brief Parsed CREATE response (MS-SMB2 §2.2.14). */
454struct Smb2CreateResp
455{
456 uint8_t file_id[16]; ///< the open handle (persistent 8 + volatile 8), for READ/WRITE/CLOSE
457 uint64_t end_of_file;
458 uint32_t create_action;
459 uint32_t file_attributes;
460};
461
462/**
463 * @brief Parse a CREATE response message (validates command + StructureSize 89, FileId in bounds).
464 * @return true on a well-formed response.
465 */
466bool pc_smb2_parse_create_response(const uint8_t *msg, size_t len, Smb2CreateResp *out);
467
468/**
469 * @brief Build a CLOSE request (header + §2.2.15 body) for an open FileId.
470 * @return total message bytes (no transport prefix), or 0 on overflow.
471 */
472size_t pc_smb2_build_close(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
473 const uint8_t file_id[16]);
474
475/** @brief Parsed CLOSE response (MS-SMB2 §2.2.16). */
476struct Smb2CloseResp
477{
478 uint64_t end_of_file;
479 uint32_t file_attributes;
480};
481
482/**
483 * @brief Parse a CLOSE response message (validates command + StructureSize 60).
484 * @return true on a well-formed response.
485 */
486bool pc_smb2_parse_close_response(const uint8_t *msg, size_t len, Smb2CloseResp *out);
487
488/**
489 * @brief Build a READ request (header + §2.2.19 body) for @p length bytes at @p offset of an open file.
490 * @return total message bytes (no transport prefix), or 0 on overflow.
491 */
492size_t pc_smb2_build_read(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
493 const uint8_t file_id[16], uint32_t length, uint64_t offset);
494
495/** @brief Parsed READ response (MS-SMB2 §2.2.20). */
496struct Smb2ReadResp
497{
498 const uint8_t *data; ///< the file bytes read (points into @p msg), or nullptr when DataLength is 0
499 uint32_t data_len;
500};
501
502/**
503 * @brief Parse a READ response message (validates command + StructureSize 17, data within bounds).
504 * @return true on a well-formed response.
505 */
506bool pc_smb2_parse_read_response(const uint8_t *msg, size_t len, Smb2ReadResp *out);
507
508/**
509 * @brief Build a WRITE request (header + §2.2.21 body) writing @p data at @p offset of an open file.
510 * @return total message bytes (no transport prefix), or 0 on overflow / empty data.
511 */
512size_t pc_smb2_build_write(uint8_t *buf, size_t cap, uint64_t message_id, uint64_t session_id, uint32_t tree_id,
513 const uint8_t file_id[16], const uint8_t *data, size_t data_len, uint64_t offset);
514
515/** @brief Parsed WRITE response (MS-SMB2 §2.2.22). */
516struct Smb2WriteResp
517{
518 uint32_t count; ///< bytes actually written
519};
520
521/**
522 * @brief Parse a WRITE response message (validates command + StructureSize 17).
523 * @return true on a well-formed response.
524 */
525bool pc_smb2_parse_write_response(const uint8_t *msg, size_t len, Smb2WriteResp *out);
526
527// ---------------------------------------------------------------------------
528// Message signing (MS-SMB2 §3.1.4.1 / §3.1.5.1) - SMB 2.x: HMAC-SHA256; SMB 3.x: AES-128-CMAC
529// ---------------------------------------------------------------------------
530
531/** @brief The per-session message-signing algorithm the client selects from the negotiated dialect. */
532enum class Smb2SignAlgo : uint8_t
533{
534 HMAC_SHA256 = 0, ///< SMB 2.0.2 / 2.1 (key = the NTLMv2 session key)
535 AES_CMAC = 1, ///< SMB 3.0 / 3.0.2 / 3.1.1 (key = the SP800-108-derived signing key)
536};
537
538/**
539 * @brief Sign an SMB2 message in place (MS-SMB2 §3.1.4.1, SMB 2.x). Sets SMB2_FLAGS_SIGNED in the Flags
540 * field, zeroes the 16-byte Signature, HMAC-SHA256s the whole message under the 16-byte session
541 * signing @p key, and writes the MAC's first 16 octets into the Signature field.
542 * @param key the session signing key (16 octets; the NTLMv2 session key for SMB 2.x).
543 * @param msg the full message (header + body), modified in place; must be at least a 64-byte header.
544 * @param msg_len total message length. A message shorter than the header is left untouched.
545 */
546void pc_smb2_sign(const uint8_t key[16], uint8_t *msg, size_t msg_len);
547
548/**
549 * @brief Verify an SMB2 message's signature (MS-SMB2 §3.1.5.1). Recomputes the HMAC-SHA256 over @p msg
550 * with the Signature field zeroed and constant-time-compares the first 16 octets to the received
551 * Signature. @p msg is restored unchanged before returning.
552 * @return true iff the signature matches; false on a mismatch or a message shorter than the header.
553 */
554bool pc_smb2_verify(const uint8_t key[16], uint8_t *msg, size_t msg_len);
555
556// ---------------------------------------------------------------------------
557// SMB 3.x signing (MS-SMB2 §3.1.4.1 / §3.1.5.1) - AES-128-CMAC (dialects 3.0 / 3.0.2 / 3.1.1)
558// ---------------------------------------------------------------------------
559
560/**
561 * @brief Sign an SMB2 message in place with AES-128-CMAC (MS-SMB2 §3.1.4.1, SMB 3.x). Identical framing
562 * to ::pc_smb2_sign - sets SMB2_FLAGS_SIGNED, zeroes the Signature, MACs the whole message under
563 * the 16-byte SMB 3.x signing @p key - but the MAC is AES-CMAC, whose full 16-octet tag is the
564 * Signature. @p msg shorter than a 64-byte header is left untouched.
565 */
566void pc_smb2_sign_cmac(const uint8_t key[16], uint8_t *msg, size_t msg_len);
567
568/**
569 * @brief Verify an AES-128-CMAC-signed SMB2 message (MS-SMB2 §3.1.5.1, SMB 3.x). Recomputes the CMAC
570 * with the Signature zeroed and constant-time-compares to the received Signature; @p msg is
571 * restored unchanged.
572 * @return true iff the signature matches; false on a mismatch or a message shorter than the header.
573 */
574bool pc_smb2_verify_cmac(const uint8_t key[16], uint8_t *msg, size_t msg_len);
575
576/**
577 * @brief Derive the 16-byte SMB 3.x signing key from the NTLM session key via the SP800-108 counter-mode
578 * KDF (MS-SMB2 §3.1.4.2), dialect-dependent:
579 * - 3.1.1: SigningKey = KDF(SessionKey, "SMBSigningKey\\0", PreauthIntegrityHashValue)
580 * - 3.0 / 3.0.2: SigningKey = KDF(SessionKey, "SMB2AESCMAC\\0", "SmbSign\\0")
581 * The KDF's fixed input is `Label || 0x00 || Context || [L=128]_32be`; the label carries its own
582 * trailing NUL, so the on-wire fixed data has the label followed by two NULs (matches Windows /
583 * Samba / impacket).
584 *
585 * @param session_key the 16-byte NTLM ExportedSessionKey (SessionBaseKey for NTLMv2 with no key exch).
586 * @param dialect the negotiated DialectRevision (only 3.1.1 vs pre-3.1.1 matters here).
587 * @param preauth the 64-byte final preauth-integrity hash; required iff @p dialect == 3.1.1, else ignored.
588 * @param out_key receives the 16-byte signing key.
589 * @return true on success; false on a null pointer, or a 3.1.1 request with a null @p preauth.
590 */
591bool pc_smb3_derive_signing_key(const uint8_t session_key[16], uint16_t dialect, const uint8_t *preauth,
592 uint8_t out_key[16]);
593
594// ---------------------------------------------------------------------------
595// SMB 3.x transport encryption - AEAD-wrapped messages (MS-SMB2 §2.2.41 TRANSFORM_HEADER, §3.1.4.3/§3.1.4.4).
596// All four SMB 3.1.1 ciphers are supported: AES-128-GCM / AES-256-GCM (crypto/aes128gcm, crypto/aesgcm) and
597// AES-128-CCM / AES-256-CCM (crypto/aesccm). The negotiated cipher (Connection.CipherId) selects the key
598// length (16 / 32) and AEAD nonce length (12 GCM / 11 CCM); the codec dispatches on the cipher id.
599// ---------------------------------------------------------------------------
600
601/** @brief TRANSFORM_HEADER size: ProtocolId(4)+Signature(16)+Nonce(16)+OriginalMessageSize(4)+Reserved(2)+
602 * Flags(2)+SessionId(8) = 52 bytes (MS-SMB2 §2.2.41). */
603#define PC_SMB2_TRANSFORM_HDR_LEN 52
604/** @brief TRANSFORM_HEADER ProtocolId 0xFD 'S' 'M' 'B' as a little-endian u32. */
605#define PC_SMB2_TRANSFORM_PROTOCOL_ID 0x424D53FDu
606/** @brief The TRANSFORM_HEADER Nonce field width (MS-SMB2 §2.2.41). The AEAD uses the leading
607 * pc_smb2_cipher_nonce_len() bytes; the rest are zero. */
608#define PC_SMB2_NONCE_FIELD_LEN 16
609/** @brief AES-GCM nonce length used within the 16-byte Nonce field. */
610#define PC_SMB2_GCM_NONCE_LEN 12
611/** @brief AES-CCM nonce length used within the 16-byte Nonce field (MS-SMB2 §3.1.4.3). */
612#define PC_SMB2_CCM_NONCE_LEN 11
613/** @brief Largest cipher key length across the four SMB 3.1.1 ciphers (AES-256), for buffer sizing. */
614#define PC_SMB2_MAX_CIPHER_KEY_LEN 32
615
616/**
617 * @brief Derive the two SMB 3.x cipher keys from the NTLM session key (MS-SMB2 §3.1.4.2), via the same
618 * SP800-108 KDF as pc_smb3_derive_signing_key, dialect-dependent:
619 * - 3.1.1: C2S = KDF(SessionKey, "SMBC2SCipherKey\\0", PreauthHash);
620 * S2C = KDF(SessionKey, "SMBS2CCipherKey\\0", PreauthHash)
621 * - 3.0 / 3.0.2: C2S = KDF(SessionKey, "SMB2AESCCM\\0", "ServerIn \\0"); (label is AESCCM even for GCM)
622 * S2C = KDF(SessionKey, "SMB2AESCCM\\0", "ServerOut\\0")
623 * @p key_len (16 for the -128 ciphers, 32 for the -256 ciphers, per pc_smb2_cipher_key_len) sets the
624 * derived key length; it is encoded as [L] in the KDF's fixed input (128 or 256 bits).
625 * @param out_c2s client->server key (ENCRYPTS our requests); @param out_s2c server->client key (DECRYPTS
626 * responses). Each receives @p key_len bytes.
627 * @return true on success; false on a null pointer, a bad @p key_len, or a 3.1.1 request with a null @p preauth.
628 */
629bool pc_smb3_derive_encryption_keys(const uint8_t session_key[16], uint16_t dialect, const uint8_t *preauth,
630 size_t key_len, uint8_t *out_c2s, uint8_t *out_s2c);
631
632/**
633 * @brief Encrypt one SMB2 message into a TRANSFORM_HEADER-wrapped blob (MS-SMB2 §3.1.4.3): a 52-byte header
634 * (ProtocolId, the AEAD tag in Signature, @p nonce, OriginalMessageSize, Flags=Encrypted, @p
635 * session_id) followed by the ciphertext. AAD is the header from the Nonce field to its end. The
636 * codec dispatches on @p cipher (AES-128/256-GCM or AES-128/256-CCM).
637 * @param cipher one of Smb2Cipher; selects the key length and AEAD nonce length.
638 * @param key cipher key (pc_smb2_cipher_key_len(@p cipher) bytes, i.e. the C2S key).
639 * @param nonce the 16-byte Nonce field; the leading nonce-length bytes must be UNIQUE per key (caller
640 * advances a counter), the rest zero.
641 * @param session_id echoed into the header; @param out needs >= PC_SMB2_TRANSFORM_HDR_LEN + @p msg_len.
642 * @return total encrypted length (52 + msg_len), or 0 on a bad cipher / null pointer / insufficient @p out_cap.
643 */
644size_t pc_smb2_encrypt(uint16_t cipher, const uint8_t *key, const uint8_t nonce[PC_SMB2_NONCE_FIELD_LEN],
645 uint64_t session_id, const uint8_t *msg, size_t msg_len, uint8_t *out, size_t out_cap);
646
647/**
648 * @brief Decrypt a TRANSFORM_HEADER-wrapped SMB2 message (MS-SMB2 §3.1.4.4): validates the ProtocolId and
649 * verifies the AEAD tag (constant time) before exposing any plaintext, dispatching on @p cipher.
650 * @param cipher one of Smb2Cipher; @param key the S2C cipher key; @param out needs >= OriginalMessageSize.
651 * @return the plaintext length, or 0 on a short/invalid header, tag mismatch, or insufficient @p out_cap.
652 */
653size_t pc_smb2_decrypt(uint16_t cipher, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out,
654 size_t out_cap);
655
656#endif // PC_ENABLE_SMB
657
658#endif // PROTOCORE_SMB2_H
User-facing configuration for ProtoCore.