blob: ad1f787bc937174238c26d4d6d4491aebeb3a4a0 [file] [log] [blame]
Gilles Peskineb4063892019-07-27 21:36:44 +02001#!/usr/bin/env python3
2
3"""Mbed TLS configuration file manipulation library and tool
4
Fredrik Hessecc207bc2021-09-28 21:06:08 +02005Basic usage, to read the Mbed TLS configuration:
Gilles Peskineb4063892019-07-27 21:36:44 +02006 config = ConfigFile()
7 if 'MBEDTLS_RSA_C' in config: print('RSA is enabled')
8"""
9
Gilles Peskinecf425362022-10-10 22:52:30 +020010# Note that as long as Mbed TLS 2.28 LTS is maintained, the version of
11# this script in the mbedtls-2.28 branch must remain compatible with
12# Python 3.4. The version in development may only use more recent features
13# in parts that are not backported to 2.28.
14
Bence Szépkúti1e148272020-08-07 13:07:28 +020015## Copyright The Mbed TLS Contributors
Dave Rodgman16799db2023-11-02 19:47:20 +000016## SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Gilles Peskineb4063892019-07-27 21:36:44 +020017##
Gilles Peskineb4063892019-07-27 21:36:44 +020018
Gilles Peskine208e4ec2019-07-29 23:43:20 +020019import os
Gilles Peskineb4063892019-07-27 21:36:44 +020020import re
21
Gabor Mezei3678dee2024-06-04 19:58:43 +020022from abc import ABCMeta, abstractmethod
23
Gilles Peskineb4063892019-07-27 21:36:44 +020024class Setting:
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +020025 """Representation of one Mbed TLS mbedtls_config.h setting.
Gilles Peskineb4063892019-07-27 21:36:44 +020026
27 Fields:
28 * name: the symbol name ('MBEDTLS_xxx').
29 * value: the value of the macro. The empty string for a plain #define
30 with no value.
31 * active: True if name is defined, False if a #define for name is
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +020032 present in mbedtls_config.h but commented out.
Gilles Peskine53d41ae2019-07-27 23:31:53 +020033 * section: the name of the section that contains this symbol.
Gilles Peskineb4063892019-07-27 21:36:44 +020034 """
35 # pylint: disable=too-few-public-methods
Gabor Mezei3678dee2024-06-04 19:58:43 +020036 def __init__(self, active, name, value='', section=None, configfile=None):
Gilles Peskineb4063892019-07-27 21:36:44 +020037 self.active = active
38 self.name = name
39 self.value = value
Gilles Peskine53d41ae2019-07-27 23:31:53 +020040 self.section = section
Gabor Mezei3678dee2024-06-04 19:58:43 +020041 self.configfile = configfile
Gilles Peskineb4063892019-07-27 21:36:44 +020042
43class Config:
44 """Representation of the Mbed TLS configuration.
45
46 In the documentation of this class, a symbol is said to be *active*
47 if there is a #define for it that is not commented out, and *known*
48 if there is a #define for it whether commented out or not.
49
50 This class supports the following protocols:
Gilles Peskinec190c902019-08-01 23:31:05 +020051 * `name in config` is `True` if the symbol `name` is active, `False`
52 otherwise (whether `name` is inactive or not known).
53 * `config[name]` is the value of the macro `name`. If `name` is inactive,
54 raise `KeyError` (even if `name` is known).
Gilles Peskineb4063892019-07-27 21:36:44 +020055 * `config[name] = value` sets the value associated to `name`. `name`
56 must be known, but does not need to be set. This does not cause
57 name to become set.
58 """
59
Gabor Mezei3678dee2024-06-04 19:58:43 +020060 def __init__(self, **kw):
Gilles Peskineb4063892019-07-27 21:36:44 +020061 self.settings = {}
62
63 def __contains__(self, name):
64 """True if the given symbol is active (i.e. set).
65
66 False if the given symbol is not set, even if a definition
67 is present but commented out.
68 """
69 return name in self.settings and self.settings[name].active
70
71 def all(self, *names):
72 """True if all the elements of names are active (i.e. set)."""
73 return all(self.__contains__(name) for name in names)
74
75 def any(self, *names):
76 """True if at least one symbol in names are active (i.e. set)."""
77 return any(self.__contains__(name) for name in names)
78
79 def known(self, name):
80 """True if a #define for name is present, whether it's commented out or not."""
81 return name in self.settings
82
83 def __getitem__(self, name):
84 """Get the value of name, i.e. what the preprocessor symbol expands to.
85
86 If name is not known, raise KeyError. name does not need to be active.
87 """
88 return self.settings[name].value
89
90 def get(self, name, default=None):
91 """Get the value of name. If name is inactive (not set), return default.
92
93 If a #define for name is present and not commented out, return
94 its expansion, even if this is the empty string.
95
96 If a #define for name is present but commented out, return default.
97 """
98 if name in self.settings:
99 return self.settings[name].value
100 else:
101 return default
102
103 def __setitem__(self, name, value):
104 """If name is known, set its value.
105
106 If name is not known, raise KeyError.
107 """
108 self.settings[name].value = value
109
110 def set(self, name, value=None):
111 """Set name to the given value and make it active.
112
113 If value is None and name is already known, don't change its value.
114 If value is None and name is not known, set its value to the empty
115 string.
116 """
117 if name in self.settings:
118 if value is not None:
119 self.settings[name].value = value
120 self.settings[name].active = True
121 else:
122 self.settings[name] = Setting(True, name, value=value)
123
124 def unset(self, name):
125 """Make name unset (inactive).
126
Gilles Peskine55cc4db2019-08-01 23:13:23 +0200127 name remains known if it was known before.
Gilles Peskineb4063892019-07-27 21:36:44 +0200128 """
Gilles Peskine55cc4db2019-08-01 23:13:23 +0200129 if name not in self.settings:
130 return
Gilles Peskineb4063892019-07-27 21:36:44 +0200131 self.settings[name].active = False
132
133 def adapt(self, adapter):
134 """Run adapter on each known symbol and (de)activate it accordingly.
135
136 `adapter` must be a function that returns a boolean. It is called as
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200137 `adapter(name, active, section)` for each setting, where `active` is
138 `True` if `name` is set and `False` if `name` is known but unset,
139 and `section` is the name of the section containing `name`. If
Gilles Peskineb4063892019-07-27 21:36:44 +0200140 `adapter` returns `True`, then set `name` (i.e. make it active),
141 otherwise unset `name` (i.e. make it known but inactive).
142 """
143 for setting in self.settings.values():
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200144 setting.active = adapter(setting.name, setting.active,
145 setting.section)
Gilles Peskineb4063892019-07-27 21:36:44 +0200146
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200147 def change_matching(self, regexs, enable):
148 """Change all symbols matching one of the regexs to the desired state."""
149 if not regexs:
150 return
151 regex = re.compile('|'.join(regexs))
152 for setting in self.settings.values():
153 if regex.search(setting.name):
154 setting.active = enable
155
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200156def is_full_section(section):
157 """Is this section affected by "config.py full" and friends?"""
Gabor Mezei3678dee2024-06-04 19:58:43 +0200158 return section is None or section.endswith('support') or section.endswith('modules')
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200159
160def realfull_adapter(_name, active, section):
Gilles Peskineba4162a2022-04-11 17:04:38 +0200161 """Activate all symbols found in the global and boolean feature sections.
162
163 This is intended for building the documentation, including the
164 documentation of settings that are activated by defining an optional
165 preprocessor macro.
166
167 Do not activate definitions in the section containing symbols that are
168 supposed to be defined and documented in their own module.
169 """
170 if section == 'Module configuration options':
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200171 return active
Gilles Peskineb4063892019-07-27 21:36:44 +0200172 return True
173
Gabor Mezei3678dee2024-06-04 19:58:43 +0200174UNSUPPORTED_FEATURE = frozenset([
175 'PSA_WANT_ALG_CBC_MAC',
176 'PSA_WANT_ALG_XTS',
177 'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_DERIVE',
178 'PSA_WANT_KEY_TYPE_DH_KEY_PAIR_DERIVE'
179])
180
181DEPRECATED_FEATURE = frozenset([
182 'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR',
183 'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR'
184])
185
186UNSTABLE_FEATURE = frozenset([
187 'PSA_WANT_ECC_SECP_K1_224'
188])
189
Gilles Peskinecfffc282020-04-12 13:55:45 +0200190# The goal of the full configuration is to have everything that can be tested
191# together. This includes deprecated or insecure options. It excludes:
192# * Options that require additional build dependencies or unusual hardware.
193# * Options that make testing less effective.
Gilles Peskinec9d04332020-04-16 20:50:17 +0200194# * Options that are incompatible with other options, or more generally that
195# interact with other parts of the code in such a way that a bulk enabling
196# is not a good way to test them.
Gilles Peskinecfffc282020-04-12 13:55:45 +0200197# * Options that remove features.
Gilles Peskinebbaa2b72020-04-12 13:33:57 +0200198EXCLUDE_FROM_FULL = frozenset([
Gilles Peskinecfffc282020-04-12 13:55:45 +0200199 #pylint: disable=line-too-long
Yanray Wanga8704672023-04-20 17:16:48 +0800200 'MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH', # interacts with CTR_DRBG_128_BIT_KEY
Gilles Peskinea8861e02023-09-05 20:20:51 +0200201 'MBEDTLS_AES_USE_HARDWARE_ONLY', # hardware dependency
Yanray Wang42be1ba2023-11-23 14:28:47 +0800202 'MBEDTLS_BLOCK_CIPHER_NO_DECRYPT', # incompatible with ECB in PSA, CBC/XTS/NIST_KW/DES
Gilles Peskinec9d04332020-04-16 20:50:17 +0200203 'MBEDTLS_CTR_DRBG_USE_128_BIT_KEY', # interacts with ENTROPY_FORCE_SHA256
Gilles Peskinecfffc282020-04-12 13:55:45 +0200204 'MBEDTLS_DEPRECATED_REMOVED', # conflicts with deprecated options
Gilles Peskine90581ee2020-04-12 14:02:47 +0200205 'MBEDTLS_DEPRECATED_WARNING', # conflicts with deprecated options
Gilles Peskinec9d04332020-04-16 20:50:17 +0200206 'MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED', # influences the use of ECDH in TLS
Janos Follath5b7c38f2023-08-01 08:51:12 +0100207 'MBEDTLS_ECP_WITH_MPI_UINT', # disables the default ECP and is experimental
Gilles Peskinec9d04332020-04-16 20:50:17 +0200208 'MBEDTLS_ENTROPY_FORCE_SHA256', # interacts with CTR_DRBG_128_BIT_KEY
Gilles Peskinecfffc282020-04-12 13:55:45 +0200209 'MBEDTLS_HAVE_SSE2', # hardware dependency
210 'MBEDTLS_MEMORY_BACKTRACE', # depends on MEMORY_BUFFER_ALLOC_C
211 'MBEDTLS_MEMORY_BUFFER_ALLOC_C', # makes sanitizers (e.g. ASan) less effective
212 'MBEDTLS_MEMORY_DEBUG', # depends on MEMORY_BUFFER_ALLOC_C
Gilles Peskinec9d04332020-04-16 20:50:17 +0200213 'MBEDTLS_NO_64BIT_MULTIPLICATION', # influences anything that uses bignum
Gilles Peskinecfffc282020-04-12 13:55:45 +0200214 'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES', # removes a feature
215 'MBEDTLS_NO_PLATFORM_ENTROPY', # removes a feature
Gilles Peskinec9d04332020-04-16 20:50:17 +0200216 'MBEDTLS_NO_UDBL_DIVISION', # influences anything that uses bignum
Gilles Peskineefaee9a2023-09-20 20:49:47 +0200217 'MBEDTLS_PSA_P256M_DRIVER_ENABLED', # influences SECP256R1 KeyGen/ECDH/ECDSA
Gilles Peskinecfffc282020-04-12 13:55:45 +0200218 'MBEDTLS_PLATFORM_NO_STD_FUNCTIONS', # removes a feature
David Horstmann6f8c95b2024-03-14 14:52:45 +0000219 'MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS', # removes a feature
Gilles Peskinef08b3f82020-11-13 17:36:48 +0100220 'MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG', # behavior change + build dependency
Ronald Cronc3623db2020-10-29 10:51:32 +0100221 'MBEDTLS_PSA_CRYPTO_KEY_ID_ENCODES_OWNER', # incompatible with USE_PSA_CRYPTO
Gilles Peskinecfffc282020-04-12 13:55:45 +0200222 'MBEDTLS_PSA_CRYPTO_SPM', # platform dependency (PSA SPM)
Gilles Peskinea08def92023-04-28 21:01:49 +0200223 'MBEDTLS_PSA_INJECT_ENTROPY', # conflicts with platform entropy sources
Gilles Peskinec9d04332020-04-16 20:50:17 +0200224 'MBEDTLS_RSA_NO_CRT', # influences the use of RSA in X.509 and TLS
Tom Cosgrove87fbfb52022-03-15 10:51:52 +0000225 'MBEDTLS_SHA256_USE_A64_CRYPTO_ONLY', # interacts with *_USE_A64_CRYPTO_IF_PRESENT
Dave Rodgman9be3cf02023-10-11 14:47:55 +0100226 'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_ONLY', # interacts with *_USE_ARMV8_A_CRYPTO_IF_PRESENT
Tom Cosgrove87fbfb52022-03-15 10:51:52 +0000227 'MBEDTLS_SHA512_USE_A64_CRYPTO_ONLY', # interacts with *_USE_A64_CRYPTO_IF_PRESENT
Dave Rodgman7cb635a2023-10-12 16:14:51 +0100228 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', # setting *_USE_ARMV8_A_CRYPTO is sufficient
Manuel Pégourié-Gonnard6240def2020-07-10 09:35:54 +0200229 'MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN', # build dependency (clang+memsan)
Manuel Pégourié-Gonnard73afa372020-08-19 10:27:38 +0200230 'MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND', # build dependency (valgrind headers)
Hanno Beckere1113562019-06-12 13:59:14 +0100231 'MBEDTLS_X509_REMOVE_INFO', # removes a feature
Gilles Peskinebbaa2b72020-04-12 13:33:57 +0200232])
233
Gilles Peskine32e889d2020-04-12 23:43:28 +0200234def is_seamless_alt(name):
Gilles Peskinec34faba2020-04-20 15:44:14 +0200235 """Whether the xxx_ALT symbol should be included in the full configuration.
Gilles Peskine32e889d2020-04-12 23:43:28 +0200236
Gilles Peskinec34faba2020-04-20 15:44:14 +0200237 Include alternative implementations of platform functions, which are
Gilles Peskine32e889d2020-04-12 23:43:28 +0200238 configurable function pointers that default to the built-in function.
239 This way we test that the function pointers exist and build correctly
240 without changing the behavior, and tests can verify that the function
241 pointers are used by modifying those pointers.
242
243 Exclude alternative implementations of library functions since they require
244 an implementation of the relevant functions and an xxx_alt.h header.
245 """
Gilles Peskinea8861e02023-09-05 20:20:51 +0200246 if name in (
247 'MBEDTLS_PLATFORM_GMTIME_R_ALT',
248 'MBEDTLS_PLATFORM_SETUP_TEARDOWN_ALT',
249 'MBEDTLS_PLATFORM_MS_TIME_ALT',
250 'MBEDTLS_PLATFORM_ZEROIZE_ALT',
251 ):
Gilles Peskinec34faba2020-04-20 15:44:14 +0200252 # Similar to non-platform xxx_ALT, requires platform_alt.h
253 return False
Gilles Peskine32e889d2020-04-12 23:43:28 +0200254 return name.startswith('MBEDTLS_PLATFORM_')
255
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200256def include_in_full(name):
257 """Rules for symbols in the "full" configuration."""
Gabor Mezei3678dee2024-06-04 19:58:43 +0200258 if name in (EXCLUDE_FROM_FULL | UNSUPPORTED_FEATURE |
259 DEPRECATED_FEATURE | UNSTABLE_FEATURE):
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200260 return False
261 if name.endswith('_ALT'):
Gilles Peskine32e889d2020-04-12 23:43:28 +0200262 return is_seamless_alt(name)
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200263 return True
264
265def full_adapter(name, active, section):
266 """Config adapter for "full"."""
267 if not is_full_section(section):
268 return active
269 return include_in_full(name)
270
Gilles Peskinecfffc282020-04-12 13:55:45 +0200271# The baremetal configuration excludes options that require a library or
272# operating system feature that is typically not present on bare metal
273# systems. Features that are excluded from "full" won't be in "baremetal"
274# either (unless explicitly turned on in baremetal_adapter) so they don't
275# need to be repeated here.
Gilles Peskinebbaa2b72020-04-12 13:33:57 +0200276EXCLUDE_FROM_BAREMETAL = frozenset([
Gilles Peskinecfffc282020-04-12 13:55:45 +0200277 #pylint: disable=line-too-long
Gilles Peskine98f8f952020-04-20 15:38:39 +0200278 'MBEDTLS_ENTROPY_NV_SEED', # requires a filesystem and FS_IO or alternate NV seed hooks
Gilles Peskinecfffc282020-04-12 13:55:45 +0200279 'MBEDTLS_FS_IO', # requires a filesystem
Gilles Peskinecfffc282020-04-12 13:55:45 +0200280 'MBEDTLS_HAVE_TIME', # requires a clock
281 'MBEDTLS_HAVE_TIME_DATE', # requires a clock
282 'MBEDTLS_NET_C', # requires POSIX-like networking
283 'MBEDTLS_PLATFORM_FPRINTF_ALT', # requires FILE* from stdio.h
Gilles Peskine98f8f952020-04-20 15:38:39 +0200284 'MBEDTLS_PLATFORM_NV_SEED_ALT', # requires a filesystem and ENTROPY_NV_SEED
285 'MBEDTLS_PLATFORM_TIME_ALT', # requires a clock and HAVE_TIME
286 'MBEDTLS_PSA_CRYPTO_SE_C', # requires a filesystem and PSA_CRYPTO_STORAGE_C
Gilles Peskinecfffc282020-04-12 13:55:45 +0200287 'MBEDTLS_PSA_CRYPTO_STORAGE_C', # requires a filesystem
288 'MBEDTLS_PSA_ITS_FILE_C', # requires a filesystem
289 'MBEDTLS_THREADING_C', # requires a threading interface
290 'MBEDTLS_THREADING_PTHREAD', # requires pthread
291 'MBEDTLS_TIMING_C', # requires a clock
Dave Rodgman9be3cf02023-10-11 14:47:55 +0100292 'MBEDTLS_SHA256_USE_A64_CRYPTO_IF_PRESENT', # requires an OS for runtime-detection
Dave Rodgman5b89c552023-10-10 14:59:02 +0100293 'MBEDTLS_SHA256_USE_ARMV8_A_CRYPTO_IF_PRESENT', # requires an OS for runtime-detection
Dave Rodgmanbe7915a2023-10-11 10:46:38 +0100294 'MBEDTLS_SHA512_USE_A64_CRYPTO_IF_PRESENT', # requires an OS for runtime-detection
Gilles Peskinebbaa2b72020-04-12 13:33:57 +0200295])
296
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200297def keep_in_baremetal(name):
298 """Rules for symbols in the "baremetal" configuration."""
Gilles Peskinebbaa2b72020-04-12 13:33:57 +0200299 if name in EXCLUDE_FROM_BAREMETAL:
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200300 return False
301 return True
302
303def baremetal_adapter(name, active, section):
304 """Config adapter for "baremetal"."""
305 if not is_full_section(section):
306 return active
307 if name == 'MBEDTLS_NO_PLATFORM_ENTROPY':
Gilles Peskinecfffc282020-04-12 13:55:45 +0200308 # No OS-provided entropy source
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200309 return True
310 return include_in_full(name) and keep_in_baremetal(name)
311
Gilles Peskine120f29d2021-09-01 19:51:19 +0200312# This set contains options that are mostly for debugging or test purposes,
313# and therefore should be excluded when doing code size measurements.
314# Options that are their own module (such as MBEDTLS_ERROR_C) are not listed
315# and therefore will be included when doing code size measurements.
316EXCLUDE_FOR_SIZE = frozenset([
317 'MBEDTLS_DEBUG_C', # large code size increase in TLS
318 'MBEDTLS_SELF_TEST', # increases the size of many modules
319 'MBEDTLS_TEST_HOOKS', # only useful with the hosted test framework, increases code size
320])
321
322def baremetal_size_adapter(name, active, section):
323 if name in EXCLUDE_FOR_SIZE:
324 return False
325 return baremetal_adapter(name, active, section)
326
Gilles Peskine31987c62020-01-31 14:23:30 +0100327def include_in_crypto(name):
328 """Rules for symbols in a crypto configuration."""
329 if name.startswith('MBEDTLS_X509_') or \
330 name.startswith('MBEDTLS_SSL_') or \
331 name.startswith('MBEDTLS_KEY_EXCHANGE_'):
332 return False
333 if name in [
Gilles Peskinecfffc282020-04-12 13:55:45 +0200334 'MBEDTLS_DEBUG_C', # part of libmbedtls
335 'MBEDTLS_NET_C', # part of libmbedtls
Nayna Jainc9deb182020-11-16 19:03:12 +0000336 'MBEDTLS_PKCS7_C', # part of libmbedx509
Gilles Peskine31987c62020-01-31 14:23:30 +0100337 ]:
338 return False
339 return True
340
341def crypto_adapter(adapter):
342 """Modify an adapter to disable non-crypto symbols.
343
344 ``crypto_adapter(adapter)(name, active, section)`` is like
345 ``adapter(name, active, section)``, but unsets all X.509 and TLS symbols.
346 """
347 def continuation(name, active, section):
348 if not include_in_crypto(name):
349 return False
350 if adapter is None:
351 return active
352 return adapter(name, active, section)
353 return continuation
354
Gilles Peskineed5c21d2022-06-27 23:02:09 +0200355DEPRECATED = frozenset([
356 'MBEDTLS_PSA_CRYPTO_SE_C',
357])
Gilles Peskine30de2e82020-04-20 21:39:22 +0200358def no_deprecated_adapter(adapter):
Gilles Peskinebe1d6092020-04-12 14:17:16 +0200359 """Modify an adapter to disable deprecated symbols.
360
Gilles Peskine30de2e82020-04-20 21:39:22 +0200361 ``no_deprecated_adapter(adapter)(name, active, section)`` is like
Gilles Peskinebe1d6092020-04-12 14:17:16 +0200362 ``adapter(name, active, section)``, but unsets all deprecated symbols
363 and sets ``MBEDTLS_DEPRECATED_REMOVED``.
364 """
365 def continuation(name, active, section):
366 if name == 'MBEDTLS_DEPRECATED_REMOVED':
367 return True
Gilles Peskineed5c21d2022-06-27 23:02:09 +0200368 if name in DEPRECATED:
369 return False
Gilles Peskinebe1d6092020-04-12 14:17:16 +0200370 if adapter is None:
371 return active
372 return adapter(name, active, section)
373 return continuation
374
Paul Elliottfb81f772023-10-18 17:44:59 +0100375def no_platform_adapter(adapter):
376 """Modify an adapter to disable platform symbols.
377
378 ``no_platform_adapter(adapter)(name, active, section)`` is like
379 ``adapter(name, active, section)``, but unsets all platform symbols other
380 ``than MBEDTLS_PLATFORM_C.
381 """
382 def continuation(name, active, section):
383 # Allow MBEDTLS_PLATFORM_C but remove all other platform symbols.
384 if name.startswith('MBEDTLS_PLATFORM_') and name != 'MBEDTLS_PLATFORM_C':
385 return False
386 if adapter is None:
387 return active
388 return adapter(name, active, section)
389 return continuation
390
Gabor Mezei3678dee2024-06-04 19:58:43 +0200391class ConfigFile(metaclass=ABCMeta):
392 def __init__(self, default_path, filename=None, name=''):
Gilles Peskineb4063892019-07-27 21:36:44 +0200393 if filename is None:
Gabor Mezei3678dee2024-06-04 19:58:43 +0200394 for candidate in default_path:
Gilles Peskinece674a92020-03-24 15:37:00 +0100395 if os.path.lexists(candidate):
396 filename = candidate
Gilles Peskine208e4ec2019-07-29 23:43:20 +0200397 break
Gilles Peskinece674a92020-03-24 15:37:00 +0100398 else:
Gabor Mezei3678dee2024-06-04 19:58:43 +0200399 raise Exception(name + ' configuration file not found',
400 default_path)
Gilles Peskineb4063892019-07-27 21:36:44 +0200401
Gabor Mezei3678dee2024-06-04 19:58:43 +0200402 self.filename = filename
403 self.templates = []
404 self.current_section = None
405 self.inclusion_guard = None
Gilles Peskineb4063892019-07-27 21:36:44 +0200406
407 _define_line_regexp = (r'(?P<indentation>\s*)' +
408 r'(?P<commented_out>(//\s*)?)' +
409 r'(?P<define>#\s*define\s+)' +
410 r'(?P<name>\w+)' +
411 r'(?P<arguments>(?:\((?:\w|\s|,)*\))?)' +
412 r'(?P<separator>\s*)' +
413 r'(?P<value>.*)')
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200414 _ifndef_line_regexp = r'#ifndef (?P<inclusion_guard>\w+)'
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200415 _section_line_regexp = (r'\s*/?\*+\s*[\\@]name\s+SECTION:\s*' +
416 r'(?P<section>.*)[ */]*')
417 _config_line_regexp = re.compile(r'|'.join([_define_line_regexp,
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200418 _ifndef_line_regexp,
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200419 _section_line_regexp]))
Gilles Peskineb4063892019-07-27 21:36:44 +0200420 def _parse_line(self, line):
Gabor Mezei3678dee2024-06-04 19:58:43 +0200421 """Parse a line in the config file and return the corresponding template."""
Gilles Peskineb4063892019-07-27 21:36:44 +0200422 line = line.rstrip('\r\n')
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200423 m = re.match(self._config_line_regexp, line)
424 if m is None:
Gabor Mezei3678dee2024-06-04 19:58:43 +0200425 self.templates.append(line)
426 return None
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200427 elif m.group('section'):
428 self.current_section = m.group('section')
Gabor Mezei3678dee2024-06-04 19:58:43 +0200429 self.templates.append(line)
430 return None
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200431 elif m.group('inclusion_guard') and self.inclusion_guard is None:
432 self.inclusion_guard = m.group('inclusion_guard')
Gabor Mezei3678dee2024-06-04 19:58:43 +0200433 self.templates.append(line)
434 return None
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200435 else:
Gilles Peskineb4063892019-07-27 21:36:44 +0200436 active = not m.group('commented_out')
437 name = m.group('name')
438 value = m.group('value')
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200439 if name == self.inclusion_guard and value == '':
440 # The file double-inclusion guard is not an option.
Gabor Mezei3678dee2024-06-04 19:58:43 +0200441 self.templates.append(line)
442 return None
Gilles Peskineb4063892019-07-27 21:36:44 +0200443 template = (name,
444 m.group('indentation'),
445 m.group('define') + name +
446 m.group('arguments') + m.group('separator'))
Gabor Mezei3678dee2024-06-04 19:58:43 +0200447 self.templates.append(template)
Gilles Peskineb4063892019-07-27 21:36:44 +0200448
Gabor Mezei3678dee2024-06-04 19:58:43 +0200449 return (active, name, value, self.current_section)
450
451 def parse_file(self):
452 with open(self.filename, 'r', encoding='utf-8') as file:
453 for line in file:
454 setting = self._parse_line(line)
455 if setting is not None:
456 yield setting
457 self.current_section = None
458
459 @abstractmethod
460 def _format_template(self, setting, name, indent, middle):
461 pass
462
463 def write_to_stream(self, settings, output):
464 """Write the whole configuration to output."""
465 for template in self.templates:
466 if isinstance(template, str):
467 line = template
468 else:
469 name, _, _ = template
470 line = self._format_template(settings[name], *template)
471 output.write(line + '\n')
472
473 def write(self, settings, filename=None):
474 """Write the whole configuration to the file it was read from.
475
476 If filename is specified, write to this file instead.
477 """
478 if filename is None:
479 filename = self.filename
480 with open(filename, 'w', encoding='utf-8') as output:
481 self.write_to_stream(settings, output)
482
483class MbedtlsConfigFile(ConfigFile):
484 _path_in_tree = 'include/mbedtls/mbedtls_config.h'
485 default_path = [_path_in_tree,
486 os.path.join(os.path.dirname(__file__),
487 os.pardir,
488 _path_in_tree),
489 os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
490 _path_in_tree)]
491
492 def __init__(self, filename=None):
493 super().__init__(self.default_path, filename, 'Mbed TLS')
494 self.current_section = 'header'
495
496 def _format_template(self, setting, name, indent, middle):
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +0200497 """Build a line for mbedtls_config.h for the given setting.
Gilles Peskineb4063892019-07-27 21:36:44 +0200498
Gilles Peskinec190c902019-08-01 23:31:05 +0200499 The line has the form "<indent>#define <name> <value>"
500 where <middle> is "#define <name> ".
Gilles Peskineb4063892019-07-27 21:36:44 +0200501 """
Gilles Peskinef6860422019-09-04 22:51:47 +0200502 value = setting.value
503 if value is None:
504 value = ''
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800505 # Normally the whitespace to separate the symbol name from the
Gilles Peskinef6860422019-09-04 22:51:47 +0200506 # value is part of middle, and there's no whitespace for a symbol
507 # with no value. But if a symbol has been changed from having a
508 # value to not having one, the whitespace is wrong, so fix it.
509 if value:
510 if middle[-1] not in '\t ':
511 middle += ' '
512 else:
513 middle = middle.rstrip()
Gilles Peskineb4063892019-07-27 21:36:44 +0200514 return ''.join([indent,
515 '' if setting.active else '//',
516 middle,
Gilles Peskinef6860422019-09-04 22:51:47 +0200517 value]).rstrip()
Gilles Peskineb4063892019-07-27 21:36:44 +0200518
Gabor Mezei3678dee2024-06-04 19:58:43 +0200519class CryptoConfigFile(ConfigFile):
520 _path_in_tree = 'tf-psa-crypto/include/psa/crypto_config.h'
521 default_path = [_path_in_tree,
522 os.path.join(os.path.dirname(__file__),
523 os.pardir,
524 _path_in_tree),
525 os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
526 _path_in_tree)]
527
528 def __init__(self, filename=None):
529 super().__init__(self.default_path, filename, 'Crypto')
530
531 def _format_template(self, setting, name, indent, middle):
532 """Build a line for crypto_config.h for the given setting.
533
534 The line has the form "<indent>#define <name> <value>"
535 where <middle> is "#define <name> ".
536 """
537 value = setting.value
538 if value is None:
539 value = '1'
540 if middle[-1] not in '\t ':
541 middle += ' '
542 return ''.join([indent,
543 '' if setting.active else '//',
544 middle,
545 value]).rstrip()
546
547class MbedtlsConfig(Config):
548 """Representation of the Mbed TLS configuration read for a file.
549
550 See the documentation of the `Config` class for methods to query
551 and modify the configuration.
552 """
553 def __init__(self, mbedtls_config=None, **kw):
554 """Read the Mbed TLS configuration file."""
555 super().__init__()
556 self.mbedtls_config = MbedtlsConfigFile(mbedtls_config)
557 self.settings.update({name: Setting(active, name, value, section, self.mbedtls_config)
558 for (active, name, value, section)
559 in self.mbedtls_config.parse_file()})
560
561 def set(self, name, value=None):
562 if name not in self.settings:
563 self.mbedtls_config.templates.append((name, '', '#define ' + name + ' '))
564 super().set(name, value)
Gilles Peskineb4063892019-07-27 21:36:44 +0200565
566 def write(self, filename=None):
Gabor Mezei3678dee2024-06-04 19:58:43 +0200567 self.mbedtls_config.write(self.settings, filename)
Gilles Peskineb4063892019-07-27 21:36:44 +0200568
Gabor Mezei3678dee2024-06-04 19:58:43 +0200569 def filename(self, name):
570 return self.mbedtls_config.filename
571
572class CryptoConfig(Config):
573 """Representation of the PSA crypto configuration read for a file.
574
575 See the documentation of the `Config` class for methods to query
576 and modify the configuration.
577 """
578 def __init__(self, crypto_config=None, **kw):
579 """Read the PSA crypto configuration file."""
580 super().__init__()
581 self.crypto_config = CryptoConfigFile(crypto_config)
582 self.settings.update({name: Setting(active, name, value, section, self.crypto_config)
583 for (active, name, value, section)
584 in self.crypto_config.parse_file()})
585
586 def set(self, name, value=None):
587 if name in UNSUPPORTED_FEATURE:
588 raise ValueError('Feature is unsupported: \'{}\''.format(name))
589 if name in UNSTABLE_FEATURE:
590 raise ValueError('Feature is unstable: \'{}\''.format(name))
591
592 if name not in self.settings:
593 self.crypto_config.templates.append((name, '', '#define ' + name + ' ' + '1'))
594 super().set(name, value)
595
596 def write(self, filename=None):
597 self.crypto_config.write(self.settings, filename)
598
599 def filename(self, name):
600 return self.crypto_config.filename
601
602class MultiConfig(MbedtlsConfig, CryptoConfig):
603
604 def __init__(self, mbedtls_config, crypto_config):
605 super().__init__(mbedtls_config=mbedtls_config, crypto_config=crypto_config)
606
607 _crypto_regexp = re.compile(r'$PSA_.*')
608 def _get_related_config(self, name):
609 if re.match(self._crypto_regexp, name):
610 return CryptoConfig
611 else:
612 return MbedtlsConfig
613
614 def set(self, name, value=None):
615 super(self._get_related_config(name), self).set(name, value)
616
617 def write(self, mbedtls_file=None, crypto_file=None):
618 self.mbedtls_config.write(self.settings, mbedtls_file)
619 self.crypto_config.write(self.settings, crypto_file)
620
621 def filename(self, name):
622 return self.settings[name].configfile
Gilles Peskineb4063892019-07-27 21:36:44 +0200623
624if __name__ == '__main__':
625 def main():
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +0200626 """Command line mbedtls_config.h manipulation tool."""
Gilles Peskineb4063892019-07-27 21:36:44 +0200627 parser = argparse.ArgumentParser(description="""
Fredrik Hesse0ec8a902021-10-04 22:13:51 +0200628 Mbed TLS configuration file manipulation tool.
Gilles Peskineb4063892019-07-27 21:36:44 +0200629 """)
630 parser.add_argument('--file', '-f',
631 help="""File to read (and modify if requested).
632 Default: {}.
Gabor Mezei3678dee2024-06-04 19:58:43 +0200633 """.format(MbedtlsConfigFile.default_path))
634 parser.add_argument('--cryptofile', '-c',
635 help="""Crypto file to read (and modify if requested).
636 Default: {}.
637 """.format(CryptoConfigFile.default_path))
Gilles Peskineb4063892019-07-27 21:36:44 +0200638 parser.add_argument('--force', '-o',
Gilles Peskine435ce222019-08-01 23:13:47 +0200639 action='store_true',
Gilles Peskineb4063892019-07-27 21:36:44 +0200640 help="""For the set command, if SYMBOL is not
641 present, add a definition for it.""")
Gilles Peskinec190c902019-08-01 23:31:05 +0200642 parser.add_argument('--write', '-w', metavar='FILE',
Gilles Peskine40f103c2019-07-27 23:44:01 +0200643 help="""File to write to instead of the input file.""")
Gilles Peskineb4063892019-07-27 21:36:44 +0200644 subparsers = parser.add_subparsers(dest='command',
645 title='Commands')
646 parser_get = subparsers.add_parser('get',
647 help="""Find the value of SYMBOL
648 and print it. Exit with
649 status 0 if a #define for SYMBOL is
650 found, 1 otherwise.
651 """)
652 parser_get.add_argument('symbol', metavar='SYMBOL')
653 parser_set = subparsers.add_parser('set',
654 help="""Set SYMBOL to VALUE.
655 If VALUE is omitted, just uncomment
656 the #define for SYMBOL.
657 Error out of a line defining
658 SYMBOL (commented or not) is not
659 found, unless --force is passed.
660 """)
661 parser_set.add_argument('symbol', metavar='SYMBOL')
Gilles Peskine0c7fcd22019-08-01 23:14:00 +0200662 parser_set.add_argument('value', metavar='VALUE', nargs='?',
663 default='')
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200664 parser_set_all = subparsers.add_parser('set-all',
665 help="""Uncomment all #define
666 whose name contains a match for
667 REGEX.""")
668 parser_set_all.add_argument('regexs', metavar='REGEX', nargs='*')
Gilles Peskineb4063892019-07-27 21:36:44 +0200669 parser_unset = subparsers.add_parser('unset',
670 help="""Comment out the #define
671 for SYMBOL. Do nothing if none
672 is present.""")
673 parser_unset.add_argument('symbol', metavar='SYMBOL')
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200674 parser_unset_all = subparsers.add_parser('unset-all',
675 help="""Comment out all #define
676 whose name contains a match for
677 REGEX.""")
678 parser_unset_all.add_argument('regexs', metavar='REGEX', nargs='*')
Gilles Peskineb4063892019-07-27 21:36:44 +0200679
680 def add_adapter(name, function, description):
681 subparser = subparsers.add_parser(name, help=description)
682 subparser.set_defaults(adapter=function)
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200683 add_adapter('baremetal', baremetal_adapter,
684 """Like full, but exclude features that require platform
685 features such as file input-output.""")
Gilles Peskine120f29d2021-09-01 19:51:19 +0200686 add_adapter('baremetal_size', baremetal_size_adapter,
687 """Like baremetal, but exclude debugging features.
688 Useful for code size measurements.""")
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200689 add_adapter('full', full_adapter,
690 """Uncomment most features.
691 Exclude alternative implementations and platform support
692 options, as well as some options that are awkward to test.
693 """)
Gilles Peskine30de2e82020-04-20 21:39:22 +0200694 add_adapter('full_no_deprecated', no_deprecated_adapter(full_adapter),
Gilles Peskinebe1d6092020-04-12 14:17:16 +0200695 """Uncomment most non-deprecated features.
696 Like "full", but without deprecated features.
697 """)
Paul Elliottfb81f772023-10-18 17:44:59 +0100698 add_adapter('full_no_platform', no_platform_adapter(full_adapter),
699 """Uncomment most non-platform features.
700 Like "full", but without platform features.
701 """)
Gilles Peskineb4063892019-07-27 21:36:44 +0200702 add_adapter('realfull', realfull_adapter,
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200703 """Uncomment all boolean #defines.
704 Suitable for generating documentation, but not for building.""")
Gilles Peskine31987c62020-01-31 14:23:30 +0100705 add_adapter('crypto', crypto_adapter(None),
706 """Only include crypto features. Exclude X.509 and TLS.""")
707 add_adapter('crypto_baremetal', crypto_adapter(baremetal_adapter),
708 """Like baremetal, but with only crypto features,
709 excluding X.509 and TLS.""")
710 add_adapter('crypto_full', crypto_adapter(full_adapter),
711 """Like full, but with only crypto features,
712 excluding X.509 and TLS.""")
Gilles Peskineb4063892019-07-27 21:36:44 +0200713
714 args = parser.parse_args()
Gabor Mezei3678dee2024-06-04 19:58:43 +0200715 config = MultiConfig(args.file, args.cryptofile)
Gilles Peskine90b30b62019-07-28 00:36:53 +0200716 if args.command is None:
717 parser.print_help()
718 return 1
719 elif args.command == 'get':
Gilles Peskineb4063892019-07-27 21:36:44 +0200720 if args.symbol in config:
721 value = config[args.symbol]
722 if value:
723 sys.stdout.write(value + '\n')
Gilles Peskinee22a4da2020-03-24 15:43:49 +0100724 return 0 if args.symbol in config else 1
Gilles Peskineb4063892019-07-27 21:36:44 +0200725 elif args.command == 'set':
Gilles Peskine98eb3652019-07-28 16:39:19 +0200726 if not args.force and args.symbol not in config.settings:
Gilles Peskineb4063892019-07-27 21:36:44 +0200727 sys.stderr.write("A #define for the symbol {} "
Gilles Peskine221df1e2019-08-01 23:14:29 +0200728 "was not found in {}\n"
Gabor Mezei3678dee2024-06-04 19:58:43 +0200729 .format(args.symbol, config.filename(args.symbol)))
Gilles Peskineb4063892019-07-27 21:36:44 +0200730 return 1
731 config.set(args.symbol, value=args.value)
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200732 elif args.command == 'set-all':
733 config.change_matching(args.regexs, True)
Gilles Peskineb4063892019-07-27 21:36:44 +0200734 elif args.command == 'unset':
735 config.unset(args.symbol)
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200736 elif args.command == 'unset-all':
737 config.change_matching(args.regexs, False)
Gilles Peskineb4063892019-07-27 21:36:44 +0200738 else:
739 config.adapt(args.adapter)
Gilles Peskine40f103c2019-07-27 23:44:01 +0200740 config.write(args.write)
Gilles Peskinee22a4da2020-03-24 15:43:49 +0100741 return 0
Gilles Peskineb4063892019-07-27 21:36:44 +0200742
743 # Import modules only used by main only if main is defined and called.
744 # pylint: disable=wrong-import-position
745 import argparse
746 import sys
747 sys.exit(main())