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 325 last_tpm_id = 8800 326 327 328 @staticmethod 329 def reserve_TPM_id(): 330 """@return session unique TPM identifier.""" 331 ret = str(EAPConfig.last_tpm_id) 332 EAPConfig.last_tpm_id += 1 333 return ret 334 335 336 def __init__(self, security='802_1x', file_suffix=None, use_system_cas=None, 337 server_ca_cert=None, server_cert=None, server_key=None, 338 server_eap_users=None, 339 client_ca_cert=None, client_cert=None, client_key=None, 340 client_cert_id=None, client_key_id=None, 341 eap_identity=None, ft_mode=WPAConfig.FT_MODE_DEFAULT): 342 """Construct an EAPConfig. 343 344 @param file_suffix string unique file suffix on DUT. 345 @param use_system_cas False iff we should ignore server certificates. 346 @param server_ca_cert string PEM encoded CA certificate for the server. 347 @param server_cert string PEM encoded identity certificate for server. 348 @param server_key string PEM encoded private key for server. 349 @param server_eap_users string contents of EAP user file. 350 @param client_ca_cert string PEM encoded CA certificate for client. 351 @param client_cert string PEM encoded identity certificate for client. 352 @param client_key string PEM encoded private key for client. 353 @param client_cert_id string identifier for client certificate in TPM. 354 @param client_key_id string identifier for client private key in TPM. 355 @param eap_identity string user to authenticate as during EAP. 356 @param ft_mode int one of the FT_MODE_* in SecurityConfig. 357 358 """ 359 super(EAPConfig, self).__init__(security=security) 360 self.use_system_cas = use_system_cas 361 self.server_ca_cert = server_ca_cert 362 self.server_cert = server_cert 363 self.server_key = server_key 364 self.server_eap_users = server_eap_users or self.DEFAULT_EAP_USERS 365 self.client_ca_cert = client_ca_cert 366 self.client_cert = client_cert 367 self.client_key = client_key 368 if file_suffix is None: 369 suffix_letters = string.ascii_lowercase + string.digits 370 file_suffix = ''.join(random.choice(suffix_letters) 371 for x in range(10)) 372 logging.debug('Choosing unique file_suffix %s.', file_suffix) 373 self.server_ca_cert_file = '/tmp/hostapd_ca_cert_file.' + file_suffix 374 self.server_cert_file = '/tmp/hostapd_cert_file.' + file_suffix 375 self.server_key_file = '/tmp/hostapd_key_file.' + file_suffix 376 self.server_eap_user_file = '/tmp/hostapd_eap_user_file.' + file_suffix 377 # While these paths won't make it across the network, the suffix will. 378 self.file_suffix = file_suffix 379 self.client_cert_id = client_cert_id or self.reserve_TPM_id() 380 self.client_key_id = client_key_id or self.reserve_TPM_id() 381 # This gets filled in at install time. 382 self.pin = None 383 # The slot where the certificate/key are installed in the TPM. 384 self.client_cert_slot_id = None 385 self.client_key_slot_id = None 386 self.eap_identity = eap_identity or self.DEFAULT_EAP_IDENTITY 387 self.ft_mode = ft_mode 388 389 390 def install_router_credentials(self, host): 391 """Install the necessary credentials on the router. 392 393 @param host host object representing the router. 394 395 """ 396 files = [(self.server_ca_cert, self.server_ca_cert_file), 397 (self.server_cert, self.server_cert_file), 398 (self.server_key, self.server_key_file), 399 (self.server_eap_users, self.server_eap_user_file)] 400 for content, path in files: 401 # If we omit a parameter, just omit copying a file over. 402 if content is None: 403 continue 404 # Write the contents to local disk first so we can use the easy 405 # built in mechanism to do this. 406 with tempfile.NamedTemporaryFile() as f: 407 f.write(content) 408 f.flush() 409 os.chmod(f.name, stat.S_IRUSR | stat.S_IWUSR | 410 stat.S_IRGRP | stat.S_IWGRP | 411 stat.S_IROTH | stat.S_IWOTH) 412 host.send_file(f.name, path, delete_dest=True) 413 414 415 def install_client_credentials(self, tpm_store): 416 """Install credentials on the local host (hopefully a DUT). 417 418 Only call this if we're running on a DUT in a WiFi test. This 419 method can do things like install credentials into the TPM. 420 421 @param tpm_store TPMStore object representing the TPM on our DUT. 422 423 """ 424 if self.client_cert: 425 tpm_store.install_certificate(self.client_cert, self.client_cert_id) 426 self.client_cert_slot_id = tpm_store.SLOT_ID 427 self.pin = tpm_store.PIN 428 if self.client_key: 429 tpm_store.install_private_key(self.client_key, self.client_key_id) 430 self.client_key_slot_id = tpm_store.SLOT_ID 431 self.pin = tpm_store.PIN 432 433 434 def get_shill_service_properties(self): 435 """@return dict of shill service properties.""" 436 ret = {self.SERVICE_PROPERTY_EAP_IDENTITY: self.eap_identity} 437 if self.pin: 438 ret[self.SERVICE_PROPERTY_EAP_PIN] = self.pin 439 if self.client_ca_cert: 440 # Technically, we could accept a list of certificates here, but we 441 # have no such tests. 442 ret[self.SERVICE_PROPERTY_CA_CERT_PEM] = [self.client_ca_cert] 443 if self.client_cert: 444 ret[self.SERVICE_PROPERTY_CLIENT_CERT_ID] = ( 445 '%s:%s' % (self.client_cert_slot_id, self.client_cert_id)) 446 if self.client_key: 447 ret[self.SERVICE_PROPERTY_PRIVATE_KEY_ID] = ( 448 '%s:%s' % (self.client_key_slot_id, self.client_key_id)) 449 if self.use_system_cas is not None: 450 ret[self.SERVICE_PROPERTY_USE_SYSTEM_CAS] = self.use_system_cas 451 if self.ft_mode & WPAConfig.FT_MODE_PURE: 452 ret[self.SERVICE_PROPERTY_FT_ENABLED] = True 453 return ret 454 455 456 def get_hostapd_config(self): 457 """@return dict fragment of hostapd configuration for security.""" 458 return {'ieee8021x': 1, # Enable 802.1x support. 459 'eap_server' : 1, # Do EAP inside hostapd to avoid RADIUS. 460 'ca_cert': self.server_ca_cert_file, 461 'server_cert': self.server_cert_file, 462 'private_key': self.server_key_file, 463 'eap_user_file': self.server_eap_user_file} 464 465 466class DynamicWEPConfig(EAPConfig): 467 """Configuration settings bundle for dynamic WEP. 468 469 This is a WEP encrypted connection where the keys are negotiated after the 470 client authenticates via 802.1x. 471 472 """ 473 474 DEFAULT_REKEY_PERIOD = 20 475 476 477 def __init__(self, use_short_keys=False, 478 wep_rekey_period=DEFAULT_REKEY_PERIOD, 479 server_ca_cert=None, server_cert=None, server_key=None, 480 client_ca_cert=None, client_cert=None, client_key=None, 481 file_suffix=None, client_cert_id=None, client_key_id=None): 482 """Construct a DynamicWEPConfig. 483 484 @param use_short_keys bool force hostapd to use 40 bit WEP keys. 485 @param wep_rekey_period int number of second between rekeys. 486 @param server_ca_cert string PEM encoded CA certificate for the server. 487 @param server_cert string PEM encoded identity certificate for server. 488 @param server_key string PEM encoded private key for server. 489 @param client_ca_cert string PEM encoded CA certificate for client. 490 @param client_cert string PEM encoded identity certificate for client. 491 @param client_key string PEM encoded private key for client. 492 @param file_suffix string unique file suffix on DUT. 493 @param client_cert_id string identifier for client certificate in TPM. 494 @param client_key_id string identifier for client private key in TPM. 495 496 """ 497 super(DynamicWEPConfig, self).__init__( 498 security='wep', file_suffix=file_suffix, 499 server_ca_cert=server_ca_cert, server_cert=server_cert, 500 server_key=server_key, client_ca_cert=client_ca_cert, 501 client_cert=client_cert, client_key=client_key, 502 client_cert_id=client_cert_id, client_key_id=client_key_id) 503 self.use_short_keys = use_short_keys 504 self.wep_rekey_period = wep_rekey_period 505 506 507 def get_hostapd_config(self): 508 """@return dict fragment of hostapd configuration for security.""" 509 ret = super(DynamicWEPConfig, self).get_hostapd_config() 510 key_len = 13 # 128 bit WEP, 104 secret bits. 511 if self.use_short_keys: 512 key_len = 5 # 64 bit WEP, 40 bits of secret. 513 ret.update({'wep_key_len_broadcast': key_len, 514 'wep_key_len_unicast': key_len, 515 'wep_rekey_period': self.wep_rekey_period}) 516 return ret 517 518 519 def get_shill_service_properties(self): 520 """@return dict of shill service properties.""" 521 ret = super(DynamicWEPConfig, self).get_shill_service_properties() 522 ret.update({self.SERVICE_PROPERTY_EAP_KEY_MGMT: 'IEEE8021X'}) 523 return ret 524 525 526class WPAEAPConfig(EAPConfig): 527 """Security type to set up a WPA tunnel via EAP-TLS negotiation.""" 528 529 def __init__(self, file_suffix=None, use_system_cas=None, 530 server_ca_cert=None, server_cert=None, server_key=None, 531 client_ca_cert=None, client_cert=None, client_key=None, 532 client_cert_id=None, client_key_id=None, 533 eap_identity=None, server_eap_users=None, 534 wpa_mode=WPAConfig.MODE_PURE_WPA, 535 ft_mode=WPAConfig.FT_MODE_DEFAULT): 536 """Construct a DynamicWEPConfig. 537 538 @param file_suffix string unique file suffix on DUT. 539 @param use_system_cas False iff we should ignore server certificates. 540 @param server_ca_cert string PEM encoded CA certificate for the server. 541 @param server_cert string PEM encoded identity certificate for server. 542 @param server_key string PEM encoded private key for server. 543 @param client_ca_cert string PEM encoded CA certificate for client. 544 @param client_cert string PEM encoded identity certificate for client. 545 @param client_key string PEM encoded private key for client. 546 @param client_cert_id string identifier for client certificate in TPM. 547 @param client_key_id string identifier for client private key in TPM. 548 @param eap_identity string user to authenticate as during EAP. 549 @param server_eap_users string contents of server EAP users file. 550 @param ft_mode int one of the FT_MODE_* in SecurityConfig 551 552 """ 553 super(WPAEAPConfig, self).__init__( 554 file_suffix=file_suffix, use_system_cas=use_system_cas, 555 server_ca_cert=server_ca_cert, server_cert=server_cert, 556 server_key=server_key, client_ca_cert=client_ca_cert, 557 client_cert=client_cert, client_key=client_key, 558 client_cert_id=client_cert_id, client_key_id=client_key_id, 559 eap_identity=eap_identity, server_eap_users=server_eap_users, 560 ft_mode=ft_mode) 561 self.wpa_mode = wpa_mode 562 563 564 def get_hostapd_config(self): 565 """@return dict fragment of hostapd configuration for security.""" 566 ret = super(WPAEAPConfig, self).get_hostapd_config() 567 # If we wanted to expand test coverage to WPA2/PEAP combinations 568 # or particular ciphers, we'd have to let people set these 569 # settings manually. But for now, do the simple thing. 570 ret.update({'wpa': self.wpa_mode, 571 'wpa_pairwise': WPAConfig.CIPHER_CCMP, 572 'wpa_key_mgmt':'WPA-EAP'}) 573 if self.ft_mode == WPAConfig.FT_MODE_PURE: 574 ret['wpa_key_mgmt'] = 'FT-EAP' 575 elif self.ft_mode == WPAConfig.FT_MODE_MIXED: 576 ret['wpa_key_mgmt'] = 'WPA-EAP FT-EAP' 577 return ret 578 579 580class Tunneled1xConfig(WPAEAPConfig): 581 """Security type to set up a TTLS/PEAP connection. 582 583 Both PEAP and TTLS are tunneled protocols which use EAP inside of a TLS 584 secured tunnel. The secured tunnel is a symmetric key encryption scheme 585 negotiated under the protection of a public key in the server certificate. 586 Thus, we'll see server credentials in the form of certificates, but client 587 credentials in the form of passwords and a CA Cert to root the trust chain. 588 589 """ 590 591 TTLS_PREFIX = 'TTLS-' 592 593 LAYER1_TYPE_PEAP = 'PEAP' 594 LAYER1_TYPE_TTLS = 'TTLS' 595 596 LAYER2_TYPE_GTC = 'GTC' 597 LAYER2_TYPE_MSCHAPV2 = 'MSCHAPV2' 598 LAYER2_TYPE_MD5 = 'MD5' 599 LAYER2_TYPE_TTLS_MSCHAPV2 = TTLS_PREFIX + 'MSCHAPV2' 600 LAYER2_TYPE_TTLS_MSCHAP = TTLS_PREFIX + 'MSCHAP' 601 LAYER2_TYPE_TTLS_PAP = TTLS_PREFIX + 'PAP' 602 603 def __init__(self, server_ca_cert, server_cert, server_key, 604 client_ca_cert, eap_identity, password, 605 outer_protocol=LAYER1_TYPE_PEAP, 606 inner_protocol=LAYER2_TYPE_MD5, 607 client_password=None, file_suffix=None): 608 self.password = password 609 if client_password is not None: 610 # Override the password used on the client. This lets us set 611 # bad passwords for testing. However, we use the real password 612 # below for the server config. 613 self.password = client_password 614 self.inner_protocol = inner_protocol 615 # hostapd wants these surrounded in double quotes. 616 quote = lambda x: '"' + x + '"' 617 eap_users = map(' '.join, [('*', outer_protocol), 618 (quote(eap_identity), inner_protocol, 619 quote(password), '[2]')]) 620 super(Tunneled1xConfig, self).__init__( 621 server_ca_cert=server_ca_cert, 622 server_cert=server_cert, 623 server_key=server_key, 624 server_eap_users='\n'.join(eap_users), 625 client_ca_cert=client_ca_cert, 626 eap_identity=eap_identity, 627 file_suffix=file_suffix) 628 629 630 def get_shill_service_properties(self): 631 """@return dict of shill service properties.""" 632 ret = super(Tunneled1xConfig, self).get_shill_service_properties() 633 ret.update({self.SERVICE_PROPERTY_EAP_PASSWORD: self.password}) 634 if self.inner_protocol.startswith(self.TTLS_PREFIX): 635 auth_str = 'auth=' + self.inner_protocol[len(self.TTLS_PREFIX):] 636 ret.update({self.SERVICE_PROPERTY_INNER_EAP: auth_str}) 637 return ret 638