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 target_name: Optional[str] = None,
459 req_flags: Optional[GSS_C_FLAGS] = None,
460 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS,
461 ):
462 """
463 GSS_Init_sec_context: client-side call for the SSP
464 """
465 raise NotImplementedError
466
467 @abc.abstractmethod
468 def GSS_Accept_sec_context(
469 self,
470 Context: CONTEXT,
471 token=None,
472 req_flags: Optional[GSS_S_FLAGS] = GSS_S_FLAGS.GSS_S_ALLOW_MISSING_BINDINGS,
473 chan_bindings: GssChannelBindings = GSS_C_NO_CHANNEL_BINDINGS,
474 ):
475 """
476 GSS_Accept_sec_context: server-side call for the SSP
477 """
478 raise NotImplementedError
479
480 # Passive
481
482 @abc.abstractmethod
483 def GSS_Passive(self, Context: CONTEXT, token=None):
484 """
485 GSS_Passive: client/server call for the SSP in passive mode
486 """
487 raise NotImplementedError
488
489 def GSS_Passive_set_Direction(self, Context: CONTEXT, IsAcceptor=False):
490 """
491 GSS_Passive_set_Direction: used to swap the direction in passive mode
492 """
493 pass
494
495 # MS additions (*Ex functions)
496
497 @dataclass
498 class WRAP_MSG:
499 conf_req_flag: bool
500 sign: bool
501 data: bytes
502
503 @abc.abstractmethod
504 def GSS_WrapEx(
505 self,
506 Context: CONTEXT,
507 msgs: List[WRAP_MSG],
508 qop_req: int = GSS_C_QOP_DEFAULT,
509 ) -> Tuple[List[WRAP_MSG], Any]:
510 """
511 GSS_WrapEx
512
513 :param Context: the SSP context
514 :param qop_req: int (0 specifies default QOP)
515 :param msgs: list of WRAP_MSG
516
517 :returns: (data, signature)
518 """
519 raise NotImplementedError
520
521 @abc.abstractmethod
522 def GSS_UnwrapEx(
523 self, Context: CONTEXT, msgs: List[WRAP_MSG], signature
524 ) -> List[WRAP_MSG]:
525 """
526 :param Context: the SSP context
527 :param msgs: list of WRAP_MSG
528 :param signature: the signature
529
530 :raises ValueError: if MIC failure.
531 :returns: data
532 """
533 raise NotImplementedError
534
535 @dataclass
536 class MIC_MSG:
537 sign: bool
538 data: bytes
539
540 @abc.abstractmethod
541 def GSS_GetMICEx(
542 self,
543 Context: CONTEXT,
544 msgs: List[MIC_MSG],
545 qop_req: int = GSS_C_QOP_DEFAULT,
546 ) -> Any:
547 """
548 GSS_GetMICEx
549
550 :param Context: the SSP context
551 :param qop_req: int (0 specifies default QOP)
552 :param msgs: list of VERIF_MSG
553
554 :returns: signature
555 """
556 raise NotImplementedError
557
558 @abc.abstractmethod
559 def GSS_VerifyMICEx(
560 self,
561 Context: CONTEXT,
562 msgs: List[MIC_MSG],
563 signature,
564 ) -> None:
565 """
566 :param Context: the SSP context
567 :param msgs: list of VERIF_MSG
568 :param signature: the signature
569
570 :raises ValueError: if MIC failure.
571 """
572 raise NotImplementedError
573
574 @abc.abstractmethod
575 def MaximumSignatureLength(self, Context: CONTEXT):
576 """
577 Returns the Maximum Signature length.
578
579 This will be used in auth_len in DceRpc5, and is necessary for
580 PFC_SUPPORT_HEADER_SIGN to work properly.
581 """
582 raise NotImplementedError
583
584 # RFC 2743
585
586 # sect 2.3.1
587
588 def GSS_GetMIC(
589 self,
590 Context: CONTEXT,
591 message: bytes,
592 qop_req: int = GSS_C_QOP_DEFAULT,
593 ):
594 return self.GSS_GetMICEx(
595 Context,
596 [
597 self.MIC_MSG(
598 sign=True,
599 data=message,
600 )
601 ],
602 qop_req=qop_req,
603 )
604
605 # sect 2.3.2
606
607 def GSS_VerifyMIC(
608 self,
609 Context: CONTEXT,
610 message: bytes,
611 signature,
612 ):
613 self.GSS_VerifyMICEx(
614 Context,
615 [
616 self.MIC_MSG(
617 sign=True,
618 data=message,
619 )
620 ],
621 signature,
622 )
623
624 # sect 2.3.3
625
626 def GSS_Wrap(
627 self,
628 Context: CONTEXT,
629 input_message: bytes,
630 conf_req_flag: bool,
631 qop_req: int = GSS_C_QOP_DEFAULT,
632 ):
633 _msgs, signature = self.GSS_WrapEx(
634 Context,
635 [
636 self.WRAP_MSG(
637 conf_req_flag=conf_req_flag,
638 sign=True,
639 data=input_message,
640 )
641 ],
642 qop_req=qop_req,
643 )
644 if _msgs[0].data:
645 signature /= _msgs[0].data
646 return signature
647
648 # sect 2.3.4
649
650 def GSS_Unwrap(self, Context: CONTEXT, signature):
651 data = b""
652 if signature.payload:
653 # signature has a payload that is the data. Let's get that payload
654 # in its original form, and use it for verifying the checksum.
655 if signature.payload.original:
656 data = signature.payload.original
657 else:
658 data = bytes(signature.payload)
659 signature = signature.copy()
660 signature.remove_payload()
661 return self.GSS_UnwrapEx(
662 Context,
663 [
664 self.WRAP_MSG(
665 conf_req_flag=True,
666 sign=True,
667 data=data,
668 )
669 ],
670 signature,
671 )[0].data
672
673 # MISC
674
675 def NegTokenInit2(self):
676 """
677 Server-Initiation
678 See [MS-SPNG] sect 3.2.5.2
679 """
680 return None, None
681
682 def canMechListMIC(self, Context: CONTEXT):
683 """
684 Returns whether or not mechListMIC can be computed
685 """
686 return False
687
688 def getMechListMIC(self, Context, input):
689 """
690 Compute mechListMIC
691 """
692 return bytes(self.GSS_GetMIC(Context, input))
693
694 def verifyMechListMIC(self, Context, otherMIC, input):
695 """
696 Verify mechListMIC
697 """
698 return self.GSS_VerifyMIC(Context, input, otherMIC)
699
700 def LegsAmount(self, Context: CONTEXT):
701 """
702 Returns the amount of 'legs' (how MS calls it) of the SSP.
703
704 i.e. 2 for Kerberos, 3 for NTLM and Netlogon
705 """
706 return 2