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) Philippe Biondi <phil@secdev.org> 5 6""" 7ISAKMP (Internet Security Association and Key Management Protocol). 8""" 9 10# Mostly based on https://tools.ietf.org/html/rfc2408 11 12import struct 13from scapy.config import conf 14from scapy.packet import Packet, bind_bottom_up, bind_top_down, bind_layers 15from scapy.compat import chb 16from scapy.fields import ( 17 ByteEnumField, 18 ByteField, 19 FieldLenField, 20 FieldListField, 21 FlagsField, 22 IPField, 23 IntEnumField, 24 IntField, 25 MultipleTypeField, 26 PacketLenField, 27 ShortEnumField, 28 ShortField, 29 StrLenEnumField, 30 StrLenField, 31 XByteField, 32 XStrFixedLenField, 33 XStrLenField, 34) 35from scapy.layers.inet import IP, UDP 36from scapy.layers.ipsec import NON_ESP 37from scapy.sendrecv import sr 38from scapy.volatile import RandString 39from scapy.error import warning 40from functools import reduce 41 42# TODO: some ISAKMP payloads are not implemented, 43# and inherit a default ISAKMP_payload 44 45 46# see https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml#ipsec-registry-2 for details # noqa: E501 47ISAKMPAttributeTypes = { 48 "Encryption": (1, {"DES-CBC": 1, 49 "IDEA-CBC": 2, 50 "Blowfish-CBC": 3, 51 "RC5-R16-B64-CBC": 4, 52 "3DES-CBC": 5, 53 "CAST-CBC": 6, 54 "AES-CBC": 7, 55 "CAMELLIA-CBC": 8, }, 0), 56 "Hash": (2, {"MD5": 1, 57 "SHA": 2, 58 "Tiger": 3, 59 "SHA2-256": 4, 60 "SHA2-384": 5, 61 "SHA2-512": 6, }, 0), 62 "Authentication": (3, {"PSK": 1, 63 "DSS": 2, 64 "RSA Sig": 3, 65 "RSA Encryption": 4, 66 "RSA Encryption Revised": 5, 67 "ElGamal Encryption": 6, 68 "ElGamal Encryption Revised": 7, 69 "ECDSA Sig": 8, 70 "HybridInitRSA": 64221, 71 "HybridRespRSA": 64222, 72 "HybridInitDSS": 64223, 73 "HybridRespDSS": 64224, 74 "XAUTHInitPreShared": 65001, 75 "XAUTHRespPreShared": 65002, 76 "XAUTHInitDSS": 65003, 77 "XAUTHRespDSS": 65004, 78 "XAUTHInitRSA": 65005, 79 "XAUTHRespRSA": 65006, 80 "XAUTHInitRSAEncryption": 65007, 81 "XAUTHRespRSAEncryption": 65008, 82 "XAUTHInitRSARevisedEncryption": 65009, # noqa: E501 83 "XAUTHRespRSARevisedEncryptio": 65010, }, 0), # noqa: E501 84 "GroupDesc": (4, {"768MODPgr": 1, 85 "1024MODPgr": 2, 86 "EC2Ngr155": 3, 87 "EC2Ngr185": 4, 88 "1536MODPgr": 5, 89 "2048MODPgr": 14, 90 "3072MODPgr": 15, 91 "4096MODPgr": 16, 92 "6144MODPgr": 17, 93 "8192MODPgr": 18, }, 0), 94 "GroupType": (5, {"MODP": 1, 95 "ECP": 2, 96 "EC2N": 3}, 0), 97 "GroupPrime": (6, {}, 1), 98 "GroupGenerator1": (7, {}, 1), 99 "GroupGenerator2": (8, {}, 1), 100 "GroupCurveA": (9, {}, 1), 101 "GroupCurveB": (10, {}, 1), 102 "LifeType": (11, {"Seconds": 1, 103 "Kilobytes": 2}, 0), 104 "LifeDuration": (12, {}, 1), 105 "PRF": (13, {}, 0), 106 "KeyLength": (14, {}, 0), 107 "FieldSize": (15, {}, 0), 108 "GroupOrder": (16, {}, 1), 109} 110 111# see https://www.iana.org/assignments/isakmp-registry/isakmp-registry.xhtml#isakmp-registry-13 for details # noqa: E501 112IPSECAttributeTypes = { 113 "LifeType": (1, {"Reserved": 0, 114 "seconds": 1, 115 "kilobytes": 2}, 0), 116 "LifeDuration": (2, {}, 1), 117 "GroupDesc": (3, ISAKMPAttributeTypes["GroupDesc"][1], 0), 118 "EncapsulationMode": (4, {"Reserved": 0, 119 "Tunnel": 1, 120 "Transport": 2, 121 "UDP-Encapsulated-Tunnel": 3, 122 "UDP-Encapsulated-Transport": 4}, 0), 123 "AuthenticationAlgorithm": (5, {"HMAC-MD5": 1, 124 "HMAC-SHA": 2, 125 "DES-MAC": 3, 126 "KPDK": 4, 127 "HMAC-SHA2-256": 5, 128 "HMAC-SHA2-384": 6, 129 "HMAC-SHA2-512": 7, 130 "HMAC-RIPEMD": 8, 131 "AES-XCBC-MAC": 9, 132 "SIG-RSA": 10, 133 "AES-128-GMAC": 11, 134 "AES-192-GMAC": 12, 135 "AES-256-GMAC": 13}, 0), 136 "KeyLength": (6, {}, 0), 137 "KeyRounds": (7, {}, 0), 138 "CompressDictionarySize": (8, {}, 0), 139 "CompressPrivateAlgorithm": (9, {}, 1), 140} 141 142_rev = lambda x: { 143 v[0]: (k, {vv: kk for kk, vv in v[1].items()}, v[2]) 144 for k, v in x.items() 145} 146ISAKMPTransformNum = _rev(ISAKMPAttributeTypes) 147IPSECTransformNum = _rev(IPSECAttributeTypes) 148 149# See IPSEC Security Protocol Identifiers entry in 150# https://www.iana.org/assignments/isakmp-registry/isakmp-registry.xhtml#isakmp-registry-3 151PROTO_ISAKMP = 1 152PROTO_IPSEC_AH = 2 153PROTO_IPSEC_ESP = 3 154PROTO_IPCOMP = 4 155PROTO_GIGABEAM_RADIO = 5 156 157 158class ISAKMPTransformSetField(StrLenField): 159 islist = 1 160 161 @staticmethod 162 def type2num(type_val_tuple, proto=0): 163 typ, val = type_val_tuple 164 if proto == PROTO_ISAKMP: 165 type_val, enc_dict, tlv = ISAKMPAttributeTypes.get(typ, (typ, {}, 0)) 166 elif proto == PROTO_IPSEC_ESP: 167 type_val, enc_dict, tlv = IPSECAttributeTypes.get(typ, (typ, {}, 0)) 168 else: 169 type_val, enc_dict, tlv = (typ, {}, 0) 170 val = enc_dict.get(val, val) 171 if isinstance(val, str): 172 raise ValueError("Unknown attribute '%s'" % val) 173 s = b"" 174 if (val & ~0xffff): 175 if not tlv: 176 warning("%r should not be TLV but is too big => using TLV encoding" % typ) # noqa: E501 177 n = 0 178 while val: 179 s = chb(val & 0xff) + s 180 val >>= 8 181 n += 1 182 val = n 183 else: 184 type_val |= 0x8000 185 return struct.pack("!HH", type_val, val) + s 186 187 @staticmethod 188 def num2type(typ, enc, proto=0): 189 if proto == PROTO_ISAKMP: 190 val = ISAKMPTransformNum.get(typ, (typ, {})) 191 elif proto == PROTO_IPSEC_ESP: 192 val = IPSECTransformNum.get(typ, (typ, {})) 193 else: 194 val = (typ, {}) 195 enc = val[1].get(enc, enc) 196 return (val[0], enc) 197 198 def _get_proto(self, pkt): 199 # Ugh 200 cur = pkt 201 while cur and getattr(cur, "proto", None) is None: 202 cur = cur.parent or cur.underlayer 203 if cur is None: 204 return PROTO_ISAKMP 205 return cur.proto 206 207 def i2m(self, pkt, i): 208 if i is None: 209 return b"" 210 proto = self._get_proto(pkt) 211 i = [ISAKMPTransformSetField.type2num(e, proto=proto) for e in i] 212 return b"".join(i) 213 214 def m2i(self, pkt, m): 215 # I try to ensure that we don't read off the end of our packet based 216 # on bad length fields we're provided in the packet. There are still 217 # conditions where struct.unpack() may not get enough packet data, but 218 # worst case that should result in broken attributes (which would 219 # be expected). (wam) 220 lst = [] 221 proto = self._get_proto(pkt) 222 while len(m) >= 4: 223 trans_type, = struct.unpack("!H", m[:2]) 224 is_tlv = not (trans_type & 0x8000) 225 if is_tlv: 226 # We should probably check to make sure the attribute type we 227 # are looking at is allowed to have a TLV format and issue a 228 # warning if we're given an TLV on a basic attribute. 229 value_len, = struct.unpack("!H", m[2:4]) 230 if value_len + 4 > len(m): 231 warning("Bad length for ISAKMP transform type=%#6x" % trans_type) # noqa: E501 232 value = m[4:4 + value_len] 233 value = reduce(lambda x, y: (x << 8) | y, struct.unpack("!%s" % ("B" * len(value),), value), 0) # noqa: E501 234 else: 235 trans_type &= 0x7fff 236 value_len = 0 237 value, = struct.unpack("!H", m[2:4]) 238 m = m[4 + value_len:] 239 lst.append(ISAKMPTransformSetField.num2type(trans_type, value, proto=proto)) 240 if len(m) > 0: 241 warning("Extra bytes after ISAKMP transform dissection [%r]" % m) 242 return lst 243 244 245ISAKMP_payload_type = { 246 0: "None", 247 1: "SA", 248 2: "Proposal", 249 3: "Transform", 250 4: "KE", 251 5: "ID", 252 6: "CERT", 253 7: "CR", 254 8: "Hash", 255 9: "SIG", 256 10: "Nonce", 257 11: "Notification", 258 12: "Delete", 259 13: "VendorID", 260} 261 262ISAKMP_exchange_type = { 263 0: "None", 264 1: "base", 265 2: "identity protection", 266 3: "authentication only", 267 4: "aggressive", 268 5: "informational", 269 32: "quick mode", 270} 271 272# https://www.iana.org/assignments/isakmp-registry/isakmp-registry.xhtml#isakmp-registry-3 273# IPSEC Security Protocol Identifiers 274ISAKMP_protos = { 275 1: "ISAKMP", 276 2: "IPSEC_AH", 277 3: "IPSEC_ESP", 278 4: "IPCOMP", 279 5: "GIGABEAM_RADIO" 280} 281 282ISAKMP_doi = { 283 0: "ISAKMP", 284 1: "IPSEC", 285} 286 287 288class _ISAKMP_class(Packet): 289 def default_payload_class(self, payload): 290 if self.next_payload == 0: 291 return conf.raw_layer 292 return ISAKMP_payload 293 294# -- ISAKMP 295 296 297class ISAKMP(_ISAKMP_class): # rfc2408 298 name = "ISAKMP" 299 fields_desc = [ 300 XStrFixedLenField("init_cookie", "", 8), 301 XStrFixedLenField("resp_cookie", "", 8), 302 ByteEnumField("next_payload", 0, ISAKMP_payload_type), 303 XByteField("version", 0x10), 304 ByteEnumField("exch_type", 0, ISAKMP_exchange_type), 305 FlagsField("flags", 0, 8, ["encryption", "commit", "auth_only"]), 306 IntField("id", 0), 307 IntField("length", None) 308 ] 309 310 def guess_payload_class(self, payload): 311 if self.flags & 1: 312 return conf.raw_layer 313 return _ISAKMP_class.guess_payload_class(self, payload) 314 315 def answers(self, other): 316 if isinstance(other, ISAKMP): 317 if other.init_cookie == self.init_cookie: 318 return 1 319 return 0 320 321 def post_build(self, p, pay): 322 p += pay 323 if self.length is None: 324 p = p[:24] + struct.pack("!I", len(p)) + p[28:] 325 return p 326 327 328# -- ISAKMP payloads 329 330class ISAKMP_payload(_ISAKMP_class): 331 name = "ISAKMP payload" 332 show_indent = 0 333 fields_desc = [ 334 ByteEnumField("next_payload", None, ISAKMP_payload_type), 335 ByteField("res", 0), 336 ShortField("length", None), 337 XStrLenField("load", "", length_from=lambda x:x.length - 4), 338 ] 339 340 def post_build(self, pkt, pay): 341 if self.length is None: 342 pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:] 343 return pkt + pay 344 345 346class ISAKMP_payload_Transform(ISAKMP_payload): 347 name = "IKE Transform" 348 deprecated_fields = { 349 "num": ("transform_count", ("2.5.0")), 350 "id": ("transform_id", ("2.5.0")), 351 } 352 fields_desc = ISAKMP_payload.fields_desc[:3] + [ 353 ByteField("transform_count", None), 354 ByteEnumField("transform_id", 1, {1: "KEY_IKE"}), 355 ShortField("res2", 0), 356 ISAKMPTransformSetField("transforms", None, length_from=lambda x: x.length - 8) # noqa: E501 357 # XIntField("enc",0x80010005L), 358 # XIntField("hash",0x80020002L), 359 # XIntField("auth",0x80030001L), 360 # XIntField("group",0x80040002L), 361 # XIntField("life_type",0x800b0001L), 362 # XIntField("durationh",0x000c0004L), 363 # XIntField("durationl",0x00007080L), 364 ] 365 366 367# https://tools.ietf.org/html/rfc2408#section-3.5 368class ISAKMP_payload_Proposal(ISAKMP_payload): 369 name = "IKE proposal" 370 fields_desc = ISAKMP_payload.fields_desc[:3] + [ 371 ByteField("proposal", 1), 372 ByteEnumField("proto", 1, ISAKMP_protos), 373 FieldLenField("SPIsize", None, "SPI", "B"), 374 ByteField("trans_nb", None), 375 StrLenField("SPI", "", length_from=lambda x: x.SPIsize), 376 PacketLenField("trans", conf.raw_layer(), ISAKMP_payload_Transform, length_from=lambda x: x.length - 8), # noqa: E501 377 ] 378 379 380# VendorID: https://www.rfc-editor.org/rfc/rfc2408#section-3.16 381 382# packet-isakmp.c from wireshark 383ISAKMP_VENDOR_IDS = { 384 b"\x09\x00\x26\x89\xdf\xd6\xb7\x12": "XAUTH", 385 b"\xaf\xca\xd7\x13h\xa1\xf1\xc9k\x86\x96\xfcwW\x01\x00": "RFC 3706 DPD", 386 b"@H\xb7\xd5n\xbc\xe8\x85%\xe7\xde\x7f\x00\xd6\xc2\xd3\x80": "Cisco Fragmentation", 387 b"J\x13\x1c\x81\x07\x03XE\\W(\xf2\x0e\x95E/": "RFC 3947 Negotiation of NAT-Transversal", # noqa: E501 388 b"\x90\xcb\x80\x91>\xbbin\x08c\x81\xb5\xecB{\x1f": "draft-ietf-ipsec-nat-t-ike-02", 389} 390 391 392class ISAKMP_payload_VendorID(ISAKMP_payload): 393 name = "ISAKMP Vendor ID" 394 fields_desc = ISAKMP_payload.fields_desc[:3] + [ 395 StrLenEnumField("VendorID", b"", 396 ISAKMP_VENDOR_IDS, 397 length_from=lambda x: x.length - 4) 398 ] 399 400 401class ISAKMP_payload_SA(ISAKMP_payload): 402 name = "ISAKMP SA" 403 fields_desc = ISAKMP_payload.fields_desc[:3] + [ 404 IntEnumField("doi", 1, ISAKMP_doi), 405 IntEnumField("situation", 1, {1: "identity"}), 406 PacketLenField("prop", conf.raw_layer(), ISAKMP_payload_Proposal, length_from=lambda x: x.length - 12), # noqa: E501 407 ] 408 409 410class ISAKMP_payload_Nonce(ISAKMP_payload): 411 name = "ISAKMP Nonce" 412 413 414class ISAKMP_payload_KE(ISAKMP_payload): 415 name = "ISAKMP Key Exchange" 416 417 418class ISAKMP_payload_ID(ISAKMP_payload): 419 name = "ISAKMP Identification" 420 fields_desc = ISAKMP_payload.fields_desc[:3] + [ 421 ByteEnumField("IDtype", 1, { 422 # Beware, apparently in-the-wild the values used 423 # appear to be the ones from IKEv2 (RFC4306 sect 3.5) 424 # and not ISAKMP (RFC2408 sect A.4) 425 1: "IPv4_addr", 426 11: "Key" 427 }), 428 ByteEnumField("ProtoID", 0, {0: "Unused"}), 429 ShortEnumField("Port", 0, {0: "Unused"}), 430 MultipleTypeField( 431 [ 432 (IPField("IdentData", "127.0.0.1"), 433 lambda pkt: pkt.IDtype == 1), 434 ], 435 StrLenField("IdentData", "", length_from=lambda x: x.length - 8), 436 ) 437 ] 438 439 440class ISAKMP_payload_Hash(ISAKMP_payload): 441 name = "ISAKMP Hash" 442 443 444NotifyMessageType = { 445 1: "INVALID-PAYLOAD-TYPE", 446 2: "DOI-NOT-SUPPORTED", 447 3: "SITUATION-NOT-SUPPORTED", 448 4: "INVALID-COOKIE", 449 5: "INVALID-MAJOR-VERSION", 450 6: "INVALID-MINOR-VERSION", 451 7: "INVALID-EXCHANGE-TYPE", 452 8: "INVALID-FLAGS", 453 9: "INVALID-MESSAGE-ID", 454 10: "INVALID-PROTOCOL-ID", 455 11: "INVALID-SPI", 456 12: "INVALID-TRANSFORM-ID", 457 13: "ATTRIBUTES-NOT-SUPPORTED", 458 14: "NO-PROPOSAL-CHOSEN", 459 15: "BAD-PROPOSAL-SYNTAX", 460 16: "PAYLOAD-MALFORMED", 461 17: "INVALID-KEY-INFORMATION", 462 18: "INVALID-ID-INFORMATION", 463 19: "INVALID-CERT-ENCODING", 464 20: "INVALID-CERTIFICATE", 465 21: "CERT-TYPE-UNSUPPORTED", 466 22: "INVALID-CERT-AUTHORITY", 467 23: "INVALID-HASH-INFORMATION", 468 24: "AUTHENTICATION-FAILED", 469 25: "INVALID-SIGNATURE", 470 26: "ADDRESS-NOTIFICATION", 471 27: "NOTIFY-SA-LIFETIME", 472 28: "CERTIFICATE-UNAVAILABLE", 473 29: "UNSUPPORTED-EXCHANGE-TYPE", 474 # RFC 3706 475 36136: "R-U-THERE", 476 36137: "R-U-THERE-ACK", 477} 478 479 480class ISAKMP_payload_Notify(ISAKMP_payload): 481 name = "ISAKMP Notify (Notification)" 482 fields_desc = ISAKMP_payload.fields_desc[:3] + [ 483 IntEnumField("doi", 0, ISAKMP_doi), 484 ByteEnumField("proto", 1, ISAKMP_protos), 485 FieldLenField("SPIsize", None, "SPI", "B"), 486 ShortEnumField("notify_msg_type", None, NotifyMessageType), 487 StrLenField("SPI", "", length_from=lambda x: x.SPIsize), 488 StrLenField("notify_data", "", 489 length_from=lambda x: x.length - x.SPIsize - 12) 490 ] 491 492 493class ISAKMP_payload_Delete(ISAKMP_payload): 494 name = "ISAKMP Delete" 495 fields_desc = ISAKMP_payload.fields_desc[:3] + [ 496 IntEnumField("doi", 0, ISAKMP_doi), 497 ByteEnumField("proto", 1, ISAKMP_protos), 498 FieldLenField("SPIsize", None, length_of="SPIs", fmt="B", 499 adjust=lambda pkt, x: x and x // len(pkt.SPIs)), 500 FieldLenField("SPIcount", None, count_of="SPIs", fmt="H"), 501 FieldListField("SPIs", [], 502 StrLenField("", "", length_from=lambda pkt: pkt.SPIsize), 503 count_from=lambda pkt: pkt.SPIcount), 504 ] 505 506 507bind_bottom_up(UDP, ISAKMP, dport=500) 508bind_bottom_up(UDP, ISAKMP, sport=500) 509bind_top_down(UDP, ISAKMP, dport=500, sport=500) 510 511bind_bottom_up(NON_ESP, ISAKMP) 512 513# Add bindings 514bind_top_down(_ISAKMP_class, ISAKMP_payload, next_payload=0) 515bind_layers(_ISAKMP_class, ISAKMP_payload_SA, next_payload=1) 516bind_layers(_ISAKMP_class, ISAKMP_payload_Proposal, next_payload=2) 517bind_layers(_ISAKMP_class, ISAKMP_payload_Transform, next_payload=3) 518bind_layers(_ISAKMP_class, ISAKMP_payload_KE, next_payload=4) 519bind_layers(_ISAKMP_class, ISAKMP_payload_ID, next_payload=5) 520# bind_layers(_ISAKMP_class, ISAKMP_payload_CERT, next_payload=6) 521# bind_layers(_ISAKMP_class, ISAKMP_payload_CR, next_payload=7) 522bind_layers(_ISAKMP_class, ISAKMP_payload_Hash, next_payload=8) 523# bind_layers(_ISAKMP_class, ISAKMP_payload_SIG, next_payload=9) 524bind_layers(_ISAKMP_class, ISAKMP_payload_Nonce, next_payload=10) 525bind_layers(_ISAKMP_class, ISAKMP_payload_Notify, next_payload=11) 526bind_layers(_ISAKMP_class, ISAKMP_payload_Delete, next_payload=12) 527bind_layers(_ISAKMP_class, ISAKMP_payload_VendorID, next_payload=13) 528 529 530def ikescan(ip): 531 """Sends/receives a ISAMPK payload SA with payload proposal""" 532 pkt = IP(dst=ip) 533 pkt /= UDP() 534 pkt /= ISAKMP(init_cookie=RandString(8), exch_type=2) 535 pkt /= ISAKMP_payload_SA(prop=ISAKMP_payload_Proposal()) 536 return sr(pkt) 537