• 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) 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