]>
Commit | Line | Data |
---|---|---|
acc9a635 JM |
1 | # EAP Re-authentication Protocol (ERP) tests |
2 | # Copyright (c) 2014, Jouni Malinen <j@w1.fi> | |
3 | # | |
4 | # This software may be distributed under the terms of the BSD license. | |
5 | # See README for more details. | |
6 | ||
5b3c40a6 | 7 | import binascii |
acc9a635 JM |
8 | import logging |
9 | logger = logging.getLogger() | |
5b3c40a6 JM |
10 | import os |
11 | import time | |
acc9a635 JM |
12 | |
13 | import hostapd | |
14 | from test_ap_eap import int_eap_server_params | |
5b3c40a6 | 15 | from test_ap_psk import find_wpas_process, read_process_memory, verify_not_present, get_key_locations |
acc9a635 JM |
16 | |
17 | def test_erp_initiate_reauth_start(dev, apdev): | |
18 | """Authenticator sending EAP-Initiate/Re-auth-Start, but ERP disabled on peer""" | |
19 | params = hostapd.wpa2_eap_params(ssid="test-wpa2-eap") | |
20 | params['erp_send_reauth_start'] = '1' | |
21 | params['erp_domain'] = 'example.com' | |
22 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
23 | ||
24 | dev[0].request("ERP_FLUSH") | |
25 | dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", | |
26 | eap="PAX", identity="pax.user@example.com", | |
27 | password_hex="0123456789abcdef0123456789abcdef", | |
28 | scan_freq="2412") | |
29 | ||
30 | def test_erp_enabled_on_server(dev, apdev): | |
31 | """ERP enabled on internal EAP server, but disabled on peer""" | |
32 | params = int_eap_server_params() | |
33 | params['erp_send_reauth_start'] = '1' | |
34 | params['erp_domain'] = 'example.com' | |
35 | params['eap_server_erp'] = '1' | |
36 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
37 | ||
38 | dev[0].request("ERP_FLUSH") | |
39 | dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", | |
40 | eap="PAX", identity="pax.user@example.com", | |
41 | password_hex="0123456789abcdef0123456789abcdef", | |
42 | scan_freq="2412") | |
43 | ||
44 | def test_erp(dev, apdev): | |
45 | """ERP enabled on server and peer""" | |
46 | capab = dev[0].get_capability("erp") | |
47 | if not capab or 'ERP' not in capab: | |
48 | return "skip" | |
49 | params = int_eap_server_params() | |
50 | params['erp_send_reauth_start'] = '1' | |
51 | params['erp_domain'] = 'example.com' | |
52 | params['eap_server_erp'] = '1' | |
53 | params['disable_pmksa_caching'] = '1' | |
54 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
55 | ||
56 | dev[0].request("ERP_FLUSH") | |
57 | dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", | |
58 | eap="PSK", identity="psk.user@example.com", | |
59 | password_hex="0123456789abcdef0123456789abcdef", | |
60 | erp="1", scan_freq="2412") | |
61 | for i in range(3): | |
62 | dev[0].request("DISCONNECT") | |
5f35a5e2 | 63 | dev[0].wait_disconnected(timeout=15) |
acc9a635 JM |
64 | dev[0].request("RECONNECT") |
65 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-SUCCESS"], timeout=15) | |
66 | if ev is None: | |
67 | raise Exception("EAP success timed out") | |
68 | if "EAP re-authentication completed successfully" not in ev: | |
69 | raise Exception("Did not use ERP") | |
5f35a5e2 | 70 | dev[0].wait_connected(timeout=15, error="Reconnection timed out") |
acc9a635 | 71 | |
0e40d7da JM |
72 | def test_erp_server_no_match(dev, apdev): |
73 | """ERP enabled on server and peer, but server has no key match""" | |
74 | capab = dev[0].get_capability("erp") | |
75 | if not capab or 'ERP' not in capab: | |
76 | return "skip" | |
77 | params = int_eap_server_params() | |
78 | params['erp_send_reauth_start'] = '1' | |
79 | params['erp_domain'] = 'example.com' | |
80 | params['eap_server_erp'] = '1' | |
81 | params['disable_pmksa_caching'] = '1' | |
82 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
83 | ||
84 | dev[0].request("ERP_FLUSH") | |
85 | id = dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", | |
86 | eap="PSK", identity="psk.user@example.com", | |
87 | password_hex="0123456789abcdef0123456789abcdef", | |
88 | erp="1", scan_freq="2412") | |
89 | dev[0].request("DISCONNECT") | |
5f35a5e2 | 90 | dev[0].wait_disconnected(timeout=15) |
0e40d7da JM |
91 | hapd.request("ERP_FLUSH") |
92 | dev[0].request("RECONNECT") | |
93 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-SUCCESS", | |
94 | "CTRL-EVENT-EAP-FAILURE"], timeout=15) | |
95 | if ev is None: | |
96 | raise Exception("EAP result timed out") | |
97 | if "CTRL-EVENT-EAP-SUCCESS" in ev: | |
98 | raise Exception("Unexpected EAP success") | |
99 | dev[0].request("DISCONNECT") | |
100 | dev[0].select_network(id) | |
101 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-SUCCESS"], timeout=15) | |
102 | if ev is None: | |
103 | raise Exception("EAP success timed out") | |
104 | if "EAP re-authentication completed successfully" in ev: | |
105 | raise Exception("Unexpected use of ERP") | |
5f35a5e2 | 106 | dev[0].wait_connected(timeout=15, error="Reconnection timed out") |
0e40d7da | 107 | |
acc9a635 JM |
108 | def start_erp_as(apdev): |
109 | params = { "ssid": "as", "beacon_int": "2000", | |
110 | "radius_server_clients": "auth_serv/radius_clients.conf", | |
111 | "radius_server_auth_port": '18128', | |
112 | "eap_server": "1", | |
113 | "eap_user_file": "auth_serv/eap_user.conf", | |
114 | "ca_cert": "auth_serv/ca.pem", | |
115 | "server_cert": "auth_serv/server.pem", | |
116 | "private_key": "auth_serv/server.key", | |
117 | "eap_sim_db": "unix:/tmp/hlr_auc_gw.sock", | |
118 | "dh_file": "auth_serv/dh.conf", | |
119 | "pac_opaque_encr_key": "000102030405060708090a0b0c0d0e0f", | |
120 | "eap_fast_a_id": "101112131415161718191a1b1c1d1e1f", | |
121 | "eap_fast_a_id_info": "test server", | |
122 | "eap_server_erp": "1", | |
123 | "erp_domain": "example.com" } | |
124 | hostapd.add_ap(apdev['ifname'], params) | |
125 | ||
126 | def test_erp_radius(dev, apdev): | |
127 | """ERP enabled on RADIUS server and peer""" | |
128 | capab = dev[0].get_capability("erp") | |
129 | if not capab or 'ERP' not in capab: | |
130 | return "skip" | |
131 | start_erp_as(apdev[1]) | |
132 | params = hostapd.wpa2_eap_params(ssid="test-wpa2-eap") | |
133 | params['auth_server_port'] = "18128" | |
134 | params['erp_send_reauth_start'] = '1' | |
135 | params['erp_domain'] = 'example.com' | |
136 | params['disable_pmksa_caching'] = '1' | |
137 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
138 | ||
139 | dev[0].request("ERP_FLUSH") | |
140 | dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", | |
141 | eap="PSK", identity="psk.user@example.com", | |
142 | password_hex="0123456789abcdef0123456789abcdef", | |
143 | erp="1", scan_freq="2412") | |
144 | for i in range(3): | |
145 | dev[0].request("DISCONNECT") | |
5f35a5e2 | 146 | dev[0].wait_disconnected(timeout=15) |
acc9a635 JM |
147 | dev[0].request("RECONNECT") |
148 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-SUCCESS"], timeout=15) | |
149 | if ev is None: | |
150 | raise Exception("EAP success timed out") | |
151 | if "EAP re-authentication completed successfully" not in ev: | |
152 | raise Exception("Did not use ERP") | |
5f35a5e2 | 153 | dev[0].wait_connected(timeout=15, error="Reconnection timed out") |
acc9a635 JM |
154 | |
155 | def erp_test(dev, hapd, **kwargs): | |
156 | hapd.dump_monitor() | |
157 | dev.dump_monitor() | |
158 | dev.request("ERP_FLUSH") | |
159 | id = dev.connect("test-wpa2-eap", key_mgmt="WPA-EAP", erp="1", | |
160 | scan_freq="2412", **kwargs) | |
161 | dev.request("DISCONNECT") | |
5f35a5e2 | 162 | dev.wait_disconnected(timeout=15) |
acc9a635 JM |
163 | hapd.dump_monitor() |
164 | dev.request("RECONNECT") | |
165 | ev = dev.wait_event(["CTRL-EVENT-EAP-SUCCESS"], timeout=15) | |
166 | if ev is None: | |
167 | raise Exception("EAP success timed out") | |
168 | if "EAP re-authentication completed successfully" not in ev: | |
169 | raise Exception("Did not use ERP") | |
5f35a5e2 | 170 | dev.wait_connected(timeout=15, error="Reconnection timed out") |
acc9a635 JM |
171 | ev = hapd.wait_event([ "AP-STA-CONNECTED" ], timeout=5) |
172 | if ev is None: | |
173 | raise Exception("No connection event received from hostapd") | |
174 | dev.request("DISCONNECT") | |
175 | ||
176 | def test_erp_radius_eap_methods(dev, apdev): | |
177 | """ERP enabled on RADIUS server and peer""" | |
178 | capab = dev[0].get_capability("erp") | |
179 | if not capab or 'ERP' not in capab: | |
180 | return "skip" | |
181 | start_erp_as(apdev[1]) | |
182 | params = hostapd.wpa2_eap_params(ssid="test-wpa2-eap") | |
183 | params['auth_server_port'] = "18128" | |
184 | params['erp_send_reauth_start'] = '1' | |
185 | params['erp_domain'] = 'example.com' | |
186 | params['disable_pmksa_caching'] = '1' | |
187 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
188 | ||
189 | erp_test(dev[0], hapd, eap="AKA", identity="0232010000000000@example.com", | |
190 | password="90dca4eda45b53cf0f12d7c9c3bc6a89:cb9cccc4b9258e6dca4760379fb82581:000000000123") | |
191 | erp_test(dev[0], hapd, eap="AKA'", identity="6555444333222111@example.com", | |
192 | password="5122250214c33e723a5dd523fc145fc0:981d464c7c52eb6e5036234984ad0bcf:000000000123") | |
193 | # TODO: EKE getSession | |
194 | #erp_test(dev[0], hapd, eap="EKE", identity="erp-eke@example.com", | |
195 | # password="hello") | |
196 | erp_test(dev[0], hapd, eap="FAST", identity="erp-fast@example.com", | |
197 | password="password", ca_cert="auth_serv/ca.pem", phase2="auth=GTC", | |
198 | phase1="fast_provisioning=2", pac_file="blob://fast_pac_auth_erp") | |
199 | erp_test(dev[0], hapd, eap="GPSK", identity="erp-gpsk@example.com", | |
200 | password="abcdefghijklmnop0123456789abcdef") | |
f41f670e JM |
201 | erp_test(dev[0], hapd, eap="IKEV2", identity="erp-ikev2@example.com", |
202 | password="password") | |
acc9a635 JM |
203 | erp_test(dev[0], hapd, eap="PAX", identity="erp-pax@example.com", |
204 | password_hex="0123456789abcdef0123456789abcdef") | |
205 | # TODO: PEAP (EMSK) | |
206 | #erp_test(dev[0], hapd, eap="PEAP", identity="erp-peap@example.com", | |
207 | # password="password", ca_cert="auth_serv/ca.pem", | |
208 | # phase2="auth=MSCHAPV2") | |
209 | erp_test(dev[0], hapd, eap="PSK", identity="erp-psk@example.com", | |
210 | password_hex="0123456789abcdef0123456789abcdef") | |
211 | erp_test(dev[0], hapd, eap="PWD", identity="erp-pwd@example.com", | |
212 | password="secret password") | |
213 | erp_test(dev[0], hapd, eap="SAKE", identity="erp-sake@example.com", | |
214 | password_hex="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") | |
215 | erp_test(dev[0], hapd, eap="SIM", identity="1232010000000000@example.com", | |
216 | password="90dca4eda45b53cf0f12d7c9c3bc6a89:cb9cccc4b9258e6dca4760379fb82581") | |
217 | erp_test(dev[0], hapd, eap="TLS", identity="erp-tls@example.com", | |
218 | ca_cert="auth_serv/ca.pem", client_cert="auth_serv/user.pem", | |
219 | private_key="auth_serv/user.key") | |
220 | erp_test(dev[0], hapd, eap="TTLS", identity="erp-ttls@example.com", | |
221 | password="password", ca_cert="auth_serv/ca.pem", phase2="auth=PAP") | |
5b3c40a6 JM |
222 | |
223 | def test_erp_key_lifetime_in_memory(dev, apdev, params): | |
224 | """ERP and key lifetime in memory""" | |
225 | capab = dev[0].get_capability("erp") | |
226 | if not capab or 'ERP' not in capab: | |
227 | return "skip" | |
228 | p = int_eap_server_params() | |
229 | p['erp_send_reauth_start'] = '1' | |
230 | p['erp_domain'] = 'example.com' | |
231 | p['eap_server_erp'] = '1' | |
232 | p['disable_pmksa_caching'] = '1' | |
233 | hapd = hostapd.add_ap(apdev[0]['ifname'], p) | |
234 | password = "63d2d21ac3c09ed567ee004a34490f1d16e7fa5835edf17ddba70a63f1a90a25" | |
235 | ||
236 | pid = find_wpas_process(dev[0]) | |
237 | ||
238 | dev[0].request("ERP_FLUSH") | |
239 | dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", eap="TTLS", | |
240 | identity="pap-secret@example.com", password=password, | |
241 | ca_cert="auth_serv/ca.pem", phase2="auth=PAP", | |
242 | erp="1", scan_freq="2412") | |
243 | ||
244 | time.sleep(0.1) | |
245 | buf = read_process_memory(pid, password) | |
246 | ||
247 | dev[0].request("DISCONNECT") | |
248 | dev[0].wait_disconnected(timeout=15) | |
249 | ||
250 | dev[0].relog() | |
251 | rRK = None | |
252 | rIK = None | |
253 | pmk = None | |
254 | ptk = None | |
255 | gtk = None | |
256 | with open(os.path.join(params['logdir'], 'log0'), 'r') as f: | |
257 | for l in f.readlines(): | |
258 | if "EAP: ERP rRK - hexdump" in l: | |
259 | val = l.strip().split(':')[3].replace(' ', '') | |
260 | rRK = binascii.unhexlify(val) | |
261 | if "EAP: ERP rIK - hexdump" in l: | |
262 | val = l.strip().split(':')[3].replace(' ', '') | |
263 | rIK = binascii.unhexlify(val) | |
264 | if "WPA: PMK - hexdump" in l: | |
265 | val = l.strip().split(':')[3].replace(' ', '') | |
266 | pmk = binascii.unhexlify(val) | |
267 | if "WPA: PTK - hexdump" in l: | |
268 | val = l.strip().split(':')[3].replace(' ', '') | |
269 | ptk = binascii.unhexlify(val) | |
270 | if "WPA: Group Key - hexdump" in l: | |
271 | val = l.strip().split(':')[3].replace(' ', '') | |
272 | gtk = binascii.unhexlify(val) | |
273 | if not rIK or not rRK or not pmk or not ptk or not gtk: | |
274 | raise Exception("Could not find keys from debug log") | |
275 | if len(gtk) != 16: | |
276 | raise Exception("Unexpected GTK length") | |
277 | ||
278 | kck = ptk[0:16] | |
279 | kek = ptk[16:32] | |
280 | tk = ptk[32:48] | |
281 | ||
282 | fname = os.path.join(params['logdir'], | |
283 | 'erp_key_lifetime_in_memory.memctx-') | |
284 | ||
285 | logger.info("Checking keys in memory while associated") | |
286 | get_key_locations(buf, password, "Password") | |
287 | get_key_locations(buf, pmk, "PMK") | |
288 | get_key_locations(buf, rRK, "rRK") | |
289 | get_key_locations(buf, rIK, "rIK") | |
290 | if password not in buf: | |
291 | print("Password not found while associated") | |
292 | return "skip" | |
293 | if pmk not in buf: | |
294 | print("PMK not found while associated") | |
295 | return "skip" | |
296 | if kck not in buf: | |
297 | raise Exception("KCK not found while associated") | |
298 | if kek not in buf: | |
299 | raise Exception("KEK not found while associated") | |
300 | if tk in buf: | |
301 | raise Exception("TK found from memory") | |
302 | if gtk in buf: | |
303 | raise Exception("GTK found from memory") | |
304 | ||
305 | logger.info("Checking keys in memory after disassociation") | |
306 | buf = read_process_memory(pid, password) | |
307 | ||
308 | # Note: Password is still present in network configuration | |
309 | # Note: PMK is in EAP fast re-auth data | |
310 | ||
311 | get_key_locations(buf, password, "Password") | |
312 | get_key_locations(buf, pmk, "PMK") | |
313 | get_key_locations(buf, rRK, "rRK") | |
314 | get_key_locations(buf, rIK, "rIK") | |
315 | verify_not_present(buf, kck, fname, "KCK") | |
316 | verify_not_present(buf, kek, fname, "KEK") | |
317 | verify_not_present(buf, tk, fname, "TK") | |
318 | verify_not_present(buf, gtk, fname, "GTK") | |
319 | ||
320 | dev[0].request("RECONNECT") | |
321 | ev = dev[0].wait_event(["CTRL-EVENT-EAP-SUCCESS"], timeout=15) | |
322 | if ev is None: | |
323 | raise Exception("EAP success timed out") | |
324 | if "EAP re-authentication completed successfully" not in ev: | |
325 | raise Exception("Did not use ERP") | |
326 | dev[0].wait_connected(timeout=15, error="Reconnection timed out") | |
327 | ||
328 | dev[0].request("DISCONNECT") | |
329 | dev[0].wait_disconnected(timeout=15) | |
330 | ||
331 | dev[0].relog() | |
332 | pmk = None | |
333 | ptk = None | |
334 | gtk = None | |
335 | with open(os.path.join(params['logdir'], 'log0'), 'r') as f: | |
336 | for l in f.readlines(): | |
337 | if "WPA: PMK - hexdump" in l: | |
338 | val = l.strip().split(':')[3].replace(' ', '') | |
339 | pmk = binascii.unhexlify(val) | |
340 | if "WPA: PTK - hexdump" in l: | |
341 | val = l.strip().split(':')[3].replace(' ', '') | |
342 | ptk = binascii.unhexlify(val) | |
343 | if "WPA: GTK in EAPOL-Key - hexdump" in l: | |
344 | val = l.strip().split(':')[3].replace(' ', '') | |
345 | gtk = binascii.unhexlify(val) | |
346 | if not pmk or not ptk or not gtk: | |
347 | raise Exception("Could not find keys from debug log") | |
348 | ||
349 | kck = ptk[0:16] | |
350 | kek = ptk[16:32] | |
351 | tk = ptk[32:48] | |
352 | ||
353 | logger.info("Checking keys in memory after ERP and disassociation") | |
354 | buf = read_process_memory(pid, password) | |
355 | ||
356 | # Note: Password is still present in network configuration | |
357 | ||
358 | get_key_locations(buf, password, "Password") | |
359 | get_key_locations(buf, pmk, "PMK") | |
360 | get_key_locations(buf, rRK, "rRK") | |
361 | get_key_locations(buf, rIK, "rIK") | |
362 | verify_not_present(buf, kck, fname, "KCK") | |
363 | verify_not_present(buf, kek, fname, "KEK") | |
364 | verify_not_present(buf, tk, fname, "TK") | |
365 | verify_not_present(buf, gtk, fname, "GTK") | |
366 | ||
367 | dev[0].request("REMOVE_NETWORK all") | |
368 | ||
369 | logger.info("Checking keys in memory after network profile removal") | |
370 | buf = read_process_memory(pid, password) | |
371 | ||
372 | # Note: rRK and rIK are still in memory | |
373 | ||
374 | get_key_locations(buf, password, "Password") | |
375 | get_key_locations(buf, pmk, "PMK") | |
376 | get_key_locations(buf, rRK, "rRK") | |
377 | get_key_locations(buf, rIK, "rIK") | |
378 | verify_not_present(buf, password, fname, "password") | |
379 | verify_not_present(buf, pmk, fname, "PMK") | |
380 | verify_not_present(buf, kck, fname, "KCK") | |
381 | verify_not_present(buf, kek, fname, "KEK") | |
382 | verify_not_present(buf, tk, fname, "TK") | |
383 | verify_not_present(buf, gtk, fname, "GTK") | |
384 | ||
385 | dev[0].request("ERP_FLUSH") | |
386 | logger.info("Checking keys in memory after ERP_FLUSH") | |
387 | buf = read_process_memory(pid, password) | |
388 | get_key_locations(buf, rRK, "rRK") | |
389 | get_key_locations(buf, rIK, "rIK") | |
390 | verify_not_present(buf, rRK, fname, "rRK") | |
391 | verify_not_present(buf, rIK, fname, "rIK") |