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