• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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