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