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
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
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>
6"""
7SPNEGO
9Implements parts of:
11- GSSAPI SPNEGO: RFC4178 > RFC2478
12- [MS-SPNG]: Microsoft's SPNEGO extensions + "LF" (Late Fallback mechanism)
13- GSSAPI SPNEGO NEGOEX: [MS-NEGOEX]
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"""
20import os
21import struct
22from uuid import UUID
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)
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)
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)
102# Typing imports
103from typing import (
104 Dict,
105 List,
106 Optional,
107 Tuple,
108)
110# SPNEGO negTokenInit
111# https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.1
114class SPNEGO_MechType(ASN1_Packet):
115 ASN1_codec = ASN1_Codecs.BER
116 ASN1_root = ASN1F_OID("oid", None)
119class SPNEGO_MechTypes(ASN1_Packet):
120 ASN1_codec = ASN1_Codecs.BER
121 ASN1_root = ASN1F_SEQUENCE_OF("mechTypes", None, SPNEGO_MechType)
124class SPNEGO_MechListMIC(ASN1_Packet):
125 ASN1_codec = ASN1_Codecs.BER
126 ASN1_root = ASN1F_STRING_ENCAPS("value", "", GSSAPI_BLOB_SIGNATURE)
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}
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))
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
158class SPNEGO_Token(ASN1_Packet):
159 ASN1_codec = ASN1_Codecs.BER
160 ASN1_root = _SPNEGO_Token_Field("value", None)
163_ContextFlags = [
164 "delegFlag",
165 "mutualFlag",
166 "replayFlag",
167 "sequenceFlag",
168 "superseded",
169 "anonFlag",
170 "confFlag",
171 "integFlag",
172]
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 )
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 )
214# SPNEGO negTokenTarg
215# https://datatracker.ietf.org/doc/html/rfc4178#section-4.2.2
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 )
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 )
272# Register for the GSS API Blob
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
278def mechListMIC(oids):
279 """
280 Implementation of RFC 4178 - Appendix D. mechListMIC Computation
282 NOTE: The documentation on mechListMIC isn't super clear, so note that:
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.
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))
296# NEGOEX
297# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-negoex/0ad7a003-ab56-4839-a204-b555ca6759a2
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}
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 ]
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
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
355class NEGOEX_BYTE_VECTOR(Packet):
356 fields_desc = [
357 LEIntField("ByteArrayBufferOffset", 0),
358 LEIntField("ByteArrayLength", 0),
359 ]
361 def guess_payload_class(self, payload):
362 return conf.padding_layer
365class NEGOEX_EXTENSION_VECTOR(Packet):
366 fields_desc = [
367 LELongField("ExtensionArrayOffset", 0),
368 LEShortField("ExtensionCount", 0),
369 ]
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 ]
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 )
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
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}
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
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 ]
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 ]
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 ]
516bind_layers(NEGOEX_NEGO_MESSAGE, NEGOEX_NEGO_MESSAGE)
519_mechDissector["1.3.6.1.4.1.311.2.2.30"] = NEGOEX_NEGO_MESSAGE
521# -- SSP
524class SPNEGOSSP(SSP):
525 """
526 The SPNEGO SSP
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.
531 Example::
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
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 """
557 __slots__ = [
558 "ssps",
559 "SUPPORT_LATE_FALLBACK",
560 ]
562 auth_type = 0x09
564 class STATE(SSP.STATE):
565 FIRST = 1
566 SUBSEQUENT = 2
567 MICONLY = 3
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 ]
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__()
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 ]
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())
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
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 )
647 # 4. Return wrapped in MechType
648 return [SPNEGO_MechType(oid=ASN1_OID(oid)) for oid in mechs]
650 def get_mechListMIC(self):
651 """
652 Return the binary used for mechListMIC
653 """
654 # See help(mechListMIC) for more details
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
666 return mechListMIC(mechtypes)
668 def negotiate_ssp(self) -> None:
669 """
670 Perform SSP negotiation.
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.
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
683 if other_mechtypes is not None:
684 other_oids = [x.oid.val for x in other_mechtypes]
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
693 if self.ssp is not None:
694 # ssp has already been negotiated, return.
695 return
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 )
716 # Check whether the selected SSP was the one preferred by the client
717 self.first_choice = ssp_oid == other_oids[0]
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
730 # Get the associated ssp dissection class and mechtype
731 self.ssp_mechtype = SPNEGO_MechType(oid=ASN1_OID(ssp_oid))
733 # Reset the ssp context
734 self.ssp_context = None
736 # Passthrough attributes and functions
738 def clifailure(self):
739 if self.ssp_context is not None:
740 self.ssp_context.clifailure()
742 def __getattr__(self, attr):
743 try:
744 return object.__getattribute__(self, attr)
745 except AttributeError:
746 return getattr(self.ssp_context, attr)
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)
754 # Passthrough the flags property
756 @property
757 def flags(self):
758 if self.ssp_context:
759 return self.ssp_context.flags
760 return GSS_C_FLAGS(0)
762 @flags.setter
763 def flags(self, x):
764 if not self.ssp_context:
765 return
766 self.ssp_context.flags = x
768 def __repr__(self):
769 return "SPNEGOSSP[%s%s]" % (
770 repr(self.ssp_context),
771 "/LF" if self.late_fallback_negotiated else "",
772 )
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)
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.
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.
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
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
843 return WinSSP()
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
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"]
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
881 password = prompt("Password: ", is_password=True)
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
891 # Import into a Ticketer object
892 t = Ticketer()
893 t.open_ccache(ccache)
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
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 )
952 # We have a UPN and secret. This allows to support 3 cases:
953 # Kerberos, IAKerb and U2U
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 )
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 )
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))
1009 if not ssps:
1010 raise ValueError("Unexpected case ! Please report.")
1012 # Build the SSP
1013 return cls(ssps)
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")
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 )
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 :/
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
1060 def GSS_WrapEx(self, Context, *args, **kwargs):
1061 # Passthrough
1062 return Context.ssp.GSS_WrapEx(Context.ssp_context, *args, **kwargs)
1064 def GSS_UnwrapEx(self, Context, *args, **kwargs):
1065 # Passthrough
1066 return Context.ssp.GSS_UnwrapEx(Context.ssp_context, *args, **kwargs)
1068 def GSS_GetMICEx(self, Context, *args, **kwargs):
1069 # Passthrough
1070 return Context.ssp.GSS_GetMICEx(Context.ssp_context, *args, **kwargs)
1072 def GSS_VerifyMICEx(self, Context, *args, **kwargs):
1073 # Passthrough
1074 return Context.ssp.GSS_VerifyMICEx(Context.ssp_context, *args, **kwargs)
1076 def LegsAmount(self, Context: CONTEXT):
1077 return 4
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
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 = []
1101 if not Context.IsAcceptor:
1102 Context.server_mechtypes = other_mechtypes
1103 else:
1104 Context.client_mechtypes = other_mechtypes
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 )
1123 input_token_inner = None
1124 negState = None
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
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]
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)
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
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
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 )
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 )
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
1218 # Raw processing ends here.
1219 if Context.raw:
1220 return Context, output_token_inner, status
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
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()
1240 # [MS-SPNG] Late Fallback Mechanism
1241 Context.all_mechtypes.append(self._LATE_FALLBACK_INITIATOR)
1242 Context.all_mechtypes += Context.client_mechtypes
1244 # Now build the token
1245 spnego_tok = GSSAPI_BLOB(
1246 innerToken=SPNEGO_negToken(
1247 token=SPNEGO_negTokenInit(mechTypes=Context.client_mechtypes)
1248 )
1249 )
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 )
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 )
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
1279 Context.state = SPNEGOSSP.STATE.MICONLY
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 )
1287 # Update the state
1288 if Context.state != SPNEGOSSP.STATE.MICONLY:
1289 Context.state = SPNEGOSSP.STATE.SUBSEQUENT
1291 return Context, spnego_tok, status
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 )
1309 input_token_inner = None
1310 _mechListMIC = None
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
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)
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
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
1356 output_token_inner = None
1357 status = GSS_S_CONTINUE_NEEDED
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 )
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
1392 # Raw processing ends here.
1393 if Context.raw:
1394 return Context, output_token_inner, status
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()
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
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]
1416 # Add the output token if provided
1417 if output_token_inner:
1418 spnego_tok.token.responseToken = SPNEGO_Token(value=output_token_inner)
1420 # Update the state
1421 Context.state = SPNEGOSSP.STATE.SUBSEQUENT
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 )
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
1436 # Set negState
1437 spnego_tok.token.negState = self.MapStatusToNegState(status)
1439 return Context, spnego_tok, status
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
1456 input_token_inner = None
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
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]
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
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
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
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
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 )
1521 return Context, status
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 )
1529 def MaximumSignatureLength(self, Context: CONTEXT):
1530 return Context.ssp.MaximumSignatureLength(Context.ssp_context)