1#!/usr/bin/env python3.4 2# 3# Copyright 2016 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import pprint 18import random 19import time 20 21from acts import asserts 22from acts import base_test 23from acts import signals 24from acts.test_utils.wifi import wifi_test_utils as wutils 25 26WifiEnums = wutils.WifiEnums 27 28# EAP Macros 29EAP = WifiEnums.Eap 30EapPhase2 = WifiEnums.EapPhase2 31# Enterprise Config Macros 32Ent = WifiEnums.Enterprise 33 34class WifiEnterpriseTest(base_test.BaseTestClass): 35 36 def __init__(self, controllers): 37 base_test.BaseTestClass.__init__(self, controllers) 38 self.tests = ( 39 "test_eap_connect", 40 "test_eap_connect_negative", 41 ) 42 43 def setup_class(self): 44 self.dut = self.android_devices[0] 45 wutils.wifi_test_device_init(self.dut) 46 required_userparam_names = ( 47 "ca_cert", 48 "client_cert", 49 "client_key", 50 "passpoint_ca_cert", 51 "passpoint_client_cert", 52 "passpoint_client_key", 53 "eap_identity", 54 "eap_password", 55 "invalid_ca_cert", 56 "invalid_client_cert", 57 "invalid_client_key", 58 "fqdn", 59 "provider_friendly_name", 60 "realm", 61 "ssid_peap0", 62 "ssid_peap1", 63 "ssid_tls", 64 "ssid_ttls", 65 "ssid_pwd", 66 "ssid_sim", 67 "ssid_aka", 68 "ssid_aka_prime", 69 "ssid_passpoint", 70 "device_password", 71 "ping_addr" 72 ) 73 self.unpack_userparams(required_userparam_names, 74 roaming_consortium_ids=None, 75 plmn=None) 76 # Default configs for EAP networks. 77 self.config_peap0 = { 78 Ent.EAP: EAP.PEAP, 79 Ent.CA_CERT: self.ca_cert, 80 Ent.IDENTITY: self.eap_identity, 81 Ent.PASSWORD: self.eap_password, 82 Ent.PHASE2: EapPhase2.MSCHAPV2, 83 WifiEnums.SSID_KEY: self.ssid_peap0 84 } 85 self.config_peap1 = dict(self.config_peap0) 86 self.config_peap1[WifiEnums.SSID_KEY] = self.ssid_peap1 87 self.config_tls = { 88 Ent.EAP: EAP.TLS, 89 Ent.CA_CERT: self.ca_cert, 90 WifiEnums.SSID_KEY: self.ssid_tls, 91 Ent.CLIENT_CERT: self.client_cert, 92 Ent.PRIVATE_KEY_ID: self.client_key, 93 Ent.IDENTITY: self.eap_identity, 94 } 95 self.config_ttls = { 96 Ent.EAP: EAP.TTLS, 97 Ent.CA_CERT: self.ca_cert, 98 Ent.IDENTITY: self.eap_identity, 99 Ent.PASSWORD: self.eap_password, 100 Ent.PHASE2: EapPhase2.MSCHAPV2, 101 WifiEnums.SSID_KEY: self.ssid_ttls 102 } 103 self.config_pwd = { 104 Ent.EAP: EAP.PWD, 105 Ent.IDENTITY: self.eap_identity, 106 Ent.PASSWORD: self.eap_password, 107 WifiEnums.SSID_KEY: self.ssid_pwd 108 } 109 self.config_sim = { 110 Ent.EAP: EAP.SIM, 111 WifiEnums.SSID_KEY: self.ssid_sim, 112 } 113 self.config_aka = { 114 Ent.EAP: EAP.AKA, 115 WifiEnums.SSID_KEY: self.ssid_aka, 116 } 117 self.config_aka_prime = { 118 Ent.EAP: EAP.AKA_PRIME, 119 WifiEnums.SSID_KEY: self.ssid_aka_prime, 120 } 121 122 # Base config for passpoint networks. 123 self.config_passpoint = { 124 Ent.FQDN: self.fqdn, 125 Ent.FRIENDLY_NAME: self.provider_friendly_name, 126 Ent.REALM: self.realm, 127 Ent.CA_CERT: self.passpoint_ca_cert 128 } 129 if self.plmn: 130 self.config_passpoint[Ent.PLMN] = self.plmn 131 if self.roaming_consortium_ids: 132 self.config_passpoint[Ent.ROAMING_IDS] = self.roaming_consortium_ids 133 134 # Default configs for passpoint networks. 135 self.config_passpoint_tls = dict(self.config_tls) 136 self.config_passpoint_tls.update(self.config_passpoint) 137 self.config_passpoint_tls[Ent.CLIENT_CERT] = self.passpoint_client_cert 138 self.config_passpoint_tls[Ent.PRIVATE_KEY_ID] = self.passpoint_client_key 139 del self.config_passpoint_tls[WifiEnums.SSID_KEY] 140 self.config_passpoint_ttls = dict(self.config_ttls) 141 self.config_passpoint_ttls.update(self.config_passpoint) 142 del self.config_passpoint_ttls[WifiEnums.SSID_KEY] 143 # Set screen lock password so ConfigStore is unlocked. 144 self.dut.droid.setDevicePassword(self.device_password) 145 146 def teardown_class(self): 147 wutils.reset_wifi(self.dut) 148 self.dut.droid.disableDevicePassword() 149 self.dut.ed.clear_all_events() 150 151 def setup_test(self): 152 self.dut.droid.wifiStartTrackingStateChange() 153 self.dut.droid.wakeLockAcquireBright() 154 self.dut.droid.wakeUpNow() 155 wutils.reset_wifi(self.dut) 156 self.dut.ed.clear_all_events() 157 158 def teardown_test(self): 159 self.dut.droid.wakeLockRelease() 160 self.dut.droid.goToSleepNow() 161 self.dut.droid.wifiStopTrackingStateChange() 162 163 def on_fail(self, test_name, begin_time): 164 self.dut.cat_adb_log(test_name, begin_time) 165 166 """Helper Functions""" 167 168 def eap_negative_connect_logic(self, config, ad): 169 """Tries to connect to an enterprise network with invalid credentials 170 and expect a failure. 171 172 Args: 173 config: A dict representing an invalid EAP credential. 174 175 Returns: 176 True if connection failed as expected, False otherwise. 177 """ 178 with asserts.assert_raises(signals.TestFailure, extras=config): 179 verdict = wutils.eap_connect(config, ad) 180 asserts.explicit_pass("Connection failed as expected.") 181 182 def expand_config_by_phase2(self, config): 183 """Take an enterprise config and generate a list of configs, each with 184 a different phase2 auth type. 185 186 Args: 187 config: A dict representing enterprise config. 188 189 Returns 190 A list of enterprise configs. 191 """ 192 results = [] 193 for phase2_type in EapPhase2: 194 # Skip a special case for passpoint TTLS. 195 if Ent.FQDN in config and phase2_type == EapPhase2.GTC: 196 continue 197 c = dict(config) 198 c[Ent.PHASE2] = phase2_type 199 results.append(c) 200 return results 201 202 def gen_eap_configs(self): 203 """Generates configurations for different EAP authentication types. 204 205 Returns: 206 A list of dicts each representing an EAP configuration. 207 """ 208 configs = [self.config_tls, 209 self.config_pwd, 210 self.config_sim, 211 self.config_aka, 212 self.config_aka_prime] 213 configs += wutils.expand_enterprise_config_by_phase2(self.config_ttls) 214 configs += wutils.expand_enterprise_config_by_phase2(self.config_peap0) 215 configs += wutils.expand_enterprise_config_by_phase2(self.config_peap1) 216 return configs 217 218 def gen_passpoint_configs(self): 219 """Generates passpoint configurations for different EAP authentication 220 types. 221 222 Returns: 223 A list of dicts each representing an EAP configuration for 224 passpoint networks. 225 """ 226 configs = [self.config_passpoint_tls] 227 configs += wutils.expand_enterprise_config_by_phase2(self.config_passpoint_ttls) 228 return configs 229 230 def gen_negative_configs(self, configs, neg_params): 231 """Generic function used to generate negative configs. 232 233 For all the valid configurations, if a param in the neg_params also 234 exists in a config, a copy of the config is made with an invalid value 235 of the param. 236 237 Args: 238 configs: A list of valid configurations. 239 neg_params: A dict that has all the invalid values. 240 241 Returns: 242 A list of invalid configurations generated based on the valid 243 configurations. Each invalid configuration has a different invalid 244 field. 245 """ 246 results = [] 247 for c in configs: 248 for k, v in neg_params.items(): 249 # Skip negative test for TLS's identity field since it's not 250 # used for auth. 251 if c[Ent.EAP] == EAP.TLS and k == Ent.IDENTITY: 252 continue 253 if k in c: 254 nc = dict(c) 255 nc[k] = v 256 nc["invalid_field"] = k 257 results.append(nc) 258 return results 259 260 def gen_negative_eap_configs(self): 261 """Generates invalid configurations for different EAP authentication 262 types. 263 264 For all the valid EAP configurations, if a param that is part of the 265 authentication info exists in a config, a copy of the config is made 266 with an invalid value of the param. 267 268 Returns: 269 A list of dicts each representing an invalid EAP configuration. 270 """ 271 neg_params = { 272 Ent.CLIENT_CERT: self.invalid_client_cert, 273 Ent.CA_CERT: self.invalid_ca_cert, 274 Ent.PRIVATE_KEY_ID: self.invalid_client_key, 275 Ent.IDENTITY: "fake_identity", 276 Ent.PASSWORD: "wrong_password" 277 } 278 configs = self.gen_eap_configs() 279 return self.gen_negative_configs(configs, neg_params) 280 281 def gen_negative_passpoint_configs(self): 282 """Generates invalid configurations for different EAP authentication 283 types with passpoint support. 284 285 Returns: 286 A list of dicts each representing an invalid EAP configuration 287 with passpoint fields. 288 """ 289 neg_params = { 290 Ent.CLIENT_CERT: self.invalid_client_cert, 291 Ent.CA_CERT: self.invalid_ca_cert, 292 Ent.PRIVATE_KEY_ID: self.invalid_client_key, 293 Ent.IDENTITY: "fake_identity", 294 Ent.PASSWORD: "wrong_password", 295 Ent.FQDN: "fake_fqdn", 296 Ent.REALM: "where_no_one_has_gone_before", 297 Ent.PLMN: "fake_plmn", 298 Ent.ROAMING_IDS: [1234567890, 9876543210] 299 } 300 configs = self.gen_passpoint_configs() 301 return self.gen_negative_configs(configs, neg_params) 302 303 def gen_eap_test_name(self, config, ad): 304 """Generates a test case name based on an EAP configuration. 305 306 Args: 307 config: A dict representing an EAP credential. 308 ad: Discarded. This is here because name function signature needs 309 to be consistent with logic function signature for generated 310 test cases. 311 312 Returns: 313 A string representing the name of a generated EAP test case. 314 """ 315 eap_name = config[Ent.EAP].name 316 if "peap0" in config[WifiEnums.SSID_KEY].lower(): 317 eap_name = "PEAP0" 318 if "peap1" in config[WifiEnums.SSID_KEY].lower(): 319 eap_name = "PEAP1" 320 name = "test_connect-%s" % eap_name 321 if Ent.PHASE2 in config: 322 name += "-{}".format(config[Ent.PHASE2].name) 323 return name 324 325 def gen_passpoint_test_name(self, config, ad): 326 """Generates a test case name based on an EAP passpoint configuration. 327 328 Args: 329 config: A dict representing an EAP passpoint credential. 330 ad: Discarded. This is here because name function signature needs 331 to be consistent with logic function signature for generated 332 test cases. 333 334 Returns: 335 A string representing the name of a generated EAP passpoint connect 336 test case. 337 """ 338 name = self.gen_eap_test_name(config, ad) 339 name = name.replace("connect", "passpoint_connect") 340 return name 341 342 """Tests""" 343 @signals.generated_test 344 def test_eap_connect(self): 345 """Test connecting to enterprise networks of different authentication 346 types. 347 348 The authentication types tested are: 349 EAP-TLS 350 EAP-PEAP with different phase2 types. 351 EAP-TTLS with different phase2 types. 352 353 Procedures: 354 For each enterprise wifi network 355 1. Connect to the network. 356 2. Send a GET request to a website and check response. 357 358 Expect: 359 Successful connection and Internet access through the enterprise 360 networks. 361 """ 362 eap_configs = self.gen_eap_configs() 363 self.log.info("Testing %d different configs." % len(eap_configs)) 364 random.shuffle(eap_configs) 365 failed = self.run_generated_testcases( 366 wutils.eap_connect, 367 eap_configs, 368 args=(self.dut,), 369 name_func=self.gen_eap_test_name) 370 msg = ("The following configs failed EAP connect test: %s" % 371 pprint.pformat(failed)) 372 asserts.assert_equal(len(failed), 0, msg) 373 374 @signals.generated_test 375 def test_eap_connect_negative(self): 376 """Test connecting to enterprise networks. 377 378 Procedures: 379 For each enterprise wifi network 380 1. Connect to the network with invalid credentials. 381 382 Expect: 383 Fail to establish connection. 384 """ 385 neg_eap_configs = self.gen_negative_eap_configs() 386 self.log.info("Testing %d different configs." % len(neg_eap_configs)) 387 random.shuffle(neg_eap_configs) 388 def name_gen(config, ad): 389 name = self.gen_eap_test_name(config, ad) 390 name += "-with_wrong-{}".format(config["invalid_field"]) 391 return name 392 failed = self.run_generated_testcases( 393 self.eap_negative_connect_logic, 394 neg_eap_configs, 395 args=(self.dut,), 396 name_func=name_gen) 397 msg = ("The following configs failed negative EAP connect test: %s" % 398 pprint.pformat(failed)) 399 asserts.assert_equal(len(failed), 0, msg) 400 401 @signals.generated_test 402 def test_passpoint_connect(self): 403 """Test connecting to enterprise networks of different authentication 404 types with passpoint support. 405 406 The authentication types tested are: 407 EAP-TLS 408 EAP-TTLS with MSCHAPV2 as phase2. 409 410 Procedures: 411 For each enterprise wifi network 412 1. Connect to the network. 413 2. Send a GET request to a website and check response. 414 415 Expect: 416 Successful connection and Internet access through the enterprise 417 networks with passpoint support. 418 """ 419 asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(), 420 "Passpoint is not supported on device %s" % self.dut.model) 421 passpoint_configs = self.gen_passpoint_configs() 422 self.log.info("Testing %d different configs." % len(passpoint_configs)) 423 random.shuffle(passpoint_configs) 424 failed = self.run_generated_testcases( 425 wutils.eap_connect, 426 passpoint_configs, 427 args=(self.dut,), 428 name_func=self.gen_passpoint_test_name) 429 msg = ("The following configs failed passpoint connect test: %s" % 430 pprint.pformat(failed)) 431 asserts.assert_equal(len(failed), 0, msg) 432 433 @signals.generated_test 434 def test_passpoint_connect_negative(self): 435 """Test connecting to enterprise networks. 436 437 Procedures: 438 For each enterprise wifi network 439 1. Connect to the network with invalid credentials. 440 441 Expect: 442 Fail to establish connection. 443 """ 444 asserts.skip_if(not self.dut.droid.wifiIsPasspointSupported(), 445 "Passpoint is not supported on device %s" % self.dut.model) 446 neg_passpoint_configs = self.gen_negative_passpoint_configs() 447 self.log.info("Testing %d different configs." % len(neg_passpoint_configs)) 448 random.shuffle(neg_passpoint_configs) 449 def name_gen(config, ad): 450 name = self.gen_passpoint_test_name(config, ad) 451 name += "-with_wrong-{}".format(config["invalid_field"]) 452 return name 453 failed = self.run_generated_testcases( 454 self.eap_negative_connect_logic, 455 neg_passpoint_configs, 456 args=(self.dut,), 457 name_func=name_gen) 458 msg = ("The following configs failed negative passpoint connect test: " 459 "%s") % pprint.pformat(failed) 460 asserts.assert_equal(len(failed), 0, msg) 461