1# Lint as: python2, python3 2# Copyright (c) 2013 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6from __future__ import absolute_import 7from __future__ import division 8from __future__ import print_function 9 10import logging 11import os 12import random 13import six 14from six.moves import map 15from six.moves import range 16import stat 17import string 18import sys 19import tempfile 20 21from autotest_lib.client.common_lib import error 22from autotest_lib.client.common_lib.cros import xmlrpc_types 23 24 25def deserialize(serialized): 26 """Deserialize a SecurityConfig. 27 28 @param serialized dict representing a serialized SecurityConfig. 29 @return a SecurityConfig object built from |serialized|. 30 31 """ 32 return xmlrpc_types.deserialize(serialized, module=sys.modules[__name__]) 33 34 35class SecurityConfig(xmlrpc_types.XmlRpcStruct): 36 """Abstracts the security configuration for a WiFi network. 37 38 This bundle of credentials can be passed to both HostapConfig and 39 AssociationParameters so that both shill and hostapd can set up and connect 40 to an encrypted WiFi network. By default, we'll assume we're connecting 41 to an open network. 42 43 """ 44 SERVICE_PROPERTY_PASSPHRASE = 'Passphrase' 45 46 def __init__(self, security='none'): 47 super(SecurityConfig, self).__init__() 48 self.security = security 49 50 51 def get_hostapd_config(self): 52 """@return dict fragment of hostapd configuration for security.""" 53 return {} 54 55 56 def get_shill_service_properties(self): 57 """@return dict of shill service properties.""" 58 return {} 59 60 61 def get_wpa_cli_properties(self): 62 """@return dict values to be set with wpa_cli set_network.""" 63 return {'key_mgmt': 'NONE'} 64 65 66 def install_router_credentials(self, host, install_dir): 67 """Install the necessary credentials on the router. 68 69 @param host host object representing the router. 70 @param install_dir the directory on host to install the files. 71 72 """ 73 pass # Many authentication methods have no special router credentials. 74 75 76 def install_client_credentials(self, tpm_store): 77 """Install credentials on the local host (hopefully a DUT). 78 79 Only call this if we're running on a DUT in a WiFi test. This 80 method can do things like install credentials into the TPM. 81 82 @param tpm_store TPMStore object representing the TPM on our DUT. 83 84 """ 85 pass # Many authentication methods have no special client credentials. 86 87 88 def __repr__(self): 89 return '%s(%s)' % (self.__class__.__name__, ', '.join( 90 ['%s=%r' % item for item in six.iteritems(vars(self))])) 91 92 93class WEPConfig(SecurityConfig): 94 """Abstracts security configuration for a WiFi network using static WEP.""" 95 # Open system authentication means that we don't do a 4 way AUTH handshake, 96 # and simply start using the WEP keys after association finishes. 97 AUTH_ALGORITHM_OPEN = 1 98 # This refers to a mode where the AP sends a plaintext challenge and the 99 # client sends back the challenge encrypted with the WEP key as part of a 4 100 # part auth handshake. 101 AUTH_ALGORITHM_SHARED = 2 102 AUTH_ALGORITHM_DEFAULT = AUTH_ALGORITHM_OPEN 103 104 @staticmethod 105 def _format_key(key, ascii_key_formatter): 106 """Returns a key formatted to for its appropriate consumer. 107 108 Both hostapd and wpa_cli want their ASCII encoded WEP keys formatted 109 in a particular way. Hex string on the other hand can be given raw. 110 Other key formats aren't even accepted, and this method will raise 111 and exception if it sees such a key. 112 113 @param key string a 40/104 bit WEP key. 114 @param ascii_key_formatter converter function that escapes a WEP 115 string-encoded passphrase. This conversion varies in format 116 depending on the consumer. 117 @return string corrected formatted WEP key. 118 119 """ 120 if len(key) in (5, 13): 121 # These are 'ASCII' strings, or at least N-byte strings 122 # of the right size. 123 return ascii_key_formatter(key) 124 125 if len(key) in (10, 26): 126 # These are hex encoded byte strings. 127 return key 128 129 raise error.TestFail('Invalid WEP key: %r' % key) 130 131 132 def __init__(self, wep_keys, wep_default_key=0, 133 auth_algorithm=AUTH_ALGORITHM_DEFAULT): 134 """Construct a WEPConfig object. 135 136 @param wep_keys list of string WEP keys. 137 @param wep_default_key int 0 based index into |wep_keys| for the default 138 key. 139 @param auth_algorithm int bitfield of AUTH_ALGORITHM_* defined above. 140 141 """ 142 super(WEPConfig, self).__init__(security='wep') 143 self.wep_keys = wep_keys 144 self.wep_default_key = wep_default_key 145 self.auth_algorithm = auth_algorithm 146 if self.auth_algorithm & ~(self.AUTH_ALGORITHM_OPEN | 147 self.AUTH_ALGORITHM_SHARED): 148 raise error.TestFail('Invalid authentication mode specified (%d).' % 149 self.auth_algorithm) 150 151 if self.wep_keys and len(self.wep_keys) > 4: 152 raise error.TestFail('More than 4 WEP keys specified (%d).' % 153 len(self.wep_keys)) 154 155 156 def get_hostapd_config(self): 157 """@return dict fragment of hostapd configuration for security.""" 158 ret = {} 159 quote = lambda x: '"%s"' % x 160 for idx,key in enumerate(self.wep_keys): 161 ret['wep_key%d' % idx] = self._format_key(key, quote) 162 ret['wep_default_key'] = self.wep_default_key 163 ret['auth_algs'] = self.auth_algorithm 164 return ret 165 166 167 def get_shill_service_properties(self): 168 """@return dict of shill service properties.""" 169 return {self.SERVICE_PROPERTY_PASSPHRASE: '%d:%s' % ( 170 self.wep_default_key, 171 self.wep_keys[self.wep_default_key])} 172 173 174 def get_wpa_cli_properties(self): 175 properties = super(WEPConfig, self).get_wpa_cli_properties() 176 quote = lambda x: '\\"%s\\"' % x 177 for idx, key in enumerate(self.wep_keys): 178 properties['wep_key%d' % idx] = self._format_key(key, quote) 179 properties['wep_tx_keyidx'] = self.wep_default_key 180 if self.auth_algorithm == self.AUTH_ALGORITHM_SHARED: 181 properties['auth_alg'] = 'SHARED' 182 return properties 183 184 185class WPAConfig(SecurityConfig): 186 """Abstracts security configuration for a WPA encrypted WiFi network.""" 187 188 # We have the option of turning on combinations of WPA, WPA2, or WPA3 via a 189 # bitfield. 190 MODE_PURE_WPA = 1 191 MODE_PURE_WPA2 = 2 192 MODE_PURE_WPA3 = 4 193 MODE_MIXED_WPA = MODE_PURE_WPA | MODE_PURE_WPA2 194 MODE_MIXED_WPA3 = MODE_PURE_WPA2 | MODE_PURE_WPA3 195 MODE_DEFAULT = MODE_MIXED_WPA 196 197 # WPA2 mandates the use of AES in CCMP mode. 198 # WPA allows the use of 'ordinary' AES, but mandates support for TKIP. 199 # The protocol however seems to indicate that you just list a bunch of 200 # different ciphers that you support and we'll start speaking one. 201 CIPHER_CCMP = 'CCMP' 202 CIPHER_TKIP = 'TKIP' 203 204 # Fast Transition (FT) mode for WPA network. 205 FT_MODE_NONE = 1 206 FT_MODE_PURE = 2 207 FT_MODE_MIXED = FT_MODE_NONE | FT_MODE_PURE 208 FT_MODE_DEFAULT = FT_MODE_NONE 209 210 def __init__(self, psk='', wpa_mode=MODE_DEFAULT, wpa_ciphers=[], 211 wpa2_ciphers=[], wpa_ptk_rekey_period=None, 212 wpa_gtk_rekey_period=None, wpa_gmk_rekey_period=None, 213 use_strict_rekey=None, ft_mode=FT_MODE_NONE): 214 """Construct a WPAConfig. 215 216 @param psk string a passphrase (64 hex characters or an ASCII phrase up 217 to 63 characters long). 218 @param wpa_mode int one of MODE_* above. 219 @param wpa_ciphers list of ciphers to advertise in the WPA IE. 220 @param wpa2_ciphers list of ciphers to advertise in the WPA2 IE. 221 hostapd will fall back on WPA ciphers for WPA2 if this is 222 left unpopulated. 223 @param wpa_ptk_rekey_period int number of seconds between PTK rekeys. 224 @param wpa_gtk_rekey_period int number of second between GTK rekeys. 225 @param wpa_gmk_rekey_period int number of seconds between GMK rekeys. 226 The GMK is a key internal to hostapd used to generate GTK. 227 It is the 'main' key. 228 @param use_strict_rekey bool True iff hostapd should refresh the GTK 229 whenever any client leaves the group. 230 @param ft_mode int one of the FT_MODE_* in SecurityConfig. 231 232 """ 233 super(WPAConfig, self).__init__(security='psk') 234 self.psk = psk 235 self.wpa_mode = wpa_mode 236 self.wpa_ciphers = wpa_ciphers 237 self.wpa2_ciphers = wpa2_ciphers 238 self.wpa_ptk_rekey_period = wpa_ptk_rekey_period 239 self.wpa_gtk_rekey_period = wpa_gtk_rekey_period 240 self.wpa_gmk_rekey_period = wpa_gmk_rekey_period 241 self.use_strict_rekey = use_strict_rekey 242 self.ft_mode = ft_mode 243 if len(psk) > 64: 244 raise error.TestFail('WPA passphrases can be no longer than 63 ' 245 'characters (or 64 hex digits).') 246 247 if len(psk) == 64: 248 for c in psk: 249 if c not in '0123456789abcdefABCDEF': 250 raise error.TestFail('Invalid PMK: %r' % psk) 251 252 253 def get_hostapd_config(self): 254 """@return dict fragment of hostapd configuration for security.""" 255 mode = 0 256 # WPA2 and WPA3 are both RSN, so hostapd lumps these together for wpa=. 257 if self.wpa_mode & (self.MODE_PURE_WPA2 | self.MODE_PURE_WPA3): 258 mode |= self.MODE_PURE_WPA2 259 # WPA. 260 if self.wpa_mode & self.MODE_PURE_WPA: 261 mode |= self.MODE_PURE_WPA 262 if not mode: 263 raise error.TestFail('Cannot configure WPA unless we know which ' 264 'mode to use.') 265 266 if mode & self.MODE_PURE_WPA and not self.wpa_ciphers: 267 raise error.TestFail('Cannot configure WPA unless we know which ' 268 'ciphers to use.') 269 270 if not self.wpa_ciphers and not self.wpa2_ciphers: 271 raise error.TestFail('Cannot configure WPA2 unless we have some ' 272 'ciphers.') 273 274 key_mgmt = [] 275 if self.ft_mode & self.FT_MODE_NONE: 276 if self.wpa_mode & self.MODE_MIXED_WPA: 277 key_mgmt += ['WPA-PSK'] 278 if self.wpa_mode & self.MODE_PURE_WPA3: 279 key_mgmt += ['SAE'] 280 if self.ft_mode & self.FT_MODE_PURE: 281 if self.wpa_mode & self.MODE_MIXED_WPA: 282 key_mgmt += ['FT-PSK'] 283 if self.wpa_mode & self.MODE_PURE_WPA3: 284 key_mgmt += ['FT-SAE'] 285 286 ret = {'wpa': mode, 'wpa_key_mgmt': ' '.join(key_mgmt)} 287 288 if len(self.psk) == 64: 289 ret['wpa_psk'] = self.psk 290 else: 291 ret['wpa_passphrase'] = self.psk 292 293 if self.wpa_ciphers: 294 ret['wpa_pairwise'] = ' '.join(self.wpa_ciphers) 295 if self.wpa2_ciphers: 296 ret['rsn_pairwise'] = ' '.join(self.wpa2_ciphers) 297 if self.wpa_ptk_rekey_period: 298 ret['wpa_ptk_rekey'] = self.wpa_ptk_rekey_period 299 if self.wpa_gtk_rekey_period: 300 ret['wpa_group_rekey'] = self.wpa_gtk_rekey_period 301 if self.wpa_gmk_rekey_period: 302 ret['wpa_gmk_rekey'] = self.wpa_gmk_rekey_period 303 if self.use_strict_rekey: 304 ret['wpa_strict_rekey'] = 1 305 return ret 306 307 308 def get_shill_service_properties(self): 309 """@return dict of shill service properties.""" 310 ret = {self.SERVICE_PROPERTY_PASSPHRASE: self.psk} 311 return ret 312 313 314 def get_wpa_cli_properties(self): 315 properties = super(WPAConfig, self).get_wpa_cli_properties() 316 # TODO(wiley) This probably doesn't work for raw PMK. 317 protos = [] 318 if self.wpa_mode & self.MODE_PURE_WPA: 319 protos.append('WPA') 320 if self.wpa_mode & (self.MODE_PURE_WPA2 | self.MODE_PURE_WPA3): 321 protos.append('RSN') 322 key_mgmt = [] 323 if self.ft_mode & self.FT_MODE_NONE: 324 if self.wpa_mode & self.MODE_MIXED_WPA: 325 key_mgmt += ['WPA-PSK'] 326 if self.wpa_mode & self.MODE_PURE_WPA3: 327 key_mgmt += ['SAE'] 328 if self.ft_mode & self.FT_MODE_PURE: 329 if self.wpa_mode & self.MODE_MIXED_WPA: 330 key_mgmt += ['FT-PSK'] 331 if self.wpa_mode & self.MODE_PURE_WPA3: 332 key_mgmt += ['FT-SAE'] 333 properties.update({ 334 'psk': '\\"%s\\"' % self.psk, 335 'key_mgmt': ' '.join(key_mgmt), 336 'proto': ' '.join(protos) 337 }) 338 return properties 339 340 341class EAPConfig(SecurityConfig): 342 """Abstract superclass that implements certificate/key installation.""" 343 344 DEFAULT_EAP_USERS = '* TLS' 345 DEFAULT_EAP_IDENTITY = 'chromeos' 346 347 SERVICE_PROPERTY_CA_CERT_PEM = 'EAP.CACertPEM' 348 SERVICE_PROPERTY_CLIENT_CERT_ID = 'EAP.CertID' 349 SERVICE_PROPERTY_EAP_IDENTITY = 'EAP.Identity' 350 SERVICE_PROPERTY_EAP_KEY_MGMT = 'EAP.KeyMgmt' 351 SERVICE_PROPERTY_EAP_PASSWORD = 'EAP.Password' 352 SERVICE_PROPERTY_EAP_PIN = 'EAP.PIN' 353 SERVICE_PROPERTY_INNER_EAP= 'EAP.InnerEAP' 354 SERVICE_PROPERTY_PRIVATE_KEY_ID = 'EAP.KeyID' 355 SERVICE_PROPERTY_USE_SYSTEM_CAS = 'EAP.UseSystemCAs' 356 SERVICE_PROPERTY_ALTSUBJECT_MATCH = 'EAP.SubjectAlternativeNameMatch' 357 358 last_tpm_id = 8800 359 360 # Credential file prefixes. 361 SERVER_CA_CERT_FILE_PREFIX = 'hostapd_ca_cert_file.' 362 SERVER_CERT_FILE_PREFIX = 'hostapd_cert_file.' 363 SERVER_KEY_FILE_PREFIX = 'hostapd_key_file.' 364 SERVER_EAP_USER_FILE_PREFIX = 'hostapd_eap_user_file.' 365 366 @staticmethod 367 def reserve_TPM_id(): 368 """@return session unique TPM identifier.""" 369 ret = str(EAPConfig.last_tpm_id) 370 EAPConfig.last_tpm_id += 1 371 return ret 372 373 374 def __init__(self, security='802_1x', file_suffix=None, use_system_cas=None, 375 server_ca_cert=None, server_cert=None, server_key=None, 376 server_eap_users=None, 377 client_ca_cert=None, client_cert=None, client_key=None, 378 client_cert_id=None, client_key_id=None, 379 eap_identity=None, ft_mode=WPAConfig.FT_MODE_DEFAULT, 380 altsubject_match=None): 381 """Construct an EAPConfig. 382 383 @param file_suffix string unique file suffix on DUT. 384 @param use_system_cas False iff we should ignore server certificates. 385 @param server_ca_cert string PEM encoded CA certificate for the server. 386 @param server_cert string PEM encoded identity certificate for server. 387 @param server_key string PEM encoded private key for server. 388 @param server_eap_users string contents of EAP user file. 389 @param client_ca_cert string PEM encoded CA certificate for client. 390 @param client_cert string PEM encoded identity certificate for client. 391 @param client_key string PEM encoded private key for client. 392 @param client_cert_id string identifier for client certificate in TPM. 393 @param client_key_id string identifier for client private key in TPM. 394 @param eap_identity string user to authenticate as during EAP. 395 @param ft_mode int one of the FT_MODE_* in SecurityConfig. 396 @param altsubject_match list of strings in the format of shill 397 EAP.SubjectAlternativeNameMatch property. 398 399 """ 400 super(EAPConfig, self).__init__(security=security) 401 self.use_system_cas = use_system_cas 402 self.server_ca_cert = server_ca_cert 403 self.server_cert = server_cert 404 self.server_key = server_key 405 self.server_eap_users = server_eap_users or self.DEFAULT_EAP_USERS 406 self.client_ca_cert = client_ca_cert 407 self.client_cert = client_cert 408 self.client_key = client_key 409 if file_suffix is None: 410 suffix_letters = string.ascii_lowercase + string.digits 411 file_suffix = ''.join(random.choice(suffix_letters) 412 for x in range(10)) 413 logging.debug('Choosing unique file_suffix %s.', file_suffix) 414 # The key paths will be determined in install_router_credentials. 415 self.server_ca_cert_file = None 416 self.server_cert_file = None 417 self.server_key_file = None 418 self.server_eap_user_file = None 419 # While these paths won't make it across the network, the suffix will. 420 self.file_suffix = file_suffix 421 self.client_cert_id = client_cert_id or self.reserve_TPM_id() 422 self.client_key_id = client_key_id or self.reserve_TPM_id() 423 # This gets filled in at install time. 424 self.pin = None 425 # The slot where the certificate/key are installed in the TPM. 426 self.client_cert_slot_id = None 427 self.client_key_slot_id = None 428 self.eap_identity = eap_identity or self.DEFAULT_EAP_IDENTITY 429 self.ft_mode = ft_mode 430 self.altsubject_match = altsubject_match 431 432 433 def install_router_credentials(self, host, install_dir): 434 """Install the necessary credentials on the router. 435 436 @param host host object representing the router. 437 438 """ 439 self.server_ca_cert_file = os.path.join( 440 install_dir, self.SERVER_CA_CERT_FILE_PREFIX + self.file_suffix) 441 self.server_cert_file = os.path.join( 442 install_dir, self.SERVER_CERT_FILE_PREFIX + self.file_suffix) 443 self.server_key_file = os.path.join( 444 install_dir, self.SERVER_KEY_FILE_PREFIX + self.file_suffix) 445 self.server_eap_user_file = os.path.join( 446 install_dir, self.SERVER_EAP_USER_FILE_PREFIX + self.file_suffix) 447 448 files = [(self.server_ca_cert, self.server_ca_cert_file), 449 (self.server_cert, self.server_cert_file), 450 (self.server_key, self.server_key_file), 451 (self.server_eap_users, self.server_eap_user_file)] 452 for content, path in files: 453 # If we omit a parameter, just omit copying a file over. 454 if content is None: 455 continue 456 # Write the contents to local disk first so we can use the easy 457 # built in mechanism to do this. 458 with tempfile.NamedTemporaryFile() as f: 459 f.write(content) 460 f.flush() 461 os.chmod(f.name, stat.S_IRUSR | stat.S_IWUSR | 462 stat.S_IRGRP | stat.S_IWGRP | 463 stat.S_IROTH | stat.S_IWOTH) 464 host.send_file(f.name, path, delete_dest=True) 465 466 467 def install_client_credentials(self, tpm_store): 468 """Install credentials on the local host (hopefully a DUT). 469 470 Only call this if we're running on a DUT in a WiFi test. This 471 method can do things like install credentials into the TPM. 472 473 @param tpm_store TPMStore object representing the TPM on our DUT. 474 475 """ 476 if self.client_cert: 477 tpm_store.install_certificate(self.client_cert, self.client_cert_id) 478 self.client_cert_slot_id = tpm_store.SLOT_ID 479 self.pin = tpm_store.PIN 480 if self.client_key: 481 tpm_store.install_private_key(self.client_key, self.client_key_id) 482 self.client_key_slot_id = tpm_store.SLOT_ID 483 self.pin = tpm_store.PIN 484 485 486 def get_shill_service_properties(self): 487 """@return dict of shill service properties.""" 488 ret = {self.SERVICE_PROPERTY_EAP_IDENTITY: self.eap_identity} 489 if self.pin: 490 ret[self.SERVICE_PROPERTY_EAP_PIN] = self.pin 491 if self.client_ca_cert: 492 # Technically, we could accept a list of certificates here, but we 493 # have no such tests. 494 ret[self.SERVICE_PROPERTY_CA_CERT_PEM] = [self.client_ca_cert] 495 if self.client_cert: 496 ret[self.SERVICE_PROPERTY_CLIENT_CERT_ID] = ( 497 '%s:%s' % (self.client_cert_slot_id, self.client_cert_id)) 498 if self.client_key: 499 ret[self.SERVICE_PROPERTY_PRIVATE_KEY_ID] = ( 500 '%s:%s' % (self.client_key_slot_id, self.client_key_id)) 501 if self.use_system_cas is not None: 502 ret[self.SERVICE_PROPERTY_USE_SYSTEM_CAS] = self.use_system_cas 503 if self.altsubject_match: 504 ret[self.SERVICE_PROPERTY_ALTSUBJECT_MATCH] = self.altsubject_match 505 return ret 506 507 508 def get_hostapd_config(self): 509 """@return dict fragment of hostapd configuration for security.""" 510 return {'ieee8021x': 1, # Enable 802.1x support. 511 'eap_server' : 1, # Do EAP inside hostapd to avoid RADIUS. 512 'ca_cert': self.server_ca_cert_file, 513 'server_cert': self.server_cert_file, 514 'private_key': self.server_key_file, 515 'eap_user_file': self.server_eap_user_file} 516 517 518class DynamicWEPConfig(EAPConfig): 519 """Configuration settings bundle for dynamic WEP. 520 521 This is a WEP encrypted connection where the keys are negotiated after the 522 client authenticates via 802.1x. 523 524 """ 525 526 DEFAULT_REKEY_PERIOD = 20 527 528 529 def __init__(self, use_short_keys=False, 530 wep_rekey_period=DEFAULT_REKEY_PERIOD, 531 server_ca_cert=None, server_cert=None, server_key=None, 532 client_ca_cert=None, client_cert=None, client_key=None, 533 file_suffix=None, client_cert_id=None, client_key_id=None): 534 """Construct a DynamicWEPConfig. 535 536 @param use_short_keys bool force hostapd to use 40 bit WEP keys. 537 @param wep_rekey_period int number of second between rekeys. 538 @param server_ca_cert string PEM encoded CA certificate for the server. 539 @param server_cert string PEM encoded identity certificate for server. 540 @param server_key string PEM encoded private key for server. 541 @param client_ca_cert string PEM encoded CA certificate for client. 542 @param client_cert string PEM encoded identity certificate for client. 543 @param client_key string PEM encoded private key for client. 544 @param file_suffix string unique file suffix on DUT. 545 @param client_cert_id string identifier for client certificate in TPM. 546 @param client_key_id string identifier for client private key in TPM. 547 548 """ 549 super(DynamicWEPConfig, self).__init__( 550 security='wep', file_suffix=file_suffix, 551 server_ca_cert=server_ca_cert, server_cert=server_cert, 552 server_key=server_key, client_ca_cert=client_ca_cert, 553 client_cert=client_cert, client_key=client_key, 554 client_cert_id=client_cert_id, client_key_id=client_key_id) 555 self.use_short_keys = use_short_keys 556 self.wep_rekey_period = wep_rekey_period 557 558 559 def get_hostapd_config(self): 560 """@return dict fragment of hostapd configuration for security.""" 561 ret = super(DynamicWEPConfig, self).get_hostapd_config() 562 key_len = 13 # 128 bit WEP, 104 secret bits. 563 if self.use_short_keys: 564 key_len = 5 # 64 bit WEP, 40 bits of secret. 565 ret.update({'wep_key_len_broadcast': key_len, 566 'wep_key_len_unicast': key_len, 567 'wep_rekey_period': self.wep_rekey_period}) 568 return ret 569 570 571 def get_shill_service_properties(self): 572 """@return dict of shill service properties.""" 573 ret = super(DynamicWEPConfig, self).get_shill_service_properties() 574 ret.update({self.SERVICE_PROPERTY_EAP_KEY_MGMT: 'IEEE8021X'}) 575 return ret 576 577 578class WPAEAPConfig(EAPConfig): 579 """Security type to set up a WPA tunnel via EAP-TLS negotiation.""" 580 581 def __init__(self, file_suffix=None, use_system_cas=None, 582 server_ca_cert=None, server_cert=None, server_key=None, 583 client_ca_cert=None, client_cert=None, client_key=None, 584 client_cert_id=None, client_key_id=None, eap_identity=None, 585 server_eap_users=None, altsubject_match=None, 586 wpa_mode=WPAConfig.MODE_PURE_WPA, 587 ft_mode=WPAConfig.FT_MODE_DEFAULT): 588 """Construct a DynamicWEPConfig. 589 590 @param file_suffix string unique file suffix on DUT. 591 @param use_system_cas False iff we should ignore server certificates. 592 @param server_ca_cert string PEM encoded CA certificate for the server. 593 @param server_cert string PEM encoded identity certificate for server. 594 @param server_key string PEM encoded private key for server. 595 @param client_ca_cert string PEM encoded CA certificate for client. 596 @param client_cert string PEM encoded identity certificate for client. 597 @param client_key string PEM encoded private key for client. 598 @param client_cert_id string identifier for client certificate in TPM. 599 @param client_key_id string identifier for client private key in TPM. 600 @param eap_identity string user to authenticate as during EAP. 601 @param server_eap_users string contents of server EAP users file. 602 @param ft_mode int one of the FT_MODE_* in SecurityConfig 603 @param altsubject_match list of strings in the format of shill 604 EAP.SubjectAlternativeNameMatch property. 605 606 """ 607 super(WPAEAPConfig, self).__init__( 608 file_suffix=file_suffix, use_system_cas=use_system_cas, 609 server_ca_cert=server_ca_cert, server_cert=server_cert, 610 server_key=server_key, client_ca_cert=client_ca_cert, 611 client_cert=client_cert, client_key=client_key, 612 client_cert_id=client_cert_id, client_key_id=client_key_id, 613 eap_identity=eap_identity, server_eap_users=server_eap_users, 614 ft_mode=ft_mode, altsubject_match=altsubject_match) 615 self.wpa_mode = wpa_mode 616 617 618 def get_hostapd_config(self): 619 """@return dict fragment of hostapd configuration for security.""" 620 ret = super(WPAEAPConfig, self).get_hostapd_config() 621 # If we wanted to expand test coverage to WPA2/PEAP combinations 622 # or particular ciphers, we'd have to let people set these 623 # settings manually. But for now, do the simple thing. 624 ret.update({'wpa': self.wpa_mode, 625 'wpa_pairwise': WPAConfig.CIPHER_CCMP, 626 'wpa_key_mgmt':'WPA-EAP'}) 627 if self.ft_mode == WPAConfig.FT_MODE_PURE: 628 ret['wpa_key_mgmt'] = 'FT-EAP' 629 elif self.ft_mode == WPAConfig.FT_MODE_MIXED: 630 ret['wpa_key_mgmt'] = 'WPA-EAP FT-EAP' 631 return ret 632 633 634class Tunneled1xConfig(WPAEAPConfig): 635 """Security type to set up a TTLS/PEAP connection. 636 637 Both PEAP and TTLS are tunneled protocols which use EAP inside of a TLS 638 secured tunnel. The secured tunnel is a symmetric key encryption scheme 639 negotiated under the protection of a public key in the server certificate. 640 Thus, we'll see server credentials in the form of certificates, but client 641 credentials in the form of passwords and a CA Cert to root the trust chain. 642 643 """ 644 645 TTLS_PREFIX = 'TTLS-' 646 647 LAYER1_TYPE_PEAP = 'PEAP' 648 LAYER1_TYPE_TTLS = 'TTLS' 649 650 LAYER2_TYPE_GTC = 'GTC' 651 LAYER2_TYPE_MSCHAPV2 = 'MSCHAPV2' 652 LAYER2_TYPE_MD5 = 'MD5' 653 LAYER2_TYPE_TTLS_MSCHAPV2 = TTLS_PREFIX + 'MSCHAPV2' 654 LAYER2_TYPE_TTLS_MSCHAP = TTLS_PREFIX + 'MSCHAP' 655 LAYER2_TYPE_TTLS_PAP = TTLS_PREFIX + 'PAP' 656 657 def __init__(self, server_ca_cert, server_cert, server_key, 658 client_ca_cert, eap_identity, password, 659 outer_protocol=LAYER1_TYPE_PEAP, 660 inner_protocol=LAYER2_TYPE_MD5, 661 client_password=None, file_suffix=None, 662 altsubject_match=None): 663 self.password = password 664 if client_password is not None: 665 # Override the password used on the client. This lets us set 666 # bad passwords for testing. However, we use the real password 667 # below for the server config. 668 self.password = client_password 669 self.inner_protocol = inner_protocol 670 # hostapd wants these surrounded in double quotes. 671 quote = lambda x: '"' + x + '"' 672 eap_users = list(map(' '.join, [('*', outer_protocol), 673 (quote(eap_identity), inner_protocol, quote(password), '[2]')])) 674 super(Tunneled1xConfig, self).__init__( 675 server_ca_cert=server_ca_cert, 676 server_cert=server_cert, 677 server_key=server_key, 678 server_eap_users='\n'.join(eap_users), 679 client_ca_cert=client_ca_cert, 680 eap_identity=eap_identity, 681 file_suffix=file_suffix, 682 altsubject_match=altsubject_match) 683 684 685 def get_shill_service_properties(self): 686 """@return dict of shill service properties.""" 687 ret = super(Tunneled1xConfig, self).get_shill_service_properties() 688 ret.update({self.SERVICE_PROPERTY_EAP_PASSWORD: self.password}) 689 if self.inner_protocol.startswith(self.TTLS_PREFIX): 690 auth_str = 'auth=' + self.inner_protocol[len(self.TTLS_PREFIX):] 691 ret.update({self.SERVICE_PROPERTY_INNER_EAP: auth_str}) 692 return ret 693