esp32_bluetooth_classic_sni.../libs/scapy/contrib/macsec.py
Matheus Eduardo Garbelini 86890704fd initial commit
todo: add documentation & wireshark dissector
2021-08-31 19:51:03 +08:00

241 lines
8.7 KiB
Python
Executable file

# This file is part of Scapy
# See http://www.secdev.org/projects/scapy for more information
# Copyright (C) Sabrina Dubroca <sd@queasysnail.net>
# This program is published under a GPLv2 license
# scapy.contrib.description = 802.1AE - IEEE MAC Security standard (MACsec)
# scapy.contrib.status = loads
"""
Classes and functions for MACsec.
"""
from __future__ import absolute_import
from __future__ import print_function
import struct
import copy
from scapy.config import conf
from scapy.fields import BitField, ConditionalField, IntField, PacketField, \
XShortEnumField
from scapy.packet import Packet, Raw, bind_layers
from scapy.layers.l2 import Ether, Dot1AD, Dot1Q
from scapy.layers.eap import MACsecSCI
from scapy.layers.inet import IP
from scapy.layers.inet6 import IPv6
from scapy.compat import raw
from scapy.data import ETH_P_MACSEC, ETHER_TYPES, ETH_P_IP, ETH_P_IPV6
from scapy.error import log_loading
import scapy.modules.six as six
if conf.crypto_valid:
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import (
Cipher,
algorithms,
modes,
)
else:
log_loading.info("Can't import python-cryptography v1.7+. "
"Disabled MACsec encryption/authentication.")
NOSCI_LEN = 14 + 6
SCI_LEN = 8
DEFAULT_ICV_LEN = 16
class MACsecSA(object):
"""Representation of a MACsec Secure Association
Provides encapsulation, decapsulation, encryption, and decryption
of MACsec frames
"""
def __init__(self, sci, an, pn, key, icvlen, encrypt, send_sci, xpn_en=False, ssci=None, salt=None): # noqa: E501
if isinstance(sci, six.integer_types):
self.sci = struct.pack('!Q', sci)
elif isinstance(sci, bytes):
self.sci = sci
else:
raise TypeError("SCI must be either bytes or int")
self.an = an
self.pn = pn
self.key = key
self.icvlen = icvlen
self.do_encrypt = encrypt
self.send_sci = send_sci
self.xpn_en = xpn_en
if self.xpn_en:
# Get SSCI (32 bits)
if isinstance(ssci, six.integer_types):
self.ssci = struct.pack('!L', ssci)
elif isinstance(ssci, bytes):
self.ssci = ssci
else:
raise TypeError("SSCI must be either bytes or int")
# Get Salt (96 bits, only bytes allowed)
if isinstance(salt, bytes):
self.salt = salt
else:
raise TypeError("Salt must be bytes")
def make_iv(self, pkt):
"""generate an IV for the packet"""
if self.xpn_en:
tmp_pn = (self.pn & 0xFFFFFFFF00000000) | (pkt[MACsec].pn & 0xFFFFFFFF) # noqa: E501
tmp_iv = self.ssci + struct.pack('!Q', tmp_pn)
return bytes(bytearray([a ^ b for a, b in zip(bytearray(tmp_iv), bytearray(self.salt))])) # noqa: E501
else:
return self.sci + struct.pack('!I', pkt[MACsec].pn)
@staticmethod
def split_pkt(pkt, assoclen, icvlen=0):
"""
split the packet into associated data, plaintext or ciphertext, and
optional ICV
"""
data = raw(pkt)
assoc = data[:assoclen]
if icvlen:
icv = data[-icvlen:]
enc = data[assoclen:-icvlen]
else:
icv = b''
enc = data[assoclen:]
return assoc, enc, icv
def e_bit(self):
"""returns the value of the E bit for packets sent through this SA"""
return self.do_encrypt
def c_bit(self):
"""returns the value of the C bit for packets sent through this SA"""
return self.do_encrypt or self.icvlen != DEFAULT_ICV_LEN
@staticmethod
def shortlen(pkt):
"""determine shortlen for a raw packet (not encapsulated yet)"""
datalen = len(pkt) - 2 * 6
if datalen < 48:
return datalen
return 0
def encap(self, pkt):
"""encapsulate a frame using this Secure Association"""
if pkt.name != Ether().name:
raise TypeError('cannot encapsulate packet in MACsec, must be Ethernet') # noqa: E501
hdr = copy.deepcopy(pkt)
payload = hdr.payload
del hdr.payload
tag = MACsec(sci=self.sci, an=self.an,
SC=self.send_sci,
E=self.e_bit(), C=self.c_bit(),
shortlen=MACsecSA.shortlen(pkt),
pn=(self.pn & 0xFFFFFFFF), type=pkt.type)
hdr.type = ETH_P_MACSEC
return hdr / tag / payload
# this doesn't really need to be a method, but for symmetry with
# encap(), it is
def decap(self, orig_pkt):
"""decapsulate a MACsec frame"""
if orig_pkt.name != Ether().name or orig_pkt.payload.name != MACsec().name: # noqa: E501
raise TypeError('cannot decapsulate MACsec packet, must be Ethernet/MACsec') # noqa: E501
packet = copy.deepcopy(orig_pkt)
prev_layer = packet[MACsec].underlayer
prev_layer.type = packet[MACsec].type
next_layer = packet[MACsec].payload
del prev_layer.payload
if prev_layer.name == Ether().name:
return Ether(raw(prev_layer / next_layer))
return prev_layer / next_layer
def encrypt(self, orig_pkt, assoclen=None):
"""encrypt a MACsec frame for this Secure Association"""
hdr = copy.deepcopy(orig_pkt)
del hdr[MACsec].payload
del hdr[MACsec].type
pktlen = len(orig_pkt)
if self.send_sci:
hdrlen = NOSCI_LEN + SCI_LEN
else:
hdrlen = NOSCI_LEN
if assoclen is None or not self.do_encrypt:
if self.do_encrypt:
assoclen = hdrlen
else:
assoclen = pktlen
iv = self.make_iv(orig_pkt)
assoc, pt, _ = MACsecSA.split_pkt(orig_pkt, assoclen)
encryptor = Cipher(
algorithms.AES(self.key),
modes.GCM(iv),
backend=default_backend()
).encryptor()
encryptor.authenticate_additional_data(assoc)
ct = encryptor.update(pt) + encryptor.finalize()
hdr[MACsec].payload = Raw(assoc[hdrlen:assoclen] + ct + encryptor.tag)
return hdr
def decrypt(self, orig_pkt, assoclen=None):
"""decrypt a MACsec frame for this Secure Association"""
hdr = copy.deepcopy(orig_pkt)
del hdr[MACsec].payload
pktlen = len(orig_pkt)
if self.send_sci:
hdrlen = NOSCI_LEN + SCI_LEN
else:
hdrlen = NOSCI_LEN
if assoclen is None or not self.do_encrypt:
if self.do_encrypt:
assoclen = hdrlen
else:
assoclen = pktlen - self.icvlen
iv = self.make_iv(hdr)
assoc, ct, icv = MACsecSA.split_pkt(orig_pkt, assoclen, self.icvlen)
decryptor = Cipher(
algorithms.AES(self.key),
modes.GCM(iv, icv),
backend=default_backend()
).decryptor()
decryptor.authenticate_additional_data(assoc)
pt = assoc[hdrlen:assoclen]
pt += decryptor.update(ct)
pt += decryptor.finalize()
hdr[MACsec].type = struct.unpack('!H', pt[0:2])[0]
hdr[MACsec].payload = Raw(pt[2:])
return hdr
class MACsec(Packet):
"""representation of one MACsec frame"""
name = '802.1AE'
fields_desc = [BitField('Ver', 0, 1),
BitField('ES', 0, 1),
BitField('SC', 0, 1),
BitField('SCB', 0, 1),
BitField('E', 0, 1),
BitField('C', 0, 1),
BitField('an', 0, 2),
BitField('reserved', 0, 2),
BitField('shortlen', 0, 6),
IntField("pn", 1),
ConditionalField(PacketField("sci", None, MACsecSCI), lambda pkt: pkt.SC), # noqa: E501
ConditionalField(XShortEnumField("type", None, ETHER_TYPES),
lambda pkt: pkt.type is not None)]
def mysummary(self):
summary = self.sprintf("an=%MACsec.an%, pn=%MACsec.pn%")
if self.SC:
summary += self.sprintf(", sci=%MACsec.sci%")
if self.type is not None:
summary += self.sprintf(", %MACsec.type%")
return summary
bind_layers(MACsec, IP, type=ETH_P_IP)
bind_layers(MACsec, IPv6, type=ETH_P_IPV6)
bind_layers(Dot1AD, MACsec, type=ETH_P_MACSEC)
bind_layers(Dot1Q, MACsec, type=ETH_P_MACSEC)
bind_layers(Ether, MACsec, type=ETH_P_MACSEC)