Skip to main content

picky_krb/negoex/
data_types.rs

1use std::io::{self, Read, Write};
2
3use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
4use uuid::Uuid;
5
6use super::{CHECKSUM_SCHEME_RFC3961, NEGOEXTS_MESSAGE_SIGNATURE, NegoexDataType};
7
8const GUID_SIZE: usize = 16;
9pub(crate) const CHECKSUM_HEADER_LEN: u32 = 4 /* header_len */ + 4 /* checksum_scheme */ + 4 /* type */ + 8 /* checksum vector header */;
10
11pub type Guid = Uuid;
12
13impl NegoexDataType for Guid {
14    type Error = io::Error;
15
16    fn size(&self) -> usize {
17        GUID_SIZE
18    }
19
20    fn decode(mut from: impl Read, _message: &[u8]) -> Result<Self, Self::Error> {
21        let mut id_bytes = [0; GUID_SIZE];
22        from.read_exact(&mut id_bytes)?;
23
24        Ok(Self::from_bytes_le(id_bytes))
25    }
26
27    fn encode_with_payload(&self, _offset: usize, mut to: impl Write, _data: impl Write) -> Result<usize, Self::Error> {
28        to.write_all(&self.to_bytes_le())?;
29
30        Ok(0)
31    }
32
33    fn encode(&self, to: impl Write) -> Result<(), Self::Error> {
34        self.encode_with_payload(0, to, &mut [] as &mut [u8])?;
35
36        Ok(())
37    }
38}
39
40/// [2.2.2 GUID typedefs](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
41/// ```not_rust
42/// typedef GUID CONVERSATION_ID;
43/// ```
44pub type ConversationId = Guid;
45
46/// [2.2.2 GUID typedefs](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
47/// ```not_rust
48/// typedef GUID AUTH_SCHEME;
49/// ```
50pub type AuthScheme = Guid;
51
52//= message type are always have a size of 4 bytes =//
53const MESSAGE_TYPE_SIZE: usize = 4;
54
55/// [2.2.6.1 MESSAGE_TYPE](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
56/// ```not_rust
57/// enum
58/// {
59///     MESSAGE_TYPE_INITIATOR_NEGO = 0,
60///     MESSAGE_TYPE_ACCEPTOR_NEGO,
61///     MESSAGE_TYPE_INITIATOR_META_DATA,
62///     MESSAGE_TYPE_ACCEPTOR_META_DATA,
63///     MESSAGE_TYPE_CHALLENGE,
64///     MESSAGE_TYPE_AP_REQUEST,
65///     MESSAGE_TYPE_VERIFY,
66///     MESSAGE_TYPE_ALERT
67/// } MESSAGE_TYPE;
68/// ```
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub enum MessageType {
71    InitiatorNego,
72    AcceptorNego,
73    InitiatorMetaData,
74    AcceptorMetaData,
75    Challenge,
76    ApRequest,
77    Verify,
78    Alert,
79}
80
81impl NegoexDataType for MessageType {
82    type Error = io::Error;
83
84    fn size(&self) -> usize {
85        MESSAGE_TYPE_SIZE
86    }
87
88    fn decode(mut from: impl Read, _message: &[u8]) -> Result<Self, Self::Error> {
89        MessageType::try_from(from.read_u32::<LittleEndian>()?)
90    }
91
92    fn encode_with_payload(&self, _offset: usize, mut to: impl Write, _data: impl Write) -> Result<usize, Self::Error> {
93        to.write_u32::<LittleEndian>(self.into())?;
94
95        Ok(0)
96    }
97
98    fn encode(&self, to: impl Write) -> Result<(), Self::Error> {
99        self.encode_with_payload(0, to, &mut [] as &mut [u8])?;
100
101        Ok(())
102    }
103}
104
105impl TryFrom<u32> for MessageType {
106    type Error = io::Error;
107
108    fn try_from(type_raw: u32) -> Result<Self, Self::Error> {
109        match type_raw {
110            0 => Ok(MessageType::InitiatorNego),
111            1 => Ok(MessageType::AcceptorNego),
112            2 => Ok(MessageType::InitiatorMetaData),
113            3 => Ok(MessageType::AcceptorMetaData),
114            4 => Ok(MessageType::Challenge),
115            5 => Ok(MessageType::ApRequest),
116            6 => Ok(MessageType::Verify),
117            7 => Ok(MessageType::Alert),
118            _ => Err(io::Error::new(io::ErrorKind::InvalidData, "invalid MessageType")),
119        }
120    }
121}
122
123impl From<&MessageType> for u32 {
124    fn from(message_type: &MessageType) -> Self {
125        match message_type {
126            MessageType::InitiatorNego => 0,
127            MessageType::AcceptorNego => 1,
128            MessageType::InitiatorMetaData => 2,
129            MessageType::AcceptorMetaData => 3,
130            MessageType::Challenge => 4,
131            MessageType::ApRequest => 5,
132            MessageType::Verify => 6,
133            MessageType::Alert => 7,
134        }
135    }
136}
137
138/// [2.2.6.2 MESSAGE_HEADER](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
139/// ```not_rust
140/// struct
141/// {
142///     ULONG64 Signature;
143///     MESSAGE_TYPE MessageType;
144///     ULONG SequenceNum;
145///     ULONG cbHeaderLength;
146///     ULONG cbMessageLength;
147///     CONVERSATION_ID ConversationId;
148/// } MESSAGE_HEADER;
149/// ```
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct MessageHeader {
152    pub signature: u64,
153    pub message_type: MessageType,
154    pub sequence_num: u32,
155    pub header_len: u32,
156    pub message_len: u32,
157    pub conversation_id: ConversationId,
158}
159
160impl NegoexDataType for MessageHeader {
161    type Error = io::Error;
162
163    fn size(&self) -> usize {
164        8 /* signature */ +
165        self.message_type.size() +
166        4 /* sequence_num */ +
167        4 /* header_len */ +
168        4 /* message_len */ +
169        self.conversation_id.size()
170    }
171
172    fn decode(mut from: impl Read, message: &[u8]) -> Result<Self, Self::Error> {
173        let signature = from.read_u64::<LittleEndian>()?;
174
175        if signature != NEGOEXTS_MESSAGE_SIGNATURE {
176            return Err(io::Error::new(
177                io::ErrorKind::InvalidData,
178                format!("invalid message signature: {signature:x?}. expected: {NEGOEXTS_MESSAGE_SIGNATURE:x?}"),
179            ));
180        }
181
182        let message_type = MessageType::decode(&mut from, message)?;
183
184        let sequence_num = from.read_u32::<LittleEndian>()?;
185
186        let header_len = from.read_u32::<LittleEndian>()?;
187
188        let message_len = from.read_u32::<LittleEndian>()?;
189
190        let conversation_id = ConversationId::decode(&mut from, message)?;
191
192        Ok(Self {
193            signature,
194            message_type,
195            sequence_num,
196            header_len,
197            message_len,
198            conversation_id,
199        })
200    }
201
202    fn encode_with_payload(
203        &self,
204        offset: usize,
205        mut to: impl Write,
206        mut data: impl Write,
207    ) -> Result<usize, Self::Error> {
208        to.write_u64::<LittleEndian>(self.signature)?;
209
210        let message_type_offset = self.message_type.encode_with_payload(offset, &mut to, &mut data)?;
211
212        to.write_u32::<LittleEndian>(self.sequence_num)?;
213
214        to.write_u32::<LittleEndian>(self.header_len)?;
215
216        to.write_u32::<LittleEndian>(self.message_len)?;
217
218        let conversation_id_offset = self.conversation_id.encode_with_payload(offset, &mut to, &mut data)?;
219
220        Ok(message_type_offset + conversation_id_offset)
221    }
222
223    fn encode(&self, to: impl Write) -> Result<(), Self::Error> {
224        self.encode_with_payload(0, to, &mut [] as &mut [u8])?;
225
226        Ok(())
227    }
228}
229
230/// [2.2.5.1.4 EXTENSION](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
231/// ```not_rust
232/// struct
233/// {
234///     ULONG ExtensionType;
235///     BYTE_VECTOR ExtensionValue;
236/// } EXTENSION;
237/// ```
238#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct Extension {
240    pub extension_type: u32,
241    pub extension_value: ByteVector,
242}
243
244impl NegoexDataType for Extension {
245    type Error = io::Error;
246
247    fn size(&self) -> usize {
248        4 /* extension_type */ + self.extension_value.len()
249    }
250
251    fn decode(mut from: impl Read, message: &[u8]) -> Result<Self, Self::Error> {
252        let extension_type = from.read_u32::<LittleEndian>()?;
253
254        let extension_value = ByteVector::decode(&mut from, message)?;
255
256        Ok(Self {
257            extension_type,
258            extension_value,
259        })
260    }
261
262    fn encode_with_payload(
263        &self,
264        offset: usize,
265        mut to: impl Write,
266        mut data: impl Write,
267    ) -> Result<usize, Self::Error> {
268        to.write_u32::<LittleEndian>(self.extension_type)?;
269
270        self.extension_value.encode_with_payload(offset, &mut to, &mut data)
271    }
272
273    fn encode(&self, mut to: impl Write) -> Result<(), Self::Error> {
274        let offset = 12;
275
276        let mut header = Vec::new();
277        let mut data = Vec::new();
278
279        self.encode_with_payload(offset, &mut header, &mut data)?;
280
281        to.write_all(&header)?;
282        to.write_all(&data)?;
283
284        Ok(())
285    }
286}
287
288/// [2.2.5.2.3 BYTE_VECTOR](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
289/// ```not_rust
290/// struct
291/// {
292///     ULONG ByteArrayOffset;
293///     ULONG ByteArrayLength;
294/// } BYTE_VECTOR;
295/// ```
296pub type ByteVector = Vec<u8>;
297
298/// [2.2.5.2.2 AUTH_SCHEME_VECTOR](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
299/// ```not_rust
300/// struct
301/// {
302///     ULONG AuthSchemeArrayOffset;
303///     USHORT AuthSchemeCount;
304/// } AUTH_SCHEME_VECTOR;
305/// ```
306pub type AuthSchemeVector = Vec<AuthScheme>;
307
308/// [2.2.5.2.4 EXTENSION_VECTOR](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
309/// ```not_rust
310/// struct
311/// {
312///     ULONG ExtensionArrayOffset;
313///     USHORT ExtensionCount;
314/// } EXTENSION_VECTOR;
315/// ```
316pub type ExtensionVector = Vec<Extension>;
317
318/// [2.2.5.1.3 CHECKSUM](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-NEGOEX/%5bMS-NEGOEX%5d.pdf)
319/// ```not_rust
320/// struct
321/// {
322///     ULONG cbHeaderLength;
323///     ULONG ChecksumScheme;
324///     ULONG ChecksumType;
325///     BYTE_VECTOR ChecksumValue;
326/// } CHECKSUM;
327/// ```
328#[derive(Debug, Clone, PartialEq, Eq)]
329pub struct Checksum {
330    pub header_len: u32,
331    pub checksum_scheme: u32,
332    pub checksum_type: u32,
333    pub checksum_value: Vec<u8>,
334}
335
336impl NegoexDataType for Checksum {
337    type Error = io::Error;
338
339    fn size(&self) -> usize {
340        4 /* header_len */ +
341        4 /* checksum_scheme */ +
342        4 /* checksum type */ +
343        4 /* padding of 4 bytes */ +
344        self.checksum_value.size()
345    }
346
347    fn decode(mut from: impl Read, message: &[u8]) -> Result<Self, Self::Error> {
348        let header_len = from.read_u32::<LittleEndian>()?;
349
350        let checksum_scheme = from.read_u32::<LittleEndian>()?;
351
352        if checksum_scheme != CHECKSUM_SCHEME_RFC3961 {
353            return Err(io::Error::new(
354                io::ErrorKind::InvalidInput,
355                format!("invalid checksum scheme: {checksum_scheme}. Expected: {CHECKSUM_SCHEME_RFC3961}"),
356            ));
357        }
358
359        let checksum_type = from.read_u32::<LittleEndian>()?;
360
361        let checksum_value = Vec::decode(&mut from, message)?;
362
363        Ok(Self {
364            header_len,
365            checksum_scheme,
366            checksum_type,
367            checksum_value,
368        })
369    }
370
371    fn encode_with_payload(
372        &self,
373        offset: usize,
374        mut to: impl Write,
375        mut data: impl Write,
376    ) -> Result<usize, Self::Error> {
377        to.write_u32::<LittleEndian>(self.header_len)?;
378
379        to.write_u32::<LittleEndian>(self.checksum_scheme)?;
380
381        to.write_u32::<LittleEndian>(self.checksum_type)?;
382
383        self.checksum_value.encode_with_payload(offset, &mut to, &mut data)
384    }
385
386    fn encode(&self, mut to: impl Write) -> Result<(), Self::Error> {
387        let offset = self.header_len as usize;
388
389        let mut header = Vec::new();
390        let mut data = Vec::new();
391
392        self.encode_with_payload(offset, &mut header, &mut data)?;
393
394        to.write_all(&header)?;
395        to.write_all(&data)?;
396
397        Ok(())
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use std::str::FromStr;
404
405    use uuid::Uuid;
406
407    use crate::constants::cksum_types::HMAC_SHA1_96_AES256;
408    use crate::negoex::NegoexDataType;
409    use crate::negoex::data_types::Guid;
410
411    use super::{CHECKSUM_SCHEME_RFC3961, Checksum, Extension, MessageHeader, MessageType, NEGOEXTS_MESSAGE_SIGNATURE};
412
413    #[test]
414    fn guid_encode() {
415        let guid = Uuid::from_str("0d53335c-f9ea-4d0d-b2ec-4ae3786ec308").unwrap();
416
417        let mut encoded = Vec::new();
418        guid.encode(&mut encoded).unwrap();
419
420        assert_eq!(
421            &[92, 51, 83, 13, 234, 249, 13, 77, 178, 236, 74, 227, 120, 110, 195, 8],
422            encoded.as_slice()
423        );
424    }
425
426    #[test]
427    fn guid_decode() {
428        let encoded_guid = [90, 7, 41, 59, 145, 243, 51, 175, 161, 180, 162, 18, 36, 157, 124, 180];
429
430        let guid = Guid::decode(&encoded_guid as &[u8], &encoded_guid).unwrap();
431
432        assert_eq!(Uuid::from_str("3b29075a-f391-af33-a1b4-a212249d7cb4").unwrap(), guid);
433    }
434
435    #[test]
436    fn message_type_decode() {
437        let encoded = [1, 0, 0, 0];
438
439        let message_type = MessageType::decode(&encoded as &[u8], &encoded).unwrap();
440
441        assert_eq!(MessageType::AcceptorNego, message_type);
442    }
443
444    #[test]
445    fn message_type_encode() {
446        let message_type = MessageType::ApRequest;
447
448        let mut encoded = Vec::new();
449        message_type.encode(&mut encoded).unwrap();
450
451        assert_eq!(&[5, 0, 0, 0], encoded.as_slice());
452    }
453
454    #[test]
455    fn message_header_encode() {
456        let message_header = MessageHeader {
457            signature: NEGOEXTS_MESSAGE_SIGNATURE,
458            message_type: MessageType::AcceptorNego,
459            sequence_num: 2,
460            header_len: 96,
461            message_len: 112,
462            conversation_id: Guid::from_str("3b29075a-f391-af33-a1b4-a212249d7cb4").unwrap(),
463        };
464
465        let mut encoded = Vec::new();
466        message_header.encode(&mut encoded).unwrap();
467
468        assert_eq!(
469            &[
470                78, 69, 71, 79, 69, 88, 84, 83, 1, 0, 0, 0, 2, 0, 0, 0, 96, 0, 0, 0, 112, 0, 0, 0, 90, 7, 41, 59, 145,
471                243, 51, 175, 161, 180, 162, 18, 36, 157, 124, 180
472            ],
473            encoded.as_slice(),
474        );
475    }
476
477    #[test]
478    fn message_header_decode() {
479        let encoded = [
480            78, 69, 71, 79, 69, 88, 84, 83, 1, 0, 0, 0, 2, 0, 0, 0, 96, 0, 0, 0, 112, 0, 0, 0, 90, 7, 41, 59, 145, 243,
481            51, 175, 161, 180, 162, 18, 36, 157, 124, 180,
482        ];
483
484        let message_header = MessageHeader::decode(&encoded as &[u8], &encoded).unwrap();
485
486        assert_eq!(
487            MessageHeader {
488                signature: NEGOEXTS_MESSAGE_SIGNATURE,
489                message_type: MessageType::AcceptorNego,
490                sequence_num: 2,
491                header_len: 96,
492                message_len: 112,
493                conversation_id: Guid::from_str("3b29075a-f391-af33-a1b4-a212249d7cb4").unwrap(),
494            },
495            message_header,
496        );
497    }
498
499    #[test]
500    fn extension_encode() {
501        let extension = Extension {
502            extension_type: 3,
503            extension_value: vec![1, 2, 3, 4, 5, 6],
504        };
505
506        let mut encoded = Vec::new();
507        extension.encode(&mut encoded).unwrap();
508
509        assert_eq!(
510            &[3, 0, 0, 0, 12, 0, 0, 0, 6, 0, 0, 0, 1, 2, 3, 4, 5, 6],
511            encoded.as_slice()
512        );
513    }
514
515    #[test]
516    fn extension_decode() {
517        let encoded = [3, 0, 0, 0, 12, 0, 0, 0, 6, 0, 0, 0, 1, 2, 3, 4, 5, 6];
518
519        let extension = Extension::decode(&encoded as &[u8], &encoded).unwrap();
520
521        assert_eq!(
522            Extension {
523                extension_type: 3,
524                extension_value: vec![1, 2, 3, 4, 5, 6],
525            },
526            extension
527        );
528    }
529
530    #[test]
531    fn checksum_decode() {
532        // NEGOEX VERIFY message that contains the checksum
533        let negoex_verify = [
534            78, 69, 71, 79, 69, 88, 84, 83, 6, 0, 0, 0, 7, 0, 0, 0, 80, 0, 0, 0, 92, 0, 0, 0, 90, 7, 41, 59, 145, 243,
535            51, 175, 161, 180, 162, 18, 36, 157, 124, 180, 92, 51, 83, 13, 234, 249, 13, 77, 178, 236, 74, 227, 120,
536            110, 195, 8, 20, 0, 0, 0, 1, 0, 0, 0, 16, 0, 0, 0, 80, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 228, 167, 112,
537            148, 23, 131, 204, 12, 13, 36, 58, 87,
538        ];
539
540        // 56 - start of the Checksum struct
541        let checksum = Checksum::decode(&negoex_verify[56..], &negoex_verify).unwrap();
542
543        assert_eq!(
544            Checksum {
545                header_len: 20,
546                checksum_scheme: CHECKSUM_SCHEME_RFC3961,
547                checksum_type: HMAC_SHA1_96_AES256 as u32,
548                checksum_value: vec![228, 167, 112, 148, 23, 131, 204, 12, 13, 36, 58, 87],
549            },
550            checksum
551        );
552    }
553
554    #[test]
555    fn checksum_encode() {
556        let checksum = Checksum {
557            header_len: 20,
558            checksum_scheme: CHECKSUM_SCHEME_RFC3961,
559            checksum_type: HMAC_SHA1_96_AES256 as u32,
560            checksum_value: vec![228, 167, 112, 148, 23, 131, 204, 12, 13, 36, 58, 87],
561        };
562
563        let mut encoded = Vec::new();
564        checksum.encode(&mut encoded).unwrap();
565
566        assert_eq!(
567            &[
568                20, 0, 0, 0, 1, 0, 0, 0, 16, 0, 0, 0, 20, 0, 0, 0, 12, 0, 0, 0, 228, 167, 112, 148, 23, 131, 204, 12,
569                13, 36, 58, 87
570            ],
571            encoded.as_slice(),
572        )
573    }
574}