1# -*- coding: utf-8 -*-
2# Copyright: (c) 2020, Jordan Borean (@jborean93) <jborean93@gmail.com>
3# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
4
5import datetime
6import struct
7
8import pytest
9
10import spnego._ntlm_raw.messages as messages
11
12from .._ntlm_raw import (
13    TEST_NTLMV1_FLAGS,
14    TEST_NTLMV2_FLAGS,
15    TEST_SERVER_CHALLENGE,
16    TEST_SERVER_NAME,
17    TEST_USER,
18    TEST_USER_DOM,
19    TEST_WORKSTATION_NAME,
20)
21
22
23class UTC10(datetime.tzinfo):
24    """ Test UTC+10 timezone class. """
25
26    def utcoffset(self, dt):
27        return datetime.timedelta(hours=10)
28
29    def tzname(self, dt):
30        return "UTC+10"
31
32    def dst(self, dt):
33        return datetime.timedelta(hours=10)
34
35
36def test_negotiate_flags_native_labels():
37    actual = messages.NegotiateFlags.native_labels()
38
39    assert isinstance(actual, dict)
40    assert actual[messages.NegotiateFlags.key_56] == 'NTLMSSP_NEGOTIATE_56'
41
42
43def test_av_id_native_labels():
44    actual = messages.AvId.native_labels()
45
46    assert isinstance(actual, dict)
47    assert actual[messages.AvId.channel_bindings] == 'MSV_AV_CHANNEL_BINDINGS'
48
49
50def test_av_flags_native_labels():
51    actual = messages.AvFlags.native_labels()
52
53    assert isinstance(actual, dict)
54    assert actual[messages.AvFlags.mic] == 'MIC_PROVIDED'
55
56
57def test_message_type_native_labels():
58    actual = messages.MessageType.native_labels()
59
60    assert isinstance(actual, dict)
61    assert actual[messages.MessageType.challenge] == 'CHALLENGE_MESSAGE'
62
63
64def test_negotiate_pack_defaults():
65    negotiate = messages.Negotiate()
66
67    assert negotiate.flags == 0
68    assert negotiate.domain_name is None
69    assert negotiate.workstation is None
70    assert negotiate.version is None
71
72    actual = negotiate.pack()
73
74    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
75                     b"\x01\x00\x00\x00" \
76                     b"\x00\x00\x00\x00" \
77                     b"\x00\x00" \
78                     b"\x00\x00" \
79                     b"\x20\x00\x00\x00" \
80                     b"\x00\x00" \
81                     b"\x00\x00" \
82                     b"\x20\x00\x00\x00"
83
84    negotiate.flags = 1
85
86    actual = negotiate.pack()
87
88    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
89                     b"\x01\x00\x00\x00" \
90                     b"\x01\x00\x00\x00" \
91                     b"\x00\x00" \
92                     b"\x00\x00" \
93                     b"\x20\x00\x00\x00" \
94                     b"\x00\x00" \
95                     b"\x00\x00" \
96                     b"\x20\x00\x00\x00"
97
98
99def test_negotiate_pack_with_domain():
100    negotiate = messages.Negotiate(domain_name="café", workstation="café")
101
102    assert negotiate.flags == messages.NegotiateFlags.oem_workstation_supplied | \
103        messages.NegotiateFlags.oem_domain_name_supplied
104    assert negotiate.domain_name == "café"
105    assert negotiate.workstation == "café"
106    assert negotiate.version is None
107
108    actual = negotiate.pack()
109
110    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
111                     b"\x01\x00\x00\x00" \
112                     b"\x00\x30\x00\x00" \
113                     b"\x04\x00" \
114                     b"\x04\x00" \
115                     b"\x20\x00\x00\x00" \
116                     b"\x04\x00" \
117                     b"\x04\x00" \
118                     b"\x24\x00\x00\x00" \
119                     b"\x63\x61\x66\xE9" \
120                     b"\x63\x61\x66\xE9"
121
122
123def test_negotiate_pack_with_all_fields():
124    negotiate = messages.Negotiate(domain_name="café", workstation="café", version=messages.Version(1, 1, 1))
125
126    assert negotiate.flags == messages.NegotiateFlags.oem_workstation_supplied | \
127        messages.NegotiateFlags.oem_domain_name_supplied | \
128        messages.NegotiateFlags.version
129    assert negotiate.domain_name == "café"
130    assert negotiate.workstation == "café"
131    assert negotiate.version == messages.Version(1, 1, 1)
132
133    actual = negotiate.pack()
134
135    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
136                     b"\x01\x00\x00\x00" \
137                     b"\x00\x30\x00\x02" \
138                     b"\x04\x00" \
139                     b"\x04\x00" \
140                     b"\x28\x00\x00\x00" \
141                     b"\x04\x00" \
142                     b"\x04\x00" \
143                     b"\x2C\x00\x00\x00" \
144                     b"\x01\x01\x01\x00\x00\x00\x00\x0F" \
145                     b"\x63\x61\x66\xE9" \
146                     b"\x63\x61\x66\xE9"
147
148
149def test_negotiate_pack_encoding():
150    negotiate = messages.Negotiate(domain_name="café", workstation="café", encoding='utf-8')
151
152    assert negotiate.flags == messages.NegotiateFlags.oem_workstation_supplied | \
153        messages.NegotiateFlags.oem_domain_name_supplied
154    assert negotiate.domain_name == "café"
155    assert negotiate.workstation == "café"
156    assert negotiate.version is None
157
158    actual = negotiate.pack()
159
160    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
161                     b"\x01\x00\x00\x00" \
162                     b"\x00\x30\x00\x00" \
163                     b"\x05\x00" \
164                     b"\x05\x00" \
165                     b"\x20\x00\x00\x00" \
166                     b"\x05\x00" \
167                     b"\x05\x00" \
168                     b"\x25\x00\x00\x00" \
169                     b"\x63\x61\x66\xC3\xA9" \
170                     b"\x63\x61\x66\xC3\xA9"
171
172
173def test_negotiate_unpack():
174    actual = messages.Negotiate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
175                                       b"\x01\x00\x00\x00"
176                                       b"\x00\x00\x00\x00"
177                                       b"\x00\x00"
178                                       b"\x00\x00"
179                                       b"\x20\x00\x00\x00"
180                                       b"\x00\x00"
181                                       b"\x00\x00"
182                                       b"\x20\x00\x00\x00")
183
184    assert actual.flags == 0
185    assert actual.domain_name is None
186    assert actual.workstation is None
187    assert actual.version is None
188
189
190def test_negotiate_unpack_with_domain():
191    actual = messages.Negotiate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
192                                       b"\x01\x00\x00\x00"
193                                       b"\x00\x30\x00\x00"
194                                       b"\x04\x00"
195                                       b"\x04\x00"
196                                       b"\x20\x00\x00\x00"
197                                       b"\x04\x00"
198                                       b"\x04\x00"
199                                       b"\x24\x00\x00\x00"
200                                       b"\x63\x61\x66\xE9"
201                                       b"\x63\x61\x66\xE9")
202
203    assert actual.flags == messages.NegotiateFlags.oem_workstation_supplied | \
204        messages.NegotiateFlags.oem_domain_name_supplied
205    assert actual.domain_name == "café"
206    assert actual.workstation == "café"
207    assert actual.version is None
208
209
210def test_negotiate_unpack_with_all_fields():
211    actual = messages.Negotiate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
212                                       b"\x01\x00\x00\x00"
213                                       b"\x00\x30\x00\x02"
214                                       b"\x04\x00"
215                                       b"\x04\x00"
216                                       b"\x28\x00\x00\x00"
217                                       b"\x04\x00"
218                                       b"\x04\x00"
219                                       b"\x2C\x00\x00\x00"
220                                       b"\x01\x01\x01\x00\x00\x00\x00\x0F"
221                                       b"\x63\x61\x66\xE9"
222                                       b"\x63\x61\x66\xE9")
223
224    assert actual.flags == messages.NegotiateFlags.oem_workstation_supplied | \
225        messages.NegotiateFlags.oem_domain_name_supplied | \
226        messages.NegotiateFlags.version
227
228    assert actual.domain_name == "café"
229    assert actual.workstation == "café"
230    assert actual.version == messages.Version(1, 1, 1)
231
232
233def test_negotiate_unpack_encoding():
234    actual = messages.Negotiate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
235                                       b"\x01\x00\x00\x00"
236                                       b"\x00\x30\x00\x00"
237                                       b"\x05\x00"
238                                       b"\x05\x00"
239                                       b"\x20\x00\x00\x00"
240                                       b"\x05\x00"
241                                       b"\x05\x00"
242                                       b"\x25\x00\x00\x00"
243                                       b"\x63\x61\x66\xC3\xA9"
244                                       b"\x63\x61\x66\xC3\xA9", encoding='utf-8')
245
246    assert actual.flags == messages.NegotiateFlags.oem_workstation_supplied | \
247        messages.NegotiateFlags.oem_domain_name_supplied
248    assert actual.domain_name == "café"
249    assert actual.workstation == "café"
250    assert actual.version is None
251
252
253def test_negotiate_unpack_invalid_encoding():
254    # While the cars are invalid UTF-8 chars we don't want that to raise an exception for the Negotiate msg.
255    actual = messages.Negotiate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
256                                       b"\x01\x00\x00\x00"
257                                       b"\x00\x30\x00\x00"
258                                       b"\x04\x00"
259                                       b"\x04\x00"
260                                       b"\x20\x00\x00\x00"
261                                       b"\x04\x00"
262                                       b"\x04\x00"
263                                       b"\x24\x00\x00\x00"
264                                       b"\x63\x61\x66\xE9"
265                                       b"\x63\x61\x66\xE9", encoding='utf-8')
266
267    assert actual.flags == messages.NegotiateFlags.oem_workstation_supplied | \
268        messages.NegotiateFlags.oem_domain_name_supplied
269    assert actual.domain_name == "caf�"
270    assert actual.workstation == "caf�"
271    assert actual.version is None
272
273
274def test_negotiate_invalid_size():
275    with pytest.raises(ValueError, match="Invalid NTLM Negotiate raw byte length"):
276        messages.Negotiate.unpack(b"NTLMSSP\x00\x01\x00\x00\x00")
277
278
279def test_challenge_pack():
280    challenge = messages.Challenge()
281
282    assert challenge.flags == 0
283    assert challenge.server_challenge == b"\x00" * 8
284    assert challenge.target_info is None
285    assert challenge.target_name is None
286    assert challenge.version is None
287
288    actual = challenge.pack()
289
290    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
291                     b"\x02\x00\x00\x00" \
292                     b"\x00\x00" \
293                     b"\x00\x00" \
294                     b"\x30\x00\x00\x00" \
295                     b"\x00\x00\x00\x00" \
296                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
297                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
298                     b"\x00\x00" \
299                     b"\x00\x00" \
300                     b"\x30\x00\x00\x00"
301
302
303def test_challenge_pack_target_name():
304    challenge = messages.Challenge(flags=messages.NegotiateFlags.unicode, server_challenge=b"\x11" * 8,
305                                   target_name="café")
306
307    assert challenge.flags == messages.NegotiateFlags.unicode | messages.NegotiateFlags.request_target
308    assert challenge.server_challenge == b"\x11" * 8
309    assert challenge.target_name == "café"
310
311    actual = challenge.pack()
312
313    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
314                     b"\x02\x00\x00\x00" \
315                     b"\x08\x00" \
316                     b"\x08\x00" \
317                     b"\x30\x00\x00\x00" \
318                     b"\x05\x00\x00\x00" \
319                     b"\x11\x11\x11\x11\x11\x11\x11\x11" \
320                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
321                     b"\x00\x00" \
322                     b"\x00\x00" \
323                     b"\x38\x00\x00\x00" \
324                     b"\x63\x00\x61\x00\x66\x00\xE9\x00"
325
326
327def test_challenge_pack_target_name_oem():
328    challenge = messages.Challenge(flags=messages.NegotiateFlags.oem, server_challenge=b"\x11" * 8,
329                                   target_name="café")
330
331    assert challenge.flags == messages.NegotiateFlags.oem | messages.NegotiateFlags.request_target
332    assert challenge.server_challenge == b"\x11" * 8
333    assert challenge.target_name == "café"
334
335    actual = challenge.pack()
336
337    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
338                     b"\x02\x00\x00\x00" \
339                     b"\x04\x00" \
340                     b"\x04\x00" \
341                     b"\x30\x00\x00\x00" \
342                     b"\x06\x00\x00\x00" \
343                     b"\x11\x11\x11\x11\x11\x11\x11\x11" \
344                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
345                     b"\x00\x00" \
346                     b"\x00\x00" \
347                     b"\x34\x00\x00\x00" \
348                     b"\x63\x61\x66\xE9"
349
350
351def test_challenge_pack_target_info():
352    ti = messages.TargetInfo()
353    ti[messages.AvId.dns_computer_name] = "café"
354    # Even with the OEM encoding flag, the target info should still be utf-16-le.
355    challenge = messages.Challenge(flags=messages.NegotiateFlags.oem, server_challenge=b"\x11" * 8,
356                                   target_info=ti)
357
358    assert challenge.flags == messages.NegotiateFlags.oem | messages.NegotiateFlags.target_info
359    assert challenge.server_challenge == b"\x11" * 8
360    assert challenge.target_name is None
361    assert len(challenge.target_info) == 2
362    assert challenge.target_info[messages.AvId.dns_computer_name] == "café"
363    assert challenge.target_info[messages.AvId.eol] == b""
364    assert challenge.version is None
365
366    actual = challenge.pack()
367
368    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
369                     b"\x02\x00\x00\x00" \
370                     b"\x00\x00" \
371                     b"\x00\x00" \
372                     b"\x30\x00\x00\x00" \
373                     b"\x02\x00\x80\x00" \
374                     b"\x11\x11\x11\x11\x11\x11\x11\x11" \
375                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
376                     b"\x10\x00" \
377                     b"\x10\x00" \
378                     b"\x30\x00\x00\x00" \
379                     b"\x03\x00\x08\x00\x63\x00\x61\x00\x66\x00\xE9\x00" \
380                     b"\x00\x00\x00\x00"
381
382
383def test_challenge_pack_all_fields():
384    ti = messages.TargetInfo()
385    ti[messages.AvId.dns_computer_name] = "café"
386    challenge = messages.Challenge(flags=messages.NegotiateFlags.unicode, server_challenge=b"\x11" * 8,
387                                   target_name="café", target_info=ti, version=messages.Version(2, 2, 2))
388
389    assert challenge.flags == messages.NegotiateFlags.target_info | messages.NegotiateFlags.request_target | \
390        messages.NegotiateFlags.version | messages.NegotiateFlags.unicode
391    assert challenge.server_challenge == b"\x11" * 8
392    assert challenge.target_name == "café"
393    assert len(challenge.target_info) == 2
394    assert challenge.target_info[messages.AvId.dns_computer_name] == "café"
395    assert challenge.target_info[messages.AvId.eol] == b""
396    assert challenge.version == messages.Version(2, 2, 2)
397
398    actual = challenge.pack()
399
400    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
401                     b"\x02\x00\x00\x00" \
402                     b"\x08\x00" \
403                     b"\x08\x00" \
404                     b"\x38\x00\x00\x00" \
405                     b"\x05\x00\x80\x02" \
406                     b"\x11\x11\x11\x11\x11\x11\x11\x11" \
407                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
408                     b"\x10\x00" \
409                     b"\x10\x00" \
410                     b"\x40\x00\x00\x00" \
411                     b"\x02\x02\x02\x00\x00\x00\x00\x0F" \
412                     b"\x63\x00\x61\x00\x66\x00\xE9" \
413                     b"\x00\x03\x00\x08\x00\x63\x00\x61\x00\x66\x00\xE9\x00" \
414                     b"\x00\x00\x00\x00"
415
416
417def test_challenge_set_fields():
418    challenge = messages.Challenge()
419
420    challenge.flags = 10
421    challenge.server_challenge = b"\xFF" * 8
422
423    actual = challenge.pack()
424
425    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
426                     b"\x02\x00\x00\x00" \
427                     b"\x00\x00" \
428                     b"\x00\x00" \
429                     b"\x30\x00\x00\x00" \
430                     b"\x0A\x00\x00\x00" \
431                     b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" \
432                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
433                     b"\x00\x00" \
434                     b"\x00\x00" \
435                     b"\x30\x00\x00\x00"
436
437
438def test_challenge_invalid_server_challenge_length():
439    expected = "NTLM Challenge ServerChallenge must be 8 bytes long"
440    with pytest.raises(ValueError, match=expected):
441        messages.Challenge(server_challenge=b"\x00")
442
443    challenge = messages.Challenge()
444    with pytest.raises(ValueError, match=expected):
445        challenge.server_challenge = b"\x08"
446
447
448def test_challenge_invalid_size():
449    with pytest.raises(ValueError, match="Invalid NTLM Challenge raw byte length"):
450        messages.Challenge.unpack(b"NTLMSSP\x00\x02\x00\x00\x00")
451
452
453def test_challenge_unpack_ntlmv1():
454    # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/ee78f3ad-ae29-4de1-96a0-fe46e64b6e31
455    actual = messages.Challenge.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
456                                       b"\x02\x00\x00\x00"
457                                       b"\x0C\x00"
458                                       b"\x0C\x00"
459                                       b"\x38\x00\x00\x00"
460                                       b"\x33\x82\x02\xE2"
461                                       b"\x01\x23\x45\x67\x89\xAB\xCD\xEF"
462                                       b"\x00\x00\x00\x00\x00\x00\x00\x00"
463                                       b"\x00\x00"
464                                       b"\x00\x00"
465                                       b"\x00\x00\x00\x00"
466                                       b"\x06\x00\x70\x17\x00\x00\x00\x0F"
467                                       b"\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00")
468
469    assert actual.target_name == TEST_SERVER_NAME
470    assert actual.flags == TEST_NTLMV1_FLAGS
471    assert actual.server_challenge == TEST_SERVER_CHALLENGE
472    assert actual.target_info is None
473    assert actual.version == messages.Version(6, 0, 6000)
474
475
476def test_challenge_unpack_ntlmv2():
477    # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/bc612491-fb0b-4829-91bc-7c6b95ff67fe
478    actual = messages.Challenge.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
479                                       b"\x02\x00\x00\x00"
480                                       b"\x0C\x00"
481                                       b"\x0C\x00"
482                                       b"\x38\x00\x00\x00"
483                                       b"\x33\x82\x8A\xE2"
484                                       b"\x01\x23\x45\x67\x89\xAB\xCD\xEF"
485                                       b"\x00\x00\x00\x00\x00\x00\x00\x00"
486                                       b"\x24\x00"
487                                       b"\x24\x00"
488                                       b"\x44\x00\x00\x00"
489                                       b"\x06\x00\x70\x17\x00\x00\x00\x0F"
490                                       b"\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00"
491                                       b"\x02\x00\x0C\x00\x44\x00\x6F\x00\x6D\x00\x61\x00\x69\x00\x6E\x00"
492                                       b"\x01\x00\x0C\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00"
493                                       b"\x00\x00\x00\x00")
494
495    assert actual.target_name == TEST_SERVER_NAME
496    assert actual.flags == TEST_NTLMV2_FLAGS
497    assert actual.server_challenge == TEST_SERVER_CHALLENGE
498    assert len(actual.target_info) == 3
499    assert actual.target_info[messages.AvId.nb_domain_name] == TEST_USER_DOM
500    assert actual.target_info[messages.AvId.nb_computer_name] == TEST_SERVER_NAME
501    assert actual.target_info[messages.AvId.eol] == b""
502    assert actual.version == messages.Version(6, 0, 6000)
503
504
505def test_challenge_unpack_encoding():
506    actual = messages.Challenge.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
507                                       b"\x02\x00\x00\x00"
508                                       b"\x04\x00"
509                                       b"\x04\x00"
510                                       b"\x30\x00\x00\x00"
511                                       b"\x06\x00\x00\x00"
512                                       b"\x11\x11\x11\x11\x11\x11\x11\x11"
513                                       b"\x00\x00\x00\x00\x00\x00\x00\x00"
514                                       b"\x00\x00"
515                                       b"\x00\x00"
516                                       b"\x34\x00\x00\x00"
517                                       b"\x63\x61\x66\xE9")
518
519    assert actual.target_name == "café"
520    assert actual.flags == messages.NegotiateFlags.oem | messages.NegotiateFlags.request_target
521    assert actual.server_challenge == b"\x11" * 8
522    assert actual.target_info is None
523    assert actual.version is None
524
525
526def test_authenticate_pack():
527    authenticate = messages.Authenticate()
528
529    assert authenticate.lm_challenge_response is None
530    assert authenticate.nt_challenge_response is None
531    assert authenticate.domain_name is None
532    assert authenticate.user_name is None
533    assert authenticate.workstation is None
534    assert authenticate.encrypted_random_session_key is None
535    assert authenticate.flags == 0
536    assert authenticate.version is None
537    assert authenticate.mic == b"\x00" * 16
538
539    actual = authenticate.pack()
540
541    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
542                     b"\x03\x00\x00\x00" \
543                     b"\x00\x00" \
544                     b"\x00\x00" \
545                     b"\x50\x00\x00\x00" \
546                     b"\x00\x00" \
547                     b"\x00\x00" \
548                     b"\x50\x00\x00\x00" \
549                     b"\x00\x00" \
550                     b"\x00\x00" \
551                     b"\x50\x00\x00\x00" \
552                     b"\x00\x00" \
553                     b"\x00\x00" \
554                     b"\x50\x00\x00\x00" \
555                     b"\x00\x00" \
556                     b"\x00\x00" \
557                     b"\x50\x00\x00\x00" \
558                     b"\x00\x00" \
559                     b"\x00\x00" \
560                     b"\x50\x00\x00\x00" \
561                     b"\x00\x00\x00\x00" \
562                     b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
563
564
565def test_authenticate_pack_domain_version():
566    authenticate = messages.Authenticate(flags=messages.NegotiateFlags.unicode, domain_name="café",
567                                         version=messages.Version(1, 1, 1))
568
569    assert authenticate.lm_challenge_response is None
570    assert authenticate.nt_challenge_response is None
571    assert authenticate.domain_name == "café"
572    assert authenticate.user_name is None
573    assert authenticate.workstation is None
574    assert authenticate.encrypted_random_session_key is None
575    assert authenticate.flags == messages.NegotiateFlags.version | messages.NegotiateFlags.unicode
576    assert authenticate.mic == b"\x00" * 16
577    assert authenticate.version == messages.Version(1, 1, 1)
578
579    actual = authenticate.pack()
580
581    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
582                     b"\x03\x00\x00\x00" \
583                     b"\x00\x00" \
584                     b"\x00\x00" \
585                     b"\x58\x00\x00\x00" \
586                     b"\x00\x00" \
587                     b"\x00\x00" \
588                     b"\x58\x00\x00\x00" \
589                     b"\x08\x00" \
590                     b"\x08\x00" \
591                     b"\x58\x00\x00\x00" \
592                     b"\x00\x00" \
593                     b"\x00\x00" \
594                     b"\x60\x00\x00\x00" \
595                     b"\x00\x00" \
596                     b"\x00\x00" \
597                     b"\x60\x00\x00\x00" \
598                     b"\x00\x00" \
599                     b"\x00\x00" \
600                     b"\x60\x00\x00\x00" \
601                     b"\x01\x00\x00\x02" \
602                     b"\x01\x01\x01\x00\x00\x00\x00\x0F" \
603                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
604                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
605                     b"\x63\x00\x61\x00\x66\x00\xE9\x00"
606
607
608def test_authenticate_pack_oem_encoding():
609    authenticate = messages.Authenticate(flags=messages.NegotiateFlags.oem, domain_name="café",
610                                         version=messages.Version(1, 1, 1))
611
612    assert authenticate.lm_challenge_response is None
613    assert authenticate.nt_challenge_response is None
614    assert authenticate.domain_name == "café"
615    assert authenticate.user_name is None
616    assert authenticate.workstation is None
617    assert authenticate.encrypted_random_session_key is None
618    assert authenticate.flags == messages.NegotiateFlags.version | messages.NegotiateFlags.oem
619    assert authenticate.mic == b"\x00" * 16
620    assert authenticate.version == messages.Version(1, 1, 1)
621
622    actual = authenticate.pack()
623
624    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
625                     b"\x03\x00\x00\x00" \
626                     b"\x00\x00" \
627                     b"\x00\x00" \
628                     b"\x58\x00\x00\x00" \
629                     b"\x00\x00" \
630                     b"\x00\x00" \
631                     b"\x58\x00\x00\x00" \
632                     b"\x04\x00" \
633                     b"\x04\x00" \
634                     b"\x58\x00\x00\x00" \
635                     b"\x00\x00" \
636                     b"\x00\x00" \
637                     b"\x5C\x00\x00\x00" \
638                     b"\x00\x00" \
639                     b"\x00\x00" \
640                     b"\x5C\x00\x00\x00" \
641                     b"\x00\x00" \
642                     b"\x00\x00" \
643                     b"\x5C\x00\x00\x00" \
644                     b"\x02\x00\x00\x02" \
645                     b"\x01\x01\x01\x00\x00\x00\x00\x0F" \
646                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
647                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
648                     b"\x63\x61\x66\xE9"
649
650
651def test_authenticate_set_flags():
652    authenticate = messages.Authenticate()
653    authenticate.flags = 1
654
655    actual = authenticate.pack()
656
657    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
658                     b"\x03\x00\x00\x00" \
659                     b"\x00\x00" \
660                     b"\x00\x00" \
661                     b"\x50\x00\x00\x00" \
662                     b"\x00\x00" \
663                     b"\x00\x00" \
664                     b"\x50\x00\x00\x00" \
665                     b"\x00\x00" \
666                     b"\x00\x00" \
667                     b"\x50\x00\x00\x00" \
668                     b"\x00\x00" \
669                     b"\x00\x00" \
670                     b"\x50\x00\x00\x00" \
671                     b"\x00\x00" \
672                     b"\x00\x00" \
673                     b"\x50\x00\x00\x00" \
674                     b"\x00\x00" \
675                     b"\x00\x00" \
676                     b"\x50\x00\x00\x00" \
677                     b"\x01\x00\x00\x00" \
678                     b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
679
680
681def test_authenticate_set_mic():
682    authenticate = messages.Authenticate()
683    authenticate.mic = b"\xFF" * 16
684
685    actual = authenticate.pack()
686
687    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
688                     b"\x03\x00\x00\x00" \
689                     b"\x00\x00" \
690                     b"\x00\x00" \
691                     b"\x50\x00\x00\x00" \
692                     b"\x00\x00" \
693                     b"\x00\x00" \
694                     b"\x50\x00\x00\x00" \
695                     b"\x00\x00" \
696                     b"\x00\x00" \
697                     b"\x50\x00\x00\x00" \
698                     b"\x00\x00" \
699                     b"\x00\x00" \
700                     b"\x50\x00\x00\x00" \
701                     b"\x00\x00" \
702                     b"\x00\x00" \
703                     b"\x50\x00\x00\x00" \
704                     b"\x00\x00" \
705                     b"\x00\x00" \
706                     b"\x50\x00\x00\x00" \
707                     b"\x00\x00\x00\x00" \
708                     b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
709
710
711def test_authenticate_set_encrypted_session_key():
712    authenticate = messages.Authenticate(encrypted_session_key=b"\x01")
713
714    actual = authenticate.pack()
715
716    assert actual == b"\x4E\x54\x4C\x4D\x53\x53\x50\x00" \
717                     b"\x03\x00\x00\x00" \
718                     b"\x00\x00" \
719                     b"\x00\x00" \
720                     b"\x50\x00\x00\x00" \
721                     b"\x00\x00" \
722                     b"\x00\x00" \
723                     b"\x50\x00\x00\x00" \
724                     b"\x00\x00" \
725                     b"\x00\x00" \
726                     b"\x50\x00\x00\x00" \
727                     b"\x00\x00" \
728                     b"\x00\x00" \
729                     b"\x50\x00\x00\x00" \
730                     b"\x00\x00" \
731                     b"\x00\x00" \
732                     b"\x50\x00\x00\x00" \
733                     b"\x01\x00" \
734                     b"\x01\x00" \
735                     b"\x50\x00\x00\x00" \
736                     b"\x00\x00\x00\x40" \
737                     b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
738                     b"\x01"
739
740
741def test_authenticate_invalid_mic_length():
742    expected = "NTLM Authenticate MIC must be 16 bytes long"
743
744    with pytest.raises(ValueError, match=expected):
745        messages.Authenticate(mic=b"\x00")
746
747    authenticate = messages.Authenticate()
748    with pytest.raises(ValueError, match=expected):
749        authenticate.mic = b"\x00"
750
751
752def test_authenticate_set_mic_not_present():
753    authenticate = messages.Authenticate.unpack(b"NTLMSSP\x00\x03\x00\x00\x00" + (b"\x00" * 52))
754
755    assert authenticate.mic is None
756
757    with pytest.raises(ValueError, match="Cannot set MIC on an Authenticate message with no MIC present"):
758        authenticate.mic = b"\x11" * 16
759
760
761def test_authenticate_unpack_empty():
762    actual = messages.Authenticate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
763                                          b"\x03\x00\x00\x00"
764                                          b"\x00\x00"
765                                          b"\x00\x00"
766                                          b"\x50\x00\x00\x00"
767                                          b"\x00\x00"
768                                          b"\x00\x00"
769                                          b"\x50\x00\x00\x00"
770                                          b"\x00\x00"
771                                          b"\x00\x00"
772                                          b"\x50\x00\x00\x00"
773                                          b"\x00\x00"
774                                          b"\x00\x00"
775                                          b"\x50\x00\x00\x00"
776                                          b"\x00\x00"
777                                          b"\x00\x00"
778                                          b"\x50\x00\x00\x00"
779                                          b"\x00\x00"
780                                          b"\x00\x00"
781                                          b"\x50\x00\x00\x00"
782                                          b"\x00\x00\x00\x00"
783                                          b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
784
785    assert actual.lm_challenge_response is None
786    assert actual.nt_challenge_response is None
787    assert actual.domain_name is None
788    assert actual.user_name is None
789    assert actual.workstation is None
790    assert actual.encrypted_random_session_key is None
791    assert actual.flags == 0
792    assert actual.version is None
793    assert actual.mic == b"\x00" * 16
794
795
796def test_authenticate_unpack_ntlmv1():
797    # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/ee78f3ad-ae29-4de1-96a0-fe46e64b6e31
798    actual = messages.Authenticate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
799                                          b"\x03\x00\x00\x00"
800                                          b"\x18\x00"
801                                          b"\x18\x00"
802                                          b"\x6C\x00\x00\x00"
803                                          b"\x18\x00"
804                                          b"\x18\x00"
805                                          b"\x84\x00\x00\x00"
806                                          b"\x0C\x00"
807                                          b"\x0C\x00"
808                                          b"\x48\x00\x00\x00"
809                                          b"\x08\x00"
810                                          b"\x08\x00"
811                                          b"\x54\x00\x00\x00"
812                                          b"\x10\x00"
813                                          b"\x10\x00"
814                                          b"\x5C\x00\x00\x00"
815                                          b"\x10\x00"
816                                          b"\x10\x00"
817                                          b"\x9C\x00\x00\x00"
818                                          b"\x35\x82\x80\xE2"
819                                          b"\x05\x01\x28\x0A\x00\x00\x00\x0F"
820                                          b"\x44\x00\x6F\x00\x6D\x00\x61\x00\x69\x00\x6E\x00"
821                                          b"\x55\x00\x73\x00\x65\x00\x72\x00"
822                                          b"\x43\x00\x4F\x00\x4D\x00\x50\x00\x55\x00\x54\x00\x45\x00\x52\x00"
823                                          b"\x98\xDE\xF7\xB8\x7F\x88\xAA\x5D\xAF\xE2\xDF\x77\x96\x88\xA1\x72"
824                                          b"\xDE\xF1\x1C\x7D\x5C\xCD\xEF\x13"
825                                          b"\x67\xC4\x30\x11\xF3\x02\x98\xA2\xAD\x35\xEC\xE6\x4F\x16\x33\x1C"
826                                          b"\x44\xBD\xBE\xD9\x27\x84\x1F\x94"
827                                          b"\x51\x88\x22\xB1\xB3\xF3\x50\xC8\x95\x86\x82\xEC\xBB\x3E\x3C\xB7")
828
829    assert actual.lm_challenge_response == b"\x98\xDE\xF7\xB8\x7F\x88\xAA\x5D" \
830                                           b"\xAF\xE2\xDF\x77\x96\x88\xA1\x72" \
831                                           b"\xDE\xF1\x1C\x7D\x5C\xCD\xEF\x13"
832    assert actual.nt_challenge_response == b"\x67\xC4\x30\x11\xF3\x02\x98\xA2" \
833                                           b"\xAD\x35\xEC\xE6\x4F\x16\x33\x1C" \
834                                           b"\x44\xBD\xBE\xD9\x27\x84\x1F\x94"
835    assert actual.domain_name == TEST_USER_DOM
836    assert actual.user_name == TEST_USER
837    assert actual.workstation == TEST_WORKSTATION_NAME
838    assert actual.encrypted_random_session_key == b"\x51\x88\x22\xB1\xB3\xF3\x50\xC8" \
839                                                  b"\x95\x86\x82\xEC\xBB\x3E\x3C\xB7"
840    assert actual.flags == 3800072757
841    assert actual.mic is None
842    assert actual.version == messages.Version(5, 1, 2600)
843
844
845def test_authenticate_unpack_ntlmv2():
846    # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/bc612491-fb0b-4829-91bc-7c6b95ff67fe
847    actual = messages.Authenticate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
848                                          b"\x03\x00\x00\x00"
849                                          b"\x18\x00"
850                                          b"\x18\x00"
851                                          b"\x6C\x00\x00\x00"
852                                          b"\x54\x00"
853                                          b"\x54\x00"
854                                          b"\x84\x00\x00\x00"
855                                          b"\x0C\x00"
856                                          b"\x0C\x00"
857                                          b"\x48\x00\x00\x00"
858                                          b"\x08\x00"
859                                          b"\x08\x00"
860                                          b"\x54\x00\x00\x00"
861                                          b"\x10\x00"
862                                          b"\x10\x00"
863                                          b"\x5C\x00\x00\x00"
864                                          b"\x10\x00"
865                                          b"\x10\x00"
866                                          b"\xD8\x00\x00\x00"
867                                          b"\x35\x82\x88\xE2"
868                                          b"\x05\x01\x28\x0A\x00\x00\x00\x0F"
869                                          b"\x44\x00\x6F\x00\x6D\x00\x61\x00\x69\x00\x6E\x00"
870                                          b"\x55\x00\x73\x00\x65\x00\x72\x00"
871                                          b"\x43\x00\x4F\x00\x4D\x00\x50\x00\x55\x00\x54\x00\x45\x00\x52\x00"
872                                          b"\x86\xC3\x50\x97\xAC\x9C\xEC\x10\x25\x54\x76\x4A\x57\xCC\xCC\x19"
873                                          b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"
874                                          b"\x68\xCD\x0A\xB8\x51\xE5\x1C\x96\xAA\xBC\x92\x7B\xEB\xEF\x6A\x1C"
875                                          b"\x01"
876                                          b"\x01"
877                                          b"\x00\x00"
878                                          b"\x00\x00\x00\x00"
879                                          b"\x00\x00\x00\x00\x00\x00\x00\x00"
880                                          b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"
881                                          b"\x00\x00\x00\x00"
882                                          b"\x02\x00\x0C\x00\x44\x00\x6F\x00\x6D\x00\x61\x00\x69\x00\x6E\x00"
883                                          b"\x01\x00\x0C\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00"
884                                          b"\x00\x00\x00\x00"
885                                          b"\x00\x00\x00\x00"
886                                          b"\xC5\xDA\xD2\x54\x4F\xC9\x79\x90\x94\xCE\x1C\xE9\x0B\xC9\xD0\x3E")
887
888    assert actual.lm_challenge_response == b"\x86\xC3\x50\x97\xAC\x9C\xEC\x10" \
889                                           b"\x25\x54\x76\x4A\x57\xCC\xCC\x19" \
890                                           b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"
891    assert actual.nt_challenge_response == b"\x68\xCD\x0A\xB8\x51\xE5\x1C\x96" \
892                                           b"\xAA\xBC\x92\x7B\xEB\xEF\x6A\x1C" \
893                                           b"\x01" \
894                                           b"\x01" \
895                                           b"\x00\x00" \
896                                           b"\x00\x00\x00\x00" \
897                                           b"\x00\x00\x00\x00\x00\x00\x00\x00" \
898                                           b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" \
899                                           b"\x00\x00\x00\x00" \
900                                           b"\x02\x00\x0C\x00\x44\x00\x6F\x00\x6D\x00\x61\x00\x69\x00\x6E\x00" \
901                                           b"\x01\x00\x0C\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00" \
902                                           b"\x00\x00\x00\x00" \
903                                           b"\x00\x00\x00\x00"
904    assert actual.domain_name == TEST_USER_DOM
905    assert actual.user_name == TEST_USER
906    assert actual.workstation == TEST_WORKSTATION_NAME
907    assert actual.encrypted_random_session_key == b"\xC5\xDA\xD2\x54\x4F\xC9\x79\x90" \
908                                                  b"\x94\xCE\x1C\xE9\x0B\xC9\xD0\x3E"
909    assert actual.flags == 3800597045
910    assert actual.mic is None
911    assert actual.version == messages.Version(5, 1, 2600)
912
913
914def test_authenticate_unpack_mic_no_version():
915    actual = messages.Authenticate.unpack(b"\x4E\x54\x4C\x4D\x53\x53\x50\x00"
916                                          b"\x03\x00\x00\x00"
917                                          b"\x18\x00"
918                                          b"\x18\x00"
919                                          b"\x50\x00\x00\x00"
920                                          b"\x54\x00"
921                                          b"\x54\x00"
922                                          b"\x68\x00\x00\x00"
923                                          b"\x08\x00"
924                                          b"\x08\x00"
925                                          b"\xBC\x00\x00\x00"
926                                          b"\x08\x00"
927                                          b"\x08\x00"
928                                          b"\xC4\x00\x00\x00"
929                                          b"\x08\x00"
930                                          b"\x08\x00"
931                                          b"\xCC\x00\x00\x00"
932                                          b"\x00\x00"
933                                          b"\x00\x00"
934                                          b"\xD4\x00\x00\x00"
935                                          b"\x31\x82\x8A\xE2"
936                                          b"\x11\x11\x11\x11\x11\x11\x11\x11"
937                                          b"\x11\x11\x11\x11\x11\x11\x11\x11"
938                                          b"\x86\xC3\x50\x97\xAC\x9C\xEC\x10"
939                                          b"\x25\x54\x76\x4A\x57\xCC\xCC\x19"
940                                          b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"
941                                          b"\x68\xCD\x0A\xB8\x51\xE5\x1C\x96"
942                                          b"\xAA\xBC\x92\x7B\xEB\xEF\x6A\x1C"
943                                          b"\x01"
944                                          b"\x01"
945                                          b"\x00\x00"
946                                          b"\x00\x00\x00\x00"
947                                          b"\x00\x00\x00\x00\x00\x00\x00\x00"
948                                          b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"
949                                          b"\x00\x00\x00\x00"
950                                          b"\x02\x00\x0C\x00\x44\x00\x6F\x00"
951                                          b"\x6D\x00\x61\x00\x69\x00\x6E\x00"
952                                          b"\x01\x00\x0C\x00\x53\x00\x65\x00"
953                                          b"\x72\x00\x76\x00\x65\x00\x72\x00"
954                                          b"\x00\x00\x00\x00"
955                                          b"\x00\x00\x00\x00"
956                                          b"\x63\x00\x61\x00\x66\x00\xE9\x00"
957                                          b"\x63\x00\x61\x00\x66\x00\xE9\x00"
958                                          b"\x63\x00\x61\x00\x66\x00\xE9\x00")
959
960    assert actual.lm_challenge_response == b"\x86\xC3\x50\x97\xAC\x9C\xEC\x10" \
961                                           b"\x25\x54\x76\x4A\x57\xCC\xCC\x19" \
962                                           b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA"
963    assert actual.nt_challenge_response == b"\x68\xCD\x0A\xB8\x51\xE5\x1C\x96" \
964                                           b"\xAA\xBC\x92\x7B\xEB\xEF\x6A\x1C" \
965                                           b"\x01" \
966                                           b"\x01" \
967                                           b"\x00\x00" \
968                                           b"\x00\x00\x00\x00" \
969                                           b"\x00\x00\x00\x00\x00\x00\x00\x00" \
970                                           b"\xAA\xAA\xAA\xAA\xAA\xAA\xAA\xAA" \
971                                           b"\x00\x00\x00\x00" \
972                                           b"\x02\x00\x0C\x00\x44\x00\x6F\x00\x6D\x00\x61\x00\x69\x00\x6E\x00" \
973                                           b"\x01\x00\x0C\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00" \
974                                           b"\x00\x00\x00\x00" \
975                                           b"\x00\x00\x00\x00"
976    assert actual.domain_name == "café"
977    assert actual.user_name == "café"
978    assert actual.workstation == "café"
979    assert actual.encrypted_random_session_key is None
980    assert actual.flags == 3800728113
981    assert actual.mic == b"\x11" * 16
982    assert actual.version is None
983
984
985def test_authenticate_invalid_size():
986    with pytest.raises(ValueError, match="Invalid NTLM Authenticate raw byte length"):
987        messages.Authenticate.unpack(b"NTLMSSP\x00\x03\x00\x00\x00")
988
989
990def test_filetime_pack():
991    filetime = messages.FileTime(1970, 1, 1, 0, 0, 0)
992    actual = filetime.pack()
993
994    assert actual == b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01"
995
996
997def test_filetime_unpack():
998    actual = messages.FileTime.unpack(b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01")
999
1000    assert isinstance(actual, messages.FileTime)
1001    assert str(actual) == '1970-01-01T00:00:00Z'
1002    assert actual.year == 1970
1003    assert actual.month == 1
1004    assert actual.day == 1
1005    assert actual.hour == 0
1006    assert actual.minute == 0
1007    assert actual.second == 0
1008    assert actual.microsecond == 0
1009    assert actual.nanosecond == 0
1010
1011
1012def test_filetime_from_datetime_nanoseconds():
1013    filetime = messages.FileTime.from_datetime(datetime.datetime(1970, 1, 1, 0, 0, 0), ns=500)
1014    actual = filetime.pack()
1015
1016    assert str(filetime) == '1970-01-01T00:00:00.0000005Z'
1017    assert filetime.nanosecond == 500
1018
1019    assert actual == b"\x05\x80\x3E\xD5\xDE\xB1\x9D\x01"
1020
1021
1022def test_filetime_now():
1023    current = messages.FileTime.from_datetime(datetime.datetime.now())
1024    now = messages.FileTime.now()
1025
1026    current_int = struct.unpack("<Q", current.pack())[0]
1027    now_int = struct.unpack("<Q", now.pack())[0]
1028
1029    assert now_int >= current_int
1030
1031
1032def test_filetime_with_timezone():
1033    filetime = messages.FileTime(1970, 1, 1, 10, 0, 0, tzinfo=UTC10())
1034
1035    assert str(filetime) == "1970-01-01T10:00:00+10:00"
1036
1037    actual = filetime.pack()  # Should be the same as EPOCH in UTC as FILETIME.
1038    assert actual == b"\x00\x80\x3E\xD5\xDE\xB1\x9D\x01"
1039
1040
1041def test_nt_challenge_pack():
1042    challenge = messages.NTClientChallengeV2()
1043    assert challenge.resp_type == 1
1044    assert challenge.hi_resp_type == 1
1045    assert isinstance(challenge.time_stamp, messages.FileTime)
1046    assert challenge.challenge_from_client == b"\x00" * 8
1047    assert len(challenge.av_pairs) == 1
1048
1049    challenge.time_stamp = messages.FileTime.unpack(b"\x00\x00\x00\x00\x00\x00\x00\x00")
1050
1051    actual = challenge.pack()
1052
1053    assert actual == b"\x01" \
1054                     b"\x01" \
1055                     b"\x00\x00" \
1056                     b"\x00\x00\x00\x00" \
1057                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
1058                     b"\x00\x00\x00\x00\x00\x00\x00\x00" \
1059                     b"\x00\x00\x00\x00" \
1060                     b"\x00\x00\x00\x00"
1061
1062
1063def test_nt_challenge_create():
1064    ft = messages.FileTime.unpack(b"\x11" * 8)
1065    av = messages.TargetInfo()
1066    av[messages.AvId.dns_domain_name] = "test"
1067
1068    actual = messages.NTClientChallengeV2(time_stamp=ft, client_challenge=b"\x11" * 8, av_pairs=av)
1069
1070    assert actual.resp_type == 1
1071    assert actual.hi_resp_type == 1
1072    assert actual.time_stamp.pack() == b"\x11" * 8
1073    assert actual.challenge_from_client == b"\x11" * 8
1074    assert len(actual.av_pairs) == 2
1075    assert actual.av_pairs[messages.AvId.dns_domain_name] == "test"
1076
1077    assert actual.pack() == b"\x01" \
1078                            b"\x01" \
1079                            b"\x00\x00" \
1080                            b"\x00\x00\x00\x00" \
1081                            b"\x11\x11\x11\x11\x11\x11\x11\x11" \
1082                            b"\x11\x11\x11\x11\x11\x11\x11\x11" \
1083                            b"\x00\x00\x00\x00" \
1084                            b"\x04\x00\x08\x00" \
1085                            b"\x74\x00\x65\x00\x73\x00\x74\x00" \
1086                            b"\x00\x00\x00\x00"
1087
1088
1089def test_nt_challenge_unpack():
1090    challenge = messages.NTClientChallengeV2.unpack(b"\x01"
1091                                                    b"\x01"
1092                                                    b"\x00\x00"
1093                                                    b"\x00\x00\x00\x00"
1094                                                    b"\x00\x00\x00\x00\x00\x00\x00\x00"
1095                                                    b"\x00\x00\x00\x00\x00\x00\x00\x00"
1096                                                    b"\x00\x00\x00\x00"
1097                                                    b"\x00\x00\x00\x00")
1098
1099    assert challenge.resp_type == 1
1100    assert challenge.hi_resp_type == 1
1101    assert isinstance(challenge.time_stamp, messages.FileTime)
1102    assert challenge.time_stamp.pack() == b"\x00\x00\x00\x00\x00\x00\x00\x00"
1103    assert challenge.challenge_from_client == b"\x00\x00\x00\x00\x00\x00\x00\x00"
1104    assert isinstance(challenge.av_pairs, messages.TargetInfo)
1105    assert challenge.av_pairs.pack() == b"\x00\x00\x00\x00"
1106
1107
1108def test_nt_challenge_unpack_invalid_size():
1109    with pytest.raises(ValueError, match="Invalid NTClientChallengeV2 raw byte length"):
1110        messages.NTClientChallengeV2.unpack(b"\x00")
1111
1112
1113def test_nt_challenge_resp_type():
1114    challenge = messages.NTClientChallengeV2()
1115    assert challenge.resp_type == 1
1116    challenge.resp_type = 2
1117    assert challenge.resp_type == 2
1118    assert challenge.pack()[:1] == b"\x02"
1119
1120
1121def test_nt_challenge_hi_resp_type():
1122    challenge = messages.NTClientChallengeV2()
1123    assert challenge.hi_resp_type == 1
1124    challenge.hi_resp_type = 2
1125    assert challenge.hi_resp_type == 2
1126    assert challenge.pack()[1:2] == b"\x02"
1127
1128
1129def test_nt_challenge_client_challenge():
1130    challenge = messages.NTClientChallengeV2()
1131    assert challenge.challenge_from_client == b"\x00\x00\x00\x00\x00\x00\x00\x00"
1132    challenge.challenge_from_client = b"\xFF" * 8
1133    assert challenge.challenge_from_client == b"\xFF" * 8
1134    assert challenge.pack()[16:24] == b"\xFF" * 8
1135
1136
1137def test_nt_challenge_client_challenge_bad_length():
1138    expected = "NTClientChallengeV2 ChallengeFromClient must be 8 bytes long"
1139
1140    with pytest.raises(ValueError, match=expected):
1141        messages.NTClientChallengeV2(client_challenge=b"\x00")
1142
1143    challenge = messages.NTClientChallengeV2()
1144    with pytest.raises(ValueError, match=expected):
1145        challenge.challenge_from_client = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1146
1147
1148def test_target_info_pack():
1149    target_info = messages.TargetInfo()
1150    target_info[messages.AvId.timestamp] = b"\x00\x00\x00\x00\x00\x00\x00\x00"
1151    target_info[messages.AvId.single_host] = b"\x00" * 48
1152    target_info[messages.AvId.dns_computer_name] = "caf\u00e9-host"
1153    target_info[messages.AvId.eol] = b""
1154    target_info[messages.AvId.channel_bindings] = b"\xFF" * 16
1155    target_info[messages.AvId.flags] = messages.AvFlags.constrained
1156
1157    actual = target_info.pack()
1158
1159    assert actual == b"\x07\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
1160                     b"\x08\x00\x30\x00" \
1161                     b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
1162                     b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
1163                     b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
1164                     b"\x03\x00\x12\x00" \
1165                     b"\x63\x00\x61\x00\x66\x00\xE9\x00\x2D\x00\x68\x00\x6F\x00\x73\x00" \
1166                     b"\x74\x00" \
1167                     b"\x0A\x00\x10\x00" \
1168                     b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" \
1169                     b"\x06\x00\x04\x00" \
1170                     b"\x01\x00\x00\x00" \
1171                     b"\x00\x00\x00\x00"
1172
1173
1174def test_target_info_unpack():
1175    actual = messages.TargetInfo.unpack(b"\x07\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1176                                        b"\x08\x00\x30\x00"
1177                                        b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1178                                        b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1179                                        b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1180                                        b"\x03\x00\x12\x00"
1181                                        b"\x63\x00\x61\x00\x66\x00\xE9\x00\x2D\x00\x68\x00\x6F\x00\x73\x00"
1182                                        b"\x74\x00"
1183                                        b"\x0A\x00\x10\x00"
1184                                        b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
1185                                        b"\x06\x00\x04\x00"
1186                                        b"\x01\x00\x00\x00"
1187                                        b"\x00\x00\x00\x00")
1188
1189    assert isinstance(actual, messages.TargetInfo)
1190    assert len(actual) == 6
1191    assert actual[messages.AvId.timestamp] == messages.FileTime.unpack(b"\x00" * 8)
1192    assert actual[messages.AvId.single_host] == messages.SingleHost.unpack(b"\x00" * 48)
1193    assert actual[messages.AvId.dns_computer_name] == "caf\u00e9-host"
1194    assert actual[messages.AvId.channel_bindings] == b"\xFF" * 16
1195    assert actual[messages.AvId.flags] == messages.AvFlags.constrained
1196    assert actual[messages.AvId.eol] == b""
1197
1198
1199def test_single_host_pack():
1200    single_host = messages.SingleHost(size=4, z4=0, custom_data=b"\x01" * 8, machine_id=b"\x02" * 32)
1201    actual = single_host.pack()
1202
1203    assert actual == b"\x04\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01" \
1204                     b"\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02" \
1205                     b"\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02"
1206
1207
1208def test_single_host_defaults():
1209    actual = messages.SingleHost()
1210
1211    assert actual.size == 0
1212    assert actual.z4 == 0
1213    assert actual.custom_data == b"\x00" * 8
1214    assert actual.machine_id == b"\x00" * 32
1215
1216
1217def test_single_host_invalid_size():
1218    with pytest.raises(ValueError, match="SingleHost bytes must have a length of 48"):
1219        messages.SingleHost.unpack(b_data=b"\x00")
1220
1221
1222def test_single_host_invalid_custom_data_size():
1223    single_host = messages.SingleHost()
1224
1225    with pytest.raises(ValueError, match="custom_data length must be 8 bytes long"):
1226        single_host.custom_data = b"\x00"
1227
1228
1229def test_single_host_invalid_machine_id_size():
1230    single_host = messages.SingleHost()
1231
1232    with pytest.raises(ValueError, match="machine_id length must be 32 bytes long"):
1233        single_host.machine_id = b"\x00"
1234
1235
1236def test_single_host_unpack():
1237    actual = messages.SingleHost.unpack(b"\x04\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01"
1238                                        b"\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02"
1239                                        b"\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02")
1240
1241    assert isinstance(actual, messages.SingleHost)
1242    assert actual.size == 4
1243    assert actual.z4 == 0
1244    assert actual.custom_data == b"\x01" * 8
1245    assert actual.machine_id == b"\x02" * 32
1246
1247
1248def test_single_host_eq():
1249    assert messages.SingleHost.unpack(b"\x00" * 48) == b"\x00" * 48
1250    assert messages.SingleHost.unpack(b"\x00" * 48) != b"\x11" * 48
1251    assert messages.SingleHost.unpack(b"\x00" * 48) != 1
1252    assert messages.SingleHost.unpack(b"\x00" * 48) == messages.SingleHost.unpack(b"\x00" * 48)
1253
1254
1255def test_version_pack():
1256    version = messages.Version(major=1, minor=2, build=3, revision=4)
1257    assert version.major == 1
1258    assert version.minor == 2
1259    assert version.build == 3
1260    assert version.revision == 4
1261    assert len(version) == 8
1262
1263    version.major = 1
1264    version.minor = 1
1265    version.build = 1
1266    version.revision = 10
1267    actual = version.pack()
1268
1269    assert actual == b"\x01\x01\x01\x00\x00\x00\x00\x0A"
1270
1271
1272def test_version_defaults():
1273    version = messages.Version()
1274
1275    assert version.major == 0
1276    assert version.minor == 0
1277    assert version.build == 0
1278    assert version.reserved == b"\x00\x00\x00"
1279    assert version.revision == 15
1280    assert len(version) == 8
1281
1282
1283def test_version_unpack():
1284    actual = messages.Version.unpack(b"\x01\x01\x01\x00\x00\x00\x00\x0F")
1285
1286    assert isinstance(actual, messages.Version)
1287    assert str(actual) == "1.1.1.15"
1288    assert repr(actual) == "<spnego._ntlm_raw.messages.Version 1.1.1.15>"
1289    assert actual.major == 1
1290    assert actual.minor == 1
1291    assert actual.build == 1
1292    assert actual.reserved == b"\x00\x00\x00"
1293    assert actual.revision == 15
1294    assert len(actual) == 8
1295
1296
1297def test_version_unpack_incorrect_length():
1298    with pytest.raises(ValueError, match="Version bytes must have a length of 8"):
1299        messages.Version.unpack(b"\x00")
1300
1301
1302def test_version_get_current():
1303    actual = messages.Version.get_current()
1304
1305    assert isinstance(actual, messages.Version)
1306    assert len(actual) == 8
1307    assert len(actual.pack()) == 8
1308
1309
1310@pytest.mark.parametrize('version, major, minor, build', [
1311    ('0.1.0.dev1', 0, 1, 0),
1312    ('0.1.0b1', 0, 1, 0),
1313    ('0.1.10b1', 0, 1, 10),
1314    ('0.1.0a1', 0, 1, 0),
1315    ('0.1.0', 0, 1, 0),
1316    ('0', 0, 0, 0),
1317    ('1', 1, 0, 0),
1318    ('1.2', 1, 2, 0),
1319    ('1..', 1, 0, 0),
1320])
1321def test_version_get_current_formats(version, major, minor, build, monkeypatch):
1322    monkeypatch.setattr(messages, 'pyspnego_version', version)
1323
1324    actual = messages.Version.get_current()
1325    assert actual.major == major
1326    assert actual.minor == minor
1327    assert actual.build == build
1328
1329
1330def test_version_eq():
1331    assert messages.Version.get_current() != messages.Version()
1332    assert messages.Version() == b"\x00" * 7 + b"\x0F"
1333    assert messages.Version() != b"\x11" * 8
1334    assert messages.Version() != 1
1335    assert messages.Version.get_current() == messages.Version.get_current()
1336