# This file is part of Scapy # See http://www.secdev.org/projects/scapy for more information # Copyright (C) Philippe Biondi # This program is published under a GPLv2 license """ Extensible Authentication Protocol (EAP) """ from __future__ import absolute_import from __future__ import print_function import struct from scapy.fields import BitField, ByteField, XByteField,\ ShortField, IntField, XIntField, ByteEnumField, StrLenField, XStrField,\ XStrLenField, XStrFixedLenField, LenField, FieldLenField, FieldListField,\ PacketField, PacketListField, ConditionalField, PadField from scapy.packet import Packet, Padding, bind_layers from scapy.layers.l2 import SourceMACField, Ether, CookedLinux, GRE, SNAP from scapy.config import conf from scapy.compat import orb, chb # # EAPOL # ######################################################################### # # EAPOL protocol version # IEEE Std 802.1X-2010 - Section 11.3.1 ######################################################################### # eapol_versions = { 0x1: "802.1X-2001", 0x2: "802.1X-2004", 0x3: "802.1X-2010", } ######################################################################### # # EAPOL Packet Types # IEEE Std 802.1X-2010 - Table 11.3 ######################################################################### # eapol_types = { 0x0: "EAP-Packet", # "EAPOL-EAP" in 801.1X-2010 0x1: "EAPOL-Start", 0x2: "EAPOL-Logoff", 0x3: "EAPOL-Key", 0x4: "EAPOL-Encapsulated-ASF-Alert", 0x5: "EAPOL-MKA", 0x6: "EAPOL-Announcement (Generic)", 0x7: "EAPOL-Announcement (Specific)", 0x8: "EAPOL-Announcement-Req" } class EAPOL(Packet): """ EAPOL - IEEE Std 802.1X-2010 """ name = "EAPOL" fields_desc = [ ByteEnumField("version", 1, eapol_versions), ByteEnumField("type", 0, eapol_types), LenField("len", None, "H") ] EAP_PACKET = 0 START = 1 LOGOFF = 2 KEY = 3 ASF = 4 def extract_padding(self, s): tmp_len = self.len return s[:tmp_len], s[tmp_len:] def hashret(self): return chb(self.type) + self.payload.hashret() def answers(self, other): if isinstance(other, EAPOL): if ((self.type == self.EAP_PACKET) and (other.type == self.EAP_PACKET)): return self.payload.answers(other.payload) return 0 def mysummary(self): return self.sprintf("EAPOL %EAPOL.type%") # # EAP # ######################################################################### # # EAP methods types # http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-4 ######################################################################### # eap_types = { 0: "Reserved", 1: "Identity", 2: "Notification", 3: "Legacy Nak", 4: "MD5-Challenge", 5: "One-Time Password (OTP)", 6: "Generic Token Card (GTC)", 7: "Allocated - RFC3748", 8: "Allocated - RFC3748", 9: "RSA Public Key Authentication", 10: "DSS Unilateral", 11: "KEA", 12: "KEA-VALIDATE", 13: "EAP-TLS", 14: "Defender Token (AXENT)", 15: "RSA Security SecurID EAP", 16: "Arcot Systems EAP", 17: "EAP-Cisco Wireless", 18: "GSM Subscriber Identity Modules (EAP-SIM)", 19: "SRP-SHA1", 20: "Unassigned", 21: "EAP-TTLS", 22: "Remote Access Service", 23: "EAP-AKA Authentication", 24: "EAP-3Com Wireless", 25: "PEAP", 26: "MS-EAP-Authentication", 27: "Mutual Authentication w/Key Exchange (MAKE)", 28: "CRYPTOCard", 29: "EAP-MSCHAP-V2", 30: "DynamID", 31: "Rob EAP", 32: "Protected One-Time Password", 33: "MS-Authentication-TLV", 34: "SentriNET", 35: "EAP-Actiontec Wireless", 36: "Cogent Systems Biometrics Authentication EAP", 37: "AirFortress EAP", 38: "EAP-HTTP Digest", 39: "SecureSuite EAP", 40: "DeviceConnect EAP", 41: "EAP-SPEKE", 42: "EAP-MOBAC", 43: "EAP-FAST", 44: "ZoneLabs EAP (ZLXEAP)", 45: "EAP-Link", 46: "EAP-PAX", 47: "EAP-PSK", 48: "EAP-SAKE", 49: "EAP-IKEv2", 50: "EAP-AKA", 51: "EAP-GPSK", 52: "EAP-pwd", 53: "EAP-EKE Version 1", 54: "EAP Method Type for PT-EAP", 55: "TEAP", 254: "Reserved for the Expanded Type", 255: "Experimental", } ######################################################################### # # EAP codes # http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-1 ######################################################################### # eap_codes = { 1: "Request", 2: "Response", 3: "Success", 4: "Failure", 5: "Initiate", 6: "Finish" } class EAP(Packet): """ RFC 3748 - Extensible Authentication Protocol (EAP) """ name = "EAP" fields_desc = [ ByteEnumField("code", 4, eap_codes), ByteField("id", 0), ShortField("len", None), ConditionalField(ByteEnumField("type", 0, eap_types), lambda pkt:pkt.code not in [ EAP.SUCCESS, EAP.FAILURE]), ConditionalField(FieldListField( "desired_auth_types", [], ByteEnumField("auth_type", 0, eap_types), length_from=lambda pkt: pkt.len - 4 ), lambda pkt:pkt.code == EAP.RESPONSE and pkt.type == 3), # noqa: E501 ConditionalField( StrLenField("identity", '', length_from=lambda pkt: pkt.len - 5), lambda pkt: pkt.code == EAP.RESPONSE and hasattr(pkt, 'type') and pkt.type == 1), # noqa: E501 ConditionalField( StrLenField("message", '', length_from=lambda pkt: pkt.len - 5), lambda pkt: pkt.code == EAP.REQUEST and hasattr(pkt, 'type') and pkt.type == 1) # noqa: E501 ] ######################################################################### # # EAP codes # http://www.iana.org/assignments/eap-numbers/eap-numbers.xhtml#eap-numbers-1 ######################################################################### # REQUEST = 1 RESPONSE = 2 SUCCESS = 3 FAILURE = 4 INITIATE = 5 FINISH = 6 registered_methods = {} @classmethod def register_variant(cls): cls.registered_methods[cls.type.default] = cls @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): if _pkt: c = orb(_pkt[0]) if c in [1, 2] and len(_pkt) >= 5: t = orb(_pkt[4]) return cls.registered_methods.get(t, cls) return cls def answers(self, other): if isinstance(other, EAP): if self.code == self.REQUEST: return 0 elif self.code == self.RESPONSE: if ((other.code == self.REQUEST) and (other.type == self.type)): return 1 elif other.code == self.RESPONSE: return 1 return 0 def mysummary(self): summary_str = "EAP %{eap_class}.code% %{eap_class}.type%".format( eap_class=self.__class__.__name__ ) if self.type == 1 and self.code == EAP.RESPONSE: summary_str += " %{eap_class}.identity%".format( eap_class=self.__class__.__name__ ) return self.sprintf(summary_str) def post_build(self, p, pay): if self.len is None: tmp_len = len(p) + len(pay) tmp_p = p[:2] + chb((tmp_len >> 8) & 0xff) + chb(tmp_len & 0xff) p = tmp_p + p[4:] return p + pay def guess_payload_class(self, _): return Padding class EAP_MD5(EAP): """ RFC 3748 - "Extensible Authentication Protocol (EAP)" """ name = "EAP-MD5" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="optional_name", adjust=lambda p, x: x + 6 + (p.value_size or 0)), ByteEnumField("type", 4, eap_types), FieldLenField("value_size", None, fmt="B", length_of="value"), XStrLenField("value", '', length_from=lambda p: p.value_size), XStrLenField("optional_name", '', length_from=lambda p: 0 if p.len is None or p.value_size is None else (p.len - p.value_size - 6)) # noqa: E501 ] class EAP_TLS(EAP): """ RFC 5216 - "The EAP-TLS Authentication Protocol" """ name = "EAP-TLS" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="tls_data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 13, eap_types), BitField('L', 0, 1), BitField('M', 0, 1), BitField('S', 0, 1), BitField('reserved', 0, 5), ConditionalField(IntField('tls_message_len', 0), lambda pkt: pkt.L == 1), # noqa: E501 XStrLenField('tls_data', '', length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class EAP_TTLS(EAP): """ RFC 5281 - "Extensible Authentication Protocol Tunneled Transport Layer Security Authenticated Protocol Version 0 (EAP-TTLSv0)" """ name = "EAP-TTLS" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 21, eap_types), BitField("L", 0, 1), BitField("M", 0, 1), BitField("S", 0, 1), BitField("reserved", 0, 2), BitField("version", 0, 3), ConditionalField(IntField("message_len", 0), lambda pkt: pkt.L == 1), XStrLenField("data", "", length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class EAP_PEAP(EAP): """ draft-josefsson-pppext-eap-tls-eap-05.txt - "Protected EAP Protocol (PEAP)" """ name = "PEAP" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="tls_data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 25, eap_types), BitField("L", 0, 1), BitField("M", 0, 1), BitField("S", 0, 1), BitField("reserved", 0, 3), BitField("version", 1, 2), ConditionalField(IntField("tls_message_len", 0), lambda pkt: pkt.L == 1), # noqa: E501 XStrLenField("tls_data", "", length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class EAP_FAST(EAP): """ RFC 4851 - "The Flexible Authentication via Secure Tunneling Extensible Authentication Protocol Method (EAP-FAST)" """ name = "EAP-FAST" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), FieldLenField("len", None, fmt="H", length_of="data", adjust=lambda p, x: x + 10 if p.L == 1 else x + 6), ByteEnumField("type", 43, eap_types), BitField('L', 0, 1), BitField('M', 0, 1), BitField('S', 0, 1), BitField('reserved', 0, 2), BitField('version', 0, 3), ConditionalField(IntField('message_len', 0), lambda pkt: pkt.L == 1), XStrLenField('data', '', length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L)) # noqa: E501 ] class LEAP(EAP): """ Cisco LEAP (Lightweight EAP) https://freeradius.org/rfc/leap.txt """ name = "Cisco LEAP" match_subclass = True fields_desc = [ ByteEnumField("code", 1, eap_codes), ByteField("id", 0), ShortField("len", None), ByteEnumField("type", 17, eap_types), ByteField('version', 1), XByteField('unused', 0), FieldLenField("count", None, "challenge_response", "B", adjust=lambda p, x: len(p.challenge_response)), # noqa: E501 XStrLenField("challenge_response", "", length_from=lambda p: 0 or p.count), # noqa: E501 StrLenField("username", "", length_from=lambda p: p.len - (8 + (0 or p.count))) # noqa: E501 ] ############################################################################# # IEEE 802.1X-2010 - MACsec Key Agreement (MKA) protocol ############################################################################# ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11.1 ######################################################################### # _parameter_set_types = { 1: "Live Peer List", 2: "Potential Peer List", 3: "MACsec SAK Use", 4: "Distributed SAK", 5: "Distributed CAK", 6: "KMD", 7: "Announcement", 255: "ICV Indicator" } # Used by MKAParamSet::dispatch_hook() to instantiate the appropriate class _param_set_cls = { 1: "MKALivePeerListParamSet", 2: "MKAPotentialPeerListParamSet", 3: "MKASAKUseParamSet", 4: "MKADistributedSAKParamSet", 255: "MKAICVSet", } class MACsecSCI(Packet): """ Secure Channel Identifier. """ ######################################################################### # # IEEE 802.1AE-2006 standard # Section 9.9 ######################################################################### # name = "SCI" fields_desc = [ SourceMACField("system_identifier"), ShortField("port_identifier", 0) ] def extract_padding(self, s): return "", s class MKAParamSet(Packet): """ Class from which every parameter set class inherits (except MKABasicParamSet, which has no "Parameter set type" field, and must come first in the list of parameter sets). """ MACSEC_DEFAULT_ICV_LEN = 16 EAPOL_MKA_DEFAULT_KEY_WRAP_LEN = 24 @classmethod def dispatch_hook(cls, _pkt=None, *args, **kargs): """ Returns the right parameter set class. """ cls = conf.raw_layer if _pkt is not None: ptype = orb(_pkt[0]) return globals().get(_param_set_cls.get(ptype), conf.raw_layer) return cls class MKABasicParamSet(Packet): """ Basic Parameter Set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Basic Parameter Set" fields_desc = [ ByteField("mka_version_id", 0), ByteField("key_server_priority", 0), BitField("key_server", 0, 1), BitField("macsec_desired", 0, 1), BitField("macsec_capability", 0, 2), BitField("param_set_body_len", 0, 12), PacketField("SCI", MACsecSCI(), MACsecSCI), XStrFixedLenField("actor_member_id", "", length=12), XIntField("actor_message_number", 0), XIntField("algorithm_agility", 0), PadField( XStrLenField( "cak_name", "", length_from=lambda pkt: (pkt.param_set_body_len - 28) ), 4, padwith=b"\x00" ) ] def extract_padding(self, s): return "", s class MKAPeerListTuple(Packet): """ Live / Potential Peer List parameter sets tuples (802.1X-2010, section 11.11). # noqa: E501 """ name = "Peer List Tuple" fields_desc = [ XStrFixedLenField("member_id", "", length=12), XStrFixedLenField("message_number", "", length=4), ] class MKALivePeerListParamSet(MKAParamSet): """ Live Peer List parameter sets (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Live Peer List Parameter Set" fields_desc = [ PadField( ByteEnumField( "param_set_type", 1, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), PacketListField("member_id_message_num", [], MKAPeerListTuple) ] class MKAPotentialPeerListParamSet(MKAParamSet): """ Potential Peer List parameter sets (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Potential Peer List Parameter Set" fields_desc = [ PadField( ByteEnumField( "param_set_type", 2, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), PacketListField("member_id_message_num", [], MKAPeerListTuple) ] class MKASAKUseParamSet(MKAParamSet): """ SAK Use Parameter Set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "SAK Use Parameter Set" fields_desc = [ ByteEnumField("param_set_type", 3, _parameter_set_types), BitField("latest_key_an", 0, 2), BitField("latest_key_tx", 0, 1), BitField("latest_key_rx", 0, 1), BitField("old_key_an", 0, 2), BitField("old_key_tx", 0, 1), BitField("old_key_rx", 0, 1), BitField("plain_tx", 0, 1), BitField("plain_rx", 0, 1), BitField("X", 0, 1), BitField("delay_protect", 0, 1), BitField("param_set_body_len", 0, 12), XStrFixedLenField("latest_key_key_server_member_id", "", length=12), XStrFixedLenField("latest_key_key_number", "", length=4), XStrFixedLenField("latest_key_lowest_acceptable_pn", "", length=4), XStrFixedLenField("old_key_key_server_member_id", "", length=12), XStrFixedLenField("old_key_key_number", "", length=4), XStrFixedLenField("old_key_lowest_acceptable_pn", "", length=4) ] class MKADistributedSAKParamSet(MKAParamSet): """ Distributed SAK parameter set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Distributed SAK parameter set" fields_desc = [ ByteEnumField("param_set_type", 4, _parameter_set_types), BitField("distributed_an", 0, 2), BitField("confidentiality_offset", 0, 2), BitField("unused", 0, 4), ShortField("param_set_body_len", 0), XStrFixedLenField("key_number", "", length=4), ConditionalField( XStrFixedLenField("macsec_cipher_suite", "", length=8), lambda pkt: pkt.param_set_body_len > 28 ), XStrFixedLenField( "sak_aes_key_wrap", "", length=MKAParamSet.EAPOL_MKA_DEFAULT_KEY_WRAP_LEN ) ] class MKADistributedCAKParamSet(MKAParamSet): """ Distributed CAK Parameter Set (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "Distributed CAK parameter set" fields_desc = [ PadField( ByteEnumField( "param_set_type", 5, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), XStrFixedLenField( "cak_aes_key_wrap", "", length=MKAParamSet.EAPOL_MKA_DEFAULT_KEY_WRAP_LEN ), XStrField("cak_key_name", "") ] class MKAICVSet(MKAParamSet): """ ICV (802.1X-2010, section 11.11). """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "ICV" fields_desc = [ PadField( ByteEnumField( "param_set_type", 255, _parameter_set_types ), 2, padwith=b"\x00" ), ShortField("param_set_body_len", 0), XStrFixedLenField("icv", "", length=MKAParamSet.MACSEC_DEFAULT_ICV_LEN) ] class MKAParamSetPacketListField(PacketListField): """ PacketListField that handles the parameter sets. """ PARAM_SET_LEN_MASK = 0b0000111111111111 def m2i(self, pkt, m): return MKAParamSet(m) def getfield(self, pkt, s): lst = [] remain = s while remain: len_bytes = struct.unpack("!H", remain[2:4])[0] param_set_len = self.__class__.PARAM_SET_LEN_MASK & len_bytes current = remain[:4 + param_set_len] remain = remain[4 + param_set_len:] current_packet = self.m2i(pkt, current) lst.append(current_packet) return remain, lst class MKAPDU(Packet): """ MACsec Key Agreement Protocol Data Unit. """ ######################################################################### # # IEEE 802.1X-2010 standard # Section 11.11 ######################################################################### # name = "MKPDU" fields_desc = [ PacketField("basic_param_set", "", MKABasicParamSet), MKAParamSetPacketListField("parameter_sets", [], MKAParamSet), ] def extract_padding(self, s): return "", s bind_layers(Ether, EAPOL, type=34958) bind_layers(Ether, EAPOL, dst='01:80:c2:00:00:03', type=34958) bind_layers(CookedLinux, EAPOL, proto=34958) bind_layers(GRE, EAPOL, proto=34958) bind_layers(EAPOL, EAP, type=0) bind_layers(SNAP, EAPOL, code=34958) bind_layers(EAPOL, MKAPDU, type=5)