smb_msg/
negotiate.rs

1use binrw::io::{SeekFrom, TakeSeekExt};
2use binrw::prelude::*;
3use modular_bitfield::prelude::*;
4
5use smb_dtyp::{binrw_util::prelude::*, guid::Guid};
6
7#[binrw::binrw]
8#[derive(Debug, PartialEq, Eq)]
9pub struct NegotiateRequest {
10    #[bw(calc = 0x24)]
11    #[br(assert(_structure_size == 0x24))]
12    _structure_size: u16,
13    #[bw(try_calc(u16::try_from(dialects.len())))]
14    dialect_count: u16,
15    pub security_mode: NegotiateSecurityMode,
16    #[bw(calc = 0)]
17    _reserved: u16,
18    pub capabilities: GlobalCapabilities,
19    pub client_guid: Guid,
20
21    #[bw(calc = PosMarker::default())]
22    negotiate_context_offset: PosMarker<u32>,
23    #[bw(try_calc(u16::try_from(negotiate_context_list.as_ref().map(|v| v.len()).unwrap_or(0))))]
24    negotiate_context_count: u16,
25    #[bw(calc = 0)]
26    reserved2: u16,
27    #[br(count = dialect_count)]
28    pub dialects: Vec<Dialect>,
29    // Only on SMB 3.1.1 supporting clients we have negotiation contexts.
30    // Align to 8 bytes.
31    #[brw(if(dialects.contains(&Dialect::Smb0311)), align_before = 8)]
32    #[br(count = negotiate_context_count, seek_before = SeekFrom::Start(negotiate_context_offset.value as u64))]
33    #[bw(write_with = PosMarker::write_aoff, args(&negotiate_context_offset))]
34    pub negotiate_context_list: Option<Vec<NegotiateContext>>,
35}
36
37#[bitfield]
38#[derive(BinRead, BinWrite, Debug, Default, Clone, Copy, PartialEq, Eq)]
39#[bw(map = |&x| Self::into_bytes(x))]
40#[br(map = Self::from_bytes)]
41pub struct NegotiateSecurityMode {
42    pub signing_enabled: bool,
43    pub signing_required: bool,
44    #[skip]
45    __: B14,
46}
47
48#[bitfield]
49#[derive(BinRead, BinWrite, Debug, Default, Clone, Copy, PartialEq, Eq)]
50#[bw(map = |&x| Self::into_bytes(x))]
51#[br(map = Self::from_bytes)]
52pub struct GlobalCapabilities {
53    pub dfs: bool,
54    pub leasing: bool,
55    pub large_mtu: bool,
56    pub multi_channel: bool,
57
58    pub persistent_handles: bool,
59    pub directory_leasing: bool,
60    pub encryption: bool,
61    pub notifications: bool,
62
63    #[skip]
64    __: B24,
65}
66
67#[binrw::binrw]
68#[derive(Debug, PartialEq, Eq)]
69pub struct NegotiateResponse {
70    #[br(assert(_structure_size == 0x41))]
71    #[bw(calc = 0x41)]
72    _structure_size: u16,
73    pub security_mode: NegotiateSecurityMode,
74    pub dialect_revision: NegotiateDialect,
75    #[bw(try_calc(u16::try_from(negotiate_context_list.as_ref().map(|v| v.len()).unwrap_or(0))))]
76    #[br(assert(if dialect_revision == NegotiateDialect::Smb0311 { negotiate_context_count > 0 } else { negotiate_context_count == 0 }))]
77    negotiate_context_count: u16,
78    pub server_guid: Guid,
79    pub capabilities: GlobalCapabilities,
80    pub max_transact_size: u32,
81    pub max_read_size: u32,
82    pub max_write_size: u32,
83    pub system_time: FileTime,
84    pub server_start_time: FileTime,
85    #[bw(calc = PosMarker::default())]
86    _security_buffer_offset: PosMarker<u16>,
87    #[bw(try_calc(u16::try_from(buffer.len())))]
88    security_buffer_length: u16,
89    #[bw(calc = PosMarker::default())]
90    negotiate_context_offset: PosMarker<u32>,
91    #[br(count = security_buffer_length)]
92    #[bw(write_with = PosMarker::write_aoff, args(&_security_buffer_offset))]
93    pub buffer: Vec<u8>,
94
95    #[brw(if(matches!(dialect_revision, NegotiateDialect::Smb0311)), align_before = 8)]
96    #[br(count = negotiate_context_count, seek_before = SeekFrom::Start(negotiate_context_offset.value as u64))]
97    #[bw(write_with = PosMarker::write_aoff, args(&negotiate_context_offset))]
98    pub negotiate_context_list: Option<Vec<NegotiateContext>>,
99}
100
101impl NegotiateResponse {
102    pub fn get_ctx_signing_algo(&self) -> Option<SigningAlgorithmId> {
103        self.negotiate_context_list.as_ref().and_then(|contexts| {
104            contexts
105                .iter()
106                .find_map(|context| match &context.context_type {
107                    NegotiateContextType::SigningCapabilities => match &context.data {
108                        NegotiateContextValue::SigningCapabilities(caps) => {
109                            caps.signing_algorithms.first().copied()
110                        }
111                        _ => None,
112                    },
113                    _ => None,
114                })
115        })
116    }
117
118    pub fn get_ctx_integrity_algo(&self) -> Option<HashAlgorithm> {
119        self.negotiate_context_list.as_ref().and_then(|contexts| {
120            contexts
121                .iter()
122                .find_map(|context| match &context.context_type {
123                    NegotiateContextType::PreauthIntegrityCapabilities => match &context.data {
124                        NegotiateContextValue::PreauthIntegrityCapabilities(caps) => {
125                            caps.hash_algorithms.first().copied()
126                        }
127                        _ => None,
128                    },
129                    _ => None,
130                })
131        })
132    }
133
134    pub fn get_ctx_compression(&self) -> Option<&CompressionCapabilities> {
135        self.negotiate_context_list.as_ref().and_then(|contexts| {
136            contexts
137                .iter()
138                .find_map(|context| match &context.context_type {
139                    NegotiateContextType::CompressionCapabilities => match &context.data {
140                        NegotiateContextValue::CompressionCapabilities(caps) => Some(caps),
141                        _ => None,
142                    },
143                    _ => None,
144                })
145        })
146    }
147
148    pub fn get_ctx_encrypt_cipher(&self) -> Option<EncryptionCipher> {
149        self.negotiate_context_list.as_ref().and_then(|contexts| {
150            contexts
151                .iter()
152                .find_map(|context| match &context.context_type {
153                    NegotiateContextType::EncryptionCapabilities => match &context.data {
154                        NegotiateContextValue::EncryptionCapabilities(caps) => {
155                            caps.ciphers.first().copied()
156                        }
157                        _ => None,
158                    },
159                    _ => None,
160                })
161        })
162    }
163}
164
165#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
166#[brw(repr(u16))]
167pub enum Dialect {
168    Smb0202 = 0x0202,
169    Smb021 = 0x0210,
170    Smb030 = 0x0300,
171    Smb0302 = 0x0302,
172    Smb0311 = 0x0311,
173}
174
175impl Dialect {
176    pub const MAX: Dialect = Dialect::Smb0311;
177    pub const MIN: Dialect = Dialect::Smb0202;
178    pub const ALL: [Dialect; 5] = [
179        Dialect::Smb0202,
180        Dialect::Smb021,
181        Dialect::Smb030,
182        Dialect::Smb0302,
183        Dialect::Smb0311,
184    ];
185
186    #[inline]
187    pub fn is_smb3(&self) -> bool {
188        matches!(self, Dialect::Smb030 | Dialect::Smb0302 | Dialect::Smb0311)
189    }
190}
191
192/// Dialects that may be used in the SMB Negotiate Request.
193/// The same as [Dialect] but with a wildcard for SMB 2.0.
194#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
195#[brw(repr(u16))]
196pub enum NegotiateDialect {
197    Smb0202 = Dialect::Smb0202 as isize,
198    Smb021 = Dialect::Smb021 as isize,
199    Smb030 = Dialect::Smb030 as isize,
200    Smb0302 = Dialect::Smb0302 as isize,
201    Smb0311 = Dialect::Smb0311 as isize,
202    Smb02Wildcard = 0x02FF,
203}
204
205impl TryFrom<NegotiateDialect> for Dialect {
206    type Error = crate::SmbMsgError;
207
208    fn try_from(value: NegotiateDialect) -> Result<Self, Self::Error> {
209        match value {
210            NegotiateDialect::Smb0202 => Ok(Dialect::Smb0202),
211            NegotiateDialect::Smb021 => Ok(Dialect::Smb021),
212            NegotiateDialect::Smb030 => Ok(Dialect::Smb030),
213            NegotiateDialect::Smb0302 => Ok(Dialect::Smb0302),
214            NegotiateDialect::Smb0311 => Ok(Dialect::Smb0311),
215            _ => Err(Self::Error::InvalidDialect(value)),
216        }
217    }
218}
219
220/// Represent a single negotiation context item.
221///
222/// Note: This struct should usually be NOT used directly.
223/// To construct it, use `impl From<ContextValueStruct> for NegotiateContext`:
224/// ```
225/// # use smb_msg::*;
226/// let signing_ctx: NegotiateContext = SigningCapabilities {
227///     signing_algorithms: vec![SigningAlgorithmId::AesGmac]
228/// }.into();
229/// ```
230#[binrw::binrw]
231#[derive(Debug, PartialEq, Eq)]
232pub struct NegotiateContext {
233    // The entire context is 8-byte aligned.
234    #[brw(align_before = 8)]
235    pub context_type: NegotiateContextType,
236    #[bw(calc = PosMarker::default())]
237    data_length: PosMarker<u16>,
238    #[bw(calc = 0)]
239    _reserved: u32,
240    #[br(args(&context_type))]
241    #[br(map_stream = |s| s.take_seek(data_length.value as u64))]
242    #[bw(write_with = PosMarker::write_size, args(&data_length))]
243    pub data: NegotiateContextValue,
244}
245
246macro_rules! negotiate_context_type {
247    ($($name:ident = $id:literal,)+) => {
248#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
249#[brw(repr(u16))]
250pub enum NegotiateContextType {
251    $(
252        $name = $id,
253    )+
254}
255
256#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
257#[br(import(context_type: &NegotiateContextType))]
258pub enum NegotiateContextValue {
259    $(
260        #[br(pre_assert(context_type == &NegotiateContextType::$name))]
261        $name($name),
262    )+
263}
264
265impl NegotiateContextValue {
266    pub fn get_matching_type(&self) -> NegotiateContextType {
267        match self {
268            $(
269                NegotiateContextValue::$name(_) => {
270                    NegotiateContextType::$name
271                }
272            )+
273        }
274    }
275}
276
277$(
278    impl From<$name> for NegotiateContext {
279        fn from(val: $name) -> Self {
280            NegotiateContext {
281                context_type: NegotiateContextType::$name,
282                data: NegotiateContextValue::$name(val),
283            }
284        }
285    }
286)+
287    };
288}
289
290negotiate_context_type!(
291    PreauthIntegrityCapabilities = 0x0001,
292    EncryptionCapabilities = 0x0002,
293    CompressionCapabilities = 0x0003,
294    NetnameNegotiateContextId = 0x0005,
295    TransportCapabilities = 0x0006,
296    RdmaTransformCapabilities = 0x0007,
297    SigningCapabilities = 0x0008,
298);
299
300// u16 enum hash algorithms binrw 0x01 is sha512.
301#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
302#[brw(repr(u16))]
303pub enum HashAlgorithm {
304    Sha512 = 0x01,
305}
306
307#[binrw::binrw]
308#[derive(Debug, PartialEq, Eq)]
309pub struct PreauthIntegrityCapabilities {
310    #[bw(try_calc(u16::try_from(hash_algorithms.len())))]
311    hash_algorithm_count: u16,
312    #[bw(try_calc(u16::try_from(salt.len())))]
313    salt_length: u16,
314    #[br(count = hash_algorithm_count)]
315    pub hash_algorithms: Vec<HashAlgorithm>,
316    #[br(count = salt_length)]
317    pub salt: Vec<u8>,
318}
319
320#[binrw::binrw]
321#[derive(Debug, PartialEq, Eq)]
322pub struct EncryptionCapabilities {
323    #[bw(try_calc(u16::try_from(ciphers.len())))]
324    cipher_count: u16,
325    #[br(count = cipher_count)]
326    pub ciphers: Vec<EncryptionCipher>,
327}
328
329#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
330#[brw(repr(u16))]
331pub enum EncryptionCipher {
332    Aes128Ccm = 0x0001,
333    Aes128Gcm = 0x0002,
334    Aes256Ccm = 0x0003,
335    Aes256Gcm = 0x0004,
336}
337
338#[binrw::binrw]
339#[derive(Debug, PartialEq, Eq, Clone)]
340pub struct CompressionCapabilities {
341    #[bw(try_calc(u16::try_from(compression_algorithms.len())))]
342    compression_algorithm_count: u16,
343    #[bw(calc = 0)]
344    _padding: u16,
345    pub flags: CompressionCapsFlags,
346    #[br(count = compression_algorithm_count)]
347    pub compression_algorithms: Vec<CompressionAlgorithm>,
348}
349
350#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
351#[brw(repr(u16))]
352#[repr(u16)]
353pub enum CompressionAlgorithm {
354    None = 0x0000,
355    LZNT1 = 0x0001,
356    LZ77 = 0x0002,
357    LZ77Huffman = 0x0003,
358    PatternV1 = 0x0004,
359    LZ4 = 0x0005,
360}
361
362impl CompressionAlgorithm {
363    /// Relevant for processing compressed messages.
364    pub fn original_size_required(&self) -> bool {
365        matches!(
366            self,
367            CompressionAlgorithm::LZNT1
368                | CompressionAlgorithm::LZ77
369                | CompressionAlgorithm::LZ77Huffman
370                | CompressionAlgorithm::LZ4
371        )
372    }
373}
374
375impl std::fmt::Display for CompressionAlgorithm {
376    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        let message_as_string = match self {
378            CompressionAlgorithm::None => "None",
379            CompressionAlgorithm::LZNT1 => "LZNT1",
380            CompressionAlgorithm::LZ77 => "LZ77",
381            CompressionAlgorithm::LZ77Huffman => "LZ77+Huffman",
382            CompressionAlgorithm::PatternV1 => "PatternV1",
383            CompressionAlgorithm::LZ4 => "LZ4",
384        };
385        write!(f, "{} ({:#x})", message_as_string, *self as u16)
386    }
387}
388
389#[bitfield]
390#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
391#[bw(map = |&x| Self::into_bytes(x))]
392#[br(map = Self::from_bytes)]
393pub struct CompressionCapsFlags {
394    pub chained: bool,
395    #[skip]
396    __: B31,
397}
398
399#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
400pub struct NetnameNegotiateContextId {
401    #[br(parse_with = binrw::helpers::until_eof)]
402    pub netname: SizedWideString,
403}
404
405#[bitfield]
406#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
407#[bw(map = |&x| Self::into_bytes(x))]
408#[br(map = Self::from_bytes)]
409pub struct TransportCapabilities {
410    pub accept_transport_layer_security: bool,
411    #[skip]
412    __: B31,
413}
414
415#[binrw::binrw]
416#[derive(Debug, PartialEq, Eq)]
417pub struct RdmaTransformCapabilities {
418    #[bw(try_calc(u16::try_from(transforms.len())))]
419    transform_count: u16,
420
421    #[bw(calc = 0)]
422    reserved1: u16,
423    #[bw(calc = 0)]
424    reserved2: u32,
425
426    #[br(count = transform_count)]
427    pub transforms: Vec<RdmaTransformId>,
428}
429
430#[binrw::binrw]
431#[derive(Debug, PartialEq, Eq, Clone, Copy)]
432#[brw(repr(u16))]
433pub enum RdmaTransformId {
434    None = 0x0000,
435    Encryption = 0x0001,
436    Signing = 0x0002,
437}
438
439#[binrw::binrw]
440#[derive(Debug, PartialEq, Eq)]
441pub struct SigningCapabilities {
442    #[bw(try_calc(u16::try_from(signing_algorithms.len())))]
443    signing_algorithm_count: u16,
444    #[br(count = signing_algorithm_count)]
445    pub signing_algorithms: Vec<SigningAlgorithmId>,
446}
447
448#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
449#[brw(repr(u16))]
450pub enum SigningAlgorithmId {
451    HmacSha256 = 0x0000,
452    AesCmac = 0x0001,
453    AesGmac = 0x0002,
454}
455
456#[cfg(test)]
457mod tests {
458    use smb_dtyp::make_guid;
459    use smb_tests::hex_to_u8_array;
460    use time::macros::datetime;
461
462    use super::*;
463    use crate::*;
464
465    test_request! {
466        Negotiate {
467            security_mode: NegotiateSecurityMode::new().with_signing_enabled(true),
468            capabilities: GlobalCapabilities::new()
469                .with_dfs(true)
470                .with_leasing(true)
471                .with_large_mtu(true)
472                .with_multi_channel(true)
473                .with_persistent_handles(true)
474                .with_directory_leasing(true)
475                .with_encryption(true)
476                .with_notifications(true),
477            client_guid: make_guid!("{c12e0ddf-43dd-11f0-8b87-000c29801682}"),
478            dialects: vec![
479                Dialect::Smb0202,
480                Dialect::Smb021,
481                Dialect::Smb030,
482                Dialect::Smb0302,
483                Dialect::Smb0311,
484            ],
485            negotiate_context_list: Some(vec![
486                PreauthIntegrityCapabilities {
487                    hash_algorithms: vec![HashAlgorithm::Sha512],
488                    salt: hex_to_u8_array! {"ed006c304e332890b2bd98617b5ad9ef075994154673696280ffcc0f1291a15d"}
489                }.into(),
490                EncryptionCapabilities { ciphers: vec![
491                    EncryptionCipher::Aes128Gcm,
492                    EncryptionCipher::Aes128Ccm,
493                    EncryptionCipher::Aes256Gcm,
494                    EncryptionCipher::Aes256Ccm,
495                ] }.into(),
496                CompressionCapabilities {
497                    flags: CompressionCapsFlags::new().with_chained(true),
498                    compression_algorithms: vec![
499                        CompressionAlgorithm::PatternV1,
500                        CompressionAlgorithm::LZ77,
501                        CompressionAlgorithm::LZ77Huffman,
502                        CompressionAlgorithm::LZNT1,
503                        CompressionAlgorithm::LZ4,
504                    ]
505                }.into(),
506                SigningCapabilities { signing_algorithms: vec![
507                    SigningAlgorithmId::AesGmac,
508                    SigningAlgorithmId::AesCmac,
509                    SigningAlgorithmId::HmacSha256,
510                ] }.into(),
511                NetnameNegotiateContextId { netname: "localhost".into() }.into(),
512                RdmaTransformCapabilities { transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing] }.into()
513            ])
514        } => "2400050001000000ff000000df0d2ec1dd43f0118b87000c298
515        016827000000006000000020210020003020311030000010026000000
516        0000010020000100ed006c304e332890b2bd98617b5ad9ef075994154
517        673696280ffcc0f1291a15d000002000a000000000004000200010004
518        000300000000000000030012000000000005000000010000000400020
519        003000100050000000000000008000800000000000300020001000000
520        05001200000000006c006f00630061006c0068006f007300740000000
521        000000007000c0000000000020000000000000001000200"
522    }
523
524    test_response! {
525        Negotiate {
526            security_mode: NegotiateSecurityMode::new().with_signing_enabled(true),
527            dialect_revision: NegotiateDialect::Smb0311,
528            server_guid: Guid::from([
529                0xb9, 0x21, 0xf8, 0xe0, 0x15, 0x7, 0xaa, 0x41, 0xbe, 0x38, 0x67, 0xfe, 0xbf,
530                0x5e, 0x2e, 0x11
531            ]),
532            capabilities: GlobalCapabilities::new()
533                .with_dfs(true)
534                .with_leasing(true)
535                .with_large_mtu(true)
536                .with_multi_channel(true)
537                .with_directory_leasing(true),
538            max_transact_size: 8388608,
539            max_read_size: 8388608,
540            max_write_size: 8388608,
541            system_time: datetime!(2025-01-18 16:24:39.448746400).into(),
542            server_start_time: FileTime::default(),
543            buffer: [
544                0x60, 0x28, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x1e, 0x30, 0x1c,
545                0xa0, 0x1a, 0x30, 0x18, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2,
546                0x2, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa
547            ]
548            .to_vec(),
549            negotiate_context_list: Some(vec![
550                PreauthIntegrityCapabilities {
551                        hash_algorithms: vec![HashAlgorithm::Sha512],
552                        salt: [
553                            0xd5, 0x67, 0x1b, 0x24, 0xa1, 0xe9, 0xcc, 0xc8, 0x93, 0xf5, 0x55,
554                            0x5a, 0x31, 0x3, 0x43, 0x5a, 0x85, 0x2b, 0xc3, 0xcb, 0x1a, 0xd3,
555                            0x2d, 0xc5, 0x1f, 0x92, 0x80, 0x6e, 0xf3, 0xfb, 0x4d, 0xd4
556                        ]
557                        .to_vec()
558                    }
559                .into(),
560                EncryptionCapabilities {
561                    ciphers: vec![EncryptionCipher::Aes128Gcm]
562                }
563                .into(),
564                SigningCapabilities {
565                    signing_algorithms: vec![SigningAlgorithmId::AesGmac]
566                }
567                .into(),
568                RdmaTransformCapabilities {
569                    transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing]
570                }
571                .into(),
572                CompressionCapabilities {
573                    flags: CompressionCapsFlags::new().with_chained(true),
574                    compression_algorithms: vec![
575                        CompressionAlgorithm::LZ77,
576                        CompressionAlgorithm::PatternV1
577                    ]
578                }
579                .into(),
580            ])
581        } => "4100010011030500b921f8e01507aa41be3867febf5e2e112f000000000080000000800000008000a876d878c569db01000000000000000080002a00b0000000602806062b0601050502a01e301ca01a3018060a2b06010401823702021e060a2b06010401823702020a0000000000000100260000000000010020000100d5671b24a1e9ccc893f5555a3103435a852bc3cb1ad32dc51f92806ef3fb4dd40000020004000000000001000200000000000800040000000000010002000000000007000c00000000000200000000000000010002000000000003000c0000000000020000000100000002000400"
582    }
583}