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 WPA, WPA2, or both via a bitfield.
189    MODE_PURE_WPA = 1
190    MODE_PURE_WPA2 = 2
191    MODE_MIXED_WPA = MODE_PURE_WPA | MODE_PURE_WPA2
192    MODE_DEFAULT = MODE_MIXED_WPA
193
194    # WPA2 mandates the use of AES in CCMP mode.
195    # WPA allows the use of 'ordinary' AES, but mandates support for TKIP.
196    # The protocol however seems to indicate that you just list a bunch of
197    # different ciphers that you support and we'll start speaking one.
198    CIPHER_CCMP = 'CCMP'
199    CIPHER_TKIP = 'TKIP'
200
201    # Fast Transition (FT) mode for WPA network.
202    FT_MODE_NONE = 1
203    FT_MODE_PURE = 2
204    FT_MODE_MIXED = FT_MODE_NONE | FT_MODE_PURE
205    FT_MODE_DEFAULT = FT_MODE_NONE
206
207    def __init__(self, psk='', wpa_mode=MODE_DEFAULT, wpa_ciphers=[],
208                 wpa2_ciphers=[], wpa_ptk_rekey_period=None,
209                 wpa_gtk_rekey_period=None, wpa_gmk_rekey_period=None,
210                 use_strict_rekey=None, ft_mode=FT_MODE_NONE):
211        """Construct a WPAConfig.
212
213        @param psk string a passphrase (64 hex characters or an ASCII phrase up
214                to 63 characters long).
215        @param wpa_mode int one of MODE_* above.
216        @param wpa_ciphers list of ciphers to advertise in the WPA IE.
217        @param wpa2_ciphers list of ciphers to advertise in the WPA2 IE.
218                hostapd will fall back on WPA ciphers for WPA2 if this is
219                left unpopulated.
220        @param wpa_ptk_rekey_period int number of seconds between PTK rekeys.
221        @param wpa_gtk_rekey_period int number of second between GTK rekeys.
222        @param wpa_gmk_rekey_period int number of seconds between GMK rekeys.
223                The GMK is a key internal to hostapd used to generate GTK.
224                It is the 'master' key.
225        @param use_strict_rekey bool True iff hostapd should refresh the GTK
226                whenever any client leaves the group.
227        @param ft_mode int one of the FT_MODE_* in SecurityConfig.
228
229        """
230        super(WPAConfig, self).__init__(security='psk')
231        self.psk = psk
232        self.wpa_mode = wpa_mode
233        self.wpa_ciphers = wpa_ciphers
234        self.wpa2_ciphers = wpa2_ciphers
235        self.wpa_ptk_rekey_period = wpa_ptk_rekey_period
236        self.wpa_gtk_rekey_period = wpa_gtk_rekey_period
237        self.wpa_gmk_rekey_period = wpa_gmk_rekey_period
238        self.use_strict_rekey = use_strict_rekey
239        self.ft_mode = ft_mode
240        if len(psk) > 64:
241            raise error.TestFail('WPA passphrases can be no longer than 63 '
242                                 'characters (or 64 hex digits).')
243
244        if len(psk) == 64:
245            for c in psk:
246                if c not in '0123456789abcdefABCDEF':
247                    raise error.TestFail('Invalid PMK: %r' % psk)
248
249
250    def get_hostapd_config(self):
251        """@return dict fragment of hostapd configuration for security."""
252        if not self.wpa_mode:
253            raise error.TestFail('Cannot configure WPA unless we know which '
254                                 'mode to use.')
255
256        if self.MODE_PURE_WPA & self.wpa_mode and not self.wpa_ciphers:
257            raise error.TestFail('Cannot configure WPA unless we know which '
258                                 'ciphers to use.')
259
260        if not self.wpa_ciphers and not self.wpa2_ciphers:
261            raise error.TestFail('Cannot configure WPA2 unless we have some '
262                                 'ciphers.')
263
264        ret = {'wpa': self.wpa_mode,
265               'wpa_key_mgmt': 'WPA-PSK'}
266        if self.ft_mode == self.FT_MODE_PURE:
267            ret['wpa_key_mgmt'] = 'FT-PSK'
268        elif self.ft_mode == self.FT_MODE_MIXED:
269            ret['wpa_key_mgmt'] = 'WPA-PSK FT-PSK'
270        if len(self.psk) == 64:
271            ret['wpa_psk'] = self.psk
272        else:
273            ret['wpa_passphrase'] = self.psk
274
275        if self.wpa_ciphers:
276            ret['wpa_pairwise'] = ' '.join(self.wpa_ciphers)
277        if self.wpa2_ciphers:
278            ret['rsn_pairwise'] = ' '.join(self.wpa2_ciphers)
279        if self.wpa_ptk_rekey_period:
280            ret['wpa_ptk_rekey'] = self.wpa_ptk_rekey_period
281        if self.wpa_gtk_rekey_period:
282            ret['wpa_group_rekey'] = self.wpa_gtk_rekey_period
283        if self.wpa_gmk_rekey_period:
284            ret['wpa_gmk_rekey'] = self.wpa_gmk_rekey_period
285        if self.use_strict_rekey:
286            ret['wpa_strict_rekey'] = 1
287        return ret
288
289
290    def get_shill_service_properties(self):
291        """@return dict of shill service properties."""
292        ret = {self.SERVICE_PROPERTY_PASSPHRASE: self.psk}
293        return ret
294
295
296    def get_wpa_cli_properties(self):
297        properties = super(WPAConfig, self).get_wpa_cli_properties()
298        # TODO(wiley) This probably doesn't work for raw PMK.
299        protos = []
300        if self.wpa_mode & self.MODE_PURE_WPA:
301            protos.append('WPA')
302        if self.wpa_mode & self.MODE_PURE_WPA2:
303            protos.append('RSN')
304        properties.update({'psk': '\\"%s\\"' % self.psk,
305                           'key_mgmt': 'WPA-PSK',
306                           'proto': ' '.join(protos)})
307        if self.ft_mode == self.FT_MODE_PURE:
308            properties['key_mgmt'] = 'FT-PSK'
309        elif self.ft_mode == self.FT_MODE_MIXED:
310            properties['key_mgmt'] = 'WPA-PSK FT-PSK'
311        return properties
312
313
314class EAPConfig(SecurityConfig):
315    """Abstract superclass that implements certificate/key installation."""
316
317    DEFAULT_EAP_USERS = '* TLS'
318    DEFAULT_EAP_IDENTITY = 'chromeos'
319
320    SERVICE_PROPERTY_CA_CERT_PEM = 'EAP.CACertPEM'
321    SERVICE_PROPERTY_CLIENT_CERT_ID = 'EAP.CertID'
322    SERVICE_PROPERTY_EAP_IDENTITY = 'EAP.Identity'
323    SERVICE_PROPERTY_EAP_KEY_MGMT = 'EAP.KeyMgmt'
324    SERVICE_PROPERTY_EAP_PASSWORD = 'EAP.Password'
325    SERVICE_PROPERTY_EAP_PIN = 'EAP.PIN'
326    SERVICE_PROPERTY_INNER_EAP= 'EAP.InnerEAP'
327    SERVICE_PROPERTY_PRIVATE_KEY_ID = 'EAP.KeyID'
328    SERVICE_PROPERTY_USE_SYSTEM_CAS = 'EAP.UseSystemCAs'
329    SERVICE_PROPERTY_ALTSUBJECT_MATCH = 'EAP.SubjectAlternativeNameMatch'
330
331    last_tpm_id = 8800
332
333    # Credential file prefixes.
334    SERVER_CA_CERT_FILE_PREFIX = 'hostapd_ca_cert_file.'
335    SERVER_CERT_FILE_PREFIX = 'hostapd_cert_file.'
336    SERVER_KEY_FILE_PREFIX = 'hostapd_key_file.'
337    SERVER_EAP_USER_FILE_PREFIX = 'hostapd_eap_user_file.'
338
339    @staticmethod
340    def reserve_TPM_id():
341        """@return session unique TPM identifier."""
342        ret = str(EAPConfig.last_tpm_id)
343        EAPConfig.last_tpm_id += 1
344        return ret
345
346
347    def __init__(self, security='802_1x', file_suffix=None, use_system_cas=None,
348                 server_ca_cert=None, server_cert=None, server_key=None,
349                 server_eap_users=None,
350                 client_ca_cert=None, client_cert=None, client_key=None,
351                 client_cert_id=None, client_key_id=None,
352                 eap_identity=None, ft_mode=WPAConfig.FT_MODE_DEFAULT,
353                 altsubject_match=None):
354        """Construct an EAPConfig.
355
356        @param file_suffix string unique file suffix on DUT.
357        @param use_system_cas False iff we should ignore server certificates.
358        @param server_ca_cert string PEM encoded CA certificate for the server.
359        @param server_cert string PEM encoded identity certificate for server.
360        @param server_key string PEM encoded private key for server.
361        @param server_eap_users string contents of EAP user file.
362        @param client_ca_cert string PEM encoded CA certificate for client.
363        @param client_cert string PEM encoded identity certificate for client.
364        @param client_key string PEM encoded private key for client.
365        @param client_cert_id string identifier for client certificate in TPM.
366        @param client_key_id string identifier for client private key in TPM.
367        @param eap_identity string user to authenticate as during EAP.
368        @param ft_mode int one of the FT_MODE_* in SecurityConfig.
369        @param altsubject_match list of strings in the format of shill
370               EAP.SubjectAlternativeNameMatch property.
371
372        """
373        super(EAPConfig, self).__init__(security=security)
374        self.use_system_cas = use_system_cas
375        self.server_ca_cert = server_ca_cert
376        self.server_cert = server_cert
377        self.server_key = server_key
378        self.server_eap_users = server_eap_users or self.DEFAULT_EAP_USERS
379        self.client_ca_cert = client_ca_cert
380        self.client_cert = client_cert
381        self.client_key = client_key
382        if file_suffix is None:
383            suffix_letters = string.ascii_lowercase + string.digits
384            file_suffix = ''.join(random.choice(suffix_letters)
385                                  for x in range(10))
386            logging.debug('Choosing unique file_suffix %s.', file_suffix)
387        # The key paths will be determined in install_router_credentials.
388        self.server_ca_cert_file = None
389        self.server_cert_file = None
390        self.server_key_file = None
391        self.server_eap_user_file = None
392        # While these paths won't make it across the network, the suffix will.
393        self.file_suffix = file_suffix
394        self.client_cert_id = client_cert_id or self.reserve_TPM_id()
395        self.client_key_id = client_key_id or self.reserve_TPM_id()
396        # This gets filled in at install time.
397        self.pin = None
398        # The slot where the certificate/key are installed in the TPM.
399        self.client_cert_slot_id = None
400        self.client_key_slot_id = None
401        self.eap_identity = eap_identity or self.DEFAULT_EAP_IDENTITY
402        self.ft_mode = ft_mode
403        self.altsubject_match = altsubject_match
404
405
406    def install_router_credentials(self, host, install_dir):
407        """Install the necessary credentials on the router.
408
409        @param host host object representing the router.
410
411        """
412        self.server_ca_cert_file = os.path.join(
413            install_dir, self.SERVER_CA_CERT_FILE_PREFIX + self.file_suffix)
414        self.server_cert_file = os.path.join(
415            install_dir, self.SERVER_CERT_FILE_PREFIX + self.file_suffix)
416        self.server_key_file = os.path.join(
417            install_dir, self.SERVER_KEY_FILE_PREFIX + self.file_suffix)
418        self.server_eap_user_file = os.path.join(
419            install_dir, self.SERVER_EAP_USER_FILE_PREFIX + self.file_suffix)
420
421        files = [(self.server_ca_cert, self.server_ca_cert_file),
422                 (self.server_cert, self.server_cert_file),
423                 (self.server_key, self.server_key_file),
424                 (self.server_eap_users, self.server_eap_user_file)]
425        for content, path in files:
426            # If we omit a parameter, just omit copying a file over.
427            if content is None:
428                continue
429            # Write the contents to local disk first so we can use the easy
430            # built in mechanism to do this.
431            with tempfile.NamedTemporaryFile() as f:
432                f.write(content)
433                f.flush()
434                os.chmod(f.name, stat.S_IRUSR | stat.S_IWUSR |
435                                 stat.S_IRGRP | stat.S_IWGRP |
436                                 stat.S_IROTH | stat.S_IWOTH)
437                host.send_file(f.name, path, delete_dest=True)
438
439
440    def install_client_credentials(self, tpm_store):
441        """Install credentials on the local host (hopefully a DUT).
442
443        Only call this if we're running on a DUT in a WiFi test.  This
444        method can do things like install credentials into the TPM.
445
446        @param tpm_store TPMStore object representing the TPM on our DUT.
447
448        """
449        if self.client_cert:
450            tpm_store.install_certificate(self.client_cert, self.client_cert_id)
451            self.client_cert_slot_id = tpm_store.SLOT_ID
452            self.pin = tpm_store.PIN
453        if self.client_key:
454            tpm_store.install_private_key(self.client_key, self.client_key_id)
455            self.client_key_slot_id = tpm_store.SLOT_ID
456            self.pin = tpm_store.PIN
457
458
459    def get_shill_service_properties(self):
460        """@return dict of shill service properties."""
461        ret = {self.SERVICE_PROPERTY_EAP_IDENTITY: self.eap_identity}
462        if self.pin:
463            ret[self.SERVICE_PROPERTY_EAP_PIN] = self.pin
464        if self.client_ca_cert:
465            # Technically, we could accept a list of certificates here, but we
466            # have no such tests.
467            ret[self.SERVICE_PROPERTY_CA_CERT_PEM] = [self.client_ca_cert]
468        if self.client_cert:
469            ret[self.SERVICE_PROPERTY_CLIENT_CERT_ID] = (
470                    '%s:%s' % (self.client_cert_slot_id, self.client_cert_id))
471        if self.client_key:
472            ret[self.SERVICE_PROPERTY_PRIVATE_KEY_ID] = (
473                    '%s:%s' % (self.client_key_slot_id, self.client_key_id))
474        if self.use_system_cas is not None:
475            ret[self.SERVICE_PROPERTY_USE_SYSTEM_CAS] = self.use_system_cas
476        if self.altsubject_match:
477            ret[self.SERVICE_PROPERTY_ALTSUBJECT_MATCH] = self.altsubject_match
478        return ret
479
480
481    def get_hostapd_config(self):
482        """@return dict fragment of hostapd configuration for security."""
483        return {'ieee8021x': 1, # Enable 802.1x support.
484                'eap_server' : 1, # Do EAP inside hostapd to avoid RADIUS.
485                'ca_cert': self.server_ca_cert_file,
486                'server_cert': self.server_cert_file,
487                'private_key': self.server_key_file,
488                'eap_user_file': self.server_eap_user_file}
489
490
491class DynamicWEPConfig(EAPConfig):
492    """Configuration settings bundle for dynamic WEP.
493
494    This is a WEP encrypted connection where the keys are negotiated after the
495    client authenticates via 802.1x.
496
497    """
498
499    DEFAULT_REKEY_PERIOD = 20
500
501
502    def __init__(self, use_short_keys=False,
503                 wep_rekey_period=DEFAULT_REKEY_PERIOD,
504                 server_ca_cert=None, server_cert=None, server_key=None,
505                 client_ca_cert=None, client_cert=None, client_key=None,
506                 file_suffix=None, client_cert_id=None, client_key_id=None):
507        """Construct a DynamicWEPConfig.
508
509        @param use_short_keys bool force hostapd to use 40 bit WEP keys.
510        @param wep_rekey_period int number of second between rekeys.
511        @param server_ca_cert string PEM encoded CA certificate for the server.
512        @param server_cert string PEM encoded identity certificate for server.
513        @param server_key string PEM encoded private key for server.
514        @param client_ca_cert string PEM encoded CA certificate for client.
515        @param client_cert string PEM encoded identity certificate for client.
516        @param client_key string PEM encoded private key for client.
517        @param file_suffix string unique file suffix on DUT.
518        @param client_cert_id string identifier for client certificate in TPM.
519        @param client_key_id string identifier for client private key in TPM.
520
521        """
522        super(DynamicWEPConfig, self).__init__(
523                security='wep', file_suffix=file_suffix,
524                server_ca_cert=server_ca_cert, server_cert=server_cert,
525                server_key=server_key, client_ca_cert=client_ca_cert,
526                client_cert=client_cert, client_key=client_key,
527                client_cert_id=client_cert_id, client_key_id=client_key_id)
528        self.use_short_keys = use_short_keys
529        self.wep_rekey_period = wep_rekey_period
530
531
532    def get_hostapd_config(self):
533        """@return dict fragment of hostapd configuration for security."""
534        ret = super(DynamicWEPConfig, self).get_hostapd_config()
535        key_len = 13 # 128 bit WEP, 104 secret bits.
536        if self.use_short_keys:
537            key_len = 5 # 64 bit WEP, 40 bits of secret.
538        ret.update({'wep_key_len_broadcast': key_len,
539                    'wep_key_len_unicast': key_len,
540                    'wep_rekey_period': self.wep_rekey_period})
541        return ret
542
543
544    def get_shill_service_properties(self):
545        """@return dict of shill service properties."""
546        ret = super(DynamicWEPConfig, self).get_shill_service_properties()
547        ret.update({self.SERVICE_PROPERTY_EAP_KEY_MGMT: 'IEEE8021X'})
548        return ret
549
550
551class WPAEAPConfig(EAPConfig):
552    """Security type to set up a WPA tunnel via EAP-TLS negotiation."""
553
554    def __init__(self, file_suffix=None, use_system_cas=None,
555                 server_ca_cert=None, server_cert=None, server_key=None,
556                 client_ca_cert=None, client_cert=None, client_key=None,
557                 client_cert_id=None, client_key_id=None, eap_identity=None,
558                 server_eap_users=None, altsubject_match=None,
559                 wpa_mode=WPAConfig.MODE_PURE_WPA,
560                 ft_mode=WPAConfig.FT_MODE_DEFAULT):
561        """Construct a DynamicWEPConfig.
562
563        @param file_suffix string unique file suffix on DUT.
564        @param use_system_cas False iff we should ignore server certificates.
565        @param server_ca_cert string PEM encoded CA certificate for the server.
566        @param server_cert string PEM encoded identity certificate for server.
567        @param server_key string PEM encoded private key for server.
568        @param client_ca_cert string PEM encoded CA certificate for client.
569        @param client_cert string PEM encoded identity certificate for client.
570        @param client_key string PEM encoded private key for client.
571        @param client_cert_id string identifier for client certificate in TPM.
572        @param client_key_id string identifier for client private key in TPM.
573        @param eap_identity string user to authenticate as during EAP.
574        @param server_eap_users string contents of server EAP users file.
575        @param ft_mode int one of the FT_MODE_* in SecurityConfig
576        @param altsubject_match list of strings in the format of shill
577               EAP.SubjectAlternativeNameMatch property.
578
579        """
580        super(WPAEAPConfig, self).__init__(
581                file_suffix=file_suffix, use_system_cas=use_system_cas,
582                server_ca_cert=server_ca_cert, server_cert=server_cert,
583                server_key=server_key, client_ca_cert=client_ca_cert,
584                client_cert=client_cert, client_key=client_key,
585                client_cert_id=client_cert_id, client_key_id=client_key_id,
586                eap_identity=eap_identity, server_eap_users=server_eap_users,
587                ft_mode=ft_mode, altsubject_match=altsubject_match)
588        self.wpa_mode = wpa_mode
589
590
591    def get_hostapd_config(self):
592        """@return dict fragment of hostapd configuration for security."""
593        ret = super(WPAEAPConfig, self).get_hostapd_config()
594        # If we wanted to expand test coverage to WPA2/PEAP combinations
595        # or particular ciphers, we'd have to let people set these
596        # settings manually.  But for now, do the simple thing.
597        ret.update({'wpa': self.wpa_mode,
598                    'wpa_pairwise': WPAConfig.CIPHER_CCMP,
599                    'wpa_key_mgmt':'WPA-EAP'})
600        if self.ft_mode == WPAConfig.FT_MODE_PURE:
601            ret['wpa_key_mgmt'] = 'FT-EAP'
602        elif self.ft_mode == WPAConfig.FT_MODE_MIXED:
603            ret['wpa_key_mgmt'] = 'WPA-EAP FT-EAP'
604        return ret
605
606
607class Tunneled1xConfig(WPAEAPConfig):
608    """Security type to set up a TTLS/PEAP connection.
609
610    Both PEAP and TTLS are tunneled protocols which use EAP inside of a TLS
611    secured tunnel.  The secured tunnel is a symmetric key encryption scheme
612    negotiated under the protection of a public key in the server certificate.
613    Thus, we'll see server credentials in the form of certificates, but client
614    credentials in the form of passwords and a CA Cert to root the trust chain.
615
616    """
617
618    TTLS_PREFIX = 'TTLS-'
619
620    LAYER1_TYPE_PEAP = 'PEAP'
621    LAYER1_TYPE_TTLS = 'TTLS'
622
623    LAYER2_TYPE_GTC = 'GTC'
624    LAYER2_TYPE_MSCHAPV2 = 'MSCHAPV2'
625    LAYER2_TYPE_MD5 = 'MD5'
626    LAYER2_TYPE_TTLS_MSCHAPV2 = TTLS_PREFIX + 'MSCHAPV2'
627    LAYER2_TYPE_TTLS_MSCHAP = TTLS_PREFIX + 'MSCHAP'
628    LAYER2_TYPE_TTLS_PAP = TTLS_PREFIX + 'PAP'
629
630    def __init__(self, server_ca_cert, server_cert, server_key,
631                 client_ca_cert, eap_identity, password,
632                 outer_protocol=LAYER1_TYPE_PEAP,
633                 inner_protocol=LAYER2_TYPE_MD5,
634                 client_password=None, file_suffix=None,
635                 altsubject_match=None):
636        self.password = password
637        if client_password is not None:
638            # Override the password used on the client.  This lets us set
639            # bad passwords for testing.  However, we use the real password
640            # below for the server config.
641            self.password = client_password
642        self.inner_protocol = inner_protocol
643        # hostapd wants these surrounded in double quotes.
644        quote = lambda x: '"' + x + '"'
645        eap_users = list(map(' '.join, [('*', outer_protocol),
646                (quote(eap_identity), inner_protocol, quote(password), '[2]')]))
647        super(Tunneled1xConfig, self).__init__(
648                server_ca_cert=server_ca_cert,
649                server_cert=server_cert,
650                server_key=server_key,
651                server_eap_users='\n'.join(eap_users),
652                client_ca_cert=client_ca_cert,
653                eap_identity=eap_identity,
654                file_suffix=file_suffix,
655                altsubject_match=altsubject_match)
656
657
658    def get_shill_service_properties(self):
659        """@return dict of shill service properties."""
660        ret = super(Tunneled1xConfig, self).get_shill_service_properties()
661        ret.update({self.SERVICE_PROPERTY_EAP_PASSWORD: self.password})
662        if self.inner_protocol.startswith(self.TTLS_PREFIX):
663            auth_str = 'auth=' + self.inner_protocol[len(self.TTLS_PREFIX):]
664            ret.update({self.SERVICE_PROPERTY_INNER_EAP: auth_str})
665        return ret
666