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