aboutsummaryrefslogtreecommitdiff
path: root/lib/mbedtls-2.27.0/scripts/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mbedtls-2.27.0/scripts/config.py')
-rwxr-xr-xlib/mbedtls-2.27.0/scripts/config.py536
1 files changed, 536 insertions, 0 deletions
diff --git a/lib/mbedtls-2.27.0/scripts/config.py b/lib/mbedtls-2.27.0/scripts/config.py
new file mode 100755
index 0000000..01f570e
--- /dev/null
+++ b/lib/mbedtls-2.27.0/scripts/config.py
@@ -0,0 +1,536 @@
+#!/usr/bin/env python3
+
+"""Mbed TLS configuration file manipulation library and tool
+
+Basic usage, to read the Mbed TLS or Mbed Crypto configuration:
+ config = ConfigFile()
+ if 'MBEDTLS_RSA_C' in config: print('RSA is enabled')
+"""
+
+## Copyright The Mbed TLS Contributors
+## SPDX-License-Identifier: Apache-2.0
+##
+## Licensed under the Apache License, Version 2.0 (the "License"); you may
+## not use this file except in compliance with the License.
+## You may obtain a copy of the License at
+##
+## http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing, software
+## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+## See the License for the specific language governing permissions and
+## limitations under the License.
+
+import os
+import re
+
+class Setting:
+ """Representation of one Mbed TLS config.h setting.
+
+ Fields:
+ * name: the symbol name ('MBEDTLS_xxx').
+ * value: the value of the macro. The empty string for a plain #define
+ with no value.
+ * active: True if name is defined, False if a #define for name is
+ present in config.h but commented out.
+ * section: the name of the section that contains this symbol.
+ """
+ # pylint: disable=too-few-public-methods
+ def __init__(self, active, name, value='', section=None):
+ self.active = active
+ self.name = name
+ self.value = value
+ self.section = section
+
+class Config:
+ """Representation of the Mbed TLS configuration.
+
+ In the documentation of this class, a symbol is said to be *active*
+ if there is a #define for it that is not commented out, and *known*
+ if there is a #define for it whether commented out or not.
+
+ This class supports the following protocols:
+ * `name in config` is `True` if the symbol `name` is active, `False`
+ otherwise (whether `name` is inactive or not known).
+ * `config[name]` is the value of the macro `name`. If `name` is inactive,
+ raise `KeyError` (even if `name` is known).
+ * `config[name] = value` sets the value associated to `name`. `name`
+ must be known, but does not need to be set. This does not cause
+ name to become set.
+ """
+
+ def __init__(self):
+ self.settings = {}
+
+ def __contains__(self, name):
+ """True if the given symbol is active (i.e. set).
+
+ False if the given symbol is not set, even if a definition
+ is present but commented out.
+ """
+ return name in self.settings and self.settings[name].active
+
+ def all(self, *names):
+ """True if all the elements of names are active (i.e. set)."""
+ return all(self.__contains__(name) for name in names)
+
+ def any(self, *names):
+ """True if at least one symbol in names are active (i.e. set)."""
+ return any(self.__contains__(name) for name in names)
+
+ def known(self, name):
+ """True if a #define for name is present, whether it's commented out or not."""
+ return name in self.settings
+
+ def __getitem__(self, name):
+ """Get the value of name, i.e. what the preprocessor symbol expands to.
+
+ If name is not known, raise KeyError. name does not need to be active.
+ """
+ return self.settings[name].value
+
+ def get(self, name, default=None):
+ """Get the value of name. If name is inactive (not set), return default.
+
+ If a #define for name is present and not commented out, return
+ its expansion, even if this is the empty string.
+
+ If a #define for name is present but commented out, return default.
+ """
+ if name in self.settings:
+ return self.settings[name].value
+ else:
+ return default
+
+ def __setitem__(self, name, value):
+ """If name is known, set its value.
+
+ If name is not known, raise KeyError.
+ """
+ self.settings[name].value = value
+
+ def set(self, name, value=None):
+ """Set name to the given value and make it active.
+
+ If value is None and name is already known, don't change its value.
+ If value is None and name is not known, set its value to the empty
+ string.
+ """
+ if name in self.settings:
+ if value is not None:
+ self.settings[name].value = value
+ self.settings[name].active = True
+ else:
+ self.settings[name] = Setting(True, name, value=value)
+
+ def unset(self, name):
+ """Make name unset (inactive).
+
+ name remains known if it was known before.
+ """
+ if name not in self.settings:
+ return
+ self.settings[name].active = False
+
+ def adapt(self, adapter):
+ """Run adapter on each known symbol and (de)activate it accordingly.
+
+ `adapter` must be a function that returns a boolean. It is called as
+ `adapter(name, active, section)` for each setting, where `active` is
+ `True` if `name` is set and `False` if `name` is known but unset,
+ and `section` is the name of the section containing `name`. If
+ `adapter` returns `True`, then set `name` (i.e. make it active),
+ otherwise unset `name` (i.e. make it known but inactive).
+ """
+ for setting in self.settings.values():
+ setting.active = adapter(setting.name, setting.active,
+ setting.section)
+
+def is_full_section(section):
+ """Is this section affected by "config.py full" and friends?"""
+ return section.endswith('support') or section.endswith('modules')
+
+def realfull_adapter(_name, active, section):
+ """Activate all symbols found in the system and feature sections."""
+ if not is_full_section(section):
+ return active
+ return True
+
+# The goal of the full configuration is to have everything that can be tested
+# together. This includes deprecated or insecure options. It excludes:
+# * Options that require additional build dependencies or unusual hardware.
+# * Options that make testing less effective.
+# * Options that are incompatible with other options, or more generally that
+# interact with other parts of the code in such a way that a bulk enabling
+# is not a good way to test them.
+# * Options that remove features.
+EXCLUDE_FROM_FULL = frozenset([
+ #pylint: disable=line-too-long
+ 'MBEDTLS_CTR_DRBG_USE_128_BIT_KEY', # interacts with ENTROPY_FORCE_SHA256
+ 'MBEDTLS_DEPRECATED_REMOVED', # conflicts with deprecated options
+ 'MBEDTLS_DEPRECATED_WARNING', # conflicts with deprecated options
+ 'MBEDTLS_ECDH_VARIANT_EVEREST_ENABLED', # influences the use of ECDH in TLS
+ 'MBEDTLS_ECP_NO_FALLBACK', # removes internal ECP implementation
+ 'MBEDTLS_ECP_NO_INTERNAL_RNG', # removes a feature
+ 'MBEDTLS_ECP_RESTARTABLE', # incompatible with USE_PSA_CRYPTO
+ 'MBEDTLS_ENTROPY_FORCE_SHA256', # interacts with CTR_DRBG_128_BIT_KEY
+ 'MBEDTLS_HAVE_SSE2', # hardware dependency
+ 'MBEDTLS_MEMORY_BACKTRACE', # depends on MEMORY_BUFFER_ALLOC_C
+ 'MBEDTLS_MEMORY_BUFFER_ALLOC_C', # makes sanitizers (e.g. ASan) less effective
+ 'MBEDTLS_MEMORY_DEBUG', # depends on MEMORY_BUFFER_ALLOC_C
+ 'MBEDTLS_NO_64BIT_MULTIPLICATION', # influences anything that uses bignum
+ 'MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES', # removes a feature
+ 'MBEDTLS_NO_PLATFORM_ENTROPY', # removes a feature
+ 'MBEDTLS_NO_UDBL_DIVISION', # influences anything that uses bignum
+ 'MBEDTLS_PKCS11_C', # build dependency (libpkcs11-helper)
+ 'MBEDTLS_PLATFORM_NO_STD_FUNCTIONS', # removes a feature
+ 'MBEDTLS_PSA_CRYPTO_CONFIG', # toggles old/new style PSA config
+ 'MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG', # behavior change + build dependency
+ 'MBEDTLS_PSA_CRYPTO_KEY_ID_ENCODES_OWNER', # incompatible with USE_PSA_CRYPTO
+ 'MBEDTLS_PSA_CRYPTO_SPM', # platform dependency (PSA SPM)
+ 'MBEDTLS_PSA_INJECT_ENTROPY', # build dependency (hook functions)
+ 'MBEDTLS_REMOVE_3DES_CIPHERSUITES', # removes a feature
+ 'MBEDTLS_REMOVE_ARC4_CIPHERSUITES', # removes a feature
+ 'MBEDTLS_RSA_NO_CRT', # influences the use of RSA in X.509 and TLS
+ 'MBEDTLS_SHA512_NO_SHA384', # removes a feature
+ 'MBEDTLS_SSL_HW_RECORD_ACCEL', # build dependency (hook functions)
+ 'MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN', # build dependency (clang+memsan)
+ 'MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND', # build dependency (valgrind headers)
+ 'MBEDTLS_TEST_NULL_ENTROPY', # removes a feature
+ 'MBEDTLS_X509_ALLOW_UNSUPPORTED_CRITICAL_EXTENSION', # influences the use of X.509 in TLS
+ 'MBEDTLS_ZLIB_SUPPORT', # build dependency (libz)
+])
+
+def is_seamless_alt(name):
+ """Whether the xxx_ALT symbol should be included in the full configuration.
+
+ Include alternative implementations of platform functions, which are
+ configurable function pointers that default to the built-in function.
+ This way we test that the function pointers exist and build correctly
+ without changing the behavior, and tests can verify that the function
+ pointers are used by modifying those pointers.
+
+ Exclude alternative implementations of library functions since they require
+ an implementation of the relevant functions and an xxx_alt.h header.
+ """
+ if name == 'MBEDTLS_PLATFORM_SETUP_TEARDOWN_ALT':
+ # Similar to non-platform xxx_ALT, requires platform_alt.h
+ return False
+ return name.startswith('MBEDTLS_PLATFORM_')
+
+def include_in_full(name):
+ """Rules for symbols in the "full" configuration."""
+ if name in EXCLUDE_FROM_FULL:
+ return False
+ if name.endswith('_ALT'):
+ return is_seamless_alt(name)
+ return True
+
+def full_adapter(name, active, section):
+ """Config adapter for "full"."""
+ if not is_full_section(section):
+ return active
+ return include_in_full(name)
+
+# The baremetal configuration excludes options that require a library or
+# operating system feature that is typically not present on bare metal
+# systems. Features that are excluded from "full" won't be in "baremetal"
+# either (unless explicitly turned on in baremetal_adapter) so they don't
+# need to be repeated here.
+EXCLUDE_FROM_BAREMETAL = frozenset([
+ #pylint: disable=line-too-long
+ 'MBEDTLS_ENTROPY_NV_SEED', # requires a filesystem and FS_IO or alternate NV seed hooks
+ 'MBEDTLS_FS_IO', # requires a filesystem
+ 'MBEDTLS_HAVEGE_C', # requires a clock
+ 'MBEDTLS_HAVE_TIME', # requires a clock
+ 'MBEDTLS_HAVE_TIME_DATE', # requires a clock
+ 'MBEDTLS_NET_C', # requires POSIX-like networking
+ 'MBEDTLS_PLATFORM_FPRINTF_ALT', # requires FILE* from stdio.h
+ 'MBEDTLS_PLATFORM_NV_SEED_ALT', # requires a filesystem and ENTROPY_NV_SEED
+ 'MBEDTLS_PLATFORM_TIME_ALT', # requires a clock and HAVE_TIME
+ 'MBEDTLS_PSA_CRYPTO_SE_C', # requires a filesystem and PSA_CRYPTO_STORAGE_C
+ 'MBEDTLS_PSA_CRYPTO_STORAGE_C', # requires a filesystem
+ 'MBEDTLS_PSA_ITS_FILE_C', # requires a filesystem
+ 'MBEDTLS_THREADING_C', # requires a threading interface
+ 'MBEDTLS_THREADING_PTHREAD', # requires pthread
+ 'MBEDTLS_TIMING_C', # requires a clock
+])
+
+def keep_in_baremetal(name):
+ """Rules for symbols in the "baremetal" configuration."""
+ if name in EXCLUDE_FROM_BAREMETAL:
+ return False
+ return True
+
+def baremetal_adapter(name, active, section):
+ """Config adapter for "baremetal"."""
+ if not is_full_section(section):
+ return active
+ if name == 'MBEDTLS_NO_PLATFORM_ENTROPY':
+ # No OS-provided entropy source
+ return True
+ return include_in_full(name) and keep_in_baremetal(name)
+
+def include_in_crypto(name):
+ """Rules for symbols in a crypto configuration."""
+ if name.startswith('MBEDTLS_X509_') or \
+ name.startswith('MBEDTLS_SSL_') or \
+ name.startswith('MBEDTLS_KEY_EXCHANGE_'):
+ return False
+ if name in [
+ 'MBEDTLS_CERTS_C', # part of libmbedx509
+ 'MBEDTLS_DEBUG_C', # part of libmbedtls
+ 'MBEDTLS_NET_C', # part of libmbedtls
+ 'MBEDTLS_PKCS11_C', # part of libmbedx509
+ ]:
+ return False
+ return True
+
+def crypto_adapter(adapter):
+ """Modify an adapter to disable non-crypto symbols.
+
+ ``crypto_adapter(adapter)(name, active, section)`` is like
+ ``adapter(name, active, section)``, but unsets all X.509 and TLS symbols.
+ """
+ def continuation(name, active, section):
+ if not include_in_crypto(name):
+ return False
+ if adapter is None:
+ return active
+ return adapter(name, active, section)
+ return continuation
+
+DEPRECATED = frozenset([
+ 'MBEDTLS_SSL_PROTO_SSL3',
+ 'MBEDTLS_SSL_SRV_SUPPORT_SSLV2_CLIENT_HELLO',
+])
+
+def no_deprecated_adapter(adapter):
+ """Modify an adapter to disable deprecated symbols.
+
+ ``no_deprecated_adapter(adapter)(name, active, section)`` is like
+ ``adapter(name, active, section)``, but unsets all deprecated symbols
+ and sets ``MBEDTLS_DEPRECATED_REMOVED``.
+ """
+ def continuation(name, active, section):
+ if name == 'MBEDTLS_DEPRECATED_REMOVED':
+ return True
+ if name in DEPRECATED:
+ return False
+ if adapter is None:
+ return active
+ return adapter(name, active, section)
+ return continuation
+
+class ConfigFile(Config):
+ """Representation of the Mbed TLS configuration read for a file.
+
+ See the documentation of the `Config` class for methods to query
+ and modify the configuration.
+ """
+
+ _path_in_tree = 'include/mbedtls/config.h'
+ default_path = [_path_in_tree,
+ os.path.join(os.path.dirname(__file__),
+ os.pardir,
+ _path_in_tree),
+ os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
+ _path_in_tree)]
+
+ def __init__(self, filename=None):
+ """Read the Mbed TLS configuration file."""
+ if filename is None:
+ for candidate in self.default_path:
+ if os.path.lexists(candidate):
+ filename = candidate
+ break
+ else:
+ raise Exception('Mbed TLS configuration file not found',
+ self.default_path)
+ super().__init__()
+ self.filename = filename
+ self.current_section = 'header'
+ with open(filename, 'r', encoding='utf-8') as file:
+ self.templates = [self._parse_line(line) for line in file]
+ self.current_section = None
+
+ def set(self, name, value=None):
+ if name not in self.settings:
+ self.templates.append((name, '', '#define ' + name + ' '))
+ super().set(name, value)
+
+ _define_line_regexp = (r'(?P<indentation>\s*)' +
+ r'(?P<commented_out>(//\s*)?)' +
+ r'(?P<define>#\s*define\s+)' +
+ r'(?P<name>\w+)' +
+ r'(?P<arguments>(?:\((?:\w|\s|,)*\))?)' +
+ r'(?P<separator>\s*)' +
+ r'(?P<value>.*)')
+ _section_line_regexp = (r'\s*/?\*+\s*[\\@]name\s+SECTION:\s*' +
+ r'(?P<section>.*)[ */]*')
+ _config_line_regexp = re.compile(r'|'.join([_define_line_regexp,
+ _section_line_regexp]))
+ def _parse_line(self, line):
+ """Parse a line in config.h and return the corresponding template."""
+ line = line.rstrip('\r\n')
+ m = re.match(self._config_line_regexp, line)
+ if m is None:
+ return line
+ elif m.group('section'):
+ self.current_section = m.group('section')
+ return line
+ else:
+ active = not m.group('commented_out')
+ name = m.group('name')
+ value = m.group('value')
+ template = (name,
+ m.group('indentation'),
+ m.group('define') + name +
+ m.group('arguments') + m.group('separator'))
+ self.settings[name] = Setting(active, name, value,
+ self.current_section)
+ return template
+
+ def _format_template(self, name, indent, middle):
+ """Build a line for config.h for the given setting.
+
+ The line has the form "<indent>#define <name> <value>"
+ where <middle> is "#define <name> ".
+ """
+ setting = self.settings[name]
+ value = setting.value
+ if value is None:
+ value = ''
+ # Normally the whitespace to separte the symbol name from the
+ # value is part of middle, and there's no whitespace for a symbol
+ # with no value. But if a symbol has been changed from having a
+ # value to not having one, the whitespace is wrong, so fix it.
+ if value:
+ if middle[-1] not in '\t ':
+ middle += ' '
+ else:
+ middle = middle.rstrip()
+ return ''.join([indent,
+ '' if setting.active else '//',
+ middle,
+ value]).rstrip()
+
+ def write_to_stream(self, output):
+ """Write the whole configuration to output."""
+ for template in self.templates:
+ if isinstance(template, str):
+ line = template
+ else:
+ line = self._format_template(*template)
+ output.write(line + '\n')
+
+ def write(self, filename=None):
+ """Write the whole configuration to the file it was read from.
+
+ If filename is specified, write to this file instead.
+ """
+ if filename is None:
+ filename = self.filename
+ with open(filename, 'w', encoding='utf-8') as output:
+ self.write_to_stream(output)
+
+if __name__ == '__main__':
+ def main():
+ """Command line config.h manipulation tool."""
+ parser = argparse.ArgumentParser(description="""
+ Mbed TLS and Mbed Crypto configuration file manipulation tool.
+ """)
+ parser.add_argument('--file', '-f',
+ help="""File to read (and modify if requested).
+ Default: {}.
+ """.format(ConfigFile.default_path))
+ parser.add_argument('--force', '-o',
+ action='store_true',
+ help="""For the set command, if SYMBOL is not
+ present, add a definition for it.""")
+ parser.add_argument('--write', '-w', metavar='FILE',
+ help="""File to write to instead of the input file.""")
+ subparsers = parser.add_subparsers(dest='command',
+ title='Commands')
+ parser_get = subparsers.add_parser('get',
+ help="""Find the value of SYMBOL
+ and print it. Exit with
+ status 0 if a #define for SYMBOL is
+ found, 1 otherwise.
+ """)
+ parser_get.add_argument('symbol', metavar='SYMBOL')
+ parser_set = subparsers.add_parser('set',
+ help="""Set SYMBOL to VALUE.
+ If VALUE is omitted, just uncomment
+ the #define for SYMBOL.
+ Error out of a line defining
+ SYMBOL (commented or not) is not
+ found, unless --force is passed.
+ """)
+ parser_set.add_argument('symbol', metavar='SYMBOL')
+ parser_set.add_argument('value', metavar='VALUE', nargs='?',
+ default='')
+ parser_unset = subparsers.add_parser('unset',
+ help="""Comment out the #define
+ for SYMBOL. Do nothing if none
+ is present.""")
+ parser_unset.add_argument('symbol', metavar='SYMBOL')
+
+ def add_adapter(name, function, description):
+ subparser = subparsers.add_parser(name, help=description)
+ subparser.set_defaults(adapter=function)
+ add_adapter('baremetal', baremetal_adapter,
+ """Like full, but exclude features that require platform
+ features such as file input-output.""")
+ add_adapter('full', full_adapter,
+ """Uncomment most features.
+ Exclude alternative implementations and platform support
+ options, as well as some options that are awkward to test.
+ """)
+ add_adapter('full_no_deprecated', no_deprecated_adapter(full_adapter),
+ """Uncomment most non-deprecated features.
+ Like "full", but without deprecated features.
+ """)
+ add_adapter('realfull', realfull_adapter,
+ """Uncomment all boolean #defines.
+ Suitable for generating documentation, but not for building.""")
+ add_adapter('crypto', crypto_adapter(None),
+ """Only include crypto features. Exclude X.509 and TLS.""")
+ add_adapter('crypto_baremetal', crypto_adapter(baremetal_adapter),
+ """Like baremetal, but with only crypto features,
+ excluding X.509 and TLS.""")
+ add_adapter('crypto_full', crypto_adapter(full_adapter),
+ """Like full, but with only crypto features,
+ excluding X.509 and TLS.""")
+
+ args = parser.parse_args()
+ config = ConfigFile(args.file)
+ if args.command is None:
+ parser.print_help()
+ return 1
+ elif args.command == 'get':
+ if args.symbol in config:
+ value = config[args.symbol]
+ if value:
+ sys.stdout.write(value + '\n')
+ return 0 if args.symbol in config else 1
+ elif args.command == 'set':
+ if not args.force and args.symbol not in config.settings:
+ sys.stderr.write("A #define for the symbol {} "
+ "was not found in {}\n"
+ .format(args.symbol, config.filename))
+ return 1
+ config.set(args.symbol, value=args.value)
+ elif args.command == 'unset':
+ config.unset(args.symbol)
+ else:
+ config.adapt(args.adapter)
+ config.write(args.write)
+ return 0
+
+ # Import modules only used by main only if main is defined and called.
+ # pylint: disable=wrong-import-position
+ import argparse
+ import sys
+ sys.exit(main())