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