blob: f5b49764910528915b4f77a381470f78d517b003 [file] [log] [blame]
## This file is part of Scapy
## See http://www.secdev.org/projects/scapy for more informations
## Copyright (C) Philippe Biondi <phil@secdev.org>
## This program is published under a GPLv2 license
"""
Classes related to the EAP protocol.
"""
from __future__ import absolute_import
from __future__ import print_function
import struct
from scapy.fields import BitField, ByteField, XByteField, ByteEnumField,\
ShortField, IntField, XIntField, ByteEnumField, StrLenField, XStrField,\
XStrLenField, XStrFixedLenField, LenField, FieldLenField, PacketField,\
PacketListField, ConditionalField, PadField
from scapy.packet import Packet, 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):
l = self.len
return s[:l], s[l:]
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(ByteEnumField("desired_auth_type", 0, eap_types),
lambda pkt:pkt.code == EAP.RESPONSE and pkt.type == 3),
ConditionalField(
StrLenField("identity", '', length_from=lambda pkt: pkt.len - 5),
lambda pkt: pkt.code == EAP.RESPONSE and hasattr(pkt, 'type') and pkt.type == 1),
ConditionalField(
StrLenField("message", '', length_from=lambda pkt: pkt.len - 5),
lambda pkt: pkt.code == EAP.REQUEST and hasattr(pkt, 'type') and pkt.type == 1)
]
#________________________________________________________________________
#
# 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 haslayer(self, cls):
if cls == "EAP":
if isinstance(self, EAP):
return True
elif issubclass(cls, EAP):
if isinstance(self, cls):
return True
return super(EAP, self).haslayer(cls)
def getlayer(self, cls, nb=1, _track=None, _subclass=True, **flt):
return super(EAP, self).getlayer(cls, nb=nb, _track=_track,
_subclass=True, **flt)
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:
l = len(p) + len(pay)
p = p[:2] + chb((l >> 8) & 0xff) + chb(l & 0xff) + p[4:]
return p + pay
class EAP_MD5(EAP):
"""
RFC 3748 - "Extensible Authentication Protocol (EAP)"
"""
name = "EAP-MD5"
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))
]
class EAP_TLS(EAP):
"""
RFC 5216 - "The EAP-TLS Authentication Protocol"
"""
name = "EAP-TLS"
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),
XStrLenField('tls_data', '', length_from=lambda pkt: 0 if pkt.len is None else pkt.len - (6 + 4 * pkt.L))
]
class EAP_TTLS(EAP):
"""
RFC 5281 - "Extensible Authentication Protocol Tunneled Transport Layer
Security Authenticated Protocol Version 0 (EAP-TTLSv0)"
"""
name = "EAP-TTLS"
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))
]
class EAP_FAST(EAP):
"""
RFC 4851 - "The Flexible Authentication via Secure Tunneling
Extensible Authentication Protocol Method (EAP-FAST)"
"""
name = "EAP-FAST"
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))
]
class LEAP(EAP):
"""
Cisco LEAP (Lightweight EAP)
https://freeradius.org/rfc/leap.txt
"""
name = "Cisco LEAP"
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)),
XStrLenField("challenge_response", "", length_from=lambda p: 0 or p.count),
StrLenField("username", "", length_from=lambda p: p.len - (8 + (0 or p.count)))
]
#############################################################################
##### 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).
"""
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)