Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/scapy/layers/spnego.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

514 statements  

1# SPDX-License-Identifier: GPL-2.0-only 

2# This file is part of Scapy 

3# See https://scapy.net/ for more information 

4# Copyright (C) Gabriel Potter <gabriel[]potter[]fr> 

5 

6""" 

7SPNEGO 

8 

9Implements parts of: 

10 

11- GSSAPI SPNEGO: RFC4178 > RFC2478 

12- [MS-SPNG]: Microsoft's SPNEGO extensions + "LF" (Late Fallback mechanism) 

13- GSSAPI SPNEGO NEGOEX: [MS-NEGOEX] 

14 

15.. note:: 

16 You will find more complete documentation for this layer over at 

17 `GSSAPI <https://scapy.readthedocs.io/en/latest/layers/gssapi.html#spnego>`_ 

18""" 

19 

20import os 

21import struct 

22from uuid import UUID 

23 

24from scapy.asn1.asn1 import ( 

25 ASN1_Codecs, 

26 ASN1_OID, 

27 ASN1_GENERAL_STRING, 

28) 

29from scapy.asn1.mib import conf # loads conf.mib 

30from scapy.asn1fields import ( 

31 ASN1F_CHOICE, 

32 ASN1F_ENUMERATED, 

33 ASN1F_FLAGS, 

34 ASN1F_GENERAL_STRING, 

35 ASN1F_OID, 

36 ASN1F_optional, 

37 ASN1F_PACKET, 

38 ASN1F_SEQUENCE_OF, 

39 ASN1F_SEQUENCE, 

40 ASN1F_STRING_ENCAPS, 

41 ASN1F_STRING, 

42) 

43from scapy.asn1packet import ASN1_Packet 

44from scapy.consts import WINDOWS 

45from scapy.fields import ( 

46 FieldListField, 

47 LEIntEnumField, 

48 LEIntField, 

49 LELongEnumField, 

50 LELongField, 

51 LEShortField, 

52 MultipleTypeField, 

53 PacketField, 

54 PacketListField, 

55 StrField, 

56 StrFixedLenField, 

57 UUIDEnumField, 

58 UUIDField, 

59 XStrFixedLenField, 

60 XStrLenField, 

61) 

62from scapy.error import log_runtime 

63from scapy.packet import Packet, bind_layers 

64from scapy.utils import ( 

65 valid_ip, 

66 valid_ip6, 

67) 

68 

69from scapy.layers.gssapi import ( 

70 _GSSAPI_OIDS, 

71 _GSSAPI_SIGNATURE_OIDS, 

72 GSS_C_FLAGS, 

73 GSS_C_NO_CHANNEL_BINDINGS, 

74 GSS_S_BAD_MECH, 

75 GSS_S_BAD_MIC, 

76 GSS_S_COMPLETE, 

77 GSS_S_CONTINUE_NEEDED, 

78 GSS_S_FAILURE, 

79 GSS_S_FLAGS, 

80 GSSAPI_BLOB_SIGNATURE, 

81 GSSAPI_BLOB, 

82 GssChannelBindings, 

83 SSP, 

84) 

85 

86# SSP Providers 

87from scapy.layers.kerberos import ( 

88 Kerberos, 

89 KerberosSSP, 

90 _parse_spn, 

91 _parse_upn, 

92) 

93from scapy.layers.ntlm import ( 

94 NTLMSSP, 

95 MD4le, 

96 NEGOEX_EXCHANGE_NTLM, 

97 NTLM_Header, 

98 _NTLMPayloadField, 

99 _NTLMPayloadPacket, 

100) 

101 

102# Typing imports 

103from typing import ( 

104 Dict, 

105 List, 

106 Optional, 

107 Tuple, 

108) 

109 

110# SPNEGO negTokenInit 

111# https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.1 

112 

113 

114class SPNEGO_MechType(ASN1_Packet): 

115 ASN1_codec = ASN1_Codecs.BER 

116 ASN1_root = ASN1F_OID("oid", None) 

117 

118 

119class SPNEGO_MechTypes(ASN1_Packet): 

120 ASN1_codec = ASN1_Codecs.BER 

121 ASN1_root = ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType) 

122 

123 

124class SPNEGO_MechListMIC(ASN1_Packet): 

125 ASN1_codec = ASN1_Codecs.BER 

126 ASN1_root = ASN1F_STRING_ENCAPS("value", "", GSSAPI_BLOB_SIGNATURE) 

127 

128 

129_mechDissector = { 

130 "1.3.6.1.4.1.311.2.2.10": NTLM_Header, # NTLM 

131 "1.2.840.48018.1.2.2": Kerberos, # MS KRB5 - Microsoft Kerberos 5 

132 "1.2.840.113554.1.2.2": Kerberos, # Kerberos 5 

133 "1.2.840.113554.1.2.2.3": Kerberos, # Kerberos 5 - User to User 

134 "1.3.6.1.5.2.5": Kerberos, # Kerberos 5 - IAKERB 

135} 

136 

137 

138class _SPNEGO_Token_Field(ASN1F_STRING): 

139 def i2m(self, pkt, x): 

140 if x is None: 

141 x = b"" 

142 return super(_SPNEGO_Token_Field, self).i2m(pkt, bytes(x)) 

143 

144 def m2i(self, pkt, s): 

145 dat, r = super(_SPNEGO_Token_Field, self).m2i(pkt, s) 

146 types = None 

147 if isinstance(pkt.underlayer, SPNEGO_negTokenInit): 

148 types = pkt.underlayer.mechTypes 

149 elif isinstance(pkt.underlayer, SPNEGO_negTokenResp): 

150 types = [pkt.underlayer.supportedMech] 

151 if types and types[0] and types[0].oid.val in _mechDissector: 

152 return _mechDissector[types[0].oid.val](dat.val), r 

153 else: 

154 # Use heuristics 

155 return GSSAPI_BLOB(dat.val), r 

156 

157 

158class SPNEGO_Token(ASN1_Packet): 

159 ASN1_codec = ASN1_Codecs.BER 

160 ASN1_root = _SPNEGO_Token_Field("value", None) 

161 

162 

163_ContextFlags = [ 

164 "delegFlag", 

165 "mutualFlag", 

166 "replayFlag", 

167 "sequenceFlag", 

168 "superseded", 

169 "anonFlag", 

170 "confFlag", 

171 "integFlag", 

172] 

173 

174 

175class SPNEGO_negHints(ASN1_Packet): 

176 # [MS-SPNG] 2.2.1 

177 ASN1_codec = ASN1_Codecs.BER 

178 ASN1_root = ASN1F_SEQUENCE( 

179 ASN1F_optional( 

180 ASN1F_GENERAL_STRING( 

181 "hintName", "not_defined_in_RFC4178@please_ignore", explicit_tag=0xA0 

182 ), 

183 ), 

184 ASN1F_optional( 

185 ASN1F_GENERAL_STRING("hintAddress", None, explicit_tag=0xA1), 

186 ), 

187 ) 

188 

189 

190class SPNEGO_negTokenInit(ASN1_Packet): 

191 ASN1_codec = ASN1_Codecs.BER 

192 ASN1_root = ASN1F_SEQUENCE( 

193 ASN1F_optional( 

194 ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType, explicit_tag=0xA0) 

195 ), 

196 ASN1F_optional(ASN1F_FLAGS("reqFlags", None, _ContextFlags, implicit_tag=0x81)), 

197 ASN1F_optional( 

198 ASN1F_PACKET("mechToken", None, SPNEGO_Token, explicit_tag=0xA2), 

199 ), 

200 # [MS-SPNG] flavor ! 

201 ASN1F_optional( 

202 ASN1F_PACKET("negHints", None, SPNEGO_negHints, explicit_tag=0xA3) 

203 ), 

204 ASN1F_optional( 

205 ASN1F_PACKET("mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA4) 

206 ), 

207 # Compat with RFC 4178's SPNEGO_negTokenInit 

208 ASN1F_optional( 

209 ASN1F_PACKET("_mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA3) 

210 ), 

211 ) 

212 

213 

214# SPNEGO negTokenTarg 

215# https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.2 

216 

217 

218class SPNEGO_negTokenResp(ASN1_Packet): 

219 ASN1_codec = ASN1_Codecs.BER 

220 ASN1_root = ASN1F_SEQUENCE( 

221 ASN1F_optional( 

222 ASN1F_ENUMERATED( 

223 "negState", 

224 0, 

225 { 

226 0: "accept-completed", 

227 1: "accept-incomplete", 

228 2: "reject", 

229 3: "request-mic", 

230 }, 

231 explicit_tag=0xA0, 

232 ), 

233 ), 

234 ASN1F_optional( 

235 ASN1F_PACKET( 

236 "supportedMech", SPNEGO_MechType(), SPNEGO_MechType, explicit_tag=0xA1 

237 ), 

238 ), 

239 ASN1F_optional( 

240 ASN1F_PACKET("responseToken", None, SPNEGO_Token, explicit_tag=0xA2) 

241 ), 

242 ASN1F_optional( 

243 ASN1F_PACKET("mechListMIC", None, SPNEGO_MechListMIC, explicit_tag=0xA3) 

244 ), 

245 # [MS-SPNG] Late Fallback Mechanism 

246 ASN1F_optional( 

247 ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType, explicit_tag=0xA4) 

248 ), 

249 ) 

250 

251 

252class SPNEGO_negToken(ASN1_Packet): 

253 ASN1_codec = ASN1_Codecs.BER 

254 ASN1_root = ASN1F_CHOICE( 

255 "token", 

256 SPNEGO_negTokenInit(), 

257 ASN1F_PACKET( 

258 "negTokenInit", 

259 SPNEGO_negTokenInit(), 

260 SPNEGO_negTokenInit, 

261 explicit_tag=0xA0, 

262 ), 

263 ASN1F_PACKET( 

264 "negTokenResp", 

265 SPNEGO_negTokenResp(), 

266 SPNEGO_negTokenResp, 

267 explicit_tag=0xA1, 

268 ), 

269 ) 

270 

271 

272# Register for the GSS API Blob 

273 

274_GSSAPI_OIDS["1.3.6.1.5.5.2"] = SPNEGO_negToken 

275_GSSAPI_SIGNATURE_OIDS["1.3.6.1.5.5.2"] = SPNEGO_negToken 

276 

277 

278def mechListMIC(oids): 

279 """ 

280 Implementation of RFC 4178 - Appendix D. mechListMIC Computation 

281 

282 NOTE: The documentation on mechListMIC isn't super clear, so note that: 

283 

284 - The mechListMIC that the client sends is computed over the 

285 list of mechanisms that it requests. 

286 - the mechListMIC that the server sends is computed over the 

287 list of mechanisms that the client requested. 

288 

289 This also means that NegTokenInit2 added by [MS-SPNG] is NOT protected. 

290 That's not necessarily an issue, since it was optional in most cases, 

291 but it's something to keep in mind. 

292 """ 

293 return bytes(SPNEGO_MechTypes(mechTypes=oids)) 

294 

295 

296# NEGOEX 

297# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-negoex/0ad7a003-ab56-4839-a204-b555ca6759a2 

298 

299 

300_NEGOEX_AUTH_SCHEMES = { 

301 # Reversed. Is there any doc related to this? 

302 # The NEGOEX doc is very ellusive 

303 UUID("5c33530d-eaf9-0d4d-b2ec-4ae3786ec308"): "UUID('[NTLM-UUID]')", 

304} 

305 

306 

307class NEGOEX_MESSAGE_HEADER(Packet): 

308 fields_desc = [ 

309 StrFixedLenField("Signature", "NEGOEXTS", length=8), 

310 LEIntEnumField( 

311 "MessageType", 

312 0, 

313 { 

314 0x0: "INITIATOR_NEGO", 

315 0x01: "ACCEPTOR_NEGO", 

316 0x02: "INITIATOR_META_DATA", 

317 0x03: "ACCEPTOR_META_DATA", 

318 0x04: "CHALLENGE", 

319 0x05: "AP_REQUEST", 

320 0x06: "VERIFY", 

321 0x07: "ALERT", 

322 }, 

323 ), 

324 LEIntField("SequenceNum", 0), 

325 LEIntField("cbHeaderLength", None), 

326 LEIntField("cbMessageLength", None), 

327 UUIDField("ConversationId", None), 

328 ] 

329 

330 def post_build(self, pkt, pay): 

331 if self.cbHeaderLength is None: 

332 pkt = pkt[16:] + struct.pack("<I", len(pkt)) + pkt[20:] 

333 if self.cbMessageLength is None: 

334 pkt = pkt[20:] + struct.pack("<I", len(pkt) + len(pay)) + pkt[24:] 

335 return pkt + pay 

336 

337 

338def _NEGOEX_post_build(self, p, pay_offset, fields): 

339 # type: (Packet, bytes, int, Dict[str, Tuple[str, int]]) -> bytes 

340 """Util function to build the offset and populate the lengths""" 

341 for field_name, value in self.fields["Payload"]: 

342 length = self.get_field("Payload").fields_map[field_name].i2len(self, value) 

343 count = self.get_field("Payload").fields_map[field_name].i2count(self, value) 

344 offset = fields[field_name] 

345 # Offset 

346 if self.getfieldval(field_name + "BufferOffset") is None: 

347 p = p[:offset] + struct.pack("<I", pay_offset) + p[offset + 4 :] 

348 # Count 

349 if self.getfieldval(field_name + "Count") is None: 

350 p = p[: offset + 4] + struct.pack("<H", count) + p[offset + 6 :] 

351 pay_offset += length 

352 return p 

353 

354 

355class NEGOEX_BYTE_VECTOR(Packet): 

356 fields_desc = [ 

357 LEIntField("ByteArrayBufferOffset", 0), 

358 LEIntField("ByteArrayLength", 0), 

359 ] 

360 

361 def guess_payload_class(self, payload): 

362 return conf.padding_layer 

363 

364 

365class NEGOEX_EXTENSION_VECTOR(Packet): 

366 fields_desc = [ 

367 LELongField("ExtensionArrayOffset", 0), 

368 LEShortField("ExtensionCount", 0), 

369 ] 

370 

371 

372class NEGOEX_NEGO_MESSAGE(_NTLMPayloadPacket): 

373 OFFSET = 92 

374 show_indent = 0 

375 fields_desc = [ 

376 NEGOEX_MESSAGE_HEADER, 

377 XStrFixedLenField("Random", b"", length=32), 

378 LELongField("ProtocolVersion", 0), 

379 LEIntField("AuthSchemeBufferOffset", None), 

380 LEShortField("AuthSchemeCount", None), 

381 LEIntField("ExtensionBufferOffset", None), 

382 LEShortField("ExtensionCount", None), 

383 # Payload 

384 _NTLMPayloadField( 

385 "Payload", 

386 OFFSET, 

387 [ 

388 FieldListField( 

389 "AuthScheme", 

390 [], 

391 UUIDEnumField("", None, _NEGOEX_AUTH_SCHEMES), 

392 count_from=lambda pkt: pkt.AuthSchemeCount, 

393 ), 

394 PacketListField( 

395 "Extension", 

396 [], 

397 NEGOEX_EXTENSION_VECTOR, 

398 count_from=lambda pkt: pkt.ExtensionCount, 

399 ), 

400 ], 

401 length_from=lambda pkt: pkt.cbMessageLength - 92, 

402 ), 

403 # TODO: dissect extensions 

404 ] 

405 

406 def post_build(self, pkt, pay): 

407 # type: (bytes, bytes) -> bytes 

408 return ( 

409 _NEGOEX_post_build( 

410 self, 

411 pkt, 

412 self.OFFSET, 

413 { 

414 "AuthScheme": 96, 

415 "Extension": 102, 

416 }, 

417 ) 

418 + pay 

419 ) 

420 

421 @classmethod 

422 def dispatch_hook(cls, _pkt=None, *args, **kargs): 

423 if _pkt and len(_pkt) >= 12: 

424 MessageType = struct.unpack("<I", _pkt[8:12])[0] 

425 if MessageType in [0, 1]: 

426 return NEGOEX_NEGO_MESSAGE 

427 elif MessageType in [2, 3]: 

428 return NEGOEX_EXCHANGE_MESSAGE 

429 return cls 

430 

431 

432# RFC3961 

433_checksum_types = { 

434 1: "CRC32", 

435 2: "RSA-MD4", 

436 3: "RSA-MD4-DES", 

437 4: "DES-MAC", 

438 5: "DES-MAC-K", 

439 6: "RSA-MDA-DES-K", 

440 7: "RSA-MD5", 

441 8: "RSA-MD5-DES", 

442 9: "RSA-MD5-DES3", 

443 10: "SHA1", 

444 12: "HMAC-SHA1-DES3-KD", 

445 13: "HMAC-SHA1-DES3", 

446 14: "SHA1", 

447 15: "HMAC-SHA1-96-AES128", 

448 16: "HMAC-SHA1-96-AES256", 

449} 

450 

451 

452def _checksum_size(pkt): 

453 if pkt.ChecksumType == 1: 

454 return 4 

455 elif pkt.ChecksumType in [2, 4, 6, 7]: 

456 return 16 

457 elif pkt.ChecksumType in [3, 8, 9]: 

458 return 24 

459 elif pkt.ChecksumType == 5: 

460 return 8 

461 elif pkt.ChecksumType in [10, 12, 13, 14, 15, 16]: 

462 return 20 

463 return 0 

464 

465 

466class NEGOEX_CHECKSUM(Packet): 

467 fields_desc = [ 

468 LELongField("cbHeaderLength", 20), 

469 LELongEnumField("ChecksumScheme", 1, {1: "CHECKSUM_SCHEME_RFC3961"}), 

470 LELongEnumField("ChecksumType", None, _checksum_types), 

471 XStrLenField("ChecksumValue", b"", length_from=_checksum_size), 

472 ] 

473 

474 

475class NEGOEX_EXCHANGE_MESSAGE(_NTLMPayloadPacket): 

476 OFFSET = 64 

477 show_indent = 0 

478 fields_desc = [ 

479 NEGOEX_MESSAGE_HEADER, 

480 UUIDEnumField("AuthScheme", None, _NEGOEX_AUTH_SCHEMES), 

481 LEIntField("ExchangeBufferOffset", 0), 

482 LEIntField("ExchangeLen", 0), 

483 _NTLMPayloadField( 

484 "Payload", 

485 OFFSET, 

486 [ 

487 # The NEGOEX doc mentions the following blob as as an 

488 # "opaque handshake for the client authentication scheme". 

489 # NEGOEX_EXCHANGE_NTLM is a reversed interpretation, and is 

490 # probably not accurate. 

491 MultipleTypeField( 

492 [ 

493 ( 

494 PacketField("Exchange", None, NEGOEX_EXCHANGE_NTLM), 

495 lambda pkt: pkt.AuthScheme 

496 == UUID("5c33530d-eaf9-0d4d-b2ec-4ae3786ec308"), 

497 ), 

498 ], 

499 StrField("Exchange", b""), 

500 ) 

501 ], 

502 length_from=lambda pkt: pkt.cbMessageLength - pkt.cbHeaderLength, 

503 ), 

504 ] 

505 

506 

507class NEGOEX_VERIFY_MESSAGE(Packet): 

508 show_indent = 0 

509 fields_desc = [ 

510 NEGOEX_MESSAGE_HEADER, 

511 UUIDEnumField("AuthScheme", None, _NEGOEX_AUTH_SCHEMES), 

512 PacketField("Checksum", NEGOEX_CHECKSUM(), NEGOEX_CHECKSUM), 

513 ] 

514 

515 

516bind_layers(NEGOEX_NEGO_MESSAGE, NEGOEX_NEGO_MESSAGE) 

517 

518 

519_mechDissector["1.3.6.1.4.1.311.2.2.30"] = NEGOEX_NEGO_MESSAGE 

520 

521# -- SSP 

522 

523 

524class SPNEGOSSP(SSP): 

525 """ 

526 The SPNEGO SSP 

527 

528 :param ssps: a dict with keys being the SSP class, and the value being a 

529 dictionary of the keyword arguments to pass it on init. 

530 

531 Example:: 

532 

533 from scapy.layers.ntlm import NTLMSSP 

534 from scapy.layers.kerberos import KerberosSSP 

535 from scapy.layers.spnego import SPNEGOSSP 

536 from scapy.layers.smbserver import smbserver 

537 from scapy.libs.rfc3961 import Encryption, Key 

538 

539 ssp = SPNEGOSSP([ 

540 NTLMSSP( 

541 IDENTITIES={ 

542 "User1": MD4le("Password1"), 

543 "Administrator": MD4le("Password123!"), 

544 } 

545 ), 

546 KerberosSSP( 

547 SPN="cifs/server2.domain.local", 

548 KEY=Key( 

549 Encryption.AES256, 

550 key=hex_bytes("5e9255c907b2f7e969ddad816eabbec8f1f7a387c7194ecc98b827bdc9421c2b") 

551 ) 

552 ) 

553 ]) 

554 smbserver(ssp=ssp) 

555 """ 

556 

557 __slots__ = [ 

558 "ssps", 

559 "SUPPORT_LATE_FALLBACK", 

560 ] 

561 

562 auth_type = 0x09 

563 

564 class STATE(SSP.STATE): 

565 FIRST = 1 

566 SUBSEQUENT = 2 

567 MICONLY = 3 

568 

569 class CONTEXT(SSP.CONTEXT): 

570 __slots__ = [ 

571 "req_flags", 

572 "IsAcceptor", 

573 "ssps", 

574 "server_mechtypes", 

575 "client_mechtypes", 

576 "all_mechtypes", 

577 "first_choice", 

578 "require_mic", 

579 "verified_mic", 

580 "late_fallback_supported", 

581 "late_fallback_negotiated", 

582 "ssp", 

583 "ssp_context", 

584 "ssp_mechtype", 

585 "raw", 

586 ] 

587 

588 def __init__( 

589 self, 

590 IsAcceptor: bool, 

591 ssps: List[SSP], 

592 late_fallback_supported: bool, 

593 req_flags=None, 

594 ): 

595 self.state = SPNEGOSSP.STATE.FIRST 

596 self.req_flags = req_flags 

597 self.IsAcceptor = IsAcceptor 

598 # Information used during negotiation 

599 self.ssps = ssps 

600 self.server_mechtypes = None # the mechtypes the server requested 

601 self.client_mechtypes = None # the mechtypes the client requested 

602 self.all_mechtypes = [] # all mechtypes of the exchange, for mechListMIC 

603 self.first_choice = True # whether the SSP was the peer's first choice 

604 self.require_mic = False # whether the mechListMIC is required or not 

605 self.verified_mic = False # whether mechListMIC has been verified 

606 self.late_fallback_supported = late_fallback_supported 

607 self.late_fallback_negotiated = False # [MS-SPNG] exclusive 

608 # Information about the currently selected SSP 

609 self.ssp = None 

610 self.ssp_context = None 

611 self.ssp_mechtype = None 

612 self.raw = False # fallback to raw SSP 

613 super(SPNEGOSSP.CONTEXT, self).__init__() 

614 

615 # This is the order Windows chooses 

616 _PREF_ORDER = [ 

617 "1.2.840.48018.1.2.2", # MS KRB5 

618 "1.2.840.113554.1.2.2", # Kerberos 5 

619 "1.3.6.1.5.2.5", # Kerberos 5 - IAKERB 

620 "1.3.6.1.4.1.311.2.2.30", # NEGOEX 

621 "1.2.840.113554.1.2.2.3", # Kerberos 5 - User to User 

622 "1.3.6.1.4.1.311.2.2.10", # NTLM 

623 "1.3.6.1.4.1.311.2.2.40", # Late Fallback 

624 ] 

625 

626 def get_supported_mechtypes(self): 

627 """ 

628 Return an ordered list of mechtypes that are still available. 

629 """ 

630 # 1. Build mech list 

631 mechs = [] 

632 for ssp in self.ssps: 

633 mechs.extend(ssp.GSS_Inquire_names_for_mech()) 

634 

635 # Windows 24H2 / Server 2025+ 

636 if self.late_fallback_supported: 

637 mechs.append("1.3.6.1.4.1.311.2.2.40") # SPNEGO Late Fallback 

638 

639 # 2. Sort according to the selected SSP, then the preference order 

640 selected_mech_oids = ( 

641 self.ssp.GSS_Inquire_names_for_mech() if self.ssp else [] 

642 ) 

643 mechs.sort( 

644 key=lambda x: (x not in selected_mech_oids, self._PREF_ORDER.index(x)) 

645 ) 

646 

647 # 4. Return wrapped in MechType 

648 return [SPNEGO_MechType(oid=ASN1_OID(oid)) for oid in mechs] 

649 

650 def get_mechListMIC(self): 

651 """ 

652 Return the binary used for mechListMIC 

653 """ 

654 # See help(mechListMIC) for more details 

655 

656 # [MS-SPNG] sect 3.1.5.1 - Late Fallback 

657 # "When Negotiate Late Fallback is supported by both parties, 

658 # mechListMIC consumes a list of all exchanged mechTypes and 

659 # supportedMechs per the order of over-the-wire transmission 

660 # with delimiters" 

661 if self.late_fallback_negotiated: 

662 mechtypes = self.all_mechtypes 

663 else: 

664 mechtypes = self.client_mechtypes 

665 

666 return mechListMIC(mechtypes) 

667 

668 def negotiate_ssp(self) -> None: 

669 """ 

670 Perform SSP negotiation. 

671 

672 This updates our context and sets it with the first SSP that is 

673 common to both client and server. This also applies rules from 

674 [MS-SPNG] and RFC4178 to determine if mechListMIC is required. 

675 

676 If the ssp is already negotiated, this only updates meta attributes. 

677 """ 

678 if not self.IsAcceptor: 

679 other_mechtypes = self.server_mechtypes 

680 else: 

681 other_mechtypes = self.client_mechtypes 

682 

683 if other_mechtypes is not None: 

684 other_oids = [x.oid.val for x in other_mechtypes] 

685 

686 # See if the peer supports SPNEGO Late Fallback 

687 if ( 

688 self.late_fallback_supported 

689 and "1.3.6.1.4.1.311.2.2.40" in other_oids 

690 ): 

691 self.late_fallback_negotiated = True 

692 

693 if self.ssp is not None: 

694 # ssp has already been negotiated, return. 

695 return 

696 

697 if other_mechtypes is None: 

698 # We don't have any information about the peer's preferred SSPs. 

699 # This typically happens on client side, when NegTokenInit2 isn't used. 

700 self.ssp = self.ssps[0] 

701 ssp_oid = self.ssp.GSS_Inquire_names_for_mech()[0] 

702 else: 

703 # Find first common SSP 

704 try: 

705 self.ssp, ssp_oid = next( 

706 (ssp, requested_oid) 

707 for requested_oid in other_oids 

708 for ssp in self.ssps 

709 if requested_oid in ssp.GSS_Inquire_names_for_mech() 

710 ) 

711 except StopIteration: 

712 raise ValueError( 

713 "Could not find a common SSP with the remote peer !" 

714 ) 

715 

716 # Check whether the selected SSP was the one preferred by the client 

717 self.first_choice = ssp_oid == other_oids[0] 

718 

719 # Check whether mechListMIC is mandatory for this exchange 

720 if not self.first_choice: 

721 # RFC4178 rules for mechListMIC: mandatory if not the first choice. 

722 self.require_mic = True 

723 elif ssp_oid == "1.3.6.1.4.1.311.2.2.10" and self.ssp.SupportsMechListMIC(): 

724 # [MS-SPNG] note 8: "If NTLM authentication is most preferred by 

725 # the client and the server, and the client includes a MIC in 

726 # AUTHENTICATE_MESSAGE, then the mechListMIC field becomes 

727 # mandatory" 

728 self.require_mic = True 

729 

730 # Get the associated ssp dissection class and mechtype 

731 self.ssp_mechtype = SPNEGO_MechType(oid=ASN1_OID(ssp_oid)) 

732 

733 # Reset the ssp context 

734 self.ssp_context = None 

735 

736 # Passthrough attributes and functions 

737 

738 def clifailure(self): 

739 if self.ssp_context is not None: 

740 self.ssp_context.clifailure() 

741 

742 def __getattr__(self, attr): 

743 try: 

744 return object.__getattribute__(self, attr) 

745 except AttributeError: 

746 return getattr(self.ssp_context, attr) 

747 

748 def __setattr__(self, attr, val): 

749 try: 

750 return object.__setattr__(self, attr, val) 

751 except AttributeError: 

752 return setattr(self.ssp_context, attr, val) 

753 

754 # Passthrough the flags property 

755 

756 @property 

757 def flags(self): 

758 if self.ssp_context: 

759 return self.ssp_context.flags 

760 return GSS_C_FLAGS(0) 

761 

762 @flags.setter 

763 def flags(self, x): 

764 if not self.ssp_context: 

765 return 

766 self.ssp_context.flags = x 

767 

768 def __repr__(self): 

769 return "SPNEGOSSP[%s%s]" % ( 

770 repr(self.ssp_context), 

771 "/LF" if self.late_fallback_negotiated else "", 

772 ) 

773 

774 def __init__( 

775 self, 

776 ssps: List[SSP], 

777 # Disabled by default right now, enable once mainstream. 

778 SUPPORT_LATE_FALLBACK=False, 

779 **kwargs, 

780 ): 

781 self.ssps = ssps 

782 self.SUPPORT_LATE_FALLBACK = SUPPORT_LATE_FALLBACK 

783 super(SPNEGOSSP, self).__init__(**kwargs) 

784 

785 @classmethod 

786 def from_cli_arguments( 

787 cls, 

788 UPN: str, 

789 target: str, 

790 password: str = None, 

791 HashNt: bytes = None, 

792 HashAes256Sha96: bytes = None, 

793 HashAes128Sha96: bytes = None, 

794 kerberos_required: bool = False, 

795 ST=None, 

796 TGT=None, 

797 KEY=None, 

798 ccache: str = None, 

799 debug: int = 0, 

800 use_krb5ccname: bool = False, 

801 use_winssp: bool = False, 

802 ): 

803 """ 

804 Initialize a SPNEGOSSP from a list of many arguments. 

805 

806 This is useful in a CLI, as it will try to build the best SPNEGOSSP 

807 with NTLM and Kerberos based on the various parameters. 

808 

809 :param UPN: the UPN of the user to use. 

810 :param target: the target IP/hostname entered by the user. 

811 :param kerberos_required: require kerberos 

812 :param password: (string) if provided, used for auth 

813 :param HashNt: (bytes) if provided, used for auth (NTLM) 

814 :param HashAes256Sha96: (bytes) if provided, used for auth (Kerberos) 

815 :param HashAes128Sha96: (bytes) if provided, used for auth (Kerberos) 

816 :param ST: if provided, the service ticket to use (Kerberos) 

817 :param TGT: if provided, the TGT to use (Kerberos) 

818 :param KEY: if ST provided, the session key associated to the ticket (Kerberos). 

819 This can be either for the ST or TGT. Else, the user secret key. 

820 :param ccache: (str) if provided, a path to a CCACHE (Kerberos) 

821 :param use_krb5ccname: (bool) if true, the KRB5CCNAME environment variable will 

822 be used if available. 

823 :param use_winssp: (bool) (only works on Windows). Use implicit authentication 

824 through WinSSP. 

825 """ 

826 kerberos = True 

827 domain_auth = True 

828 hostname = None 

829 # Check if target is a hostname / Check IP 

830 if target and ":" in target: 

831 if not valid_ip6(target): 

832 hostname = target 

833 else: 

834 if not valid_ip(target): 

835 hostname = target 

836 

837 # If using WinSSP, this goes fast. 

838 if use_winssp: 

839 if not WINDOWS: 

840 raise OSError("Cannot use WinSSP on a non-Windows computer !") 

841 from scapy.arch.windows.sspi import WinSSP 

842 

843 return WinSSP() 

844 

845 # Check UPN 

846 try: 

847 _, realm = _parse_upn(UPN) 

848 if realm == ".": 

849 # Local 

850 domain_auth = False 

851 except ValueError: 

852 # not a UPN 

853 if hostname is not None: 

854 # Fallback to support IAKERB without a UPN 

855 domain_auth = False 

856 realm = hostname 

857 UPN = f"{UPN}@{realm}" 

858 else: 

859 # NTLM only 

860 kerberos = False 

861 

862 # If we're asked, check the environment for KRB5CCNAME 

863 if use_krb5ccname and ccache is None and "KRB5CCNAME" in os.environ: 

864 ccache = os.environ["KRB5CCNAME"] 

865 

866 # Do we need to ask the password? 

867 if all( 

868 x is None 

869 for x in [ 

870 ST, 

871 password, 

872 HashNt, 

873 HashAes256Sha96, 

874 HashAes128Sha96, 

875 ccache, 

876 ] 

877 ): 

878 # yes. 

879 from prompt_toolkit import prompt 

880 

881 password = prompt("Password: ", is_password=True) 

882 

883 ssps = [] 

884 # Kerberos 

885 if kerberos and hostname: 

886 # Get ticket if we don't already have one. 

887 if ST is None and TGT is None and ccache is not None: 

888 # In this case, load the KerberosSSP from ccache 

889 from scapy.modules.ticketer import Ticketer 

890 

891 # Import into a Ticketer object 

892 t = Ticketer() 

893 t.open_ccache(ccache) 

894 

895 # Look for the ticket that we'll use. We chose: 

896 # - either a ST if the UPN and SPN matches our target 

897 # - or a ST that matches the UPN 

898 # - else a TGT if we got nothing better 

899 tgts = [] 

900 sts = [] 

901 for i, (tkt, key, upn, spn) in t.enumerate_tickets(): 

902 spn, _ = _parse_spn(spn) 

903 spn_host = spn.split("/")[-1] 

904 # Check that it's for the correct user 

905 if upn.lower() == UPN.lower(): 

906 # Check that it's either a TGT or a ST to the correct service 

907 if spn.lower().startswith("krbtgt/"): 

908 # TGT. Keep it, and see if we don't have a better ST. 

909 tgts.append(t.ssp(i)) 

910 elif hostname.lower() == spn_host.lower(): 

911 # ST. UPN and SPN match. We're done ! 

912 ssps.append(t.ssp(i)) 

913 break 

914 else: 

915 # ST. UPN matches, Keep it 

916 sts.append(t.ssp(i)) 

917 else: 

918 # No perfect ticket found 

919 if tgts: 

920 # Using a TGT ! 

921 ssps.append(tgts[0]) 

922 elif sts: 

923 # Using a ST where at least the UPN matched ! 

924 ssps.append(sts[0]) 

925 else: 

926 # Nothing found 

927 t.show() 

928 raise ValueError( 

929 f"Could not find a ticket for {upn}, either a " 

930 f"TGT or towards {hostname}" 

931 ) 

932 elif ST is None and TGT is None: 

933 # In this case, KEY is supposed to be the user's key. 

934 from scapy.libs.rfc3961 import Key, EncryptionType 

935 

936 if KEY is None and HashAes256Sha96: 

937 KEY = Key( 

938 EncryptionType.AES256_CTS_HMAC_SHA1_96, 

939 HashAes256Sha96, 

940 ) 

941 elif KEY is None and HashAes128Sha96: 

942 KEY = Key( 

943 EncryptionType.AES128_CTS_HMAC_SHA1_96, 

944 HashAes128Sha96, 

945 ) 

946 elif KEY is None and HashNt: 

947 KEY = Key( 

948 EncryptionType.RC4_HMAC, 

949 HashNt, 

950 ) 

951 

952 # We have a UPN and secret. This allows to support 3 cases: 

953 # Kerberos, IAKerb and U2U 

954 

955 # Normal Kerberos and U2U are only supported in domain environments 

956 if domain_auth: 

957 ssps.extend( 

958 [ 

959 KerberosSSP( 

960 UPN=UPN, 

961 PASSWORD=password, 

962 KEY=KEY, 

963 debug=debug, 

964 ), 

965 KerberosSSP( 

966 UPN=UPN, 

967 PASSWORD=password, 

968 KEY=KEY, 

969 debug=debug, 

970 U2U=True, 

971 ), 

972 ] 

973 ) 

974 

975 # IAKERB is always supported when Kerberos is active 

976 ssps.append( 

977 KerberosSSP( 

978 UPN=UPN, 

979 PASSWORD=password, 

980 KEY=KEY, 

981 debug=debug, 

982 IAKERB=True, 

983 ) 

984 ) 

985 else: 

986 # We have a ST, use it with the key. 

987 ssps.append( 

988 KerberosSSP( 

989 UPN=UPN, 

990 ST=ST, 

991 TGT=TGT, 

992 KEY=KEY, 

993 debug=debug, 

994 ) 

995 ) 

996 elif kerberos_required: 

997 raise ValueError( 

998 "Kerberos required but domain not specified in the UPN, " 

999 "or target isn't a hostname !" 

1000 ) 

1001 

1002 # NTLM 

1003 if not kerberos_required: 

1004 if HashNt is None and password is not None: 

1005 HashNt = MD4le(password) 

1006 if HashNt is not None: 

1007 ssps.append(NTLMSSP(UPN=UPN, HASHNT=HashNt)) 

1008 

1009 if not ssps: 

1010 raise ValueError("Unexpected case ! Please report.") 

1011 

1012 # Build the SSP 

1013 return cls(ssps) 

1014 

1015 # [MS-SPNG] Late Fallback Mechanism 

1016 _LATE_FALLBACK_INITIATOR = ASN1_OID("0.0.99.99.99.99.99.99.99.99") 

1017 _LATE_FALLBACK_ACCEPTOR = ASN1_OID("0.0.115.115.115.115.115.115.115.115") 

1018 

1019 def NegTokenInit2(self): 

1020 """ 

1021 Server-Initiation of GSSAPI/SPNEGO. 

1022 See [MS-SPNG] sect 3.2.5.2 

1023 """ 

1024 Context = SPNEGOSSP.CONTEXT( 

1025 IsAcceptor=True, 

1026 ssps=list(self.ssps), 

1027 late_fallback_supported=self.SUPPORT_LATE_FALLBACK, 

1028 ) 

1029 Context.server_mechtypes = Context.get_supported_mechtypes() 

1030 return ( 

1031 Context, 

1032 GSSAPI_BLOB( 

1033 innerToken=SPNEGO_negToken( 

1034 token=SPNEGO_negTokenInit( 

1035 mechTypes=Context.server_mechtypes, 

1036 negHints=SPNEGO_negHints( 

1037 hintName=ASN1_GENERAL_STRING( 

1038 "not_defined_in_RFC4178@please_ignore" 

1039 ), 

1040 ), 

1041 ) 

1042 ) 

1043 ), 

1044 ) 

1045 

1046 # NOTE: NegoEX has an effect on how the SecurityContext is 

1047 # initialized, as detailed in [MS-AUTHSOD] sect 3.3.2 

1048 # But the format that the Exchange token uses appears not to 

1049 # be documented :/ 

1050 

1051 # resp.SecurityBlob.innerToken.token.mechTypes.insert( 

1052 # 0, 

1053 # # NEGOEX 

1054 # SPNEGO_MechType(oid="1.3.6.1.4.1.311.2.2.30"), 

1055 # ) 

1056 # resp.SecurityBlob.innerToken.token.mechToken = SPNEGO_Token( 

1057 # value=negoex_token 

1058 # ) # noqa: E501 

1059 

1060 def GSS_WrapEx(self, Context, *args, **kwargs): 

1061 # Passthrough 

1062 return Context.ssp.GSS_WrapEx(Context.ssp_context, *args, **kwargs) 

1063 

1064 def GSS_UnwrapEx(self, Context, *args, **kwargs): 

1065 # Passthrough 

1066 return Context.ssp.GSS_UnwrapEx(Context.ssp_context, *args, **kwargs) 

1067 

1068 def GSS_GetMICEx(self, Context, *args, **kwargs): 

1069 # Passthrough 

1070 return Context.ssp.GSS_GetMICEx(Context.ssp_context, *args, **kwargs) 

1071 

1072 def GSS_VerifyMICEx(self, Context, *args, **kwargs): 

1073 # Passthrough 

1074 return Context.ssp.GSS_VerifyMICEx(Context.ssp_context, *args, **kwargs) 

1075 

1076 def LegsAmount(self, Context: CONTEXT): 

1077 return 4 

1078 

1079 def MapStatusToNegState(self, status: int) -> int: 

1080 """ 

1081 Map a GSSAPI return code to SPNEGO negState codes 

1082 """ 

1083 if status == GSS_S_COMPLETE: 

1084 return 0 # accept_completed 

1085 elif status & GSS_S_CONTINUE_NEEDED: 

1086 return 1 # accept_incomplete 

1087 else: 

1088 return 2 # reject 

1089 

1090 def GuessOtherMechtypes(self, Context: CONTEXT, input_token): 

1091 """ 

1092 Guesses the mechtype of the peer when the "raw" fallback is used. 

1093 """ 

1094 if isinstance(input_token, NTLM_Header): 

1095 other_mechtypes = [SPNEGO_MechType(oid=ASN1_OID("1.3.6.1.4.1.311.2.2.10"))] 

1096 elif isinstance(input_token, Kerberos): 

1097 other_mechtypes = [SPNEGO_MechType(oid=ASN1_OID("1.2.840.48018.1.2.2"))] 

1098 else: 

1099 other_mechtypes = [] 

1100 

1101 if not Context.IsAcceptor: 

1102 Context.server_mechtypes = other_mechtypes 

1103 else: 

1104 Context.client_mechtypes = other_mechtypes 

1105 

1106 def GSS_Init_sec_context( 

1107 self, 

1108 Context: CONTEXT, 

1109 input_token=None, 

1110 target_name: Optional[str] = None, 

1111 req_flags: Optional[GSS_C_FLAGS] = None, 

1112 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, 

1113 ): 

1114 if Context is None: 

1115 # New Context 

1116 Context = SPNEGOSSP.CONTEXT( 

1117 IsAcceptor=False, 

1118 ssps=list(self.ssps), 

1119 late_fallback_supported=self.SUPPORT_LATE_FALLBACK, 

1120 req_flags=req_flags, 

1121 ) 

1122 

1123 input_token_inner = None 

1124 negState = None 

1125 

1126 # Extract values from GSSAPI token, if present 

1127 if input_token is not None: 

1128 if isinstance(input_token, GSSAPI_BLOB): 

1129 input_token = input_token.innerToken 

1130 if isinstance(input_token, SPNEGO_negToken): 

1131 input_token = input_token.token 

1132 if isinstance(input_token, SPNEGO_negTokenInit): 

1133 # We are handling a NegTokenInit2 request ! 

1134 # Populate context with values from the server's request 

1135 Context.server_mechtypes = input_token.mechTypes 

1136 elif isinstance(input_token, SPNEGO_negTokenResp): 

1137 # Extract token and state from the client request 

1138 if input_token.responseToken is not None: 

1139 input_token_inner = input_token.responseToken.value 

1140 if input_token.negState is not None: 

1141 negState = input_token.negState 

1142 

1143 # [MS-SPNG] Late Fallback Mechanism 

1144 if input_token.mechTypes is not None: 

1145 Context.server_mechtypes = input_token.mechTypes 

1146 Context.all_mechtypes.append(self._LATE_FALLBACK_ACCEPTOR) 

1147 Context.all_mechtypes += Context.server_mechtypes 

1148 Context.all_mechtypes += [input_token.supportedMech] 

1149 

1150 if input_token.supportedMech is not None: 

1151 # If the server selected a different SSP than the one we sent, 

1152 # retry negotiating. 

1153 if Context.ssp_mechtype != input_token.supportedMech: 

1154 log_runtime.warning( 

1155 "SPNEGOSSP: %s wasn't selected. " 

1156 "Retrying with next in queue." % repr(Context.ssp) 

1157 ) 

1158 Context.ssp = None 

1159 else: 

1160 # The blob is a raw token. We aren't using SPNEGO here. 

1161 if not Context.raw and ( 

1162 Context.client_mechtypes is not None 

1163 or Context.server_mechtypes is not None 

1164 ): 

1165 return Context, None, GSS_S_BAD_MIC 

1166 Context.raw = True 

1167 input_token_inner = input_token 

1168 self.GuessOtherMechtypes(Context, input_token) 

1169 

1170 # Perform SSP negotiation 

1171 try: 

1172 Context.negotiate_ssp() 

1173 except ValueError as ex: 

1174 # Couldn't find common SSP 

1175 log_runtime.warning("SPNEGOSSP: %s" % ex) 

1176 return Context, None, GSS_S_BAD_MECH 

1177 

1178 if Context.state == SPNEGOSSP.STATE.MICONLY: 

1179 # We have already finished the inner-ssp, and are just doing 

1180 # an extra exchange because we were asked for the mechListMIC. 

1181 if negState == 0: 

1182 return Context, None, GSS_S_COMPLETE 

1183 else: 

1184 return Context, None, GSS_S_BAD_MIC 

1185 

1186 # Call inner-SSP 

1187 Context.ssp_context, output_token_inner, status = ( 

1188 Context.ssp.GSS_Init_sec_context( 

1189 Context.ssp_context, 

1190 input_token=input_token_inner, 

1191 target_name=target_name, 

1192 req_flags=Context.req_flags, 

1193 chan_bindings=chan_bindings, 

1194 ) 

1195 ) 

1196 

1197 if negState == 2 or status not in [GSS_S_COMPLETE, GSS_S_CONTINUE_NEEDED]: 

1198 # SSP failed. Remove it from the list of SSPs we're currently running 

1199 Context.ssps.remove(Context.ssp) 

1200 log_runtime.warning( 

1201 "SPNEGOSSP: %s failed. Retrying with next in queue." % repr(Context.ssp) 

1202 ) 

1203 

1204 if Context.ssps: 

1205 # We have other SSPs remaining. Retry using another one. 

1206 Context.ssp = None 

1207 return self.GSS_Init_sec_context( 

1208 Context, 

1209 None, # No input for retry. 

1210 target_name=target_name, 

1211 req_flags=req_flags, 

1212 chan_bindings=chan_bindings, 

1213 ) 

1214 else: 

1215 # We don't have anything left 

1216 return Context, None, status 

1217 

1218 # Raw processing ends here. 

1219 if Context.raw: 

1220 return Context, output_token_inner, status 

1221 

1222 # Verify MIC if present. 

1223 if status == GSS_S_COMPLETE and input_token and input_token.mechListMIC: 

1224 # NOTE: the mechListMIC that the server sends is computed over the list of 

1225 # mechanisms that the **client requested**. 

1226 Context.ssp.VerifyMechListMIC( 

1227 Context.ssp_context, 

1228 input_token.mechListMIC.value, 

1229 Context.get_mechListMIC(), 

1230 ) 

1231 Context.verified_mic = True 

1232 

1233 if negState == 0 and status == GSS_S_COMPLETE: 

1234 # We are done. 

1235 return Context, None, status 

1236 elif Context.state == SPNEGOSSP.STATE.FIRST: 

1237 # First freeze the list of available mechtypes on the first message 

1238 Context.client_mechtypes = Context.get_supported_mechtypes() 

1239 

1240 # [MS-SPNG] Late Fallback Mechanism 

1241 Context.all_mechtypes.append(self._LATE_FALLBACK_INITIATOR) 

1242 Context.all_mechtypes += Context.client_mechtypes 

1243 

1244 # Now build the token 

1245 spnego_tok = GSSAPI_BLOB( 

1246 innerToken=SPNEGO_negToken( 

1247 token=SPNEGO_negTokenInit(mechTypes=Context.client_mechtypes) 

1248 ) 

1249 ) 

1250 

1251 # Add the output token if provided 

1252 if output_token_inner is not None: 

1253 spnego_tok.innerToken.token.mechToken = SPNEGO_Token( 

1254 value=output_token_inner, 

1255 ) 

1256 elif Context.state == SPNEGOSSP.STATE.SUBSEQUENT: 

1257 # Build subsequent client tokens: without the list of supported mechtypes 

1258 # NOTE: GSSAPI_BLOB is stripped. 

1259 spnego_tok = SPNEGO_negToken( 

1260 token=SPNEGO_negTokenResp( 

1261 supportedMech=None, 

1262 negState=None, 

1263 ) 

1264 ) 

1265 

1266 # Add the MIC if required and the exchange is finished. 

1267 if status == GSS_S_COMPLETE and Context.require_mic: 

1268 spnego_tok.token.mechListMIC = SPNEGO_MechListMIC( 

1269 value=Context.ssp.GetMechListMIC( 

1270 Context.ssp_context, 

1271 Context.get_mechListMIC(), 

1272 ), 

1273 ) 

1274 

1275 # If we still haven't verified the MIC, we aren't done. 

1276 if not Context.verified_mic: 

1277 status = GSS_S_CONTINUE_NEEDED 

1278 

1279 Context.state = SPNEGOSSP.STATE.MICONLY 

1280 

1281 # Add the output token if provided 

1282 if output_token_inner: 

1283 spnego_tok.token.responseToken = SPNEGO_Token( 

1284 value=output_token_inner, 

1285 ) 

1286 

1287 # Update the state 

1288 if Context.state != SPNEGOSSP.STATE.MICONLY: 

1289 Context.state = SPNEGOSSP.STATE.SUBSEQUENT 

1290 

1291 return Context, spnego_tok, status 

1292 

1293 def GSS_Accept_sec_context( 

1294 self, 

1295 Context: CONTEXT, 

1296 input_token=None, 

1297 req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS, 

1298 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS, 

1299 ): 

1300 if Context is None: 

1301 # New Context 

1302 Context = SPNEGOSSP.CONTEXT( 

1303 IsAcceptor=True, 

1304 ssps=list(self.ssps), 

1305 late_fallback_supported=self.SUPPORT_LATE_FALLBACK, 

1306 req_flags=req_flags, 

1307 ) 

1308 

1309 input_token_inner = None 

1310 _mechListMIC = None 

1311 

1312 # Extract values from GSSAPI token 

1313 if isinstance(input_token, GSSAPI_BLOB): 

1314 input_token = input_token.innerToken 

1315 if isinstance(input_token, SPNEGO_negToken): 

1316 input_token = input_token.token 

1317 if isinstance(input_token, SPNEGO_negTokenInit): 

1318 # Populate context with values from the client's request 

1319 if input_token.mechTypes: 

1320 Context.client_mechtypes = input_token.mechTypes 

1321 

1322 # [MS-SPNG] Late Fallback Mechanism 

1323 Context.all_mechtypes.append(self._LATE_FALLBACK_INITIATOR) 

1324 Context.all_mechtypes += Context.client_mechtypes 

1325 if input_token.mechToken: 

1326 input_token_inner = input_token.mechToken.value 

1327 _mechListMIC = input_token.mechListMIC or input_token._mechListMIC 

1328 elif isinstance(input_token, SPNEGO_negTokenResp): 

1329 if input_token.responseToken: 

1330 input_token_inner = input_token.responseToken.value 

1331 _mechListMIC = input_token.mechListMIC 

1332 else: 

1333 # The blob is a raw token. We aren't using SPNEGO here. 

1334 if not Context.raw and ( 

1335 Context.client_mechtypes is not None 

1336 or Context.server_mechtypes is not None 

1337 ): 

1338 return Context, None, GSS_S_BAD_MIC 

1339 Context.raw = True 

1340 input_token_inner = input_token 

1341 self.GuessOtherMechtypes(Context, input_token) 

1342 

1343 if Context.client_mechtypes is None: 

1344 # At this point, we should have already gotten the mechtypes from a current 

1345 # or former request. 

1346 return Context, None, GSS_S_FAILURE 

1347 

1348 # Perform SSP negotiation 

1349 try: 

1350 Context.negotiate_ssp() 

1351 except ValueError as ex: 

1352 # Couldn't find common SSP 

1353 log_runtime.warning("SPNEGOSSP: %s" % ex) 

1354 return Context, None, GSS_S_FAILURE 

1355 

1356 output_token_inner = None 

1357 status = GSS_S_CONTINUE_NEEDED 

1358 

1359 # If we didn't pick the client's first choice, the token we were passed 

1360 # isn't usable. 

1361 if not Context.first_choice: 

1362 # Typically a client opportunistically starts with Kerberos, including 

1363 # its APREQ, and we want to use NTLM. Here we add one round trip 

1364 Context.first_choice = True # Do not enter here again. 

1365 else: 

1366 # Send it to the negotiated SSP 

1367 Context.ssp_context, output_token_inner, status = ( 

1368 Context.ssp.GSS_Accept_sec_context( 

1369 Context.ssp_context, 

1370 input_token=input_token_inner, 

1371 req_flags=Context.req_flags, 

1372 chan_bindings=chan_bindings, 

1373 ) 

1374 ) 

1375 

1376 # Verify MIC if context succeeded 

1377 if status == GSS_S_COMPLETE and _mechListMIC: 

1378 # NOTE: the mechListMIC that the client sends is computed over the 

1379 # **list of mechanisms that it requests**. 

1380 if Context.ssp.SupportsMechListMIC(): 

1381 # We need to check we support checking the MIC. The only case where 

1382 # this is needed is NTLM in guest mode: the client will send a mic 

1383 # but we don't check it... 

1384 Context.ssp.VerifyMechListMIC( 

1385 Context.ssp_context, 

1386 _mechListMIC.value, 

1387 Context.get_mechListMIC(), 

1388 ) 

1389 Context.verified_mic = True 

1390 Context.require_mic = True 

1391 

1392 # Raw processing ends here. 

1393 if Context.raw: 

1394 return Context, output_token_inner, status 

1395 

1396 # 0. Build the template response token 

1397 spnego_tok = SPNEGO_negToken( 

1398 token=SPNEGO_negTokenResp( 

1399 supportedMech=None, 

1400 ) 

1401 ) 

1402 if Context.state == SPNEGOSSP.STATE.FIRST: 

1403 Context.server_mechtypes = Context.get_supported_mechtypes() 

1404 

1405 # Include the supportedMech list if this is the first message we send 

1406 # or a renegotiation. 

1407 spnego_tok.token.supportedMech = Context.ssp_mechtype 

1408 

1409 if Context.late_fallback_negotiated: 

1410 # [MS-SPNG] Late Fallback Mechanism 

1411 spnego_tok.token.mechTypes = Context.server_mechtypes 

1412 Context.all_mechtypes.append(self._LATE_FALLBACK_ACCEPTOR) 

1413 Context.all_mechtypes += Context.server_mechtypes 

1414 Context.all_mechtypes += [Context.ssp_mechtype] 

1415 

1416 # Add the output token if provided 

1417 if output_token_inner: 

1418 spnego_tok.token.responseToken = SPNEGO_Token(value=output_token_inner) 

1419 

1420 # Update the state 

1421 Context.state = SPNEGOSSP.STATE.SUBSEQUENT 

1422 

1423 # Add the MIC if required and the exchange is finished. 

1424 if status == GSS_S_COMPLETE and Context.require_mic: 

1425 spnego_tok.token.mechListMIC = SPNEGO_MechListMIC( 

1426 value=Context.ssp.GetMechListMIC( 

1427 Context.ssp_context, 

1428 Context.get_mechListMIC(), 

1429 ), 

1430 ) 

1431 

1432 # If we still haven't verified the MIC, we aren't done. 

1433 if not Context.verified_mic: 

1434 status = GSS_S_CONTINUE_NEEDED 

1435 

1436 # Set negState 

1437 spnego_tok.token.negState = self.MapStatusToNegState(status) 

1438 

1439 return Context, spnego_tok, status 

1440 

1441 def GSS_Passive( 

1442 self, 

1443 Context: CONTEXT, 

1444 input_token=None, 

1445 req_flags=None, 

1446 ): 

1447 if Context is None: 

1448 # New Context 

1449 Context = SPNEGOSSP.CONTEXT( 

1450 IsAcceptor=True, 

1451 ssps=list(self.ssps), 

1452 late_fallback_supported=self.SUPPORT_LATE_FALLBACK, 

1453 ) 

1454 Context.passive = True 

1455 

1456 input_token_inner = None 

1457 

1458 # Extract values from GSSAPI token 

1459 if isinstance(input_token, GSSAPI_BLOB): 

1460 input_token = input_token.innerToken 

1461 if isinstance(input_token, SPNEGO_negToken): 

1462 input_token = input_token.token 

1463 if isinstance(input_token, SPNEGO_negTokenInit): 

1464 if input_token.mechTypes is not None: 

1465 if Context.IsAcceptor: 

1466 # NegTokenInit 

1467 Context.client_mechtypes = input_token.mechTypes 

1468 

1469 # [MS-SPNG] Late Fallback Mechanism 

1470 Context.all_mechtypes.append(self._LATE_FALLBACK_INITIATOR) 

1471 Context.all_mechtypes += Context.client_mechtypes 

1472 else: 

1473 # NegTokenInit2 

1474 Context.server_mechtypes = input_token.mechTypes 

1475 if input_token.mechToken: 

1476 input_token_inner = input_token.mechToken.value 

1477 elif isinstance(input_token, SPNEGO_negTokenResp): 

1478 if input_token.supportedMech is not None: 

1479 Context.server_mechtypes = [input_token.supportedMech] 

1480 

1481 # [MS-SPNG] Late Fallback Mechanism 

1482 if input_token.mechTypes is not None: 

1483 Context.server_mechtypes = input_token.mechTypes 

1484 Context.all_mechtypes.append(self._LATE_FALLBACK_ACCEPTOR) 

1485 Context.all_mechtypes += Context.server_mechtypes 

1486 Context.all_mechtypes += [input_token.supportedMech] 

1487 if input_token.responseToken: 

1488 input_token_inner = input_token.responseToken.value 

1489 else: 

1490 # Raw. 

1491 input_token_inner = input_token 

1492 

1493 # Get the mechtypes of the other peer 

1494 if Context.IsAcceptor: 

1495 other_mechtypes = Context.client_mechtypes 

1496 else: 

1497 other_mechtypes = Context.server_mechtypes 

1498 

1499 # If we still haven't got a mechtype, guess (raw, most likely) 

1500 if other_mechtypes is None: 

1501 self.GuessOtherMechtypes(Context, input_token) 

1502 else: 

1503 if Context.ssp_mechtype not in other_mechtypes: 

1504 # Negotiated ssp wasn't accepted by the peer 

1505 Context.ssp = None 

1506 

1507 # Uninitialized OR allowed mechtypes have changed 

1508 try: 

1509 Context.negotiate_ssp() 

1510 except ValueError: 

1511 # Couldn't find common SSP 

1512 return Context, GSS_S_FAILURE 

1513 

1514 # Passthrough 

1515 Context.ssp_context, status = Context.ssp.GSS_Passive( 

1516 Context.ssp_context, 

1517 input_token_inner, 

1518 req_flags=req_flags, 

1519 ) 

1520 

1521 return Context, status 

1522 

1523 def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False): 

1524 Context.IsAcceptor = IsAcceptor 

1525 Context.ssp.GSS_Passive_set_Direction( 

1526 Context.ssp_context, IsAcceptor=IsAcceptor 

1527 ) 

1528 

1529 def MaximumSignatureLength(self, Context: CONTEXT): 

1530 return Context.ssp.MaximumSignatureLength(Context.ssp_context)