network_WiFi_RoamFT: add mixedPSK and mixedEAP tests

Add mixedPSK and mixedEAP autotests for Fast BSS Transition. These tests
ensure that clients that don't support 802.11r can still authenticate to
and associate with APs that support both FT and non-FT clients.

Also, modify security types so that we can specify non-FT, strict FT, and
mixed mode FT.

BUG=chromium:791202
TEST=Ran PSK and EAP tests on Kevin (TestNA) and Caroline (PASS), and they
     still work as intended. Ran mixedPSK and mixedEAP tests on Kevin (PASS)
     and Caroline (PASS), and they both pass as expected.

Change-Id: Id5b3f7ff63a8ec65db4c77f5fbeea3d532b71bbe
Reviewed-on: https://chromium-review.googlesource.com/1180380
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Matthew Wang <matthewmwang@chromium.org>
Reviewed-by: Brian Norris <briannorris@chromium.org>
diff --git a/client/common_lib/cros/network/xmlrpc_security_types.py b/client/common_lib/cros/network/xmlrpc_security_types.py
index ffff641..634734e 100644
--- a/client/common_lib/cros/network/xmlrpc_security_types.py
+++ b/client/common_lib/cros/network/xmlrpc_security_types.py
@@ -191,10 +191,16 @@
     CIPHER_CCMP = 'CCMP'
     CIPHER_TKIP = 'TKIP'
 
+    # Fast Transition (FT) mode for WPA network.
+    FT_MODE_NONE = 1
+    FT_MODE_PURE = 2
+    FT_MODE_MIXED = FT_MODE_NONE | FT_MODE_PURE
+    FT_MODE_DEFAULT = FT_MODE_NONE
+
     def __init__(self, psk='', wpa_mode=MODE_DEFAULT, wpa_ciphers=[],
                  wpa2_ciphers=[], wpa_ptk_rekey_period=None,
                  wpa_gtk_rekey_period=None, wpa_gmk_rekey_period=None,
-                 use_strict_rekey=None, use_ft=None):
+                 use_strict_rekey=None, ft_mode=FT_MODE_NONE):
         """Construct a WPAConfig.
 
         @param psk string a passphrase (64 hex characters or an ASCII phrase up
@@ -211,6 +217,7 @@
                 It is the 'master' key.
         @param use_strict_rekey bool True iff hostapd should refresh the GTK
                 whenever any client leaves the group.
+        @param ft_mode int one of the FT_MODE_* in SecurityConfig.
 
         """
         super(WPAConfig, self).__init__(security='psk')
@@ -222,7 +229,7 @@
         self.wpa_gtk_rekey_period = wpa_gtk_rekey_period
         self.wpa_gmk_rekey_period = wpa_gmk_rekey_period
         self.use_strict_rekey = use_strict_rekey
-        self.use_ft = use_ft
+        self.ft_mode = ft_mode
         if len(psk) > 64:
             raise error.TestFail('WPA passphrases can be no longer than 63 '
                                  'characters (or 64 hex digits).')
@@ -249,8 +256,10 @@
 
         ret = {'wpa': self.wpa_mode,
                'wpa_key_mgmt': 'WPA-PSK'}
-        if self.use_ft:
+        if self.ft_mode == self.FT_MODE_PURE:
             ret['wpa_key_mgmt'] = 'FT-PSK'
+        elif self.ft_mode == self.FT_MODE_MIXED:
+            ret['wpa_key_mgmt'] = 'WPA-PSK FT-PSK'
         if len(self.psk) == 64:
             ret['wpa_psk'] = self.psk
         else:
@@ -274,8 +283,8 @@
     def get_shill_service_properties(self):
         """@return dict of shill service properties."""
         ret = {self.SERVICE_PROPERTY_PASSPHRASE: self.psk}
-        if self.use_ft is not None:
-            ret[self.SERVICE_PROPERTY_FT_ENABLED] = self.use_ft
+        if self.ft_mode & self.FT_MODE_PURE:
+            ret[self.SERVICE_PROPERTY_FT_ENABLED] = True
         return ret
 
 
@@ -290,8 +299,10 @@
         properties.update({'psk': '\\"%s\\"' % self.psk,
                            'key_mgmt': 'WPA-PSK',
                            'proto': ' '.join(protos)})
-        if self.use_ft:
+        if self.ft_mode == self.FT_MODE_PURE:
             properties['key_mgmt'] = 'FT-PSK'
+        elif self.ft_mode == self.FT_MODE_MIXED:
+            properties['key_mgmt'] = 'WPA-PSK FT-PSK'
         return properties
 
 
@@ -327,7 +338,7 @@
                  server_eap_users=None,
                  client_ca_cert=None, client_cert=None, client_key=None,
                  client_cert_id=None, client_key_id=None,
-                 eap_identity=None, use_ft=None):
+                 eap_identity=None, ft_mode=WPAConfig.FT_MODE_DEFAULT):
         """Construct an EAPConfig.
 
         @param file_suffix string unique file suffix on DUT.
@@ -342,6 +353,7 @@
         @param client_cert_id string identifier for client certificate in TPM.
         @param client_key_id string identifier for client private key in TPM.
         @param eap_identity string user to authenticate as during EAP.
+        @param ft_mode int one of the FT_MODE_* in SecurityConfig.
 
         """
         super(EAPConfig, self).__init__(security=security)
@@ -372,7 +384,7 @@
         self.client_cert_slot_id = None
         self.client_key_slot_id = None
         self.eap_identity = eap_identity or self.DEFAULT_EAP_IDENTITY
-        self.use_ft = use_ft
+        self.ft_mode = ft_mode
 
 
     def install_router_credentials(self, host):
@@ -436,8 +448,8 @@
                     '%s:%s' % (self.client_key_slot_id, self.client_key_id))
         if self.use_system_cas is not None:
             ret[self.SERVICE_PROPERTY_USE_SYSTEM_CAS] = self.use_system_cas
-        if self.use_ft is not None:
-            ret[self.SERVICE_PROPERTY_FT_ENABLED] = self.use_ft
+        if self.ft_mode & WPAConfig.FT_MODE_PURE:
+            ret[self.SERVICE_PROPERTY_FT_ENABLED] = True
         return ret
 
 
@@ -519,7 +531,8 @@
                  client_ca_cert=None, client_cert=None, client_key=None,
                  client_cert_id=None, client_key_id=None,
                  eap_identity=None, server_eap_users=None,
-                 wpa_mode=WPAConfig.MODE_PURE_WPA, use_ft=None):
+                 wpa_mode=WPAConfig.MODE_PURE_WPA,
+                 ft_mode=WPAConfig.FT_MODE_DEFAULT):
         """Construct a DynamicWEPConfig.
 
         @param file_suffix string unique file suffix on DUT.
@@ -534,6 +547,7 @@
         @param client_key_id string identifier for client private key in TPM.
         @param eap_identity string user to authenticate as during EAP.
         @param server_eap_users string contents of server EAP users file.
+        @param ft_mode int one of the FT_MODE_* in SecurityConfig
 
         """
         super(WPAEAPConfig, self).__init__(
@@ -543,7 +557,7 @@
                 client_cert=client_cert, client_key=client_key,
                 client_cert_id=client_cert_id, client_key_id=client_key_id,
                 eap_identity=eap_identity, server_eap_users=server_eap_users,
-                use_ft=use_ft)
+                ft_mode=ft_mode)
         self.wpa_mode = wpa_mode
 
 
@@ -556,8 +570,10 @@
         ret.update({'wpa': self.wpa_mode,
                     'wpa_pairwise': WPAConfig.CIPHER_CCMP,
                     'wpa_key_mgmt':'WPA-EAP'})
-        if self.use_ft:
+        if self.ft_mode == WPAConfig.FT_MODE_PURE:
             ret['wpa_key_mgmt'] = 'FT-EAP'
+        elif self.ft_mode == WPAConfig.FT_MODE_MIXED:
+            ret['wpa_key_mgmt'] = 'WPA-EAP FT-EAP'
         return ret
 
 
diff --git a/server/site_tests/network_WiFi_RoamFT/control.EAP b/server/site_tests/network_WiFi_RoamFT/control.EAP
index cc613da..84fa2d8 100644
--- a/server/site_tests/network_WiFi_RoamFT/control.EAP
+++ b/server/site_tests/network_WiFi_RoamFT/control.EAP
@@ -28,7 +28,7 @@
         client_cert=site_eap_certs.client_cert_1,
         client_key=site_eap_certs.client_private_key_1,
         wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2,
-        use_ft=True)
+        ft_mode=xmlrpc_security_types.WPAConfig.FT_MODE_PURE)
     host = hosts.create_host(machine)
     job.run_test('network_WiFi_RoamFT',
                  tag=NAME.split('.')[1],
diff --git a/server/site_tests/network_WiFi_RoamFT/control.PSK b/server/site_tests/network_WiFi_RoamFT/control.PSK
index ac5cc6a..40e3898 100644
--- a/server/site_tests/network_WiFi_RoamFT/control.PSK
+++ b/server/site_tests/network_WiFi_RoamFT/control.PSK
@@ -23,7 +23,7 @@
         psk='chromeos',
         wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2,
         wpa2_ciphers=[xmlrpc_security_types.WPAConfig.CIPHER_CCMP],
-        use_ft=True)
+        ft_mode=xmlrpc_security_types.WPAConfig.FT_MODE_PURE)
     host = hosts.create_host(machine)
     job.run_test('network_WiFi_RoamFT',
                  tag=NAME.split('.')[1],
diff --git a/server/site_tests/network_WiFi_RoamFT/control.mixedEAP b/server/site_tests/network_WiFi_RoamFT/control.mixedEAP
new file mode 100644
index 0000000..90eaf67
--- /dev/null
+++ b/server/site_tests/network_WiFi_RoamFT/control.mixedEAP
@@ -0,0 +1,41 @@
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = 'matthewmwang'
+TIME = 'SHORT'
+NAME = 'network_WiFi_RoamFT.mixedEAP'
+TEST_TYPE = 'Server'
+ATTRIBUTES = ('suite:wifi_matfunc, suite:wifi_matfunc_noservo')
+DEPENDENCIES = 'wificell'
+
+DOC = """
+This test attempts to verify that we can connect to and roam to and from APs
+that support FT mixed mode for EAP in full view of the DUT. This ensures that
+devices that don't support FT are still compatible with APs that do. Fast BSS
+Transition is part of the 802.11r protocol, which describes a procedure for fast
+roaming from one AP to another within an SSID.
+"""
+
+from autotest_lib.client.common_lib.cros import site_eap_certs
+from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
+
+def run(machine):
+    ft_eap_config = xmlrpc_security_types.WPAEAPConfig(
+        server_ca_cert=site_eap_certs.ca_cert_1,
+        server_cert=site_eap_certs.server_cert_1,
+        server_key=site_eap_certs.server_private_key_1,
+        client_ca_cert=site_eap_certs.ca_cert_1,
+        client_cert=site_eap_certs.client_cert_1,
+        client_key=site_eap_certs.client_private_key_1,
+        wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2,
+        ft_mode=xmlrpc_security_types.WPAConfig.FT_MODE_MIXED)
+    host = hosts.create_host(machine)
+    job.run_test('network_WiFi_RoamFT',
+                 tag=NAME.split('.')[1],
+                 host=host,
+                 raw_cmdline_args=args,
+                 additional_params=ft_eap_config)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_RoamFT/control.mixedPSK b/server/site_tests/network_WiFi_RoamFT/control.mixedPSK
new file mode 100644
index 0000000..3199078
--- /dev/null
+++ b/server/site_tests/network_WiFi_RoamFT/control.mixedPSK
@@ -0,0 +1,36 @@
+# Copyright 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = 'matthewmwang'
+TIME = 'SHORT'
+NAME = 'network_WiFi_RoamFT.mixedPSK'
+TEST_TYPE = 'Server'
+ATTRIBUTES = ('suite:wifi_matfunc, suite:wifi_matfunc_noservo')
+DEPENDENCIES = 'wificell'
+
+DOC = """
+This test attempts to verify that we can connect to and roam to and from APs
+that support FT mixed mode for PSK in full view of the DUT. This ensures that
+devices that don't support FT are still compatible with APs that do. Fast BSS
+Transition is part of the 802.11r protocol, which describes a procedure for fast
+roaming from one AP to another within an SSID.
+"""
+
+from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
+
+def run(machine):
+    ft_psk_config = xmlrpc_security_types.WPAConfig(
+        psk='chromeos',
+        wpa_mode=xmlrpc_security_types.WPAConfig.MODE_PURE_WPA2,
+        wpa2_ciphers=[xmlrpc_security_types.WPAConfig.CIPHER_CCMP],
+        ft_mode=xmlrpc_security_types.WPAConfig.FT_MODE_MIXED)
+    host = hosts.create_host(machine)
+    job.run_test('network_WiFi_RoamFT',
+                 tag=NAME.split('.')[1],
+                 host=host,
+                 raw_cmdline_args=args,
+                 additional_params=ft_psk_config)
+
+
+parallel_simple(run, machines)
diff --git a/server/site_tests/network_WiFi_RoamFT/network_WiFi_RoamFT.py b/server/site_tests/network_WiFi_RoamFT/network_WiFi_RoamFT.py
index a311392..653dba4 100644
--- a/server/site_tests/network_WiFi_RoamFT/network_WiFi_RoamFT.py
+++ b/server/site_tests/network_WiFi_RoamFT/network_WiFi_RoamFT.py
@@ -8,6 +8,7 @@
 from autotest_lib.client.bin import utils
 from autotest_lib.client.common_lib.cros.network import iw_runner
 from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
+from autotest_lib.client.common_lib.cros.network import xmlrpc_security_types
 from autotest_lib.server.cros.network import wifi_cell_test_base
 from autotest_lib.server.cros.network import hostap_config
 
@@ -74,8 +75,10 @@
     def run_once(self,host):
         """Test body."""
 
-        self.context.client.require_capabilities(
-            [site_linux_system.LinuxSystem.CAPABILITY_SME])
+        if self._security_config.ft_mode == \
+                xmlrpc_security_types.WPAConfig.FT_MODE_PURE:
+            self.context.client.require_capabilities(
+                [site_linux_system.LinuxSystem.CAPABILITY_SME])
 
         mac0 = '02:00:00:00:03:00'
         mac1 = '02:00:00:00:04:00'