blob: 120c827d6e394bd61944b954e7d2c8dea4683cda [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):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200392 """Representation of a configuration file."""
393
Gabor Mezei3678dee2024-06-04 19:58:43 +0200394 def __init__(self, default_path, filename=None, name=''):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200395 """Check if the config file exists."""
Gilles Peskineb4063892019-07-27 21:36:44 +0200396 if filename is None:
Gabor Mezei3678dee2024-06-04 19:58:43 +0200397 for candidate in default_path:
Gilles Peskinece674a92020-03-24 15:37:00 +0100398 if os.path.lexists(candidate):
399 filename = candidate
Gilles Peskine208e4ec2019-07-29 23:43:20 +0200400 break
Gilles Peskinece674a92020-03-24 15:37:00 +0100401 else:
Gabor Mezei3678dee2024-06-04 19:58:43 +0200402 raise Exception(name + ' configuration file not found',
403 default_path)
Gilles Peskineb4063892019-07-27 21:36:44 +0200404
Gabor Mezei3678dee2024-06-04 19:58:43 +0200405 self.filename = filename
406 self.templates = []
407 self.current_section = None
408 self.inclusion_guard = None
Gilles Peskineb4063892019-07-27 21:36:44 +0200409
410 _define_line_regexp = (r'(?P<indentation>\s*)' +
411 r'(?P<commented_out>(//\s*)?)' +
412 r'(?P<define>#\s*define\s+)' +
413 r'(?P<name>\w+)' +
414 r'(?P<arguments>(?:\((?:\w|\s|,)*\))?)' +
415 r'(?P<separator>\s*)' +
416 r'(?P<value>.*)')
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200417 _ifndef_line_regexp = r'#ifndef (?P<inclusion_guard>\w+)'
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200418 _section_line_regexp = (r'\s*/?\*+\s*[\\@]name\s+SECTION:\s*' +
419 r'(?P<section>.*)[ */]*')
420 _config_line_regexp = re.compile(r'|'.join([_define_line_regexp,
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200421 _ifndef_line_regexp,
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200422 _section_line_regexp]))
Gilles Peskineb4063892019-07-27 21:36:44 +0200423 def _parse_line(self, line):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200424 """Parse a line in the config file, save the templates representing the lines
425 and return the corresponding setting element.
426 """
Gilles Peskineb4063892019-07-27 21:36:44 +0200427 line = line.rstrip('\r\n')
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200428 m = re.match(self._config_line_regexp, line)
429 if m is None:
Gabor Mezei3678dee2024-06-04 19:58:43 +0200430 self.templates.append(line)
431 return None
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200432 elif m.group('section'):
433 self.current_section = m.group('section')
Gabor Mezei3678dee2024-06-04 19:58:43 +0200434 self.templates.append(line)
435 return None
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200436 elif m.group('inclusion_guard') and self.inclusion_guard is None:
437 self.inclusion_guard = m.group('inclusion_guard')
Gabor Mezei3678dee2024-06-04 19:58:43 +0200438 self.templates.append(line)
439 return None
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200440 else:
Gilles Peskineb4063892019-07-27 21:36:44 +0200441 active = not m.group('commented_out')
442 name = m.group('name')
443 value = m.group('value')
Gilles Peskine9ba9c212024-05-23 15:03:43 +0200444 if name == self.inclusion_guard and value == '':
445 # The file double-inclusion guard is not an option.
Gabor Mezei3678dee2024-06-04 19:58:43 +0200446 self.templates.append(line)
447 return None
Gilles Peskineb4063892019-07-27 21:36:44 +0200448 template = (name,
449 m.group('indentation'),
450 m.group('define') + name +
451 m.group('arguments') + m.group('separator'))
Gabor Mezei3678dee2024-06-04 19:58:43 +0200452 self.templates.append(template)
Gilles Peskineb4063892019-07-27 21:36:44 +0200453
Gabor Mezei3678dee2024-06-04 19:58:43 +0200454 return (active, name, value, self.current_section)
455
456 def parse_file(self):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200457 """Parse the whole file and return the settings."""
Gabor Mezei3678dee2024-06-04 19:58:43 +0200458 with open(self.filename, 'r', encoding='utf-8') as file:
459 for line in file:
460 setting = self._parse_line(line)
461 if setting is not None:
462 yield setting
463 self.current_section = None
464
465 @abstractmethod
466 def _format_template(self, setting, name, indent, middle):
467 pass
468
469 def write_to_stream(self, settings, output):
470 """Write the whole configuration to output."""
471 for template in self.templates:
472 if isinstance(template, str):
473 line = template
474 else:
475 name, _, _ = template
476 line = self._format_template(settings[name], *template)
477 output.write(line + '\n')
478
479 def write(self, settings, filename=None):
480 """Write the whole configuration to the file it was read from.
481
482 If filename is specified, write to this file instead.
483 """
484 if filename is None:
485 filename = self.filename
486 with open(filename, 'w', encoding='utf-8') as output:
487 self.write_to_stream(settings, output)
488
489class MbedtlsConfigFile(ConfigFile):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200490 """Representation of an MbedTLS configuration file."""
491
Gabor Mezei3678dee2024-06-04 19:58:43 +0200492 _path_in_tree = 'include/mbedtls/mbedtls_config.h'
493 default_path = [_path_in_tree,
494 os.path.join(os.path.dirname(__file__),
495 os.pardir,
496 _path_in_tree),
497 os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
498 _path_in_tree)]
499
500 def __init__(self, filename=None):
501 super().__init__(self.default_path, filename, 'Mbed TLS')
502 self.current_section = 'header'
503
504 def _format_template(self, setting, name, indent, middle):
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +0200505 """Build a line for mbedtls_config.h for the given setting.
Gilles Peskineb4063892019-07-27 21:36:44 +0200506
Gilles Peskinec190c902019-08-01 23:31:05 +0200507 The line has the form "<indent>#define <name> <value>"
508 where <middle> is "#define <name> ".
Gilles Peskineb4063892019-07-27 21:36:44 +0200509 """
Gilles Peskinef6860422019-09-04 22:51:47 +0200510 value = setting.value
511 if value is None:
512 value = ''
Shaun Case8b0ecbc2021-12-20 21:14:10 -0800513 # Normally the whitespace to separate the symbol name from the
Gilles Peskinef6860422019-09-04 22:51:47 +0200514 # value is part of middle, and there's no whitespace for a symbol
515 # with no value. But if a symbol has been changed from having a
516 # value to not having one, the whitespace is wrong, so fix it.
517 if value:
518 if middle[-1] not in '\t ':
519 middle += ' '
520 else:
521 middle = middle.rstrip()
Gilles Peskineb4063892019-07-27 21:36:44 +0200522 return ''.join([indent,
523 '' if setting.active else '//',
524 middle,
Gilles Peskinef6860422019-09-04 22:51:47 +0200525 value]).rstrip()
Gilles Peskineb4063892019-07-27 21:36:44 +0200526
Gabor Mezei3678dee2024-06-04 19:58:43 +0200527class CryptoConfigFile(ConfigFile):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200528 """Representation of an Crypto configuration file."""
529
Gabor Mezei3678dee2024-06-04 19:58:43 +0200530 _path_in_tree = 'tf-psa-crypto/include/psa/crypto_config.h'
531 default_path = [_path_in_tree,
532 os.path.join(os.path.dirname(__file__),
533 os.pardir,
534 _path_in_tree),
535 os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
536 _path_in_tree)]
537
538 def __init__(self, filename=None):
539 super().__init__(self.default_path, filename, 'Crypto')
540
541 def _format_template(self, setting, name, indent, middle):
542 """Build a line for crypto_config.h for the given setting.
543
544 The line has the form "<indent>#define <name> <value>"
545 where <middle> is "#define <name> ".
546 """
547 value = setting.value
548 if value is None:
549 value = '1'
550 if middle[-1] not in '\t ':
551 middle += ' '
552 return ''.join([indent,
553 '' if setting.active else '//',
554 middle,
555 value]).rstrip()
556
557class MbedtlsConfig(Config):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200558 """Representation of the Mbed TLS configuration.
Gabor Mezei3678dee2024-06-04 19:58:43 +0200559
560 See the documentation of the `Config` class for methods to query
561 and modify the configuration.
562 """
563 def __init__(self, mbedtls_config=None, **kw):
564 """Read the Mbed TLS configuration file."""
565 super().__init__()
566 self.mbedtls_config = MbedtlsConfigFile(mbedtls_config)
567 self.settings.update({name: Setting(active, name, value, section, self.mbedtls_config)
568 for (active, name, value, section)
569 in self.mbedtls_config.parse_file()})
570
571 def set(self, name, value=None):
572 if name not in self.settings:
573 self.mbedtls_config.templates.append((name, '', '#define ' + name + ' '))
574 super().set(name, value)
Gilles Peskineb4063892019-07-27 21:36:44 +0200575
576 def write(self, filename=None):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200577 """Write the whole configuration to the file it was read from.
578
579 If filename is specified, write to this file instead.
580 """
Gabor Mezei3678dee2024-06-04 19:58:43 +0200581 self.mbedtls_config.write(self.settings, filename)
Gilles Peskineb4063892019-07-27 21:36:44 +0200582
Gabor Mezei3678dee2024-06-04 19:58:43 +0200583 def filename(self, name):
584 return self.mbedtls_config.filename
585
586class CryptoConfig(Config):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200587 """Representation of the PSA crypto configuration.
Gabor Mezei3678dee2024-06-04 19:58:43 +0200588
589 See the documentation of the `Config` class for methods to query
590 and modify the configuration.
591 """
592 def __init__(self, crypto_config=None, **kw):
593 """Read the PSA crypto configuration file."""
594 super().__init__()
595 self.crypto_config = CryptoConfigFile(crypto_config)
596 self.settings.update({name: Setting(active, name, value, section, self.crypto_config)
597 for (active, name, value, section)
598 in self.crypto_config.parse_file()})
599
600 def set(self, name, value=None):
601 if name in UNSUPPORTED_FEATURE:
602 raise ValueError('Feature is unsupported: \'{}\''.format(name))
603 if name in UNSTABLE_FEATURE:
604 raise ValueError('Feature is unstable: \'{}\''.format(name))
605
606 if name not in self.settings:
607 self.crypto_config.templates.append((name, '', '#define ' + name + ' ' + '1'))
608 super().set(name, value)
609
610 def write(self, filename=None):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200611 """Write the whole configuration to the file it was read from.
612
613 If filename is specified, write to this file instead.
614 """
Gabor Mezei3678dee2024-06-04 19:58:43 +0200615 self.crypto_config.write(self.settings, filename)
616
617 def filename(self, name):
618 return self.crypto_config.filename
619
620class MultiConfig(MbedtlsConfig, CryptoConfig):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200621 """Representation of MbedTLS and PSA crypto configuration
622
623 See the documentation of the `Config` class for methods to query
624 and modify the configuration.
625 """
Gabor Mezei3678dee2024-06-04 19:58:43 +0200626
627 def __init__(self, mbedtls_config, crypto_config):
628 super().__init__(mbedtls_config=mbedtls_config, crypto_config=crypto_config)
629
630 _crypto_regexp = re.compile(r'$PSA_.*')
631 def _get_related_config(self, name):
632 if re.match(self._crypto_regexp, name):
633 return CryptoConfig
634 else:
635 return MbedtlsConfig
636
637 def set(self, name, value=None):
638 super(self._get_related_config(name), self).set(name, value)
639
640 def write(self, mbedtls_file=None, crypto_file=None):
Gabor Mezei62a9bd02024-06-07 13:44:40 +0200641 """Write the whole configuration to the file it was read from.
642
643 If mbedtls_file or crypto_file is specified, write the specific configuration
644 to the corresponding file instead.
645 """
Gabor Mezei3678dee2024-06-04 19:58:43 +0200646 self.mbedtls_config.write(self.settings, mbedtls_file)
647 self.crypto_config.write(self.settings, crypto_file)
648
649 def filename(self, name):
650 return self.settings[name].configfile
Gilles Peskineb4063892019-07-27 21:36:44 +0200651
652if __name__ == '__main__':
653 def main():
Bence Szépkútibb0cfeb2021-05-28 09:42:25 +0200654 """Command line mbedtls_config.h manipulation tool."""
Gilles Peskineb4063892019-07-27 21:36:44 +0200655 parser = argparse.ArgumentParser(description="""
Fredrik Hesse0ec8a902021-10-04 22:13:51 +0200656 Mbed TLS configuration file manipulation tool.
Gilles Peskineb4063892019-07-27 21:36:44 +0200657 """)
658 parser.add_argument('--file', '-f',
659 help="""File to read (and modify if requested).
660 Default: {}.
Gabor Mezei3678dee2024-06-04 19:58:43 +0200661 """.format(MbedtlsConfigFile.default_path))
662 parser.add_argument('--cryptofile', '-c',
663 help="""Crypto file to read (and modify if requested).
664 Default: {}.
665 """.format(CryptoConfigFile.default_path))
Gilles Peskineb4063892019-07-27 21:36:44 +0200666 parser.add_argument('--force', '-o',
Gilles Peskine435ce222019-08-01 23:13:47 +0200667 action='store_true',
Gilles Peskineb4063892019-07-27 21:36:44 +0200668 help="""For the set command, if SYMBOL is not
669 present, add a definition for it.""")
Gilles Peskinec190c902019-08-01 23:31:05 +0200670 parser.add_argument('--write', '-w', metavar='FILE',
Gilles Peskine40f103c2019-07-27 23:44:01 +0200671 help="""File to write to instead of the input file.""")
Gilles Peskineb4063892019-07-27 21:36:44 +0200672 subparsers = parser.add_subparsers(dest='command',
673 title='Commands')
674 parser_get = subparsers.add_parser('get',
675 help="""Find the value of SYMBOL
676 and print it. Exit with
677 status 0 if a #define for SYMBOL is
678 found, 1 otherwise.
679 """)
680 parser_get.add_argument('symbol', metavar='SYMBOL')
681 parser_set = subparsers.add_parser('set',
682 help="""Set SYMBOL to VALUE.
683 If VALUE is omitted, just uncomment
684 the #define for SYMBOL.
685 Error out of a line defining
686 SYMBOL (commented or not) is not
687 found, unless --force is passed.
688 """)
689 parser_set.add_argument('symbol', metavar='SYMBOL')
Gilles Peskine0c7fcd22019-08-01 23:14:00 +0200690 parser_set.add_argument('value', metavar='VALUE', nargs='?',
691 default='')
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200692 parser_set_all = subparsers.add_parser('set-all',
693 help="""Uncomment all #define
694 whose name contains a match for
695 REGEX.""")
696 parser_set_all.add_argument('regexs', metavar='REGEX', nargs='*')
Gilles Peskineb4063892019-07-27 21:36:44 +0200697 parser_unset = subparsers.add_parser('unset',
698 help="""Comment out the #define
699 for SYMBOL. Do nothing if none
700 is present.""")
701 parser_unset.add_argument('symbol', metavar='SYMBOL')
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200702 parser_unset_all = subparsers.add_parser('unset-all',
703 help="""Comment out all #define
704 whose name contains a match for
705 REGEX.""")
706 parser_unset_all.add_argument('regexs', metavar='REGEX', nargs='*')
Gilles Peskineb4063892019-07-27 21:36:44 +0200707
708 def add_adapter(name, function, description):
709 subparser = subparsers.add_parser(name, help=description)
710 subparser.set_defaults(adapter=function)
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200711 add_adapter('baremetal', baremetal_adapter,
712 """Like full, but exclude features that require platform
713 features such as file input-output.""")
Gilles Peskine120f29d2021-09-01 19:51:19 +0200714 add_adapter('baremetal_size', baremetal_size_adapter,
715 """Like baremetal, but exclude debugging features.
716 Useful for code size measurements.""")
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200717 add_adapter('full', full_adapter,
718 """Uncomment most features.
719 Exclude alternative implementations and platform support
720 options, as well as some options that are awkward to test.
721 """)
Gilles Peskine30de2e82020-04-20 21:39:22 +0200722 add_adapter('full_no_deprecated', no_deprecated_adapter(full_adapter),
Gilles Peskinebe1d6092020-04-12 14:17:16 +0200723 """Uncomment most non-deprecated features.
724 Like "full", but without deprecated features.
725 """)
Paul Elliottfb81f772023-10-18 17:44:59 +0100726 add_adapter('full_no_platform', no_platform_adapter(full_adapter),
727 """Uncomment most non-platform features.
728 Like "full", but without platform features.
729 """)
Gilles Peskineb4063892019-07-27 21:36:44 +0200730 add_adapter('realfull', realfull_adapter,
Gilles Peskine53d41ae2019-07-27 23:31:53 +0200731 """Uncomment all boolean #defines.
732 Suitable for generating documentation, but not for building.""")
Gilles Peskine31987c62020-01-31 14:23:30 +0100733 add_adapter('crypto', crypto_adapter(None),
734 """Only include crypto features. Exclude X.509 and TLS.""")
735 add_adapter('crypto_baremetal', crypto_adapter(baremetal_adapter),
736 """Like baremetal, but with only crypto features,
737 excluding X.509 and TLS.""")
738 add_adapter('crypto_full', crypto_adapter(full_adapter),
739 """Like full, but with only crypto features,
740 excluding X.509 and TLS.""")
Gilles Peskineb4063892019-07-27 21:36:44 +0200741
742 args = parser.parse_args()
Gabor Mezei3678dee2024-06-04 19:58:43 +0200743 config = MultiConfig(args.file, args.cryptofile)
Gilles Peskine90b30b62019-07-28 00:36:53 +0200744 if args.command is None:
745 parser.print_help()
746 return 1
747 elif args.command == 'get':
Gilles Peskineb4063892019-07-27 21:36:44 +0200748 if args.symbol in config:
749 value = config[args.symbol]
750 if value:
751 sys.stdout.write(value + '\n')
Gilles Peskinee22a4da2020-03-24 15:43:49 +0100752 return 0 if args.symbol in config else 1
Gilles Peskineb4063892019-07-27 21:36:44 +0200753 elif args.command == 'set':
Gilles Peskine98eb3652019-07-28 16:39:19 +0200754 if not args.force and args.symbol not in config.settings:
Gilles Peskineb4063892019-07-27 21:36:44 +0200755 sys.stderr.write("A #define for the symbol {} "
Gilles Peskine221df1e2019-08-01 23:14:29 +0200756 "was not found in {}\n"
Gabor Mezei3678dee2024-06-04 19:58:43 +0200757 .format(args.symbol, config.filename(args.symbol)))
Gilles Peskineb4063892019-07-27 21:36:44 +0200758 return 1
759 config.set(args.symbol, value=args.value)
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200760 elif args.command == 'set-all':
761 config.change_matching(args.regexs, True)
Gilles Peskineb4063892019-07-27 21:36:44 +0200762 elif args.command == 'unset':
763 config.unset(args.symbol)
Gilles Peskine8e90cf42021-05-27 22:12:57 +0200764 elif args.command == 'unset-all':
765 config.change_matching(args.regexs, False)
Gilles Peskineb4063892019-07-27 21:36:44 +0200766 else:
767 config.adapt(args.adapter)
Gilles Peskine40f103c2019-07-27 23:44:01 +0200768 config.write(args.write)
Gilles Peskinee22a4da2020-03-24 15:43:49 +0100769 return 0
Gilles Peskineb4063892019-07-27 21:36:44 +0200770
771 # Import modules only used by main only if main is defined and called.
772 # pylint: disable=wrong-import-position
773 import argparse
774 import sys
775 sys.exit(main())