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