tests: Framework for EAP peer protocol tests
This introduces a minimal RADIUS authentication server using pyrad to allow simple EAP handler functions to be used for writing protocol tests for EAP peer methods. This initial commit includes test cases for EAP-SAKE. Signed-off-by: Jouni Malinen <j@w1.fi>
This commit is contained in:
parent
a47f815ff3
commit
d81731e681
2 changed files with 332 additions and 0 deletions
|
@ -5,6 +5,7 @@ ATTRIBUTE Calling-Station-Id 31 string
|
|||
ATTRIBUTE NAS-Identifier 32 string
|
||||
ATTRIBUTE Acct-Session-Id 44 string
|
||||
ATTRIBUTE Event-Timestamp 55 date
|
||||
ATTRIBUTE EAP-Message 79 string
|
||||
ATTRIBUTE Message-Authenticator 80 octets
|
||||
ATTRIBUTE Chargeable-User-Identity 89 string
|
||||
ATTRIBUTE Error-Cause 101 integer
|
||||
|
|
331
tests/hwsim/test_eap_proto.py
Normal file
331
tests/hwsim/test_eap_proto.py
Normal file
|
@ -0,0 +1,331 @@
|
|||
# EAP protocol tests
|
||||
# Copyright (c) 2014, Jouni Malinen <j@w1.fi>
|
||||
#
|
||||
# This software may be distributed under the terms of the BSD license.
|
||||
# See README for more details.
|
||||
|
||||
import hmac
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
import select
|
||||
import struct
|
||||
import threading
|
||||
import time
|
||||
|
||||
import hostapd
|
||||
|
||||
EAP_CODE_REQUEST = 1
|
||||
EAP_CODE_RESPONSE = 2
|
||||
EAP_CODE_SUCCESS = 3
|
||||
EAP_CODE_FAILURE = 4
|
||||
|
||||
EAP_TYPE_IDENTITY = 1
|
||||
EAP_TYPE_NOTIFICATION = 2
|
||||
EAP_TYPE_NAK = 3
|
||||
EAP_TYPE_MD5 = 4
|
||||
EAP_TYPE_OTP = 5
|
||||
EAP_TYPE_GTC = 6
|
||||
EAP_TYPE_TLS = 13
|
||||
EAP_TYPE_LEAP = 17
|
||||
EAP_TYPE_SIM = 18
|
||||
EAP_TYPE_TTLS = 21
|
||||
EAP_TYPE_AKA = 23
|
||||
EAP_TYPE_PEAP = 25
|
||||
EAP_TYPE_MSCHAPV2 = 26
|
||||
EAP_TYPE_TLV = 33
|
||||
EAP_TYPE_TNC = 38
|
||||
EAP_TYPE_FAST = 43
|
||||
EAP_TYPE_PAX = 46
|
||||
EAP_TYPE_PSK = 47
|
||||
EAP_TYPE_SAKE = 48
|
||||
EAP_TYPE_IKEV2 = 49
|
||||
EAP_TYPE_AKA_PRIME = 50
|
||||
EAP_TYPE_GPSK = 51
|
||||
EAP_TYPE_PWD = 52
|
||||
EAP_TYPE_EKE = 53
|
||||
|
||||
def run_pyrad_server(srv, t_stop, eap_handler):
|
||||
srv.RunWithStop(t_stop, eap_handler)
|
||||
|
||||
def start_radius_server(eap_handler):
|
||||
try:
|
||||
import pyrad.server
|
||||
import pyrad.packet
|
||||
import pyrad.dictionary
|
||||
except ImportError:
|
||||
return None
|
||||
|
||||
class TestServer(pyrad.server.Server):
|
||||
def _HandleAuthPacket(self, pkt):
|
||||
pyrad.server.Server._HandleAuthPacket(self, pkt)
|
||||
if len(pkt[79]) > 1:
|
||||
logger.info("Multiple EAP-Message attributes")
|
||||
# TODO: reassemble
|
||||
eap = pkt[79][0]
|
||||
eap_req = self.eap_handler(self.ctx, eap)
|
||||
reply = self.CreateReplyPacket(pkt)
|
||||
if eap_req:
|
||||
if len(eap_req) > 253:
|
||||
logger.info("Need to fragment EAP-Message")
|
||||
# TODO: fragment
|
||||
reply.AddAttribute("EAP-Message", eap_req)
|
||||
logger.info("No EAP request available")
|
||||
reply.code = pyrad.packet.AccessChallenge
|
||||
|
||||
hmac_obj = hmac.new(reply.secret)
|
||||
hmac_obj.update(struct.pack("B", reply.code))
|
||||
hmac_obj.update(struct.pack("B", reply.id))
|
||||
|
||||
# reply attributes
|
||||
reply.AddAttribute("Message-Authenticator",
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
attrs = reply._PktEncodeAttributes()
|
||||
|
||||
# Length
|
||||
flen = 4 + 16 + len(attrs)
|
||||
hmac_obj.update(struct.pack(">H", flen))
|
||||
hmac_obj.update(pkt.authenticator)
|
||||
hmac_obj.update(attrs)
|
||||
del reply[80]
|
||||
reply.AddAttribute("Message-Authenticator", hmac_obj.digest())
|
||||
|
||||
self.SendReplyPacket(pkt.fd, reply)
|
||||
|
||||
def RunWithStop(self, t_stop, eap_handler):
|
||||
self._poll = select.poll()
|
||||
self._fdmap = {}
|
||||
self._PrepareSockets()
|
||||
self.t_stop = t_stop
|
||||
self.eap_handler = eap_handler
|
||||
self.ctx = {}
|
||||
|
||||
while not t_stop.is_set():
|
||||
for (fd, event) in self._poll.poll(1000):
|
||||
if event == select.POLLIN:
|
||||
try:
|
||||
fdo = self._fdmap[fd]
|
||||
self._ProcessInput(fdo)
|
||||
except pyrad.server.ServerPacketError as err:
|
||||
logger.info("pyrad server dropping packet: " + str(err))
|
||||
except pyrad.packet.PacketError as err:
|
||||
logger.info("pyrad server received invalid packet: " + str(err))
|
||||
else:
|
||||
logger.error("Unexpected event in pyrad server main loop")
|
||||
|
||||
srv = TestServer(dict=pyrad.dictionary.Dictionary("dictionary.radius"),
|
||||
authport=18138, acctport=18139)
|
||||
srv.hosts["127.0.0.1"] = pyrad.server.RemoteHost("127.0.0.1",
|
||||
"radius",
|
||||
"localhost")
|
||||
srv.BindToAddress("")
|
||||
t_stop = threading.Event()
|
||||
t = threading.Thread(target=run_pyrad_server, args=(srv, t_stop, eap_handler))
|
||||
t.start()
|
||||
|
||||
return { 'srv': srv, 'stop': t_stop, 'thread': t }
|
||||
|
||||
def stop_radius_server(srv):
|
||||
srv['stop'].set()
|
||||
srv['thread'].join()
|
||||
|
||||
def start_ap(ifname):
|
||||
params = hostapd.wpa2_eap_params(ssid="eap-test")
|
||||
params['auth_server_port'] = "18138"
|
||||
hapd = hostapd.add_ap(ifname, params)
|
||||
return hapd
|
||||
|
||||
EAP_SAKE_VERSION = 2
|
||||
|
||||
EAP_SAKE_SUBTYPE_CHALLENGE = 1
|
||||
EAP_SAKE_SUBTYPE_CONFIRM = 2
|
||||
EAP_SAKE_SUBTYPE_AUTH_REJECT = 3
|
||||
EAP_SAKE_SUBTYPE_IDENTITY = 4
|
||||
|
||||
EAP_SAKE_AT_RAND_S = 1
|
||||
EAP_SAKE_AT_RAND_P = 2
|
||||
EAP_SAKE_AT_MIC_S = 3
|
||||
EAP_SAKE_AT_MIC_P = 4
|
||||
EAP_SAKE_AT_SERVERID = 5
|
||||
EAP_SAKE_AT_PEERID = 6
|
||||
EAP_SAKE_AT_SPI_S = 7
|
||||
EAP_SAKE_AT_SPI_P = 8
|
||||
EAP_SAKE_AT_ANY_ID_REQ = 9
|
||||
EAP_SAKE_AT_PERM_ID_REQ = 10
|
||||
EAP_SAKE_AT_ENCR_DATA = 128
|
||||
EAP_SAKE_AT_IV = 129
|
||||
EAP_SAKE_AT_PADDING = 130
|
||||
EAP_SAKE_AT_NEXT_TMPID = 131
|
||||
EAP_SAKE_AT_MSK_LIFE = 132
|
||||
|
||||
def test_eap_proto_sake(dev, apdev):
|
||||
"""EAP-SAKE protocol tests"""
|
||||
def sake_challenge(ctx):
|
||||
logger.info("Test: Challenge subtype")
|
||||
return struct.pack(">BBHBBBBBBLLLL", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 18,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CHALLENGE,
|
||||
EAP_SAKE_AT_RAND_S, 18, 0, 0, 0, 0)
|
||||
|
||||
def sake_handler(ctx, req):
|
||||
logger.info("sake_handler - RX " + req.encode("hex"))
|
||||
if 'num' not in ctx:
|
||||
ctx['num'] = 0
|
||||
ctx['num'] = ctx['num'] + 1
|
||||
if 'id' not in ctx:
|
||||
ctx['id'] = 1
|
||||
ctx['id'] = (ctx['id'] + 1) % 256
|
||||
|
||||
if ctx['num'] == 1:
|
||||
logger.info("Test: Missing payload")
|
||||
return struct.pack(">BBHB", EAP_CODE_REQUEST, ctx['id'], 4 + 1,
|
||||
EAP_TYPE_SAKE)
|
||||
|
||||
if ctx['num'] == 2:
|
||||
logger.info("Test: Identity subtype without any attributes")
|
||||
return struct.pack(">BBHBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_IDENTITY)
|
||||
|
||||
if ctx['num'] == 3:
|
||||
logger.info("Test: Identity subtype")
|
||||
return struct.pack(">BBHBBBBBBH", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 4,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_IDENTITY,
|
||||
EAP_SAKE_AT_ANY_ID_REQ, 4, 0)
|
||||
if ctx['num'] == 4:
|
||||
logger.info("Test: Identity subtype (different session id)")
|
||||
return struct.pack(">BBHBBBBBBH", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 4,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 1, EAP_SAKE_SUBTYPE_IDENTITY,
|
||||
EAP_SAKE_AT_PERM_ID_REQ, 4, 0)
|
||||
|
||||
if ctx['num'] == 5:
|
||||
logger.info("Test: Identity subtype with too short attribute")
|
||||
return struct.pack(">BBHBBBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 2,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_IDENTITY,
|
||||
EAP_SAKE_AT_ANY_ID_REQ, 2)
|
||||
|
||||
if ctx['num'] == 6:
|
||||
logger.info("Test: Identity subtype with truncated attribute")
|
||||
return struct.pack(">BBHBBBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 2,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_IDENTITY,
|
||||
EAP_SAKE_AT_ANY_ID_REQ, 4)
|
||||
|
||||
if ctx['num'] == 7:
|
||||
logger.info("Test: Unknown subtype")
|
||||
return struct.pack(">BBHBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, 123)
|
||||
|
||||
if ctx['num'] == 8:
|
||||
logger.info("Test: Challenge subtype without any attributes")
|
||||
return struct.pack(">BBHBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CHALLENGE)
|
||||
|
||||
if ctx['num'] == 9:
|
||||
logger.info("Test: Challenge subtype with too short AT_RAND_S")
|
||||
return struct.pack(">BBHBBBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 2,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CHALLENGE,
|
||||
EAP_SAKE_AT_RAND_S, 2)
|
||||
|
||||
if ctx['num'] == 10:
|
||||
return sake_challenge(ctx)
|
||||
if ctx['num'] == 11:
|
||||
logger.info("Test: Unexpected Identity subtype")
|
||||
return struct.pack(">BBHBBBBBBH", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 4,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_IDENTITY,
|
||||
EAP_SAKE_AT_ANY_ID_REQ, 4, 0)
|
||||
|
||||
if ctx['num'] == 12:
|
||||
return sake_challenge(ctx)
|
||||
if ctx['num'] == 13:
|
||||
logger.info("Test: Unexpected Challenge subtype")
|
||||
return struct.pack(">BBHBBBBBBLLLL", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 18,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CHALLENGE,
|
||||
EAP_SAKE_AT_RAND_S, 18, 0, 0, 0, 0)
|
||||
|
||||
if ctx['num'] == 14:
|
||||
return sake_challenge(ctx)
|
||||
if ctx['num'] == 15:
|
||||
logger.info("Test: Confirm subtype without any attributes")
|
||||
return struct.pack(">BBHBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CONFIRM)
|
||||
|
||||
if ctx['num'] == 16:
|
||||
return sake_challenge(ctx)
|
||||
if ctx['num'] == 17:
|
||||
logger.info("Test: Confirm subtype with too short AT_MIC_S")
|
||||
return struct.pack(">BBHBBBBBB", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 2,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CONFIRM,
|
||||
EAP_SAKE_AT_MIC_S, 2)
|
||||
|
||||
if ctx['num'] == 18:
|
||||
logger.info("Test: Unexpected Confirm subtype")
|
||||
return struct.pack(">BBHBBBBBBLLLL", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 18,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CONFIRM,
|
||||
EAP_SAKE_AT_MIC_S, 18, 0, 0, 0, 0)
|
||||
|
||||
if ctx['num'] == 19:
|
||||
return sake_challenge(ctx)
|
||||
if ctx['num'] == 20:
|
||||
logger.info("Test: Confirm subtype with incorrect AT_MIC_S")
|
||||
return struct.pack(">BBHBBBBBBLLLL", EAP_CODE_REQUEST, ctx['id'],
|
||||
4 + 1 + 3 + 18,
|
||||
EAP_TYPE_SAKE,
|
||||
EAP_SAKE_VERSION, 0, EAP_SAKE_SUBTYPE_CONFIRM,
|
||||
EAP_SAKE_AT_MIC_S, 18, 0, 0, 0, 0)
|
||||
|
||||
return sake_challenge(ctx)
|
||||
|
||||
srv = start_radius_server(sake_handler)
|
||||
if srv is None:
|
||||
return "skip"
|
||||
|
||||
try:
|
||||
hapd = start_ap(apdev[0]['ifname'])
|
||||
|
||||
for i in range(0, 14):
|
||||
dev[0].connect("eap-test", key_mgmt="WPA-EAP", scan_freq="2412",
|
||||
eap="SAKE", identity="sake user",
|
||||
password_hex="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
wait_connect=False)
|
||||
ev = dev[0].wait_event(["CTRL-EVENT-EAP-PROPOSED-METHOD"], timeout=15)
|
||||
if ev is None:
|
||||
raise Exception("Timeout on EAP start")
|
||||
time.sleep(0.1)
|
||||
dev[0].request("REMOVE_NETWORK all")
|
||||
|
||||
logger.info("Too short password")
|
||||
dev[0].connect("eap-test", key_mgmt="WPA-EAP", scan_freq="2412",
|
||||
eap="SAKE", identity="sake user",
|
||||
password_hex="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd",
|
||||
wait_connect=False)
|
||||
ev = dev[0].wait_event(["CTRL-EVENT-EAP-PROPOSED-METHOD"], timeout=15)
|
||||
if ev is None:
|
||||
raise Exception("Timeout on EAP start")
|
||||
time.sleep(0.1)
|
||||
finally:
|
||||
stop_radius_server(srv)
|
Loading…
Reference in a new issue