]>
Commit | Line | Data |
---|---|---|
1640a2e4 | 1 | # Test cases for SAE |
5b3c40a6 | 2 | # Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi> |
1640a2e4 JM |
3 | # |
4 | # This software may be distributed under the terms of the BSD license. | |
5 | # See README for more details. | |
6 | ||
5b3c40a6 JM |
7 | import binascii |
8 | import os | |
1640a2e4 JM |
9 | import time |
10 | import subprocess | |
11 | import logging | |
12 | logger = logging.getLogger() | |
13 | ||
14 | import hwsim_utils | |
15 | import hostapd | |
81e787b7 | 16 | from utils import HwsimSkip |
5b3c40a6 | 17 | from test_ap_psk import find_wpas_process, read_process_memory, verify_not_present, get_key_locations |
1640a2e4 JM |
18 | |
19 | def test_sae(dev, apdev): | |
20 | """SAE with default group""" | |
b9749b6a JM |
21 | if "SAE" not in dev[0].get_capability("auth_alg"): |
22 | raise HwsimSkip("SAE not supported") | |
1640a2e4 JM |
23 | params = hostapd.wpa2_params(ssid="test-sae", |
24 | passphrase="12345678") | |
25 | params['wpa_key_mgmt'] = 'SAE' | |
65038313 JM |
26 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) |
27 | key_mgmt = hapd.get_config()['key_mgmt'] | |
28 | if key_mgmt.split(' ')[0] != "SAE": | |
29 | raise Exception("Unexpected GET_CONFIG(key_mgmt): " + key_mgmt) | |
1640a2e4 JM |
30 | |
31 | dev[0].request("SET sae_groups ") | |
32 | id = dev[0].connect("test-sae", psk="12345678", key_mgmt="SAE", | |
33 | scan_freq="2412") | |
34 | if dev[0].get_status_field('sae_group') != '19': | |
35 | raise Exception("Expected default SAE group not used") | |
d463c556 JM |
36 | bss = dev[0].get_bss(apdev[0]['bssid']) |
37 | if 'flags' not in bss: | |
38 | raise Exception("Could not get BSS flags from BSS table") | |
39 | if "[WPA2-SAE-CCMP]" not in bss['flags']: | |
40 | raise Exception("Unexpected BSS flags: " + bss['flags']) | |
1640a2e4 | 41 | |
19d3a6e3 JM |
42 | def test_sae_pmksa_caching(dev, apdev): |
43 | """SAE and PMKSA caching""" | |
b9749b6a JM |
44 | if "SAE" not in dev[0].get_capability("auth_alg"): |
45 | raise HwsimSkip("SAE not supported") | |
19d3a6e3 JM |
46 | params = hostapd.wpa2_params(ssid="test-sae", |
47 | passphrase="12345678") | |
48 | params['wpa_key_mgmt'] = 'SAE' | |
49 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
50 | ||
51 | dev[0].request("SET sae_groups ") | |
52 | dev[0].connect("test-sae", psk="12345678", key_mgmt="SAE", | |
53 | scan_freq="2412") | |
7cc9a81f JM |
54 | ev = hapd.wait_event([ "AP-STA-CONNECTED" ], timeout=5) |
55 | if ev is None: | |
56 | raise Exception("No connection event received from hostapd") | |
19d3a6e3 | 57 | dev[0].request("DISCONNECT") |
7cc9a81f | 58 | dev[0].wait_disconnected() |
19d3a6e3 | 59 | dev[0].request("RECONNECT") |
5f35a5e2 | 60 | dev[0].wait_connected(timeout=15, error="Reconnect timed out") |
19d3a6e3 JM |
61 | if dev[0].get_status_field('sae_group') is not None: |
62 | raise Exception("SAE group claimed to have been used") | |
63 | ||
64 | def test_sae_pmksa_caching_disabled(dev, apdev): | |
65 | """SAE and PMKSA caching disabled""" | |
b9749b6a JM |
66 | if "SAE" not in dev[0].get_capability("auth_alg"): |
67 | raise HwsimSkip("SAE not supported") | |
19d3a6e3 JM |
68 | params = hostapd.wpa2_params(ssid="test-sae", |
69 | passphrase="12345678") | |
70 | params['wpa_key_mgmt'] = 'SAE' | |
71 | params['disable_pmksa_caching'] = '1' | |
72 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
73 | ||
74 | dev[0].request("SET sae_groups ") | |
75 | dev[0].connect("test-sae", psk="12345678", key_mgmt="SAE", | |
76 | scan_freq="2412") | |
7cc9a81f JM |
77 | ev = hapd.wait_event([ "AP-STA-CONNECTED" ], timeout=5) |
78 | if ev is None: | |
79 | raise Exception("No connection event received from hostapd") | |
19d3a6e3 | 80 | dev[0].request("DISCONNECT") |
7cc9a81f | 81 | dev[0].wait_disconnected() |
19d3a6e3 | 82 | dev[0].request("RECONNECT") |
5f35a5e2 | 83 | dev[0].wait_connected(timeout=15, error="Reconnect timed out") |
19d3a6e3 JM |
84 | if dev[0].get_status_field('sae_group') != '19': |
85 | raise Exception("Expected default SAE group not used") | |
86 | ||
1640a2e4 JM |
87 | def test_sae_groups(dev, apdev): |
88 | """SAE with all supported groups""" | |
b9749b6a JM |
89 | if "SAE" not in dev[0].get_capability("auth_alg"): |
90 | raise HwsimSkip("SAE not supported") | |
1640a2e4 JM |
91 | # This would be the full list of supported groups, but groups 14-16 |
92 | # (2048-4096 bit MODP) are a bit too slow on some VMs and can result in | |
93 | # hitting mac80211 authentication timeout, so skip them for now. | |
94 | #sae_groups = [ 19, 25, 26, 20, 21, 2, 5, 14, 15, 16, 22, 23, 24 ] | |
95 | sae_groups = [ 19, 25, 26, 20, 21, 2, 5, 22, 23, 24 ] | |
96 | groups = [str(g) for g in sae_groups] | |
97 | params = hostapd.wpa2_params(ssid="test-sae-groups", | |
98 | passphrase="12345678") | |
99 | params['wpa_key_mgmt'] = 'SAE' | |
100 | params['sae_groups'] = ' '.join(groups) | |
101 | hostapd.add_ap(apdev[0]['ifname'], params) | |
102 | ||
103 | for g in groups: | |
104 | logger.info("Testing SAE group " + g) | |
105 | dev[0].request("SET sae_groups " + g) | |
106 | id = dev[0].connect("test-sae-groups", psk="12345678", key_mgmt="SAE", | |
107 | scan_freq="2412") | |
108 | if dev[0].get_status_field('sae_group') != g: | |
109 | raise Exception("Expected SAE group not used") | |
110 | dev[0].remove_network(id) | |
111 | ||
112 | def test_sae_group_nego(dev, apdev): | |
113 | """SAE group negotiation""" | |
b9749b6a JM |
114 | if "SAE" not in dev[0].get_capability("auth_alg"): |
115 | raise HwsimSkip("SAE not supported") | |
1640a2e4 JM |
116 | params = hostapd.wpa2_params(ssid="test-sae-group-nego", |
117 | passphrase="12345678") | |
118 | params['wpa_key_mgmt'] = 'SAE' | |
119 | params['sae_groups'] = '19' | |
120 | hostapd.add_ap(apdev[0]['ifname'], params) | |
121 | ||
122 | dev[0].request("SET sae_groups 25 26 20 19") | |
123 | dev[0].connect("test-sae-group-nego", psk="12345678", key_mgmt="SAE", | |
124 | scan_freq="2412") | |
125 | if dev[0].get_status_field('sae_group') != '19': | |
126 | raise Exception("Expected SAE group not used") | |
a6cf5cd6 JM |
127 | |
128 | def test_sae_anti_clogging(dev, apdev): | |
129 | """SAE anti clogging""" | |
b9749b6a JM |
130 | if "SAE" not in dev[0].get_capability("auth_alg"): |
131 | raise HwsimSkip("SAE not supported") | |
a6cf5cd6 JM |
132 | params = hostapd.wpa2_params(ssid="test-sae", passphrase="12345678") |
133 | params['wpa_key_mgmt'] = 'SAE' | |
134 | params['sae_anti_clogging_threshold'] = '1' | |
135 | hostapd.add_ap(apdev[0]['ifname'], params) | |
136 | ||
137 | dev[0].request("SET sae_groups ") | |
138 | dev[1].request("SET sae_groups ") | |
139 | id = {} | |
140 | for i in range(0, 2): | |
141 | dev[i].scan(freq="2412") | |
142 | id[i] = dev[i].connect("test-sae", psk="12345678", key_mgmt="SAE", | |
143 | scan_freq="2412", only_add_network=True) | |
144 | for i in range(0, 2): | |
145 | dev[i].select_network(id[i]) | |
146 | for i in range(0, 2): | |
5f35a5e2 | 147 | dev[i].wait_connected(timeout=10) |
d05ff960 JM |
148 | |
149 | def test_sae_forced_anti_clogging(dev, apdev): | |
150 | """SAE anti clogging (forced)""" | |
b9749b6a JM |
151 | if "SAE" not in dev[0].get_capability("auth_alg"): |
152 | raise HwsimSkip("SAE not supported") | |
d05ff960 | 153 | params = hostapd.wpa2_params(ssid="test-sae", passphrase="12345678") |
fd4709ff | 154 | params['wpa_key_mgmt'] = 'SAE WPA-PSK' |
d05ff960 JM |
155 | params['sae_anti_clogging_threshold'] = '0' |
156 | hostapd.add_ap(apdev[0]['ifname'], params) | |
fd4709ff | 157 | dev[2].connect("test-sae", psk="12345678", scan_freq="2412") |
d05ff960 JM |
158 | for i in range(0, 2): |
159 | dev[i].request("SET sae_groups ") | |
160 | dev[i].connect("test-sae", psk="12345678", key_mgmt="SAE", | |
161 | scan_freq="2412") | |
162 | ||
163 | def test_sae_mixed(dev, apdev): | |
164 | """Mixed SAE and non-SAE network""" | |
b9749b6a JM |
165 | if "SAE" not in dev[0].get_capability("auth_alg"): |
166 | raise HwsimSkip("SAE not supported") | |
d05ff960 JM |
167 | params = hostapd.wpa2_params(ssid="test-sae", passphrase="12345678") |
168 | params['wpa_key_mgmt'] = 'SAE WPA-PSK' | |
169 | params['sae_anti_clogging_threshold'] = '0' | |
170 | hostapd.add_ap(apdev[0]['ifname'], params) | |
171 | ||
172 | dev[2].connect("test-sae", psk="12345678", scan_freq="2412") | |
173 | for i in range(0, 2): | |
174 | dev[i].request("SET sae_groups ") | |
175 | dev[i].connect("test-sae", psk="12345678", key_mgmt="SAE", | |
176 | scan_freq="2412") | |
acb63c75 JM |
177 | |
178 | def test_sae_missing_password(dev, apdev): | |
179 | """SAE and missing password""" | |
b9749b6a JM |
180 | if "SAE" not in dev[0].get_capability("auth_alg"): |
181 | raise HwsimSkip("SAE not supported") | |
acb63c75 JM |
182 | params = hostapd.wpa2_params(ssid="test-sae", |
183 | passphrase="12345678") | |
184 | params['wpa_key_mgmt'] = 'SAE' | |
185 | hapd = hostapd.add_ap(apdev[0]['ifname'], params) | |
186 | ||
187 | dev[0].request("SET sae_groups ") | |
188 | id = dev[0].connect("test-sae", | |
189 | raw_psk="46b4a73b8a951ad53ebd2e0afdb9c5483257edd4c21d12b7710759da70945858", | |
190 | key_mgmt="SAE", scan_freq="2412", wait_connect=False) | |
191 | ev = dev[0].wait_event(['CTRL-EVENT-SSID-TEMP-DISABLED'], timeout=10) | |
192 | if ev is None: | |
193 | raise Exception("Invalid network not temporarily disabled") | |
5b3c40a6 JM |
194 | |
195 | ||
196 | def test_sae_key_lifetime_in_memory(dev, apdev, params): | |
197 | """SAE and key lifetime in memory""" | |
b9749b6a JM |
198 | if "SAE" not in dev[0].get_capability("auth_alg"): |
199 | raise HwsimSkip("SAE not supported") | |
5b3c40a6 JM |
200 | password = "5ad144a7c1f5a5503baa6fa01dabc15b1843e8c01662d78d16b70b5cd23cf8b" |
201 | p = hostapd.wpa2_params(ssid="test-sae", passphrase=password) | |
202 | p['wpa_key_mgmt'] = 'SAE' | |
203 | hapd = hostapd.add_ap(apdev[0]['ifname'], p) | |
204 | ||
205 | pid = find_wpas_process(dev[0]) | |
206 | ||
207 | dev[0].request("SET sae_groups ") | |
208 | id = dev[0].connect("test-sae", psk=password, key_mgmt="SAE", | |
209 | scan_freq="2412") | |
210 | ||
54f2cae2 | 211 | time.sleep(1) |
5b3c40a6 JM |
212 | buf = read_process_memory(pid, password) |
213 | ||
214 | dev[0].request("DISCONNECT") | |
215 | dev[0].wait_disconnected() | |
216 | ||
217 | dev[0].relog() | |
218 | sae_k = None | |
219 | sae_keyseed = None | |
220 | sae_kck = None | |
221 | pmk = None | |
222 | ptk = None | |
223 | gtk = None | |
224 | with open(os.path.join(params['logdir'], 'log0'), 'r') as f: | |
225 | for l in f.readlines(): | |
226 | if "SAE: k - hexdump" in l: | |
227 | val = l.strip().split(':')[3].replace(' ', '') | |
228 | sae_k = binascii.unhexlify(val) | |
229 | if "SAE: keyseed - hexdump" in l: | |
230 | val = l.strip().split(':')[3].replace(' ', '') | |
231 | sae_keyseed = binascii.unhexlify(val) | |
232 | if "SAE: KCK - hexdump" in l: | |
233 | val = l.strip().split(':')[3].replace(' ', '') | |
234 | sae_kck = binascii.unhexlify(val) | |
235 | if "SAE: PMK - hexdump" in l: | |
236 | val = l.strip().split(':')[3].replace(' ', '') | |
237 | pmk = binascii.unhexlify(val) | |
238 | if "WPA: PTK - hexdump" in l: | |
239 | val = l.strip().split(':')[3].replace(' ', '') | |
240 | ptk = binascii.unhexlify(val) | |
241 | if "WPA: Group Key - hexdump" in l: | |
242 | val = l.strip().split(':')[3].replace(' ', '') | |
243 | gtk = binascii.unhexlify(val) | |
244 | if not sae_k or not sae_keyseed or not sae_kck or not pmk or not ptk or not gtk: | |
245 | raise Exception("Could not find keys from debug log") | |
246 | if len(gtk) != 16: | |
247 | raise Exception("Unexpected GTK length") | |
248 | ||
249 | kck = ptk[0:16] | |
250 | kek = ptk[16:32] | |
251 | tk = ptk[32:48] | |
252 | ||
253 | fname = os.path.join(params['logdir'], | |
254 | 'sae_key_lifetime_in_memory.memctx-') | |
255 | ||
256 | logger.info("Checking keys in memory while associated") | |
257 | get_key_locations(buf, password, "Password") | |
258 | get_key_locations(buf, pmk, "PMK") | |
259 | if password not in buf: | |
81e787b7 | 260 | raise HwsimSkip("Password not found while associated") |
5b3c40a6 | 261 | if pmk not in buf: |
81e787b7 | 262 | raise HwsimSkip("PMK not found while associated") |
5b3c40a6 JM |
263 | if kck not in buf: |
264 | raise Exception("KCK not found while associated") | |
265 | if kek not in buf: | |
266 | raise Exception("KEK not found while associated") | |
267 | if tk in buf: | |
268 | raise Exception("TK found from memory") | |
269 | if gtk in buf: | |
270 | raise Exception("GTK found from memory") | |
271 | verify_not_present(buf, sae_k, fname, "SAE(k)") | |
272 | verify_not_present(buf, sae_keyseed, fname, "SAE(keyseed)") | |
273 | verify_not_present(buf, sae_kck, fname, "SAE(KCK)") | |
274 | ||
275 | logger.info("Checking keys in memory after disassociation") | |
276 | buf = read_process_memory(pid, password) | |
277 | ||
278 | # Note: Password is still present in network configuration | |
279 | # Note: PMK is in PMKSA cache | |
280 | ||
281 | get_key_locations(buf, password, "Password") | |
282 | get_key_locations(buf, pmk, "PMK") | |
283 | verify_not_present(buf, kck, fname, "KCK") | |
284 | verify_not_present(buf, kek, fname, "KEK") | |
285 | verify_not_present(buf, tk, fname, "TK") | |
286 | verify_not_present(buf, gtk, fname, "GTK") | |
287 | verify_not_present(buf, sae_k, fname, "SAE(k)") | |
288 | verify_not_present(buf, sae_keyseed, fname, "SAE(keyseed)") | |
289 | verify_not_present(buf, sae_kck, fname, "SAE(KCK)") | |
290 | ||
291 | dev[0].request("PMKSA_FLUSH") | |
292 | logger.info("Checking keys in memory after PMKSA cache flush") | |
293 | buf = read_process_memory(pid, password) | |
294 | get_key_locations(buf, password, "Password") | |
295 | get_key_locations(buf, pmk, "PMK") | |
296 | verify_not_present(buf, pmk, fname, "PMK") | |
297 | ||
298 | dev[0].request("REMOVE_NETWORK all") | |
299 | ||
300 | logger.info("Checking keys in memory after network profile removal") | |
301 | buf = read_process_memory(pid, password) | |
302 | ||
303 | get_key_locations(buf, password, "Password") | |
304 | get_key_locations(buf, pmk, "PMK") | |
305 | verify_not_present(buf, password, fname, "password") | |
306 | verify_not_present(buf, pmk, fname, "PMK") | |
307 | verify_not_present(buf, kck, fname, "KCK") | |
308 | verify_not_present(buf, kek, fname, "KEK") | |
309 | verify_not_present(buf, tk, fname, "TK") | |
310 | verify_not_present(buf, gtk, fname, "GTK") | |
311 | verify_not_present(buf, sae_k, fname, "SAE(k)") | |
312 | verify_not_present(buf, sae_keyseed, fname, "SAE(keyseed)") | |
313 | verify_not_present(buf, sae_kck, fname, "SAE(KCK)") |