• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
7Generic Security Services (GSS) API
8
9Implements parts of:
10
11    - GSSAPI: RFC4121 / RFC2743
12    - GSSAPI C bindings: RFC2744
13
14This is implemented in the following SSPs:
15
16    - :class:`~scapy.layers.ntlm.NTLMSSP`
17    - :class:`~scapy.layers.kerberos.KerberosSSP`
18    - :class:`~scapy.layers.spnego.SPNEGOSSP`
19    - :class:`~scapy.layers.msrpce.msnrpc.NetlogonSSP`
20
21.. note::
22    You will find more complete documentation for this layer over at
23    `GSSAPI <https://scapy.readthedocs.io/en/latest/layers/gssapi.html>`_
24"""
25
26import abc
27
28from dataclasses import dataclass
29from enum import IntEnum, IntFlag
30
31from scapy.asn1.asn1 import (
32    ASN1_SEQUENCE,
33    ASN1_Class_UNIVERSAL,
34    ASN1_Codecs,
35)
36from scapy.asn1.ber import BERcodec_SEQUENCE
37from scapy.asn1.mib import conf  # loads conf.mib
38from scapy.asn1fields import (
39    ASN1F_OID,
40    ASN1F_PACKET,
41    ASN1F_SEQUENCE,
42)
43from scapy.asn1packet import ASN1_Packet
44from scapy.fields import (
45    FieldLenField,
46    LEIntEnumField,
47    PacketField,
48    StrLenField,
49)
50from scapy.packet import Packet
51
52# Type hints
53from typing import (
54    Any,
55    List,
56    Optional,
57    Tuple,
58)
59
60# https://datatracker.ietf.org/doc/html/rfc1508#page-48
61
62
63class ASN1_Class_GSSAPI(ASN1_Class_UNIVERSAL):
64    name = "GSSAPI"
65    APPLICATION = 0x60
66
67
68class ASN1_GSSAPI_APPLICATION(ASN1_SEQUENCE):
69    tag = ASN1_Class_GSSAPI.APPLICATION
70
71
72class BERcodec_GSSAPI_APPLICATION(BERcodec_SEQUENCE):
73    tag = ASN1_Class_GSSAPI.APPLICATION
74
75
76class ASN1F_GSSAPI_APPLICATION(ASN1F_SEQUENCE):
77    ASN1_tag = ASN1_Class_GSSAPI.APPLICATION
78
79
80# GSS API Blob
81# https://datatracker.ietf.org/doc/html/rfc4121
82
83# Filled by providers
84_GSSAPI_OIDS = {}
85_GSSAPI_SIGNATURE_OIDS = {}
86
87# section 4.1
88
89
90class GSSAPI_BLOB(ASN1_Packet):
91    ASN1_codec = ASN1_Codecs.BER
92    ASN1_root = ASN1F_GSSAPI_APPLICATION(
93        ASN1F_OID("MechType", "1.3.6.1.5.5.2"),
94        ASN1F_PACKET(
95            "innerToken",
96            None,
97            None,
98            next_cls_cb=lambda pkt: _GSSAPI_OIDS.get(pkt.MechType.val, conf.raw_layer),
99        ),
100    )
101
102    @classmethod
103    def dispatch_hook(cls, _pkt=None, *args, **kargs):
104        if _pkt and len(_pkt) >= 1:
105            if ord(_pkt[:1]) & 0xA0 >= 0xA0:
106                from scapy.layers.spnego import SPNEGO_negToken
107
108                # XXX: sometimes the token is raw, we should look from
109                # the session what to use here. For now: hardcode SPNEGO
110                # (THIS IS A VERY STRONG ASSUMPTION)
111                return SPNEGO_negToken
112            if _pkt[:7] == b"NTLMSSP":
113                from scapy.layers.ntlm import NTLM_Header
114
115                # XXX: if no mechTypes are provided during SPNEGO exchange,
116                # Windows falls back to a plain NTLM_Header.
117                return NTLM_Header.dispatch_hook(_pkt=_pkt, *args, **kargs)
118        return cls
119
120
121# Same but to store the signatures (e.g. DCE/RPC)
122
123
124class GSSAPI_BLOB_SIGNATURE(ASN1_Packet):
125    ASN1_codec = ASN1_Codecs.BER
126    ASN1_root = ASN1F_GSSAPI_APPLICATION(
127        ASN1F_OID("MechType", "1.3.6.1.5.5.2"),
128        ASN1F_PACKET(
129            "innerToken",
130            None,
131            None,
132            next_cls_cb=lambda pkt: _GSSAPI_SIGNATURE_OIDS.get(
133                pkt.MechType.val, conf.raw_layer
134            ),  # noqa: E501
135        ),
136    )
137
138    @classmethod
139    def dispatch_hook(cls, _pkt=None, *args, **kargs):
140        if _pkt and len(_pkt) >= 2:
141            # Sometimes the token is raw. Detect that with educated
142            # heuristics.
143            if _pkt[:2] in [b"\x04\x04", b"\x05\x04"]:
144                from scapy.layers.kerberos import KRB_InnerToken
145
146                return KRB_InnerToken
147            elif len(_pkt) >= 4 and _pkt[:4] == b"\x01\x00\x00\x00":
148                from scapy.layers.ntlm import NTLMSSP_MESSAGE_SIGNATURE
149
150                return NTLMSSP_MESSAGE_SIGNATURE
151        return cls
152
153
154class _GSSAPI_Field(PacketField):
155    """
156    PacketField that contains a GSSAPI_BLOB_SIGNATURE, but one that can
157    have a payload when not encrypted.
158    """
159    __slots__ = ["pay_cls"]
160
161    def __init__(self, name, pay_cls):
162        self.pay_cls = pay_cls
163        super().__init__(
164            name,
165            None,
166            GSSAPI_BLOB_SIGNATURE,
167        )
168
169    def getfield(self, pkt, s):
170        remain, val = super().getfield(pkt, s)
171        if remain and val:
172            val.payload = self.pay_cls(remain)
173            return b"", val
174        return remain, val
175
176
177# RFC2744 sect 3.9 - Status Values
178
179GSS_S_COMPLETE = 0
180
181# These errors are encoded into the 32-bit GSS status code as follows:
182#   MSB                                                        LSB
183#   |------------------------------------------------------------|
184#   |  Calling Error | Routine Error  |    Supplementary Info    |
185#   |------------------------------------------------------------|
186# Bit 31            24 23            16 15                       0
187
188GSS_C_CALLING_ERROR_OFFSET = 24
189GSS_C_ROUTINE_ERROR_OFFSET = 16
190GSS_C_SUPPLEMENTARY_OFFSET = 0
191
192# Calling errors:
193
194GSS_S_CALL_INACCESSIBLE_READ = 1 << GSS_C_CALLING_ERROR_OFFSET
195GSS_S_CALL_INACCESSIBLE_WRITE = 2 << GSS_C_CALLING_ERROR_OFFSET
196GSS_S_CALL_BAD_STRUCTURE = 3 << GSS_C_CALLING_ERROR_OFFSET
197
198# Routine errors:
199
200GSS_S_BAD_MECH = 1 << GSS_C_ROUTINE_ERROR_OFFSET
201GSS_S_BAD_NAME = 2 << GSS_C_ROUTINE_ERROR_OFFSET
202GSS_S_BAD_NAMETYPE = 3 << GSS_C_ROUTINE_ERROR_OFFSET
203GSS_S_BAD_BINDINGS = 4 << GSS_C_ROUTINE_ERROR_OFFSET
204GSS_S_BAD_STATUS = 5 << GSS_C_ROUTINE_ERROR_OFFSET
205GSS_S_BAD_SIG = 6 << GSS_C_ROUTINE_ERROR_OFFSET
206GSS_S_BAD_MIC = GSS_S_BAD_SIG
207GSS_S_NO_CRED = 7 << GSS_C_ROUTINE_ERROR_OFFSET
208GSS_S_NO_CONTEXT = 8 << GSS_C_ROUTINE_ERROR_OFFSET
209GSS_S_DEFECTIVE_TOKEN = 9 << GSS_C_ROUTINE_ERROR_OFFSET
210GSS_S_DEFECTIVE_CREDENTIAL = 10 << GSS_C_ROUTINE_ERROR_OFFSET
211GSS_S_CREDENTIALS_EXPIRED = 11 << GSS_C_ROUTINE_ERROR_OFFSET
212GSS_S_CONTEXT_EXPIRED = 12 << GSS_C_ROUTINE_ERROR_OFFSET
213GSS_S_FAILURE = 13 << GSS_C_ROUTINE_ERROR_OFFSET
214GSS_S_BAD_QOP = 14 << GSS_C_ROUTINE_ERROR_OFFSET
215GSS_S_UNAUTHORIZED = 15 << GSS_C_ROUTINE_ERROR_OFFSET
216GSS_S_UNAVAILABLE = 16 << GSS_C_ROUTINE_ERROR_OFFSET
217GSS_S_DUPLICATE_ELEMENT = 17 << GSS_C_ROUTINE_ERROR_OFFSET
218GSS_S_NAME_NOT_MN = 18 << GSS_C_ROUTINE_ERROR_OFFSET
219
220# Supplementary info bits:
221
222GSS_S_CONTINUE_NEEDED = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 0)
223GSS_S_DUPLICATE_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 1)
224GSS_S_OLD_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 2)
225GSS_S_UNSEQ_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 3)
226GSS_S_GAP_TOKEN = 1 << (GSS_C_SUPPLEMENTARY_OFFSET + 4)
227
228# Address families (RFC2744 sect 3.11)
229
230_GSS_ADDRTYPE = {
231    0: "GSS_C_AF_UNSPEC",
232    1: "GSS_C_AF_LOCAL",
233    2: "GSS_C_AF_INET",
234    3: "GSS_C_AF_IMPLINK",
235    4: "GSS_C_AF_PUP",
236    5: "GSS_C_AF_CHAOS",
237    6: "GSS_C_AF_NS",
238    7: "GSS_C_AF_NBS",
239    8: "GSS_C_AF_ECMA",
240    9: "GSS_C_AF_DATAKIT",
241    10: "GSS_C_AF_CCITT",
242    11: "GSS_C_AF_SNA",
243    12: "GSS_C_AF_DECnet",
244    13: "GSS_C_AF_DLI",
245    14: "GSS_C_AF_LAT",
246    15: "GSS_C_AF_HYLINK",
247    16: "GSS_C_AF_APPLETALK",
248    17: "GSS_C_AF_BSC",
249    18: "GSS_C_AF_DSS",
250    19: "GSS_C_AF_OSI",
251    21: "GSS_C_AF_X25",
252    255: "GSS_C_AF_NULLADDR",
253}
254
255
256# GSS Structures
257
258
259class GssBufferDesc(Packet):
260    name = "gss_buffer_desc"
261    fields_desc = [
262        FieldLenField("length", None, length_of="value", fmt="<I"),
263        StrLenField("value", "", length_from=lambda pkt: pkt.length),
264    ]
265
266    def default_payload_class(self, payload):
267        return conf.padding_layer
268
269
270class GssChannelBindings(Packet):
271    name = "gss_channel_bindings_struct"
272    fields_desc = [
273        LEIntEnumField("initiator_addrtype", 0, _GSS_ADDRTYPE),
274        PacketField("initiator_address", GssBufferDesc(), GssBufferDesc),
275        LEIntEnumField("acceptor_addrtype", 0, _GSS_ADDRTYPE),
276        PacketField("acceptor_address", GssBufferDesc(), GssBufferDesc),
277        PacketField("application_data", None, GssBufferDesc),
278    ]
279
280
281# --- The base GSSAPI SSP base class
282
283
284class GSS_C_FLAGS(IntFlag):
285    """
286    Authenticator Flags per RFC2744 req_flags
287    """
288
289    GSS_C_DELEG_FLAG = 0x01
290    GSS_C_MUTUAL_FLAG = 0x02
291    GSS_C_REPLAY_FLAG = 0x04
292    GSS_C_SEQUENCE_FLAG = 0x08
293    GSS_C_CONF_FLAG = 0x10  # confidentiality
294    GSS_C_INTEG_FLAG = 0x20  # integrity
295    # RFC4757
296    GSS_C_DCE_STYLE = 0x1000
297    GSS_C_IDENTIFY_FLAG = 0x2000
298    GSS_C_EXTENDED_ERROR_FLAG = 0x4000
299
300
301class SSP:
302    """
303    The general SSP class
304    """
305
306    auth_type = 0x00
307
308    def __init__(self, **kwargs):
309        if kwargs:
310            raise ValueError("Unknown SSP parameters: " + ",".join(list(kwargs)))
311
312    def __repr__(self):
313        return "<%s>" % self.__class__.__name__
314
315    class CONTEXT:
316        """
317        A Security context i.e. the 'state' of the secure negotiation
318        """
319
320        __slots__ = ["state", "_flags", "passive"]
321
322        def __init__(self, req_flags: Optional[GSS_C_FLAGS] = None):
323            if req_flags is None:
324                # Default
325                req_flags = (
326                    GSS_C_FLAGS.GSS_C_EXTENDED_ERROR_FLAG
327                    | GSS_C_FLAGS.GSS_C_MUTUAL_FLAG
328                )
329            self.flags = req_flags
330            self.passive = False
331
332        def clifailure(self):
333            # This allows to reset the client context without discarding it.
334            pass
335
336        # 'flags' is the most important attribute. Use a setter to sanitize it.
337
338        @property
339        def flags(self):
340            return self._flags
341
342        @flags.setter
343        def flags(self, x):
344            self._flags = GSS_C_FLAGS(int(x))
345
346        def __repr__(self):
347            return "[Default SSP]"
348
349    class STATE(IntEnum):
350        """
351        An Enum that contains the states of an SSP
352        """
353
354    @abc.abstractmethod
355    def GSS_Init_sec_context(
356        self, Context: CONTEXT, val=None, req_flags: Optional[GSS_C_FLAGS] = None
357    ):
358        """
359        GSS_Init_sec_context: client-side call for the SSP
360        """
361        raise NotImplementedError
362
363    @abc.abstractmethod
364    def GSS_Accept_sec_context(self, Context: CONTEXT, val=None):
365        """
366        GSS_Accept_sec_context: server-side call for the SSP
367        """
368        raise NotImplementedError
369
370    # Passive
371
372    @abc.abstractmethod
373    def GSS_Passive(self, Context: CONTEXT, val=None):
374        """
375        GSS_Passive: client/server call for the SSP in passive mode
376        """
377        raise NotImplementedError
378
379    def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False):
380        """
381        GSS_Passive_set_Direction: used to swap the direction in passive mode
382        """
383        pass
384
385    # MS additions (*Ex functions)
386
387    @dataclass
388    class WRAP_MSG:
389        conf_req_flag: bool
390        sign: bool
391        data: bytes
392
393    @abc.abstractmethod
394    def GSS_WrapEx(
395        self, Context: CONTEXT, msgs: List[WRAP_MSG], qop_req: int = 0
396    ) -> Tuple[List[WRAP_MSG], Any]:
397        """
398        GSS_WrapEx
399
400        :param Context: the SSP context
401        :param qop_req: int (0 specifies default QOP)
402        :param msgs: list of WRAP_MSG
403
404        :returns: (data, signature)
405        """
406        raise NotImplementedError
407
408    @abc.abstractmethod
409    def GSS_UnwrapEx(
410        self, Context: CONTEXT, msgs: List[WRAP_MSG], signature
411    ) -> List[WRAP_MSG]:
412        """
413        :param Context: the SSP context
414        :param msgs: list of WRAP_MSG
415        :param signature: the signature
416
417        :raises ValueError: if MIC failure.
418        :returns: data
419        """
420        raise NotImplementedError
421
422    @dataclass
423    class MIC_MSG:
424        sign: bool
425        data: bytes
426
427    @abc.abstractmethod
428    def GSS_GetMICEx(
429        self, Context: CONTEXT, msgs: List[MIC_MSG], qop_req: int = 0
430    ) -> Any:
431        """
432        GSS_GetMICEx
433
434        :param Context: the SSP context
435        :param qop_req: int (0 specifies default QOP)
436        :param msgs: list of VERIF_MSG
437
438        :returns: signature
439        """
440        raise NotImplementedError
441
442    @abc.abstractmethod
443    def GSS_VerifyMICEx(self, Context: CONTEXT, msgs: List[MIC_MSG], signature) -> None:
444        """
445        :param Context: the SSP context
446        :param msgs: list of VERIF_MSG
447        :param signature: the signature
448
449        :raises ValueError: if MIC failure.
450        """
451        raise NotImplementedError
452
453    @abc.abstractmethod
454    def MaximumSignatureLength(self, Context: CONTEXT):
455        """
456        Returns the Maximum Signature length.
457
458        This will be used in auth_len in DceRpc5, and is necessary for
459        PFC_SUPPORT_HEADER_SIGN to work properly.
460        """
461        raise NotImplementedError
462
463    # RFC 2743
464
465    # sect 2.3.1
466
467    def GSS_GetMIC(self, Context: CONTEXT, message: bytes, qop_req: int = 0):
468        return self.GSS_GetMICEx(
469            Context,
470            [
471                self.MIC_MSG(
472                    sign=True,
473                    data=message,
474                )
475            ],
476            qop_req=qop_req,
477        )
478
479    # sect 2.3.2
480
481    def GSS_VerifyMIC(self, Context: CONTEXT, message: bytes, signature):
482        self.GSS_VerifyMICEx(
483            Context,
484            [
485                self.MIC_MSG(
486                    sign=True,
487                    data=message,
488                )
489            ],
490            signature,
491        )
492
493    # sect 2.3.3
494
495    def GSS_Wrap(
496        self,
497        Context: CONTEXT,
498        input_message: bytes,
499        conf_req_flag: bool,
500        qop_req: int = 0,
501    ):
502        _msgs, signature = self.GSS_WrapEx(
503            Context,
504            [
505                self.WRAP_MSG(
506                    conf_req_flag=conf_req_flag,
507                    sign=True,
508                    data=input_message,
509                )
510            ],
511            qop_req=qop_req,
512        )
513        if _msgs[0].data:
514            signature /= _msgs[0].data
515        return signature
516
517    # sect 2.3.4
518
519    def GSS_Unwrap(self, Context: CONTEXT, signature):
520        data = b""
521        if signature.payload:
522            # signature has a payload that is the data. Let's get that payload
523            # in its original form, and use it for verifying the checksum.
524            if signature.payload.original:
525                data = signature.payload.original
526            else:
527                data = bytes(signature.payload)
528            signature = signature.copy()
529            signature.remove_payload()
530        return self.GSS_UnwrapEx(
531            Context,
532            [
533                self.WRAP_MSG(
534                    conf_req_flag=True,
535                    sign=True,
536                    data=data,
537                )
538            ],
539            signature,
540        )[0].data
541
542    # MISC
543
544    def NegTokenInit2(self):
545        """
546        Server-Initiation
547        See [MS-SPNG] sect 3.2.5.2
548        """
549        return None, None
550
551    def canMechListMIC(self, Context: CONTEXT):
552        """
553        Returns whether or not mechListMIC can be computed
554        """
555        return False
556
557    def getMechListMIC(self, Context, input):
558        """
559        Compute mechListMIC
560        """
561        return bytes(self.GSS_GetMIC(Context, input))
562
563    def verifyMechListMIC(self, Context, otherMIC, input):
564        """
565        Verify mechListMIC
566        """
567        return self.GSS_VerifyMIC(Context, input, otherMIC)
568
569    def LegsAmount(self, Context: CONTEXT):
570        """
571        Returns the amount of 'legs' (how MS calls it) of the SSP.
572
573        i.e. 2 for Kerberos, 3 for NTLM and Netlogon
574        """
575        return 2
576