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