From 39b0c5a2b2bf8d3ef0cb656540c578a1ecf3536f Mon Sep 17 00:00:00 2001
From: gpotter2 <gabriel@potter.fr>
Date: Wed, 12 Jul 2017 10:45:03 +0100
Subject: [PATCH] [Contrib] Add Authentication payload type to IKEv2 (#661)

* ikev2 contrib fixup

* allow unknown transform_type and transform_id

* Simplify Authentication payload type

* CERT payload: add x509Cert and x509CRL attributes

* First re-indent

* Fixed ID not being a number

* Add some small IKEv2 tests

* Replaced tabs by spaces

* Separate classes by type

* New tests- small fixes
---
 scapy/contrib/ikev2.py  | 499 +++++++++++++++++++++++++++++++---------
 scapy/contrib/ikev2.uts |  81 +++++++
 2 files changed, 477 insertions(+), 103 deletions(-)
 create mode 100644 scapy/contrib/ikev2.uts

diff --git a/scapy/contrib/ikev2.py b/scapy/contrib/ikev2.py
index c28b1aee..c511f333 100644
--- a/scapy/contrib/ikev2.py
+++ b/scapy/contrib/ikev2.py
@@ -25,6 +25,8 @@ import struct
 
 from scapy.packet import *
 from scapy.fields import *
+from scapy.layers.inet6 import *
+from scapy.layers.x509 import X509_Cert, X509_CRL
 from scapy.ansmachine import *
 from scapy.layers.inet import IP,UDP
 from scapy.layers.isakmp import ISAKMP
@@ -99,98 +101,263 @@ IKEv2AttributeTypes= { "Encryption":    (1, { "DES-IV64"  : 1,
                                                  "ESN":   1,  }, 0),
                          }
 
+IKEv2AuthenticationTypes = {
+    0 : "Reserved",
+    1 : "RSA Digital Signature",
+    2 : "Shared Key Message Integrity Code",
+    3 : "DSS Digital Signature",
+    9 : "ECDSA with SHA-256 on the P-256 curve",
+    10 : "ECDSA with SHA-384 on the P-384 curve",
+    11 : "ECDSA with SHA-512 on the P-521 curve",
+    12 : "Generic Secure Password Authentication Method",
+    13 : "NULL Authentication",
+    14 : "Digital Signature"
+}
+
 IKEv2NotifyMessageTypes = {
-  1 : "UNSUPPORTED_CRITICAL_PAYLOAD",
-  4 : "INVALID_IKE_SPI",
-  5 : "INVALID_MAJOR_VERSION",
-  7 : "INVALID_SYNTAX",
-  9 : "INVALID_MESSAGE_ID",
-  11 : "INVALID_SPI",
-  14 : "NO_PROPOSAL_CHOSEN",
-  17 : "INVALID_KE_PAYLOAD",
-  24 : "AUTHENTICATION_FAILED",
-  34 : "SINGLE_PAIR_REQUIRED",
-  35 : "NO_ADDITIONAL_SAS",
-  36 : "INTERNAL_ADDRESS_FAILURE",
-  37 : "FAILED_CP_REQUIRED",
-  38 : "TS_UNACCEPTABLE",
-  39 : "INVALID_SELECTORS",
-  40 : "UNACCEPTABLE_ADDRESSES",
-  41 : "UNEXPECTED_NAT_DETECTED",
-  42 : "USE_ASSIGNED_HoA",
-  43 : "TEMPORARY_FAILURE",
-  44 : "CHILD_SA_NOT_FOUND",
-  45 : "INVALID_GROUP_ID",
-  46 : "AUTHORIZATION_FAILED",
-  16384 : "INITIAL_CONTACT",
-  16385 : "SET_WINDOW_SIZE",
-  16386 : "ADDITIONAL_TS_POSSIBLE",
-  16387 : "IPCOMP_SUPPORTED",
-  16388 : "NAT_DETECTION_SOURCE_IP",
-  16389 : "NAT_DETECTION_DESTINATION_IP",
-  16390 : "COOKIE",
-  16391 : "USE_TRANSPORT_MODE",
-  16392 : "HTTP_CERT_LOOKUP_SUPPORTED",
-  16393 : "REKEY_SA",
-  16394 : "ESP_TFC_PADDING_NOT_SUPPORTED",
-  16395 : "NON_FIRST_FRAGMENTS_ALSO",
-  16396 : "MOBIKE_SUPPORTED",
-  16397 : "ADDITIONAL_IP4_ADDRESS",
-  16398 : "ADDITIONAL_IP6_ADDRESS",
-  16399 : "NO_ADDITIONAL_ADDRESSES",
-  16400 : "UPDATE_SA_ADDRESSES",
-  16401 : "COOKIE2",
-  16402 : "NO_NATS_ALLOWED",
-  16403 : "AUTH_LIFETIME",
-  16404 : "MULTIPLE_AUTH_SUPPORTED",
-  16405 : "ANOTHER_AUTH_FOLLOWS",
-  16406 : "REDIRECT_SUPPORTED",
-  16407 : "REDIRECT",
-  16408 : "REDIRECTED_FROM",
-  16409 : "TICKET_LT_OPAQUE",
-  16410 : "TICKET_REQUEST",
-  16411 : "TICKET_ACK",
-  16412 : "TICKET_NACK",
-  16413 : "TICKET_OPAQUE",
-  16414 : "LINK_ID",
-  16415 : "USE_WESP_MODE",
-  16416 : "ROHC_SUPPORTED",
-  16417 : "EAP_ONLY_AUTHENTICATION",
-  16418 : "CHILDLESS_IKEV2_SUPPORTED",
-  16419 : "QUICK_CRASH_DETECTION",
-  16420 : "IKEV2_MESSAGE_ID_SYNC_SUPPORTED",
-  16421 : "IPSEC_REPLAY_COUNTER_SYNC_SUPPORTED",
-  16422 : "IKEV2_MESSAGE_ID_SYNC",
-  16423 : "IPSEC_REPLAY_COUNTER_SYNC",
-  16424 : "SECURE_PASSWORD_METHODS",
-  16425 : "PSK_PERSIST",
-  16426 : "PSK_CONFIRM",
-  16427 : "ERX_SUPPORTED",
-  16428 : "IFOM_CAPABILITY",
-  16429 : "SENDER_REQUEST_ID",
-  16430 : "IKEV2_FRAGMENTATION_SUPPORTED",
-  16431 : "SIGNATURE_HASH_ALGORITHMS",
-  16432 : "CLONE_IKE_SA_SUPPORTED",
-  16433 : "CLONE_IKE_SA"
+    1 : "UNSUPPORTED_CRITICAL_PAYLOAD",
+    4 : "INVALID_IKE_SPI",
+    5 : "INVALID_MAJOR_VERSION",
+    7 : "INVALID_SYNTAX",
+    9 : "INVALID_MESSAGE_ID",
+    11 : "INVALID_SPI",
+    14 : "NO_PROPOSAL_CHOSEN",
+    17 : "INVALID_KE_PAYLOAD",
+    24 : "AUTHENTICATION_FAILED",
+    34 : "SINGLE_PAIR_REQUIRED",
+    35 : "NO_ADDITIONAL_SAS",
+    36 : "INTERNAL_ADDRESS_FAILURE",
+    37 : "FAILED_CP_REQUIRED",
+    38 : "TS_UNACCEPTABLE",
+    39 : "INVALID_SELECTORS",
+    40 : "UNACCEPTABLE_ADDRESSES",
+    41 : "UNEXPECTED_NAT_DETECTED",
+    42 : "USE_ASSIGNED_HoA",
+    43 : "TEMPORARY_FAILURE",
+    44 : "CHILD_SA_NOT_FOUND",
+    45 : "INVALID_GROUP_ID",
+    46 : "AUTHORIZATION_FAILED",
+    16384 : "INITIAL_CONTACT",
+    16385 : "SET_WINDOW_SIZE",
+    16386 : "ADDITIONAL_TS_POSSIBLE",
+    16387 : "IPCOMP_SUPPORTED",
+    16388 : "NAT_DETECTION_SOURCE_IP",
+    16389 : "NAT_DETECTION_DESTINATION_IP",
+    16390 : "COOKIE",
+    16391 : "USE_TRANSPORT_MODE",
+    16392 : "HTTP_CERT_LOOKUP_SUPPORTED",
+    16393 : "REKEY_SA",
+    16394 : "ESP_TFC_PADDING_NOT_SUPPORTED",
+    16395 : "NON_FIRST_FRAGMENTS_ALSO",
+    16396 : "MOBIKE_SUPPORTED",
+    16397 : "ADDITIONAL_IP4_ADDRESS",
+    16398 : "ADDITIONAL_IP6_ADDRESS",
+    16399 : "NO_ADDITIONAL_ADDRESSES",
+    16400 : "UPDATE_SA_ADDRESSES",
+    16401 : "COOKIE2",
+    16402 : "NO_NATS_ALLOWED",
+    16403 : "AUTH_LIFETIME",
+    16404 : "MULTIPLE_AUTH_SUPPORTED",
+    16405 : "ANOTHER_AUTH_FOLLOWS",
+    16406 : "REDIRECT_SUPPORTED",
+    16407 : "REDIRECT",
+    16408 : "REDIRECTED_FROM",
+    16409 : "TICKET_LT_OPAQUE",
+    16410 : "TICKET_REQUEST",
+    16411 : "TICKET_ACK",
+    16412 : "TICKET_NACK",
+    16413 : "TICKET_OPAQUE",
+    16414 : "LINK_ID",
+    16415 : "USE_WESP_MODE",
+    16416 : "ROHC_SUPPORTED",
+    16417 : "EAP_ONLY_AUTHENTICATION",
+    16418 : "CHILDLESS_IKEV2_SUPPORTED",
+    16419 : "QUICK_CRASH_DETECTION",
+    16420 : "IKEV2_MESSAGE_ID_SYNC_SUPPORTED",
+    16421 : "IPSEC_REPLAY_COUNTER_SYNC_SUPPORTED",
+    16422 : "IKEV2_MESSAGE_ID_SYNC",
+    16423 : "IPSEC_REPLAY_COUNTER_SYNC",
+    16424 : "SECURE_PASSWORD_METHODS",
+    16425 : "PSK_PERSIST",
+    16426 : "PSK_CONFIRM",
+    16427 : "ERX_SUPPORTED",
+    16428 : "IFOM_CAPABILITY",
+    16429 : "SENDER_REQUEST_ID",
+    16430 : "IKEV2_FRAGMENTATION_SUPPORTED",
+    16431 : "SIGNATURE_HASH_ALGORITHMS",
+    16432 : "CLONE_IKE_SA_SUPPORTED",
+    16433 : "CLONE_IKE_SA"
 }
 
 IKEv2CertificateEncodings = {
-  1 : "PKCS #7 wrapped X.509 certificate",
-  2 : "PGP Certificate",
-  3 : "DNS Signed Key",
-  4 : "X.509 Certificate - Signature",
-  6 : "Kerberos Token",
-  7 : "Certificate Revocation List (CRL)",
-  8 : "Authority Revocation List (ARL)",
-  9 : "SPKI Certificate",
-  10 : "X.509 Certificate - Attribute",
-  11 : "Raw RSA Key",
-  12 : "Hash and URL of X.509 certificate",
-  13 : "Hash and URL of X.509 bundle"
+    1 : "PKCS #7 wrapped X.509 certificate",
+    2 : "PGP Certificate",
+    3 : "DNS Signed Key",
+    4 : "X.509 Certificate - Signature",
+    6 : "Kerberos Token",
+    7 : "Certificate Revocation List (CRL)",
+    8 : "Authority Revocation List (ARL)",
+    9 : "SPKI Certificate",
+    10 : "X.509 Certificate - Attribute",
+    11 : "Raw RSA Key",
+    12 : "Hash and URL of X.509 certificate",
+    13 : "Hash and URL of X.509 bundle"
+}
+
+IKEv2TrafficSelectorTypes = {
+    7 : "TS_IPV4_ADDR_RANGE",
+    8 : "TS_IPV6_ADDR_RANGE",
+    9 : "TS_FC_ADDR_RANGE"
 }
 
-# the name 'IKEv2TransformTypes' is actually a misnomer (since the table
-# holds info for all IKEv2 Attribute types, not just transforms, but we'll
+IPProtocolIDs = {
+    0 : "All protocols",
+    1 : "Internet Control Message Protocol",
+    2 : "Internet Group Management Protocol",
+    3 : "Gateway-to-Gateway Protocol",
+    4 : "IP in IP (encapsulation)",
+    5 : "Internet Stream Protocol",
+    6 : "Transmission Control Protocol",
+    7 : "Core-based trees",
+    8 : "Exterior Gateway Protocol",
+    9 : "Interior Gateway Protocol (any private interior gateway (used by Cisco for their IGRP))",
+    10 : "BBN RCC Monitoring",
+    11 : "Network Voice Protocol",
+    12 : "Xerox PUP",
+    13 : "ARGUS",
+    14 : "EMCON",
+    15 : "Cross Net Debugger",
+    16 : "Chaos",
+    17 : "User Datagram Protocol",
+    18 : "Multiplexing",
+    19 : "DCN Measurement Subsystems",
+    20 : "Host Monitoring Protocol",
+    21 : "Packet Radio Measurement",
+    22 : "XEROX NS IDP",
+    23 : "Trunk-1",
+    24 : "Trunk-2",
+    25 : "Leaf-1",
+    26 : "Leaf-2",
+    27 : "Reliable Datagram Protocol",
+    28 : "Internet Reliable Transaction Protocol",
+    29 : "ISO Transport Protocol Class 4",
+    30 : "Bulk Data Transfer Protocol",
+    31 : "MFE Network Services Protocol",
+    32 : "MERIT Internodal Protocol",
+    33 : "Datagram Congestion Control Protocol",
+    34 : "Third Party Connect Protocol",
+    35 : "Inter-Domain Policy Routing Protocol",
+    36 : "Xpress Transport Protocol",
+    37 : "Datagram Delivery Protocol",
+    38 : "IDPR Control Message Transport Protocol",
+    39 : "TP++ Transport Protocol",
+    40 : "IL Transport Protocol",
+    41 : "IPv6 Encapsulation",
+    42 : "Source Demand Routing Protocol",
+    43 : "Routing Header for IPv6",
+    44 : "Fragment Header for IPv6",
+    45 : "Inter-Domain Routing Protocol",
+    46 : "Resource Reservation Protocol",
+    47 : "Generic Routing Encapsulation",
+    48 : "Mobile Host Routing Protocol",
+    49 : "BNA",
+    50 : "Encapsulating Security Payload",
+    51 : "Authentication Header",
+    52 : "Integrated Net Layer Security Protocol",
+    53 : "SwIPe",
+    54 : "NBMA Address Resolution Protocol",
+    55 : "IP Mobility (Min Encap)",
+    56 : "Transport Layer Security Protocol (using Kryptonet key management)",
+    57 : "Simple Key-Management for Internet Protocol",
+    58 : "ICMP for IPv6",
+    59 : "No Next Header for IPv6",
+    60 : "Destination Options for IPv6",
+    61 : "Any host internal protocol",
+    62 : "CFTP",
+    63 : "Any local network",
+    64 : "SATNET and Backroom EXPAK",
+    65 : "Kryptolan",
+    66 : "MIT Remote Virtual Disk Protocol",
+    67 : "Internet Pluribus Packet Core",
+    68 : "Any distributed file system",
+    69 : "SATNET Monitoring",
+    70 : "VISA Protocol",
+    71 : "Internet Packet Core Utility",
+    72 : "Computer Protocol Network Executive",
+    73 : "Computer Protocol Heart Beat",
+    74 : "Wang Span Network",
+    75 : "Packet Video Protocol",
+    76 : "Backroom SATNET Monitoring",
+    77 : "SUN ND PROTOCOL-Temporary",
+    78 : "WIDEBAND Monitoring",
+    79 : "WIDEBAND EXPAK",
+    80 : "International Organization for Standardization Internet Protocol",
+    81 : "Versatile Message Transaction Protocol",
+    82 : "Secure Versatile Message Transaction Protocol",
+    83 : "VINES",
+    84 : "Internet Protocol Traffic Manager",
+    85 : "NSFNET-IGP",
+    86 : "Dissimilar Gateway Protocol",
+    87 : "TCF",
+    88 : "EIGRP",
+    89 : "Open Shortest Path First",
+    90 : "Sprite RPC Protocol",
+    91 : "Locus Address Resolution Protocol",
+    92 : "Multicast Transport Protocol",
+    93 : "AX.25",
+    94 : "IP-within-IP Encapsulation Protocol",
+    95 : "Mobile Internetworking Control Protocol",
+    96 : "Semaphore Communications Sec. Pro",
+    97 : "Ethernet-within-IP Encapsulation",
+    98 : "Encapsulation Header",
+    99 : "Any private encryption scheme",
+    100 : "GMTP",
+    101 : "Ipsilon Flow Management Protocol",
+    102 : "PNNI over IP",
+    103 : "Protocol Independent Multicast",
+    104 : "IBM's ARIS (Aggregate Route IP Switching) Protocol",
+    105 : "SCPS (Space Communications Protocol Standards)",
+    106 : "QNX",
+    107 : "Active Networks",
+    108 : "IP Payload Compression Protocol",
+    109 : "Sitara Networks Protocol",
+    110 : "Compaq Peer Protocol",
+    111 : "IPX in IP",
+    112 : "Virtual Router Redundancy Protocol, Common Address Redundancy Protocol (not IANA assigned)",
+    113 : "PGM Reliable Transport Protocol",
+    114 : "Any 0-hop protocol",
+    115 : "Layer Two Tunneling Protocol Version 3",
+    116 : "D-II Data Exchange (DDX)",
+    117 : "Interactive Agent Transfer Protocol",
+    118 : "Schedule Transfer Protocol",
+    119 : "SpectraLink Radio Protocol",
+    120 : "Universal Transport Interface Protocol",
+    121 : "Simple Message Protocol",
+    122 : "Simple Multicast Protocol",
+    123 : "Performance Transparency Protocol",
+    124 : "Intermediate System to Intermediate System (IS-IS) Protocol over IPv4",
+    125 : "Flexible Intra-AS Routing Environment",
+    126 : "Combat Radio Transport Protocol",
+    127 : "Combat Radio User Datagram",
+    128 : "Service-Specific Connection-Oriented Protocol in a Multilink and Connectionless Environment",
+    129 : "IPLT",
+    130 : "Secure Packet Shield",
+    131 : "Private IP Encapsulation within IP",
+    132 : "Stream Control Transmission Protocol",
+    133 : "Fibre Channel",
+    134 : "Reservation Protocol (RSVP) End-to-End Ignore",
+    135 : "Mobility Extension Header for IPv6",
+    136 : "Lightweight User Datagram Protocol",
+    137 : "Multiprotocol Label Switching Encapsulated in IP",
+    138 : "MANET Protocols",
+    139 : "Host Identity Protocol",
+    140 : "Site Multihoming by IPv6 Intermediation",
+    141 : "Wrapped Encapsulating Security Payload",
+    142 : "Robust Header Compression",
+}
+
+# the name 'IKEv2TransformTypes' is actually a misnomer (since the table 
+# holds info for all IKEv2 Attribute types, not just transforms, but we'll 
 # keep it for backwards compatibility... for now at least
 IKEv2TransformTypes = IKEv2AttributeTypes
 
@@ -243,11 +410,11 @@ class IKEv2(IKEv2_class): # rfc4306
         StrFixedLenField("init_SPI","",8),
         StrFixedLenField("resp_SPI","",8),
         ByteEnumField("next_payload",0,IKEv2_payload_type),
-        XByteField("version",0x20), # IKEv2, right?
+        XByteField("version", 0x20),
         ByteEnumField("exch_type",0,IKEv2_exchange_type),
         FlagsField("flags",0, 8, ["res0","res1","res2","Initiator","Version","Response","res6","res7"]),
         IntField("id",0),
-        IntField("length",None)
+        IntField("length",None) # Length of total message: packets + all payloads
         ]
 
     def guess_payload_class(self, payload):
@@ -295,13 +462,13 @@ class IKEv2_payload_Proposal(IKEv2_class):
     fields_desc = [
         ByteEnumField("next_payload",None,{0:"last", 2:"Proposal"}),
         ByteField("res",0),
-        FieldLenField("length",None,"trans","H", adjust=lambda pkt,x:x+8),
+        FieldLenField("length",None,"trans","H", adjust=lambda pkt,x:x+8+(pkt.SPIsize if pkt.SPIsize else 0)),
         ByteField("proposal",1),
-        ByteEnumField("proto",1,{1:"IKEv2"}),
+        ByteEnumField("proto",1,{1:"IKEv2", 2:"AH", 3:"ESP"}),
         FieldLenField("SPIsize",None,"SPI","B"),
         ByteField("trans_nb",None),
-        StrLenField("SPI","",length_from=lambda x:x.SPIsize),
-        PacketLenField("trans",conf.raw_layer(),IKEv2_payload_Transform,length_from=lambda x:x.length-8),
+        StrLenField("SPI","",length_from=lambda pkt:pkt.SPIsize),
+        PacketLenField("trans",conf.raw_layer(),IKEv2_payload_Transform,length_from=lambda pkt:pkt.length-8-pkt.SPIsize),
         ]
 
 
@@ -315,6 +482,18 @@ class IKEv2_payload(IKEv2_class):
         ]
 
 
+class IKEv2_payload_AUTH(IKEv2_class):
+    name = "IKEv2 Authentication"
+    overload_fields = { IKEv2: { "next_payload":39 }}
+    fields_desc = [
+        ByteEnumField("next_payload",None,IKEv2_payload_type),
+        ByteField("res",0),
+        FieldLenField("length",None,"load","H", adjust=lambda pkt,x:x+8),
+        ByteEnumField("auth_type",None,IKEv2AuthenticationTypes),
+        X3BytesField("res2",0),
+        StrLenField("load","",length_from=lambda x:x.length-8),
+        ]
+
 class IKEv2_payload_VendorID(IKEv2_class):
     name = "IKEv2 Vendor ID"
     overload_fields = { IKEv2: { "next_payload":43 }}
@@ -325,6 +504,94 @@ class IKEv2_payload_VendorID(IKEv2_class):
         StrLenField("vendorID","",length_from=lambda x:x.length-4),
         ]
 
+class TrafficSelector(Packet):
+    @classmethod
+    def dispatch_hook(cls, _pkt=None, *args, **kargs):
+        if _pkt and len(_pkt) >= 16:
+            ts_type = struct.unpack("!B", _pkt[0:1])[0]
+            if ts_type == 7:
+                return IPv4TrafficSelector
+            elif ts_type == 8:
+                return IPv6TrafficSelector
+            elif ts_type == 9:
+                return EncryptedTrafficSelector
+            else:
+                return RawTrafficSelector
+        return IPv4TrafficSelector
+
+class IPv4TrafficSelector(TrafficSelector):
+    name = "IKEv2 IPv4 Traffic Selector"
+    fields_desc = [
+        ByteEnumField("TS_type",7,IKEv2TrafficSelectorTypes),
+        ByteEnumField("IP_protocol_ID",None,IPProtocolIDs),
+        ShortField("length",16),
+        ShortField("start_port",0),
+        ShortField("end_port",65535),
+        IPField("starting_address_v4","192.168.0.1"),
+        IPField("ending_address_v4","192.168.0.255"),
+        ]
+
+class IPv6TrafficSelector(TrafficSelector):
+    name = "IKEv2 IPv6 Traffic Selector"
+    fields_desc = [
+        ByteEnumField("TS_type",8,IKEv2TrafficSelectorTypes),
+        ByteEnumField("IP_protocol_ID",None,IPProtocolIDs),
+        ShortField("length",20),
+        ShortField("start_port",0),
+        ShortField("end_port",65535),
+        IP6Field("starting_address_v6","2001::"),
+        IP6Field("ending_address_v6","2001::"),
+        ]
+
+class EncryptedTrafficSelector(TrafficSelector):
+    name = "IKEv2 Encrypted Traffic Selector"
+    fields_desc = [
+        ByteEnumField("TS_type",9,IKEv2TrafficSelectorTypes),
+        ByteEnumField("IP_protocol_ID",None,IPProtocolIDs),
+        ShortField("length",16),
+        ByteField("res",0),
+        X3BytesField("starting_address_FC",0),
+        ByteField("res2",0),
+        X3BytesField("ending_address_FC",0),
+        ByteField("starting_R_CTL",0),
+        ByteField("ending_R_CTL",0),
+        ByteField("starting_type",0),
+        ByteField("ending_type",0),
+        ]
+
+class RawTrafficSelector(TrafficSelector):
+    name = "IKEv2 Encrypted Traffic Selector"
+    fields_desc = [
+        ByteEnumField("TS_type",None,IKEv2TrafficSelectorTypes),
+        ByteEnumField("IP_protocol_ID",None,IPProtocolIDs),
+        FieldLenField("length",None,"load","H", adjust=lambda pkt,x:x+4),
+        PacketField("load", "", Raw)
+        ]
+
+class IKEv2_payload_TSi(IKEv2_class):
+    name = "IKEv2 Traffic Selector - Initiator"
+    overload_fields = { IKEv2: { "next_payload":44 }}
+    fields_desc = [
+        ByteEnumField("next_payload",None,IKEv2_payload_type),
+        ByteField("res",0),
+        FieldLenField("length",None,"traffic_selector","H", adjust=lambda pkt,x:x+8),
+        ByteField("number_of_TSs",0),
+        X3BytesField("res2",0),
+        PacketListField("traffic_selector",None,TrafficSelector,length_from=lambda x:x.length-8,count_from=lambda x:x.number_of_TSs),
+        ]
+
+class IKEv2_payload_TSr(IKEv2_class):
+    name = "IKEv2 Traffic Selector - Responder"
+    overload_fields = { IKEv2: { "next_payload":45 }}
+    fields_desc = [
+        ByteEnumField("next_payload",None,IKEv2_payload_type),
+        ByteField("res",0),
+        FieldLenField("length",None,"traffic_selector","H", adjust=lambda pkt,x:x+8),
+        ByteField("number_of_TSs",0),
+        X3BytesField("res2",0),
+        PacketListField("traffic_selector",None,TrafficSelector,length_from=lambda x:x.length-8,count_from=lambda x:x.number_of_TSs),
+        ]
+
 class IKEv2_payload_Delete(IKEv2_class):
     name = "IKEv2 Vendor ID"
     overload_fields = { IKEv2: { "next_payload":42 }}
@@ -409,8 +676,6 @@ class IKEv2_payload_IDr(IKEv2_class):
         StrLenField("load","",length_from=lambda x:x.length-8),
         ]
 
-
-
 class IKEv2_payload_Encrypted(IKEv2_class):
     name = "IKEv2 Encrypted and Authenticated"
     overload_fields = { IKEv2: { "next_payload":46 }}
@@ -432,11 +697,44 @@ class IKEv2_payload_CERTREQ(IKEv2_class):
         ]
 
 class IKEv2_payload_CERT(IKEv2_class):
+    @classmethod
+    def dispatch_hook(cls, _pkt=None, *args, **kargs):
+        if _pkt and len(_pkt) >= 16:
+            ts_type = struct.unpack("!B", _pkt[4:5])[0]
+            if ts_type == 4:
+                return IKEv2_payload_CERT_CRT
+            elif ts_type == 7:
+                return IKEv2_payload_CERT_CRL
+            else:
+                return IKEv2_payload_CERT_STR
+        return IKEv2_payload_CERT_STR
+
+class IKEv2_payload_CERT_CRT(IKEv2_payload_CERT):
     name = "IKEv2 Certificate"
     fields_desc = [
         ByteEnumField("next_payload",None,IKEv2_payload_type),
         ByteField("res",0),
-        FieldLenField("length",None,"cert_data","H",adjust=lambda pkt,x:x+5),
+        FieldLenField("length",None,"x509Cert","H",adjust=lambda pkt,x: x+len(pkt.x509Cert)+5),
+        ByteEnumField("cert_type",4,IKEv2CertificateEncodings),
+        PacketLenField("x509Cert", X509_Cert(''), X509_Cert, length_from=lambda x:x.length-5),
+        ]
+
+class IKEv2_payload_CERT_CRL(IKEv2_payload_CERT):
+    name = "IKEv2 Certificate"
+    fields_desc = [
+        ByteEnumField("next_payload",None,IKEv2_payload_type),
+        ByteField("res",0),
+        FieldLenField("length",None,"x509CRL","H",adjust=lambda pkt,x: x+len(pkt.x509CRL)+5),
+        ByteEnumField("cert_type",7,IKEv2CertificateEncodings),
+        PacketLenField("x509CRL", X509_CRL(''), X509_CRL, length_from=lambda x:x.length-5),
+        ]
+
+class IKEv2_payload_CERT_STR(IKEv2_payload_CERT):
+    name = "IKEv2 Certificate"
+    fields_desc = [
+        ByteEnumField("next_payload",None,IKEv2_payload_type),
+        ByteField("res",0),
+        FieldLenField("length",None,"cert_data","H",adjust=lambda pkt,x: x+5),
         ByteEnumField("cert_type",0,IKEv2CertificateEncodings),
         StrLenField("cert_data","",length_from=lambda x:x.length-5),
         ]
@@ -456,12 +754,7 @@ split_layers(UDP, ISAKMP, dport=500)
 bind_layers( UDP,           IKEv2,        dport=500, sport=500) # TODO: distinguish IKEv1/IKEv2
 bind_layers( UDP,           IKEv2,        dport=4500, sport=4500)
 
-def ikev2scan(ip):
+def ikev2scan(ip, **kwargs):
+    """Send a IKEv2 SA to an IP and wait for answers."""
     return sr(IP(dst=ip)/UDP()/IKEv2(init_SPI=RandString(8),
-                                      exch_type=34)/IKEv2_payload_SA(prop=IKEv2_payload_Proposal()))
-
-# conf.debug_dissector = 1
-
-if __name__ == "__main__":
-    from scapy.main import interact
-    interact(mydict=globals(), mybanner="IKEv2 alpha-level protocol implementation")
+                                      exch_type=34)/IKEv2_payload_SA(prop=IKEv2_payload_Proposal()), **kwargs)
diff --git a/scapy/contrib/ikev2.uts b/scapy/contrib/ikev2.uts
new file mode 100644
index 00000000..dd6ebd6a
--- /dev/null
+++ b/scapy/contrib/ikev2.uts
@@ -0,0 +1,81 @@
+% Ikev2 Tests
+* Tests for the Ikev2 layer
+
++ Basic Layer Tests
+
+= Ikev2 build
+
+a = IKEv2()
+assert str(a) == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c'
+
+= Ikev2 dissection
+
+a = IKEv2("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! \x00\x00\x00\x00\x00\x00\x00\x00\x000\x00\x00\x00\x14\x00\x00\x00\x10\x01\x01\x00\x00\x00\x00\x00\x08\x02\x00\x00\x03")
+assert a[IKEv2_payload_Transform].transform_type == 2
+assert a[IKEv2_payload_Transform].transform_id == 3
+assert a.next_payload == 33
+assert a[IKEv2_payload_SA].next_payload == 0
+assert a[IKEv2_payload_Proposal].next_payload == 0
+assert a[IKEv2_payload_Proposal].proposal == 1
+assert a[IKEv2_payload_Transform].next_payload == 0
+a[IKEv2_payload_Transform].show()
+
+
+= Build Ikev2 SA request packet
+
+a = IKEv2(init_SPI="MySPI",exch_type=34)/IKEv2_payload_SA(prop=IKEv2_payload_Proposal())
+assert str(a) == b'MySPI\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! "\x00\x00\x00\x00\x00\x00\x00\x00(\x00\x00\x00\x0c\x00\x00\x00\x08\x01\x01\x00\x00'
+
+
+## packets taken from
+## https://github.com/wireshark/wireshark/blob/master/test/captures/ikev2-decrypt-aes128ccm12.pcap
+
+= Dissect Initiator Request
+
+a = Ether(b'\x00!k\x91#H\xb8\'\xeb\xa6XI\x08\x00E\x00\x01\x14u\xc2@\x00@\x11@\xb6\xc0\xa8\x01\x02\xc0\xa8\x01\x0e\x01\xf4\x01\xf4\x01\x00=8\xeahM!Yz\xfd6\x00\x00\x00\x00\x00\x00\x00\x00! "\x08\x00\x00\x00\x00\x00\x00\x00\xf8"\x00\x00(\x00\x00\x00$\x01\x01\x00\x03\x03\x00\x00\x0c\x01\x00\x00\x0f\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x05\x00\x00\x00\x08\x04\x00\x00\x13(\x00\x00H\x00\x13\x00\x002\xc6\xdf\xfe\\C\xb0\xd5\x81\x1f~\xaa\xa8L\x9fx\xbf\x99\xb9\x06\x9c+\x07.\x0b\x82\xf4k\xf6\xf6m\xd4_\x97\xef\x89\xee(_\xd5\xdfRzDwkR\x9f\xc9\xd8\xa9\t\xd8B\xa6\xfbY\xb9j\tS\x95ar)\x00\x00$\xb6UF-oKf\xf8r\xcc\xd7\xf0\xf4\xb4\x85w2\x92\x139\xcb\xaaR7\xed\xba$O&+h#)\x00\x00\x1c\x00\x00@\x04\x94\x9c\x9d\xb5s\x9du\xa9t\xa4\x9c\x18F\x186\x9b4\xb7\xf9B)\x00\x00\x1c\x00\x00@\x05>r\x1bF\xbe\x07\xd51\x11B]\x7f\x80\xd2\xc6\xe2 \xc6\x07.\x00\x00\x00\x10\x00\x00@/\x00\x01\x00\x02\x00\x03\x00\x04')
+assert a[IKEv2_payload_SA].prop.trans.transform_id == 15
+assert a[IKEv2_payload_Notify].next_payload == 41
+assert IP(a[IKEv2_payload_Notify].load).src == "70.24.54.155"
+assert IP(a[IKEv2_payload_Notify].payload.load).dst == "32.198.7.46"
+
+= Dissect Responder Response
+
+b = Ether(b'\xb8\'\xeb\xa6XI\x00!k\x91#H\x08\x00E\x00\x01\x0c\xd2R@\x00@\x11\xe4-\xc0\xa8\x01\x0e\xc0\xa8\x01\x02\x01\xf4\x01\xf4\x00\xf8\x07\xdd\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac! " \x00\x00\x00\x00\x00\x00\x00\xf0"\x00\x00(\x00\x00\x00$\x01\x01\x00\x03\x03\x00\x00\x0c\x01\x00\x00\x0f\x80\x0e\x00\x80\x03\x00\x00\x08\x02\x00\x00\x05\x00\x00\x00\x08\x04\x00\x00\x13(\x00\x00H\x00\x13\x00\x00,f\xbe\xad\xb6\xce\x855\xd6!\x8c\xb4\x01\xaaZ\x1e\xb4\x03[\x97\xca\xdd\xaf67J\x97\x9c\x04F\xb8\x80\x05\x06\xbf\x9do\x95\tR2k\xf3\x01\x19\x13\xda\x93\xbb\x8e@\xf8\x157k\xe1\xa0h\x01\xc0\xa6>;T)\x00\x00$\x9e]&sy\xe6\x81\xe7\xd3\x8d\x81\xc7\x10\xd3\x83@\x1d\xe7\xe3`{\x92m\x90\xa9\x95\x8a\xdc\xb5(1\xaa)\x00\x00\x1c\x00\x00@\x04z\x07\x85\'=Y 8)\xa6\x97U\x0f1\xcb\xb9N\xb7+C)\x00\x00\x1c\x00\x00@\x05\xc3\xe5\x8a\x8c\xc9\x93<\xe0\xb7\x8f*P\xe8\xde\x80\x13N\x12\xce1\x00\x00\x00\x08\x00\x00@\x14')
+assert b[UDP].dport == 500
+assert b[IKEv2_payload_KE].load == b',f\xbe\xad\xb6\xce\x855\xd6!\x8c\xb4\x01\xaaZ\x1e\xb4\x03[\x97\xca\xdd\xaf67J\x97\x9c\x04F\xb8\x80\x05\x06\xbf\x9do\x95\tR2k\xf3\x01\x19\x13\xda\x93\xbb\x8e@\xf8\x157k\xe1\xa0h\x01\xc0\xa6>;T'
+assert b[IKEv2_payload_Nonce].payload.type == 16388
+assert b[IKEv2_payload_Nonce].payload.payload.payload.next_payload == 0
+
+= Dissect Encrypted Inititor Request
+
+a = Ether(b"\x00!k\x91#H\xb8'\xeb\xa6XI\x08\x00E\x00\x00Yu\xe2@\x00@\x11AQ\xc0\xa8\x01\x02\xc0\xa8\x01\x0e\x01\xf4\x01\xf4\x00E}\xe0\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac. %\x08\x00\x00\x00\x02\x00\x00\x00=*\x00\x00!\xcc\xa0\xb3]\xe5\xab\xc5\x1c\x99\x87\xcb\xf1\xf5\xec\xff!\x0e\xb7g\xcd\xb8Qy8;\x96Mx\xe2")
+assert a[IKEv2_payload_Encrypted].next_payload == 42
+assert a[IKEv2_payload_Encrypted].load == b'\xcc\xa0\xb3]\xe5\xab\xc5\x1c\x99\x87\xcb\xf1\xf5\xec\xff!\x0e\xb7g\xcd\xb8Qy8;\x96Mx\xe2'
+
+= Dissect Encrypted Responder Response
+
+b = Ether(b"\xb8'\xeb\xa6XI\x00!k\x91#H\x08\x00E\x00\x00Q\xd5y@\x00@\x11\xe1\xc1\xc0\xa8\x01\x0e\xc0\xa8\x01\x02\x01\xf4\x01\xf4\x00=\xf9F\xeahM!Yz\xfd6\xd9\xfe*\xb2-\xac#\xac. % \x00\x00\x00\x02\x00\x00\x005\x00\x00\x00\x19\xa8\x0c\x95{\xac\x15\xc3\xf8\xaf\xdf1Z\x81\xccK|@\xe8f\rD")
+assert b[IKEv2].init_SPI == b'\xeahM!Yz\xfd6'
+assert b[IKEv2].resp_SPI == b'\xd9\xfe*\xb2-\xac#\xac'
+assert b[IKEv2].next_payload == 46
+assert b[IKEv2_payload_Encrypted].load == b'\xa8\x0c\x95{\xac\x15\xc3\xf8\xaf\xdf1Z\x81\xccK|@\xe8f\rD'
+
+= Test Certs detection
+
+a = IKEv2_payload_CERT(str(IKEv2_payload_CERT_CRL()))
+b = IKEv2_payload_CERT(str(IKEv2_payload_CERT_STR()))
+c = IKEv2_payload_CERT(str(IKEv2_payload_CERT_CRT()))
+
+assert isinstance(a, IKEv2_payload_CERT_CRL)
+assert isinstance(b, IKEv2_payload_CERT_STR)
+assert isinstance(c, IKEv2_payload_CERT_CRT)
+
+= Test TrafficSelector detection
+
+a = TrafficSelector(str(IPv4TrafficSelector()))
+b = TrafficSelector(str(IPv6TrafficSelector()))
+c = TrafficSelector(str(EncryptedTrafficSelector()))
+
+assert isinstance(a, IPv4TrafficSelector)
+assert isinstance(b, IPv6TrafficSelector)
+assert isinstance(c, EncryptedTrafficSelector)
\ No newline at end of file
-- 
GitLab