Browse Source

tests: Verify that wpa_supplicant clears keys from memory

Check that PMK and PTK and not left in memory (heap or stack)
unnecessarily after they are not needed anymore.

Signed-off-by: Jouni Malinen <j@w1.fi>
Jouni Malinen 10 years ago
parent
commit
5b3c40a65b

+ 2 - 0
tests/hwsim/auth_serv/eap_user.conf

@@ -74,6 +74,8 @@ radius_accept_attr=27:d:3
 "8"*		AKA'	[2]
 
 "pap user"	TTLS-PAP	"password"	[2]
+"pap-secret"	TTLS-PAP	"63d2d21ac3c09ed567ee004a34490f1d16e7fa5835edf17ddba70a63f1a90a25"	[2]
+"pap-secret@example.com"	TTLS-PAP	"63d2d21ac3c09ed567ee004a34490f1d16e7fa5835edf17ddba70a63f1a90a25"	[2]
 "chap user"	TTLS-CHAP	"password"	[2]
 "mschap user"	TTLS-MSCHAP	"password"	[2]
 "DOMAIN\mschapv2 user"	TTLS-MSCHAPV2	hash:8846f7eaee8fb117ad06bdd830b7586c	[2]

+ 97 - 1
tests/hwsim/test_ap_eap.py

@@ -6,6 +6,7 @@
 # See README for more details.
 
 import base64
+import binascii
 import time
 import subprocess
 import logging
@@ -14,7 +15,7 @@ import os
 
 import hwsim_utils
 import hostapd
-from test_ap_psk import check_mib
+from test_ap_psk import check_mib, find_wpas_process, read_process_memory, verify_not_present, get_key_locations
 
 def read_pem(fname):
     with open(fname, "r") as f:
@@ -2243,3 +2244,98 @@ def test_openssl_cipher_suite_config_hapd(dev, apdev):
                 anonymous_identity="ttls", password="password",
                 openssl_ciphers="HIGH:!ADH",
                 ca_cert="auth_serv/ca.pem", phase2="auth=PAP")
+
+def test_wpa2_eap_ttls_pap_key_lifetime_in_memory(dev, apdev, params):
+    """Key lifetime in memory with WPA2-Enterprise using EAP-TTLS/PAP"""
+    p = hostapd.wpa2_eap_params(ssid="test-wpa2-eap")
+    hapd = hostapd.add_ap(apdev[0]['ifname'], p)
+    password = "63d2d21ac3c09ed567ee004a34490f1d16e7fa5835edf17ddba70a63f1a90a25"
+    pid = find_wpas_process(dev[0])
+    id = eap_connect(dev[0], apdev[0], "TTLS", "pap-secret",
+                     anonymous_identity="ttls", password=password,
+                     ca_cert="auth_serv/ca.pem", phase2="auth=PAP")
+    time.sleep(0.1)
+    buf = read_process_memory(pid, password)
+
+    dev[0].request("DISCONNECT")
+    dev[0].wait_disconnected()
+
+    dev[0].relog()
+    pmk = None
+    ptk = None
+    gtk = None
+    with open(os.path.join(params['logdir'], 'log0'), 'r') as f:
+        for l in f.readlines():
+            if "WPA: PMK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                pmk = binascii.unhexlify(val)
+            if "WPA: PTK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                ptk = binascii.unhexlify(val)
+            if "WPA: Group Key - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                gtk = binascii.unhexlify(val)
+    if not pmk or not ptk or not gtk:
+        raise Exception("Could not find keys from debug log")
+    if len(gtk) != 16:
+        raise Exception("Unexpected GTK length")
+
+    kck = ptk[0:16]
+    kek = ptk[16:32]
+    tk = ptk[32:48]
+
+    fname = os.path.join(params['logdir'],
+                         'wpa2_eap_ttls_pap_key_lifetime_in_memory.memctx-')
+
+    logger.info("Checking keys in memory while associated")
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    if password not in buf:
+        print("Password not found while associated")
+        return "skip"
+    if pmk not in buf:
+        print("PMK not found while associated")
+        return "skip"
+    if kck not in buf:
+        raise Exception("KCK not found while associated")
+    if kek not in buf:
+        raise Exception("KEK not found while associated")
+    if tk in buf:
+        raise Exception("TK found from memory")
+    if gtk in buf:
+        raise Exception("GTK found from memory")
+
+    logger.info("Checking keys in memory after disassociation")
+    buf = read_process_memory(pid, password)
+
+    # Note: Password is still present in network configuration
+    # Note: PMK is in PMKSA cache and EAP fast re-auth data
+
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+
+    dev[0].request("PMKSA_FLUSH")
+    dev[0].set_network_quoted(id, "identity", "foo")
+    logger.info("Checking keys in memory after PMKSA cache and EAP fast reauth flush")
+    buf = read_process_memory(pid, password)
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    verify_not_present(buf, pmk, fname, "PMK")
+
+    dev[0].request("REMOVE_NETWORK all")
+
+    logger.info("Checking keys in memory after network profile removal")
+    buf = read_process_memory(pid, password)
+
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    verify_not_present(buf, password, fname, "password")
+    verify_not_present(buf, pmk, fname, "PMK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")

+ 108 - 1
tests/hwsim/test_ap_ft.py

@@ -4,6 +4,8 @@
 # This software may be distributed under the terms of the BSD license.
 # See README for more details.
 
+import binascii
+import os
 import time
 import subprocess
 import logging
@@ -12,7 +14,7 @@ logger = logging.getLogger()
 import hwsim_utils
 import hostapd
 from wlantest import Wlantest
-from test_ap_psk import check_mib
+from test_ap_psk import check_mib, find_wpas_process, read_process_memory, verify_not_present, get_key_locations
 
 def ft_base_rsn():
     params = { "wpa": "2",
@@ -456,3 +458,108 @@ def test_ap_ft_gtk_rekey(dev, apdev):
     if ev is None:
         raise Exception("GTK rekey timed out after FT protocol")
     hwsim_utils.test_connectivity(dev[0], hapd1)
+
+def test_ft_psk_key_lifetime_in_memory(dev, apdev, params):
+    """WPA2-PSK-FT and key lifetime in memory"""
+    ssid = "test-ft"
+    passphrase="04c2726b4b8d5f1b4db9c07aa4d9e9d8f765cb5d25ec817e6cc4fcdd5255db0"
+    psk = '93c90846ff67af9037ed83fb72b63dbeddaa81d47f926c20909b5886f1d9358d'
+    pmk = binascii.unhexlify(psk)
+    p = ft_params1(ssid=ssid, passphrase=passphrase)
+    hapd0 = hostapd.add_ap(apdev[0]['ifname'], p)
+    p = ft_params2(ssid=ssid, passphrase=passphrase)
+    hapd1 = hostapd.add_ap(apdev[1]['ifname'], p)
+
+    pid = find_wpas_process(dev[0])
+
+    dev[0].connect(ssid, psk=passphrase, key_mgmt="FT-PSK", proto="WPA2",
+                   scan_freq="2412")
+    time.sleep(0.1)
+
+    buf = read_process_memory(pid, pmk)
+
+    dev[0].request("DISCONNECT")
+    dev[0].wait_disconnected()
+
+    dev[0].relog()
+    pmkr0 = None
+    pmkr1 = None
+    ptk = None
+    gtk = None
+    with open(os.path.join(params['logdir'], 'log0'), 'r') as f:
+        for l in f.readlines():
+            if "FT: PMK-R0 - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                pmkr0 = binascii.unhexlify(val)
+            if "FT: PMK-R1 - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                pmkr1 = binascii.unhexlify(val)
+            if "FT: PTK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                ptk = binascii.unhexlify(val)
+            if "WPA: Group Key - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                gtk = binascii.unhexlify(val)
+    if not pmkr0 or not pmkr1 or not ptk or not gtk:
+        raise Exception("Could not find keys from debug log")
+    if len(gtk) != 16:
+        raise Exception("Unexpected GTK length")
+
+    kck = ptk[0:16]
+    kek = ptk[16:32]
+    tk = ptk[32:48]
+
+    logger.info("Checking keys in memory while associated")
+    get_key_locations(buf, pmk, "PMK")
+    get_key_locations(buf, pmkr0, "PMK-R0")
+    get_key_locations(buf, pmkr1, "PMK-R1")
+    if pmk not in buf:
+        print("PMK not found while associated")
+        return "skip"
+    if pmkr0 not in buf:
+        print("PMK-R0 not found while associated")
+        return "skip"
+    if pmkr1 not in buf:
+        print("PMK-R1 not found while associated")
+        return "skip"
+    if kck not in buf:
+        raise Exception("KCK not found while associated")
+    if kek not in buf:
+        raise Exception("KEK not found while associated")
+    if tk in buf:
+        raise Exception("TK found from memory")
+    if gtk in buf:
+        raise Exception("GTK found from memory")
+
+    logger.info("Checking keys in memory after disassociation")
+    buf = read_process_memory(pid, pmk)
+    get_key_locations(buf, pmk, "PMK")
+    get_key_locations(buf, pmkr0, "PMK-R0")
+    get_key_locations(buf, pmkr1, "PMK-R1")
+
+    # Note: PMK/PSK is still present in network configuration
+
+    fname = os.path.join(params['logdir'],
+                         'ft_psk_key_lifetime_in_memory.memctx-')
+    verify_not_present(buf, pmkr0, fname, "PMK-R0")
+    verify_not_present(buf, pmkr1, fname, "PMK-R1")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+
+    dev[0].request("REMOVE_NETWORK all")
+
+    logger.info("Checking keys in memory after network profile removal")
+    buf = read_process_memory(pid, pmk)
+    get_key_locations(buf, pmk, "PMK")
+    get_key_locations(buf, pmkr0, "PMK-R0")
+    get_key_locations(buf, pmkr1, "PMK-R1")
+
+    verify_not_present(buf, pmk, fname, "PMK")
+    verify_not_present(buf, pmkr0, fname, "PMK-R0")
+    verify_not_present(buf, pmkr1, fname, "PMK-R1")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")

+ 157 - 0
tests/hwsim/test_ap_psk.py

@@ -10,6 +10,7 @@ import hmac
 import logging
 logger = logging.getLogger()
 import os
+import re
 import struct
 import subprocess
 import time
@@ -818,3 +819,159 @@ def test_ap_wpa2_psk_ext_eapol_key_info(dev, apdev):
 
     reply_eapol("4/4", hapd, addr, msg, 0x030a, None, None, kck)
     hapd_connected(hapd)
+
+def find_wpas_process(dev):
+    ifname = dev.ifname
+    cmd = subprocess.Popen(['ps', 'ax'], stdout=subprocess.PIPE)
+    (data,err) = cmd.communicate()
+    for l in data.splitlines():
+        if "wpa_supplicant" not in l:
+            continue
+        if "-i" + ifname not in l:
+            continue
+        return int(l.strip().split(' ')[0])
+    raise Exception("Could not find wpa_supplicant process")
+
+def read_process_memory(pid, key=None):
+    buf = bytes()
+    with open('/proc/%d/maps' % pid, 'r') as maps, \
+         open('/proc/%d/mem' % pid, 'r') as mem:
+        for l in maps.readlines():
+            m = re.match(r'([0-9a-f]+)-([0-9a-f]+) ([-r][-w][-x][-p])', l)
+            if not m:
+                continue
+            start = int(m.group(1), 16)
+            end = int(m.group(2), 16)
+            perm = m.group(3)
+            if start > 0xffffffffffff:
+                continue
+            if end < start:
+                continue
+            if not perm.startswith('rw'):
+                continue
+            mem.seek(start)
+            data = mem.read(end - start)
+            buf += data
+            if key and key in data:
+                logger.info("Key found in " + l)
+    return buf
+
+def verify_not_present(buf, key, fname, keyname):
+    pos = buf.find(key)
+    if pos < 0:
+        return
+
+    prefix = 2048 if pos > 2048 else pos
+    with open(fname + keyname, 'w') as f:
+        f.write(buf[pos - prefix:pos + 2048])
+    raise Exception(keyname + " found after disassociation")
+
+def get_key_locations(buf, key, keyname):
+    count = 0
+    pos = 0
+    while True:
+        pos = buf.find(key, pos)
+        if pos < 0:
+            break
+        logger.info("Found %s at %d" % (keyname, pos))
+        count += 1
+        pos += len(key)
+    return count
+
+def test_wpa2_psk_key_lifetime_in_memory(dev, apdev, params):
+    """WPA2-PSK and PSK/PTK lifetime in memory"""
+    ssid = "test-wpa2-psk"
+    passphrase = 'qwertyuiop'
+    psk = '602e323e077bc63bd80307ef4745b754b0ae0a925c2638ecd13a794b9527b9e6'
+    pmk = binascii.unhexlify(psk)
+    p = hostapd.wpa2_params(ssid=ssid)
+    p['wpa_psk'] = psk
+    hapd = hostapd.add_ap(apdev[0]['ifname'], p)
+
+    pid = find_wpas_process(dev[0])
+
+    id = dev[0].connect(ssid, raw_psk=psk, scan_freq="2412",
+                        only_add_network=True)
+
+    logger.info("Checking keys in memory after network profile configuration")
+    buf = read_process_memory(pid, pmk)
+    get_key_locations(buf, pmk, "PMK")
+
+    dev[0].request("REMOVE_NETWORK all")
+    logger.info("Checking keys in memory after network profile removal")
+    buf = read_process_memory(pid, pmk)
+    get_key_locations(buf, pmk, "PMK")
+
+    id = dev[0].connect(ssid, psk=passphrase, scan_freq="2412",
+                        only_add_network=True)
+
+    logger.info("Checking keys in memory before connection")
+    buf = read_process_memory(pid, pmk)
+    get_key_locations(buf, pmk, "PMK")
+
+    dev[0].connect_network(id, timeout=20)
+    time.sleep(0.1)
+
+    buf = read_process_memory(pid, pmk)
+
+    dev[0].request("DISCONNECT")
+    dev[0].wait_disconnected()
+
+    dev[0].relog()
+    ptk = None
+    gtk = None
+    with open(os.path.join(params['logdir'], 'log0'), 'r') as f:
+        for l in f.readlines():
+            if "WPA: PTK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                ptk = binascii.unhexlify(val)
+            if "WPA: Group Key - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                gtk = binascii.unhexlify(val)
+    if not pmk or not ptk or not gtk:
+        raise Exception("Could not find keys from debug log")
+    if len(gtk) != 16:
+        raise Exception("Unexpected GTK length")
+
+    kck = ptk[0:16]
+    kek = ptk[16:32]
+    tk = ptk[32:48]
+
+    logger.info("Checking keys in memory while associated")
+    get_key_locations(buf, pmk, "PMK")
+    if pmk not in buf:
+        print("PMK not found while associated")
+        return "skip"
+    if kck not in buf:
+        raise Exception("KCK not found while associated")
+    if kek not in buf:
+        raise Exception("KEK not found while associated")
+    if tk in buf:
+        raise Exception("TK found from memory")
+    if gtk in buf:
+        raise Exception("GTK found from memory")
+
+    logger.info("Checking keys in memory after disassociation")
+    buf = read_process_memory(pid, pmk)
+    get_key_locations(buf, pmk, "PMK")
+
+    # Note: PMK/PSK is still present in network configuration
+
+    fname = os.path.join(params['logdir'],
+                         'wpa2_psk_key_lifetime_in_memory.memctx-')
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+
+    dev[0].request("REMOVE_NETWORK all")
+
+    logger.info("Checking keys in memory after network profile removal")
+    buf = read_process_memory(pid, pmk)
+    get_key_locations(buf, pmk, "PMK")
+
+    verify_not_present(buf, pmk, fname, "PMK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")

+ 174 - 0
tests/hwsim/test_erp.py

@@ -4,11 +4,15 @@
 # This software may be distributed under the terms of the BSD license.
 # See README for more details.
 
+import binascii
 import logging
 logger = logging.getLogger()
+import os
+import time
 
 import hostapd
 from test_ap_eap import int_eap_server_params
+from test_ap_psk import find_wpas_process, read_process_memory, verify_not_present, get_key_locations
 
 def test_erp_initiate_reauth_start(dev, apdev):
     """Authenticator sending EAP-Initiate/Re-auth-Start, but ERP disabled on peer"""
@@ -215,3 +219,173 @@ def test_erp_radius_eap_methods(dev, apdev):
              private_key="auth_serv/user.key")
     erp_test(dev[0], hapd, eap="TTLS", identity="erp-ttls@example.com",
              password="password", ca_cert="auth_serv/ca.pem", phase2="auth=PAP")
+
+def test_erp_key_lifetime_in_memory(dev, apdev, params):
+    """ERP and key lifetime in memory"""
+    capab = dev[0].get_capability("erp")
+    if not capab or 'ERP' not in capab:
+        return "skip"
+    p = int_eap_server_params()
+    p['erp_send_reauth_start'] = '1'
+    p['erp_domain'] = 'example.com'
+    p['eap_server_erp'] = '1'
+    p['disable_pmksa_caching'] = '1'
+    hapd = hostapd.add_ap(apdev[0]['ifname'], p)
+    password = "63d2d21ac3c09ed567ee004a34490f1d16e7fa5835edf17ddba70a63f1a90a25"
+
+    pid = find_wpas_process(dev[0])
+
+    dev[0].request("ERP_FLUSH")
+    dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", eap="TTLS",
+                   identity="pap-secret@example.com", password=password,
+                   ca_cert="auth_serv/ca.pem", phase2="auth=PAP",
+                   erp="1", scan_freq="2412")
+
+    time.sleep(0.1)
+    buf = read_process_memory(pid, password)
+
+    dev[0].request("DISCONNECT")
+    dev[0].wait_disconnected(timeout=15)
+
+    dev[0].relog()
+    rRK = None
+    rIK = None
+    pmk = None
+    ptk = None
+    gtk = None
+    with open(os.path.join(params['logdir'], 'log0'), 'r') as f:
+        for l in f.readlines():
+            if "EAP: ERP rRK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                rRK = binascii.unhexlify(val)
+            if "EAP: ERP rIK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                rIK = binascii.unhexlify(val)
+            if "WPA: PMK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                pmk = binascii.unhexlify(val)
+            if "WPA: PTK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                ptk = binascii.unhexlify(val)
+            if "WPA: Group Key - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                gtk = binascii.unhexlify(val)
+    if not rIK or not rRK or not pmk or not ptk or not gtk:
+        raise Exception("Could not find keys from debug log")
+    if len(gtk) != 16:
+        raise Exception("Unexpected GTK length")
+
+    kck = ptk[0:16]
+    kek = ptk[16:32]
+    tk = ptk[32:48]
+
+    fname = os.path.join(params['logdir'],
+                         'erp_key_lifetime_in_memory.memctx-')
+
+    logger.info("Checking keys in memory while associated")
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    get_key_locations(buf, rRK, "rRK")
+    get_key_locations(buf, rIK, "rIK")
+    if password not in buf:
+        print("Password not found while associated")
+        return "skip"
+    if pmk not in buf:
+        print("PMK not found while associated")
+        return "skip"
+    if kck not in buf:
+        raise Exception("KCK not found while associated")
+    if kek not in buf:
+        raise Exception("KEK not found while associated")
+    if tk in buf:
+        raise Exception("TK found from memory")
+    if gtk in buf:
+        raise Exception("GTK found from memory")
+
+    logger.info("Checking keys in memory after disassociation")
+    buf = read_process_memory(pid, password)
+
+    # Note: Password is still present in network configuration
+    # Note: PMK is in EAP fast re-auth data
+
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    get_key_locations(buf, rRK, "rRK")
+    get_key_locations(buf, rIK, "rIK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+
+    dev[0].request("RECONNECT")
+    ev = dev[0].wait_event(["CTRL-EVENT-EAP-SUCCESS"], timeout=15)
+    if ev is None:
+        raise Exception("EAP success timed out")
+    if "EAP re-authentication completed successfully" not in ev:
+        raise Exception("Did not use ERP")
+    dev[0].wait_connected(timeout=15, error="Reconnection timed out")
+
+    dev[0].request("DISCONNECT")
+    dev[0].wait_disconnected(timeout=15)
+
+    dev[0].relog()
+    pmk = None
+    ptk = None
+    gtk = None
+    with open(os.path.join(params['logdir'], 'log0'), 'r') as f:
+        for l in f.readlines():
+            if "WPA: PMK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                pmk = binascii.unhexlify(val)
+            if "WPA: PTK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                ptk = binascii.unhexlify(val)
+            if "WPA: GTK in EAPOL-Key - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                gtk = binascii.unhexlify(val)
+    if not pmk or not ptk or not gtk:
+        raise Exception("Could not find keys from debug log")
+
+    kck = ptk[0:16]
+    kek = ptk[16:32]
+    tk = ptk[32:48]
+
+    logger.info("Checking keys in memory after ERP and disassociation")
+    buf = read_process_memory(pid, password)
+
+    # Note: Password is still present in network configuration
+
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    get_key_locations(buf, rRK, "rRK")
+    get_key_locations(buf, rIK, "rIK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+
+    dev[0].request("REMOVE_NETWORK all")
+
+    logger.info("Checking keys in memory after network profile removal")
+    buf = read_process_memory(pid, password)
+
+    # Note: rRK and rIK are still in memory
+
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    get_key_locations(buf, rRK, "rRK")
+    get_key_locations(buf, rIK, "rIK")
+    verify_not_present(buf, password, fname, "password")
+    verify_not_present(buf, pmk, fname, "PMK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+
+    dev[0].request("ERP_FLUSH")
+    logger.info("Checking keys in memory after ERP_FLUSH")
+    buf = read_process_memory(pid, password)
+    get_key_locations(buf, rRK, "rRK")
+    get_key_locations(buf, rIK, "rIK")
+    verify_not_present(buf, rRK, fname, "rRK")
+    verify_not_present(buf, rIK, fname, "rIK")

+ 124 - 1
tests/hwsim/test_sae.py

@@ -1,9 +1,11 @@
 # Test cases for SAE
-# Copyright (c) 2013, Jouni Malinen <j@w1.fi>
+# Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi>
 #
 # This software may be distributed under the terms of the BSD license.
 # See README for more details.
 
+import binascii
+import os
 import time
 import subprocess
 import logging
@@ -11,6 +13,7 @@ logger = logging.getLogger()
 
 import hwsim_utils
 import hostapd
+from test_ap_psk import find_wpas_process, read_process_memory, verify_not_present, get_key_locations
 
 def test_sae(dev, apdev):
     """SAE with default group"""
@@ -161,3 +164,123 @@ def test_sae_missing_password(dev, apdev):
     ev = dev[0].wait_event(['CTRL-EVENT-SSID-TEMP-DISABLED'], timeout=10)
     if ev is None:
         raise Exception("Invalid network not temporarily disabled")
+
+
+def test_sae_key_lifetime_in_memory(dev, apdev, params):
+    """SAE and key lifetime in memory"""
+    password = "5ad144a7c1f5a5503baa6fa01dabc15b1843e8c01662d78d16b70b5cd23cf8b"
+    p = hostapd.wpa2_params(ssid="test-sae", passphrase=password)
+    p['wpa_key_mgmt'] = 'SAE'
+    hapd = hostapd.add_ap(apdev[0]['ifname'], p)
+
+    pid = find_wpas_process(dev[0])
+
+    dev[0].request("SET sae_groups ")
+    id = dev[0].connect("test-sae", psk=password, key_mgmt="SAE",
+                        scan_freq="2412")
+
+    time.sleep(0.1)
+    buf = read_process_memory(pid, password)
+
+    dev[0].request("DISCONNECT")
+    dev[0].wait_disconnected()
+
+    dev[0].relog()
+    sae_k = None
+    sae_keyseed = None
+    sae_kck = None
+    pmk = None
+    ptk = None
+    gtk = None
+    with open(os.path.join(params['logdir'], 'log0'), 'r') as f:
+        for l in f.readlines():
+            if "SAE: k - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                sae_k = binascii.unhexlify(val)
+            if "SAE: keyseed - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                sae_keyseed = binascii.unhexlify(val)
+            if "SAE: KCK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                sae_kck = binascii.unhexlify(val)
+            if "SAE: PMK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                pmk = binascii.unhexlify(val)
+            if "WPA: PTK - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                ptk = binascii.unhexlify(val)
+            if "WPA: Group Key - hexdump" in l:
+                val = l.strip().split(':')[3].replace(' ', '')
+                gtk = binascii.unhexlify(val)
+    if not sae_k or not sae_keyseed or not sae_kck or not pmk or not ptk or not gtk:
+        raise Exception("Could not find keys from debug log")
+    if len(gtk) != 16:
+        raise Exception("Unexpected GTK length")
+
+    kck = ptk[0:16]
+    kek = ptk[16:32]
+    tk = ptk[32:48]
+
+    fname = os.path.join(params['logdir'],
+                         'sae_key_lifetime_in_memory.memctx-')
+
+    logger.info("Checking keys in memory while associated")
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    if password not in buf:
+        print("Password not found while associated")
+        return "skip"
+    if pmk not in buf:
+        print("PMK not found while associated")
+        return "skip"
+    if kck not in buf:
+        raise Exception("KCK not found while associated")
+    if kek not in buf:
+        raise Exception("KEK not found while associated")
+    if tk in buf:
+        raise Exception("TK found from memory")
+    if gtk in buf:
+        raise Exception("GTK found from memory")
+    verify_not_present(buf, sae_k, fname, "SAE(k)")
+    verify_not_present(buf, sae_keyseed, fname, "SAE(keyseed)")
+    verify_not_present(buf, sae_kck, fname, "SAE(KCK)")
+
+    logger.info("Checking keys in memory after disassociation")
+    buf = read_process_memory(pid, password)
+
+    # Note: Password is still present in network configuration
+    # Note: PMK is in PMKSA cache
+
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+    verify_not_present(buf, sae_k, fname, "SAE(k)")
+    verify_not_present(buf, sae_keyseed, fname, "SAE(keyseed)")
+    verify_not_present(buf, sae_kck, fname, "SAE(KCK)")
+
+    dev[0].request("PMKSA_FLUSH")
+    logger.info("Checking keys in memory after PMKSA cache flush")
+    buf = read_process_memory(pid, password)
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    verify_not_present(buf, pmk, fname, "PMK")
+
+    dev[0].request("REMOVE_NETWORK all")
+
+    logger.info("Checking keys in memory after network profile removal")
+    buf = read_process_memory(pid, password)
+
+    get_key_locations(buf, password, "Password")
+    get_key_locations(buf, pmk, "PMK")
+    verify_not_present(buf, password, fname, "password")
+    verify_not_present(buf, pmk, fname, "PMK")
+    verify_not_present(buf, kck, fname, "KCK")
+    verify_not_present(buf, kek, fname, "KEK")
+    verify_not_present(buf, tk, fname, "TK")
+    verify_not_present(buf, gtk, fname, "GTK")
+    verify_not_present(buf, sae_k, fname, "SAE(k)")
+    verify_not_present(buf, sae_keyseed, fname, "SAE(keyseed)")
+    verify_not_present(buf, sae_kck, fname, "SAE(KCK)")