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