Skip to main content

smb2_client/
header.rs

1//! SMB2 sync header (MS-SMB2 §2.2.1.2) — fixed 64 bytes, little-endian.
2
3use crate::{Result, SmbError};
4
5pub mod cmd {
6    pub const NEGOTIATE: u16 = 0x0000;
7    pub const SESSION_SETUP: u16 = 0x0001;
8    pub const TREE_CONNECT: u16 = 0x0003;
9    pub const CREATE: u16 = 0x0005;
10    pub const CLOSE: u16 = 0x0006;
11    pub const READ: u16 = 0x0008;
12    pub const WRITE: u16 = 0x0009;
13    pub const IOCTL: u16 = 0x000B;
14    pub const QUERY_DIRECTORY: u16 = 0x000E;
15}
16
17pub const FLAGS_SIGNED: u32 = 0x0000_0008;
18const PROTOCOL_ID: [u8; 4] = [0xFE, b'S', b'M', b'B'];
19
20/// Build a 64-byte sync header with the signature field zeroed.
21#[allow(clippy::too_many_arguments)]
22pub fn build(
23    command: u16,
24    message_id: u64,
25    session_id: u64,
26    tree_id: u32,
27    signed: bool,
28) -> Vec<u8> {
29    let mut h = vec![0u8; 64];
30    h[0..4].copy_from_slice(&PROTOCOL_ID);
31    h[4..6].copy_from_slice(&64u16.to_le_bytes()); // StructureSize
32    h[6..8].copy_from_slice(&1u16.to_le_bytes()); // CreditCharge
33                                                  // 8..12 Status/ChannelSequence = 0
34    h[12..14].copy_from_slice(&command.to_le_bytes());
35    h[14..16].copy_from_slice(&1u16.to_le_bytes()); // CreditRequest
36    let flags = if signed { FLAGS_SIGNED } else { 0 };
37    h[16..20].copy_from_slice(&flags.to_le_bytes());
38    // 20..24 NextCommand = 0
39    h[24..32].copy_from_slice(&message_id.to_le_bytes());
40    // 32..36 Reserved (ProcessId)
41    h[36..40].copy_from_slice(&tree_id.to_le_bytes());
42    h[40..48].copy_from_slice(&session_id.to_le_bytes());
43    // 48..64 Signature = 0
44    h
45}
46
47/// Parsed fields we consume from a response header.
48#[derive(Clone, Copy, Debug)]
49pub struct Parsed {
50    pub command: u16,
51    pub status: u32,
52    pub session_id: u64,
53    pub tree_id: u32,
54}
55
56pub fn parse(buf: &[u8]) -> Result<Parsed> {
57    if buf.len() < 64 {
58        return Err(SmbError::Truncated);
59    }
60    if buf[0..4] != PROTOCOL_ID {
61        return Err(SmbError::BadProtocol);
62    }
63    Ok(Parsed {
64        status: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
65        command: u16::from_le_bytes(buf[12..14].try_into().unwrap()),
66        tree_id: u32::from_le_bytes(buf[36..40].try_into().unwrap()),
67        session_id: u64::from_le_bytes(buf[40..48].try_into().unwrap()),
68    })
69}
70
71/// SMB 2.x signing: HMAC-SHA256(session_key, message-with-zeroed-sig-and-SIGNED-flag),
72/// truncated to 16 bytes, written back into the Signature field.
73pub fn sign(message: &mut [u8], key: &[u8; 16]) {
74    use hmac::{Hmac, Mac};
75    use sha2::Sha256;
76    for b in &mut message[48..64] {
77        *b = 0;
78    }
79    let mut mac = <Hmac<Sha256>>::new_from_slice(key).expect("hmac key");
80    mac.update(message);
81    let sig = mac.finalize().into_bytes();
82    message[48..64].copy_from_slice(&sig[..16]);
83}
84
85/// SMB 3.0/3.0.2 signing: AES-128-CMAC over the message with the zeroed Signature field and
86/// the SIGNED flag set, truncated to 16 bytes.
87pub fn sign_v3(message: &mut [u8], signing_key: &[u8; 16]) {
88    use aes::Aes128;
89    use cmac::{Cmac, Mac};
90    for b in &mut message[48..64] {
91        *b = 0;
92    }
93    let mut mac = <Cmac<Aes128>>::new_from_slice(signing_key).expect("cmac key");
94    mac.update(message);
95    let sig = mac.finalize().into_bytes();
96    message[48..64].copy_from_slice(&sig[..16]);
97}
98
99/// SMB 3.0.x signing-key derivation (MS-SMB2 §3.1.4.2): SP800-108 counter-mode KDF with
100/// HMAC-SHA256 over the session key, label "SMB2AESCMAC" and context "SmbSign".
101pub fn kdf_signing_key(session_key: &[u8; 16]) -> [u8; 16] {
102    use hmac::{Hmac, Mac};
103    use sha2::Sha256;
104    let mut input = Vec::new();
105    input.extend_from_slice(&1u32.to_be_bytes()); // counter i
106    input.extend_from_slice(b"SMB2AESCMAC\0"); // label
107    input.extend_from_slice(b"SmbSign\0"); // context
108    input.extend_from_slice(&128u32.to_be_bytes()); // L (bits)
109    let mut mac = <Hmac<Sha256>>::new_from_slice(session_key).expect("hmac key");
110    mac.update(&input);
111    let out = mac.finalize().into_bytes();
112    let mut k = [0u8; 16];
113    k.copy_from_slice(&out[..16]);
114    k
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn header_is_64_bytes_and_parses() {
123        let h = build(cmd::NEGOTIATE, 3, 0xAABB, 0x11, true);
124        assert_eq!(h.len(), 64);
125        let p = parse(&h).unwrap();
126        assert_eq!(p.command, cmd::NEGOTIATE);
127        assert_eq!(p.session_id, 0xAABB);
128        assert_eq!(p.tree_id, 0x11);
129        assert_eq!(
130            u32::from_le_bytes(h[16..20].try_into().unwrap()),
131            FLAGS_SIGNED
132        );
133    }
134}