1#!/usr/bin/env python3
2
3"""Mbed TLS and PSA configuration file manipulation library and tool
4
5Basic usage, to read the Mbed TLS configuration:
6    config = CombinedConfigFile()
7    if 'MBEDTLS_RSA_C' in config: print('RSA is enabled')
8"""
9
10## Copyright The Mbed TLS Contributors
11## SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
12##
13
14import os
15import re
16import sys
17
18import framework_scripts_path # pylint: disable=unused-import
19from mbedtls_framework import config_common
20
21
22def is_boolean_setting(name, value):
23    """Is this a boolean setting?
24
25    Mbed TLS boolean settings are enabled if the preprocessor macro is
26    defined, and disabled if the preprocessor macro is not defined. The
27    macro definition line in the configuration file has an empty expansion.
28
29    PSA_WANT_xxx settings are also boolean, but when they are enabled,
30    they expand to a nonzero value. We leave them undefined when they
31    are disabled. (Setting them to 0 currently means to enable them, but
32    this might change to mean disabling them. Currently we just never set
33    them to 0.)
34    """
35    if name.startswith('PSA_WANT_'):
36        return True
37    if not value:
38        return True
39    return False
40
41def realfull_adapter(_name, _value, _active):
42    """Activate all symbols.
43
44    This is intended for building the documentation, including the
45    documentation of settings that are activated by defining an optional
46    preprocessor macro. There is no expectation that the resulting
47    configuration can be built.
48    """
49    return True
50
51PSA_UNSUPPORTED_FEATURE = frozenset([
52    'PSA_WANT_ALG_CBC_MAC',
53    'PSA_WANT_ALG_XTS',
54    'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_DERIVE',
55    'PSA_WANT_KEY_TYPE_DH_KEY_PAIR_DERIVE'
56])
57
58PSA_DEPRECATED_FEATURE = frozenset([
59    'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR',
60    'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR'
61])
62
63EXCLUDE_FROM_CRYPTO = PSA_UNSUPPORTED_FEATURE | \
64                      PSA_DEPRECATED_FEATURE
65
66# The goal of the full configuration is to have everything that can be tested
67# together. This includes deprecated or insecure options. It excludes:
68# * Options that require additional build dependencies or unusual hardware.
69# * Options that make testing less effective.
70# * Options that are incompatible with other options, or more generally that
71#   interact with other parts of the code in such a way that a bulk enabling
72#   is not a good way to test them.
73# * Options that remove features.
74EXCLUDE_FROM_FULL = frozenset([
75    #pylint: disable=line-too-long
76    'MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH', # interacts with CTR_DRBG_128_BIT_KEY
77    'MBEDTLS_AES_USE_HARDWARE_ONLY', # hardware dependency
78    'MBEDTLS_BLOCK_CIPHER_NO_DECRYPT', # incompatible with ECB in PSA, CBC/XTS/NIST_KW
79    'MBEDTLS_CTR_DRBG_USE_128_BIT_KEY', # interacts with ENTROPY_FORCE_SHA256
80    'MBEDTLS_DEPRECATED_REMOVED', # conflicts with deprecated options
81    'MBEDTLS_DEPRECATED_WARNING', # conflicts with deprecated options
82    'MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED', # influences the use of ECDH in TLS
83    'MBEDTLS_ECP_WITH_MPI_UINT', # disables the default ECP and is experimental
84    'MBEDTLS_ENTROPY_FORCE_SHA256', # interacts with CTR_DRBG_128_BIT_KEY
85    'MBEDTLS_HAVE_SSE2', # hardware dependency
86    'MBEDTLS_MEMORY_BACKTRACE', # depends on MEMORY_BUFFER_ALLOC_C
87    'MBEDTLS_MEMORY_BUFFER_ALLOC_C', # makes sanitizers (e.g. ASan) less effective
88    'MBEDTLS_MEMORY_DEBUG', # depends on MEMORY_BUFFER_ALLOC_C
89    'MBEDTLS_NO_64BIT_MULTIPLICATION', # influences anything that uses bignum
90    'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES', # removes a feature
91    'MBEDTLS_NO_UDBL_DIVISION', # influences anything that uses bignum
92    'MBEDTLS_PSA_DRIVER_GET_ENTROPY', # incompatible with MBEDTLS_PSA_BUILTIN_GET_ENTROPY
93    'MBEDTLS_PSA_P256M_DRIVER_ENABLED', # influences SECP256R1 KeyGen/ECDH/ECDSA
94    'MBEDTLS_PLATFORM_NO_STD_FUNCTIONS', # removes a feature
95    'MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS', # removes a feature
96    'MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG', # behavior change + build dependency
97    'MBEDTLS_PSA_CRYPTO_KEY_ID_ENCODES_OWNER', # interface and behavior change
98    'MBEDTLS_PSA_CRYPTO_SPM', # platform dependency (PSA SPM)
99    'MBEDTLS_RSA_NO_CRT', # influences the use of RSA in X.509 and TLS
100    'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY', # interacts with *_USE_A64_CRYPTO_IF_PRESENT
101    'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_ONLY', # interacts with *_USE_ARMV8_A_CRYPTO_IF_PRESENT
102    'MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY', # interacts with *_USE_A64_CRYPTO_IF_PRESENT
103    'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', # setting *_USE_ARMV8_A_CRYPTO is sufficient
104    'MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN', # build dependency (clang+memsan)
105    'MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND', # build dependency (valgrind headers)
106    'MBEDTLS_X509_REMOVE_INFO', # removes a feature
107    'MBEDTLS_PSA_STATIC_KEY_SLOTS', # only relevant for embedded devices
108    'MBEDTLS_PSA_STATIC_KEY_SLOT_BUFFER_SIZE', # only relevant for embedded devices
109    *PSA_UNSUPPORTED_FEATURE,
110    *PSA_DEPRECATED_FEATURE,
111])
112
113def is_seamless_alt(name):
114    """Whether the xxx_ALT symbol should be included in the full configuration.
115
116    Include alternative implementations of platform functions, which are
117    configurable function pointers that default to the built-in function.
118    This way we test that the function pointers exist and build correctly
119    without changing the behavior, and tests can verify that the function
120    pointers are used by modifying those pointers.
121
122    Exclude alternative implementations of library functions since they require
123    an implementation of the relevant functions and an xxx_alt.h header.
124    """
125    if name in (
126            'MBEDTLS_PLATFORM_GET_ENTROPY_ALT',
127            'MBEDTLS_PLATFORM_GMTIME_R_ALT',
128            'MBEDTLS_PLATFORM_SETUP_TEARDOWN_ALT',
129            'MBEDTLS_PLATFORM_MS_TIME_ALT',
130            'MBEDTLS_PLATFORM_ZEROIZE_ALT',
131    ):
132        # Similar to non-platform xxx_ALT, requires platform_alt.h
133        return False
134    return name.startswith('MBEDTLS_PLATFORM_')
135
136def include_in_full(name):
137    """Rules for symbols in the "full" configuration."""
138    if name in EXCLUDE_FROM_FULL:
139        return False
140    if name.endswith('_ALT'):
141        return is_seamless_alt(name)
142    return True
143
144def full_adapter(name, value, active):
145    """Config adapter for "full"."""
146    if not is_boolean_setting(name, value):
147        return active
148    return include_in_full(name)
149
150# The baremetal configuration excludes options that require a library or
151# operating system feature that is typically not present on bare metal
152# systems. Features that are excluded from "full" won't be in "baremetal"
153# either (unless explicitly turned on in baremetal_adapter) so they don't
154# need to be repeated here.
155EXCLUDE_FROM_BAREMETAL = frozenset([
156    #pylint: disable=line-too-long
157    'MBEDTLS_ENTROPY_NV_SEED', # requires a filesystem and FS_IO or alternate NV seed hooks
158    'MBEDTLS_FS_IO', # requires a filesystem
159    'MBEDTLS_HAVE_TIME', # requires a clock
160    'MBEDTLS_HAVE_TIME_DATE', # requires a clock
161    'MBEDTLS_NET_C', # requires POSIX-like networking
162    'MBEDTLS_PLATFORM_FPRINTF_ALT', # requires FILE* from stdio.h
163    'MBEDTLS_PLATFORM_NV_SEED_ALT', # requires a filesystem and ENTROPY_NV_SEED
164    'MBEDTLS_PLATFORM_TIME_ALT', # requires a clock and HAVE_TIME
165    'MBEDTLS_PSA_CRYPTO_STORAGE_C', # requires a filesystem
166    'MBEDTLS_PSA_ITS_FILE_C', # requires a filesystem
167    'MBEDTLS_THREADING_C', # requires a threading interface
168    'MBEDTLS_THREADING_PTHREAD', # requires pthread
169    'MBEDTLS_TIMING_C', # requires a clock
170    'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', # requires an OS for runtime-detection
171    'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_IF_PRESENT', # requires an OS for runtime-detection
172    'MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT', # requires an OS for runtime-detection
173])
174
175def keep_in_baremetal(name):
176    """Rules for symbols in the "baremetal" configuration."""
177    if name in EXCLUDE_FROM_BAREMETAL:
178        return False
179    return True
180
181def baremetal_adapter(name, value, active):
182    """Config adapter for "baremetal"."""
183    if not is_boolean_setting(name, value):
184        return active
185    if name == 'MBEDTLS_PLATFORM_GET_ENTROPY_ALT':
186        # No OS-provided entropy source
187        return True
188    return include_in_full(name) and keep_in_baremetal(name)
189
190# This set contains options that are mostly for debugging or test purposes,
191# and therefore should be excluded when doing code size measurements.
192# Options that are their own module (such as MBEDTLS_ERROR_C) are not listed
193# and therefore will be included when doing code size measurements.
194EXCLUDE_FOR_SIZE = frozenset([
195    'MBEDTLS_DEBUG_C', # large code size increase in TLS
196    'MBEDTLS_SELF_TEST', # increases the size of many modules
197    'MBEDTLS_TEST_HOOKS', # only useful with the hosted test framework, increases code size
198])
199
200def baremetal_size_adapter(name, value, active):
201    if name in EXCLUDE_FOR_SIZE:
202        return False
203    return baremetal_adapter(name, value, active)
204
205def include_in_crypto(name):
206    """Rules for symbols in a crypto configuration."""
207    if name.startswith('MBEDTLS_X509_') or \
208       name.startswith('MBEDTLS_VERSION_') or \
209       name.startswith('MBEDTLS_SSL_') or \
210       name.startswith('MBEDTLS_KEY_EXCHANGE_'):
211        return False
212    if name in [
213            'MBEDTLS_DEBUG_C', # part of libmbedtls
214            'MBEDTLS_NET_C', # part of libmbedtls
215            'MBEDTLS_PKCS7_C', # part of libmbedx509
216            'MBEDTLS_TIMING_C', # part of libmbedtls
217            'MBEDTLS_ERROR_C', # part of libmbedx509
218            'MBEDTLS_ERROR_STRERROR_DUMMY', # part of libmbedx509
219    ]:
220        return False
221    if name in EXCLUDE_FROM_CRYPTO:
222        return False
223    return True
224
225def crypto_adapter(adapter):
226    """Modify an adapter to disable non-crypto symbols.
227
228    ``crypto_adapter(adapter)(name, value, active)`` is like
229    ``adapter(name, value, active)``, but unsets all X.509 and TLS symbols.
230    """
231    def continuation(name, value, active):
232        if not include_in_crypto(name):
233            return False
234        if adapter is None:
235            return active
236        return adapter(name, value, active)
237    return continuation
238
239DEPRECATED = frozenset([
240    *PSA_DEPRECATED_FEATURE
241])
242def no_deprecated_adapter(adapter):
243    """Modify an adapter to disable deprecated symbols.
244
245    ``no_deprecated_adapter(adapter)(name, value, active)`` is like
246    ``adapter(name, value, active)``, but unsets all deprecated symbols
247    and sets ``MBEDTLS_DEPRECATED_REMOVED``.
248    """
249    def continuation(name, value, active):
250        if name == 'MBEDTLS_DEPRECATED_REMOVED':
251            return True
252        if name in DEPRECATED:
253            return False
254        if adapter is None:
255            return active
256        return adapter(name, value, active)
257    return continuation
258
259def no_platform_adapter(adapter):
260    """Modify an adapter to disable platform symbols.
261
262    ``no_platform_adapter(adapter)(name, value, active)`` is like
263    ``adapter(name, value, active)``, but unsets all platform symbols other
264    ``than MBEDTLS_PLATFORM_C.
265    """
266    def continuation(name, value, active):
267        # Allow MBEDTLS_PLATFORM_C but remove all other platform symbols.
268        if name.startswith('MBEDTLS_PLATFORM_') and name != 'MBEDTLS_PLATFORM_C':
269            return False
270        if adapter is None:
271            return active
272        return adapter(name, value, active)
273    return continuation
274
275
276class MbedTLSConfigFile(config_common.ConfigFile):
277    """Representation of an MbedTLS configuration file."""
278
279    _path_in_tree = 'include/mbedtls/mbedtls_config.h'
280    default_path = [_path_in_tree,
281                    os.path.join(os.path.dirname(__file__),
282                                 os.pardir,
283                                 _path_in_tree),
284                    os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
285                                 _path_in_tree)]
286
287    def __init__(self, filename=None):
288        super().__init__(self.default_path, 'Mbed TLS', filename)
289        self.current_section = 'header'
290
291
292class CryptoConfigFile(config_common.ConfigFile):
293    """Representation of a Crypto configuration file."""
294
295    # Temporary, while Mbed TLS does not just rely on the TF-PSA-Crypto
296    # build system to build its crypto library. When it does, the
297    # condition can just be removed.
298    _path_in_tree = ('include/psa/crypto_config.h'
299                     if not os.path.isdir(os.path.join(os.path.dirname(__file__),
300                                                       os.pardir,
301                                                       'tf-psa-crypto')) else
302                     'tf-psa-crypto/include/psa/crypto_config.h')
303    default_path = [_path_in_tree,
304                    os.path.join(os.path.dirname(__file__),
305                                 os.pardir,
306                                 _path_in_tree),
307                    os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
308                                 _path_in_tree)]
309
310    def __init__(self, filename=None):
311        super().__init__(self.default_path, 'Crypto', filename)
312
313
314class MbedTLSConfig(config_common.Config):
315    """Representation of the Mbed TLS configuration.
316
317    See the documentation of the `Config` class for methods to query
318    and modify the configuration.
319    """
320
321    def __init__(self, filename=None):
322        """Read the Mbed TLS configuration file."""
323
324        super().__init__()
325        configfile = MbedTLSConfigFile(filename)
326        self.configfiles.append(configfile)
327        self.settings.update({name: config_common.Setting(configfile, active, name, value, section)
328                              for (active, name, value, section)
329                              in configfile.parse_file()})
330
331    def set(self, name, value=None):
332        """Set name to the given value and make it active."""
333
334        if name not in self.settings:
335            self._get_configfile().templates.append((name, '', '#define ' + name + ' '))
336
337        super().set(name, value)
338
339
340class CryptoConfig(config_common.Config):
341    """Representation of the PSA crypto configuration.
342
343    See the documentation of the `Config` class for methods to query
344    and modify the configuration.
345    """
346
347    def __init__(self, filename=None):
348        """Read the PSA crypto configuration file."""
349
350        super().__init__()
351        configfile = CryptoConfigFile(filename)
352        self.configfiles.append(configfile)
353        self.settings.update({name: config_common.Setting(configfile, active, name, value, section)
354                              for (active, name, value, section)
355                              in configfile.parse_file()})
356
357    def set(self, name, value='1'):
358        """Set name to the given value and make it active."""
359
360        if name in PSA_UNSUPPORTED_FEATURE:
361            raise ValueError(f'Feature is unsupported: \'{name}\'')
362
363        if name not in self.settings:
364            self._get_configfile().templates.append((name, '', '#define ' + name + ' '))
365
366        super().set(name, value)
367
368
369class CombinedConfig(config_common.Config):
370    """Representation of MbedTLS and PSA crypto configuration
371
372    See the documentation of the `Config` class for methods to query
373    and modify the configuration.
374    """
375
376    def __init__(self, *configs):
377        super().__init__()
378        for config in configs:
379            if isinstance(config, MbedTLSConfigFile):
380                self.mbedtls_configfile = config
381            elif isinstance(config, CryptoConfigFile):
382                self.crypto_configfile = config
383            else:
384                raise ValueError(f'Invalid configfile: {config}')
385            self.configfiles.append(config)
386
387        self.settings.update({name: config_common.Setting(configfile, active, name, value, section)
388                              for configfile in [self.mbedtls_configfile, self.crypto_configfile]
389                              for (active, name, value, section) in configfile.parse_file()})
390
391    _crypto_regexp = re.compile(r'^PSA_.*')
392    def _get_configfile(self, name=None):
393        """Find a config type for a setting name"""
394
395        if name in self.settings:
396            return self.settings[name].configfile
397        elif re.match(self._crypto_regexp, name):
398            return self.crypto_configfile
399        else:
400            return self.mbedtls_configfile
401
402    def set(self, name, value=None):
403        """Set name to the given value and make it active."""
404
405        configfile = self._get_configfile(name)
406
407        if configfile == self.crypto_configfile:
408            if name in PSA_UNSUPPORTED_FEATURE:
409                raise ValueError(f'Feature is unsupported: \'{name}\'')
410
411            # The default value in the crypto config is '1'
412            if not value and re.match(self._crypto_regexp, name):
413                value = '1'
414
415        if name not in self.settings:
416            configfile.templates.append((name, '', '#define ' + name + ' '))
417
418        super().set(name, value)
419
420    #pylint: disable=arguments-differ
421    def write(self, mbedtls_file=None, crypto_file=None):
422        """Write the whole configuration to the file it was read from.
423
424        If mbedtls_file or crypto_file is specified, write the specific configuration
425        to the corresponding file instead.
426
427        Two file name parameters and not only one as in the super class as we handle
428        two configuration files in this class.
429        """
430
431        self.mbedtls_configfile.write(self.settings, mbedtls_file)
432        self.crypto_configfile.write(self.settings, crypto_file)
433
434    def filename(self, name=None):
435        """Get the name of the config files.
436
437        If 'name' is specified return the name of the config file where it is defined.
438        """
439
440        if not name:
441            return [config.filename for config in [self.mbedtls_configfile, self.crypto_configfile]]
442
443        return self._get_configfile(name).filename
444
445
446class MbedTLSConfigTool(config_common.ConfigTool):
447    """Command line mbedtls_config.h and crypto_config.h manipulation tool."""
448
449    def __init__(self):
450        super().__init__(MbedTLSConfigFile.default_path)
451        self.config = CombinedConfig(MbedTLSConfigFile(self.args.file),
452                                     CryptoConfigFile(self.args.cryptofile))
453
454    def custom_parser_options(self):
455        """Adds MbedTLS specific options for the parser."""
456
457        self.parser.add_argument(
458            '--cryptofile', '-c',
459            help="""Crypto file to read (and modify if requested). Default: {}."""
460            .format(CryptoConfigFile.default_path))
461
462        self.add_adapter(
463            'baremetal', baremetal_adapter,
464            """Like full, but exclude features that require platform features
465            such as file input-output.
466            """)
467        self.add_adapter(
468            'baremetal_size', baremetal_size_adapter,
469            """Like baremetal, but exclude debugging features. Useful for code size measurements.
470            """)
471        self.add_adapter(
472            'full', full_adapter,
473            """Uncomment most features.
474            Exclude alternative implementations and platform support options, as well as
475            some options that are awkward to test.
476            """)
477        self.add_adapter(
478            'full_no_deprecated', no_deprecated_adapter(full_adapter),
479            """Uncomment most non-deprecated features.
480            Like "full", but without deprecated features.
481            """)
482        self.add_adapter(
483            'full_no_platform', no_platform_adapter(full_adapter),
484            """Uncomment most non-platform features. Like "full", but without platform features.
485            """)
486        self.add_adapter(
487            'realfull', realfull_adapter,
488            """Uncomment all boolean #defines.
489            Suitable for generating documentation, but not for building.
490            """)
491        self.add_adapter(
492            'crypto', crypto_adapter(None),
493            """Only include crypto features. Exclude X.509 and TLS.""")
494        self.add_adapter(
495            'crypto_baremetal', crypto_adapter(baremetal_adapter),
496            """Like baremetal, but with only crypto features, excluding X.509 and TLS.""")
497        self.add_adapter(
498            'crypto_full', crypto_adapter(full_adapter),
499            """Like full, but with only crypto features, excluding X.509 and TLS.""")
500
501
502if __name__ == '__main__':
503    sys.exit(MbedTLSConfigTool().main())
504