Merge pull request #9229 from gabor-mezei-arm/9158_config.py_use_crypto_config

Adapt config.py to configuration file split
This commit is contained in:
Ronald Cron 2024-08-01 11:48:55 +00:00 committed by GitHub
commit 7790bef825
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 312 additions and 57 deletions

@ -1 +1 @@
Subproject commit e8b4ae9bc4bf7e643ee46bf8ff4ef613be2de86f Subproject commit 331565b041f794df2da76394b3b0039abce30355

View File

@ -19,6 +19,8 @@ Basic usage, to read the Mbed TLS configuration:
import os import os
import re import re
from abc import ABCMeta
class Setting: class Setting:
"""Representation of one Mbed TLS mbedtls_config.h setting. """Representation of one Mbed TLS mbedtls_config.h setting.
@ -30,12 +32,13 @@ class Setting:
present in mbedtls_config.h but commented out. present in mbedtls_config.h but commented out.
* section: the name of the section that contains this symbol. * section: the name of the section that contains this symbol.
""" """
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods, too-many-arguments
def __init__(self, active, name, value='', section=None): def __init__(self, active, name, value='', section=None, configfile=None):
self.active = active self.active = active
self.name = name self.name = name
self.value = value self.value = value
self.section = section self.section = section
self.configfile = configfile
class Config: class Config:
"""Representation of the Mbed TLS configuration. """Representation of the Mbed TLS configuration.
@ -54,6 +57,7 @@ class Config:
name to become set. name to become set.
""" """
# pylint: disable=unused-argument
def __init__(self): def __init__(self):
self.settings = {} self.settings = {}
@ -125,7 +129,13 @@ class Config:
""" """
if name not in self.settings: if name not in self.settings:
return return
self.settings[name].active = False
setting = self.settings[name]
# Check if modifying the config file
if setting.configfile and setting.active:
setting.configfile.modified = True
setting.active = False
def adapt(self, adapter): def adapt(self, adapter):
"""Run adapter on each known symbol and (de)activate it accordingly. """Run adapter on each known symbol and (de)activate it accordingly.
@ -138,8 +148,12 @@ class Config:
otherwise unset `name` (i.e. make it known but inactive). otherwise unset `name` (i.e. make it known but inactive).
""" """
for setting in self.settings.values(): for setting in self.settings.values():
is_active = setting.active
setting.active = adapter(setting.name, setting.active, setting.active = adapter(setting.name, setting.active,
setting.section) setting.section)
# Check if modifying the config file
if setting.configfile and setting.active != is_active:
setting.configfile.modified = True
def change_matching(self, regexs, enable): def change_matching(self, regexs, enable):
"""Change all symbols matching one of the regexs to the desired state.""" """Change all symbols matching one of the regexs to the desired state."""
@ -148,11 +162,18 @@ class Config:
regex = re.compile('|'.join(regexs)) regex = re.compile('|'.join(regexs))
for setting in self.settings.values(): for setting in self.settings.values():
if regex.search(setting.name): if regex.search(setting.name):
# Check if modifying the config file
if setting.configfile and setting.active != enable:
setting.configfile.modified = True
setting.active = enable setting.active = enable
def is_full_section(section): def is_full_section(section):
"""Is this section affected by "config.py full" and friends?""" """Is this section affected by "config.py full" and friends?
return section.endswith('support') or section.endswith('modules')
In a config file where the sections are not used the whole config file
is an empty section (with value None) and the whole file is affected.
"""
return section is None or section.endswith('support') or section.endswith('modules')
def realfull_adapter(_name, active, section): def realfull_adapter(_name, active, section):
"""Activate all symbols found in the global and boolean feature sections. """Activate all symbols found in the global and boolean feature sections.
@ -168,6 +189,26 @@ def realfull_adapter(_name, active, section):
return active return active
return True return True
PSA_UNSUPPORTED_FEATURE = frozenset([
'PSA_WANT_ALG_CBC_MAC',
'PSA_WANT_ALG_XTS',
'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_DERIVE',
'PSA_WANT_KEY_TYPE_DH_KEY_PAIR_DERIVE'
])
PSA_DEPRECATED_FEATURE = frozenset([
'PSA_WANT_KEY_TYPE_ECC_KEY_PAIR',
'PSA_WANT_KEY_TYPE_RSA_KEY_PAIR'
])
PSA_UNSTABLE_FEATURE = frozenset([
'PSA_WANT_ECC_SECP_K1_224'
])
EXCLUDE_FROM_CRYPTO = PSA_UNSUPPORTED_FEATURE | \
PSA_DEPRECATED_FEATURE | \
PSA_UNSTABLE_FEATURE
# The goal of the full configuration is to have everything that can be tested # The goal of the full configuration is to have everything that can be tested
# together. This includes deprecated or insecure options. It excludes: # together. This includes deprecated or insecure options. It excludes:
# * Options that require additional build dependencies or unusual hardware. # * Options that require additional build dependencies or unusual hardware.
@ -210,6 +251,9 @@ EXCLUDE_FROM_FULL = frozenset([
'MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN', # build dependency (clang+memsan) 'MBEDTLS_TEST_CONSTANT_FLOW_MEMSAN', # build dependency (clang+memsan)
'MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND', # build dependency (valgrind headers) 'MBEDTLS_TEST_CONSTANT_FLOW_VALGRIND', # build dependency (valgrind headers)
'MBEDTLS_X509_REMOVE_INFO', # removes a feature 'MBEDTLS_X509_REMOVE_INFO', # removes a feature
*PSA_UNSUPPORTED_FEATURE,
*PSA_DEPRECATED_FEATURE,
*PSA_UNSTABLE_FEATURE
]) ])
def is_seamless_alt(name): def is_seamless_alt(name):
@ -316,6 +360,8 @@ def include_in_crypto(name):
'MBEDTLS_PKCS7_C', # part of libmbedx509 'MBEDTLS_PKCS7_C', # part of libmbedx509
]: ]:
return False return False
if name in EXCLUDE_FROM_CRYPTO:
return False
return True return True
def crypto_adapter(adapter): def crypto_adapter(adapter):
@ -334,6 +380,7 @@ def crypto_adapter(adapter):
DEPRECATED = frozenset([ DEPRECATED = frozenset([
'MBEDTLS_PSA_CRYPTO_SE_C', 'MBEDTLS_PSA_CRYPTO_SE_C',
*PSA_DEPRECATED_FEATURE
]) ])
def no_deprecated_adapter(adapter): def no_deprecated_adapter(adapter):
"""Modify an adapter to disable deprecated symbols. """Modify an adapter to disable deprecated symbols.
@ -368,43 +415,25 @@ def no_platform_adapter(adapter):
return adapter(name, active, section) return adapter(name, active, section)
return continuation return continuation
class ConfigFile(Config): class ConfigFile(metaclass=ABCMeta):
"""Representation of the Mbed TLS configuration read for a file. """Representation of a configuration file."""
See the documentation of the `Config` class for methods to query def __init__(self, default_path, name, filename=None):
and modify the configuration. """Check if the config file exists."""
"""
_path_in_tree = 'include/mbedtls/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: if filename is None:
for candidate in self.default_path: for candidate in default_path:
if os.path.lexists(candidate): if os.path.lexists(candidate):
filename = candidate filename = candidate
break break
else: else:
raise Exception('Mbed TLS configuration file not found', raise FileNotFoundError(f'{name} configuration file not found: '
self.default_path) f'{filename if filename else default_path}')
super().__init__()
self.filename = filename
self.inclusion_guard = None
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): self.filename = filename
if name not in self.settings: self.templates = []
self.templates.append((name, '', '#define ' + name + ' ')) self.current_section = None
super().set(name, value) self.inclusion_guard = None
self.modified = False
_define_line_regexp = (r'(?P<indentation>\s*)' + _define_line_regexp = (r'(?P<indentation>\s*)' +
r'(?P<commented_out>(//\s*)?)' + r'(?P<commented_out>(//\s*)?)' +
@ -420,39 +449,57 @@ class ConfigFile(Config):
_ifndef_line_regexp, _ifndef_line_regexp,
_section_line_regexp])) _section_line_regexp]))
def _parse_line(self, line): def _parse_line(self, line):
"""Parse a line in mbedtls_config.h and return the corresponding template.""" """Parse a line in the config file, save the templates representing the lines
and return the corresponding setting element.
"""
line = line.rstrip('\r\n') line = line.rstrip('\r\n')
m = re.match(self._config_line_regexp, line) m = re.match(self._config_line_regexp, line)
if m is None: if m is None:
return line self.templates.append(line)
return None
elif m.group('section'): elif m.group('section'):
self.current_section = m.group('section') self.current_section = m.group('section')
return line self.templates.append(line)
return None
elif m.group('inclusion_guard') and self.inclusion_guard is None: elif m.group('inclusion_guard') and self.inclusion_guard is None:
self.inclusion_guard = m.group('inclusion_guard') self.inclusion_guard = m.group('inclusion_guard')
return line self.templates.append(line)
return None
else: else:
active = not m.group('commented_out') active = not m.group('commented_out')
name = m.group('name') name = m.group('name')
value = m.group('value') value = m.group('value')
if name == self.inclusion_guard and value == '': if name == self.inclusion_guard and value == '':
# The file double-inclusion guard is not an option. # The file double-inclusion guard is not an option.
return line self.templates.append(line)
return None
template = (name, template = (name,
m.group('indentation'), m.group('indentation'),
m.group('define') + name + m.group('define') + name +
m.group('arguments') + m.group('separator')) m.group('arguments') + m.group('separator'))
self.settings[name] = Setting(active, name, value, self.templates.append(template)
self.current_section)
return template
def _format_template(self, name, indent, middle): return (active, name, value, self.current_section)
"""Build a line for mbedtls_config.h for the given setting.
def parse_file(self):
"""Parse the whole file and return the settings."""
with open(self.filename, 'r', encoding='utf-8') as file:
for line in file:
setting = self._parse_line(line)
if setting is not None:
yield setting
self.current_section = None
#pylint: disable=no-self-use
def _format_template(self, setting, indent, middle):
"""Build a line for the config file for the given setting.
The line has the form "<indent>#define <name> <value>" The line has the form "<indent>#define <name> <value>"
where <middle> is "#define <name> ". where <middle> is "#define <name> ".
""" """
setting = self.settings[name]
value = setting.value value = setting.value
if value is None: if value is None:
value = '' value = ''
@ -470,26 +517,230 @@ class ConfigFile(Config):
middle, middle,
value]).rstrip() value]).rstrip()
def write_to_stream(self, output): def write_to_stream(self, settings, output):
"""Write the whole configuration to output.""" """Write the whole configuration to output."""
for template in self.templates: for template in self.templates:
if isinstance(template, str): if isinstance(template, str):
line = template line = template
else: else:
line = self._format_template(*template) name, indent, middle = template
line = self._format_template(settings[name], indent, middle)
output.write(line + '\n') output.write(line + '\n')
def write(self, settings, 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
# Not modified so no need to write to the file
if not self.modified and filename == self.filename:
return
with open(filename, 'w', encoding='utf-8') as output:
self.write_to_stream(settings, output)
class MbedTLSConfigFile(ConfigFile):
"""Representation of an MbedTLS configuration file."""
_path_in_tree = 'include/mbedtls/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):
super().__init__(self.default_path, 'Mbed TLS', filename)
self.current_section = 'header'
class CryptoConfigFile(ConfigFile):
"""Representation of a Crypto configuration file."""
# Temporary, while Mbed TLS does not just rely on the TF-PSA-Crypto
# build system to build its crypto library. When it does, the
# condition can just be removed.
_path_in_tree = 'include/psa/crypto_config.h' \
if os.path.isfile('include/psa/crypto_config.h') else \
'tf-psa-crypto/include/psa/crypto_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):
super().__init__(self.default_path, 'Crypto', filename)
class MbedTLSConfig(Config):
"""Representation of the Mbed TLS configuration.
See the documentation of the `Config` class for methods to query
and modify the configuration.
"""
def __init__(self, filename=None):
"""Read the Mbed TLS configuration file."""
super().__init__()
self.configfile = MbedTLSConfigFile(filename)
self.settings.update({name: Setting(active, name, value, section, self.configfile)
for (active, name, value, section)
in self.configfile.parse_file()})
def set(self, name, value=None):
"""Set name to the given value and make it active."""
if name not in self.settings:
self.configfile.templates.append((name, '', '#define ' + name + ' '))
super().set(name, value)
def write(self, filename=None): def write(self, filename=None):
"""Write the whole configuration to the file it was read from. """Write the whole configuration to the file it was read from.
If filename is specified, write to this file instead. If filename is specified, write to this file instead.
""" """
if filename is None:
filename = self.filename self.configfile.write(self.settings, filename)
with open(filename, 'w', encoding='utf-8') as output:
self.write_to_stream(output) def filename(self):
"""Get the name of the config file."""
return self.configfile.filename
class CryptoConfig(Config):
"""Representation of the PSA crypto configuration.
See the documentation of the `Config` class for methods to query
and modify the configuration.
"""
def __init__(self, filename=None):
"""Read the PSA crypto configuration file."""
super().__init__()
self.configfile = CryptoConfigFile(filename)
self.settings.update({name: Setting(active, name, value, section, self.configfile)
for (active, name, value, section)
in self.configfile.parse_file()})
def set(self, name, value='1'):
"""Set name to the given value and make it active."""
if name in PSA_UNSUPPORTED_FEATURE:
raise ValueError(f'Feature is unsupported: \'{name}\'')
if name in PSA_UNSTABLE_FEATURE:
raise ValueError(f'Feature is unstable: \'{name}\'')
if name not in self.settings:
self.configfile.templates.append((name, '', '#define ' + name + ' '))
super().set(name, value)
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.
"""
self.configfile.write(self.settings, filename)
def filename(self):
"""Get the name of the config file."""
return self.configfile.filename
class CombinedConfig(Config):
"""Representation of MbedTLS and PSA crypto configuration
See the documentation of the `Config` class for methods to query
and modify the configuration.
"""
def __init__(self, *configs):
super().__init__()
for config in configs:
if isinstance(config, MbedTLSConfigFile):
self.mbedtls_configfile = config
elif isinstance(config, CryptoConfigFile):
self.crypto_configfile = config
else:
raise ValueError(f'Invalid configfile: {config}')
self.settings.update({name: Setting(active, name, value, section, configfile)
for configfile in [self.mbedtls_configfile, self.crypto_configfile]
for (active, name, value, section) in configfile.parse_file()})
_crypto_regexp = re.compile(r'$PSA_.*')
def _get_configfile(self, name):
"""Find a config type for a setting name"""
if name in self.settings:
return self.settings[name].configfile
elif re.match(self._crypto_regexp, name):
return self.crypto_configfile
else:
return self.mbedtls_configfile
def __setitem__(self, name, value):
super().__setitem__(name, value)
self.settings[name].configfile.modified = True
def set(self, name, value=None):
"""Set name to the given value and make it active."""
configfile = self._get_configfile(name)
if configfile == self.crypto_configfile:
if name in PSA_UNSUPPORTED_FEATURE:
raise ValueError(f'Feature is unsupported: \'{name}\'')
if name in PSA_UNSTABLE_FEATURE:
raise ValueError(f'Feature is unstable: \'{name}\'')
# The default value in the crypto config is '1'
if not value:
value = '1'
if name in self.settings:
setting = self.settings[name]
if not setting.active or (value is not None and setting.value != value):
configfile.modified = True
else:
configfile.templates.append((name, '', '#define ' + name + ' '))
configfile.modified = True
super().set(name, value)
def write(self, mbedtls_file=None, crypto_file=None):
"""Write the whole configuration to the file it was read from.
If mbedtls_file or crypto_file is specified, write the specific configuration
to the corresponding file instead.
"""
self.mbedtls_configfile.write(self.settings, mbedtls_file)
self.crypto_configfile.write(self.settings, crypto_file)
def filename(self, name=None):
"""Get the names of the config files.
If 'name' is specified return the name of the config file where it is defined.
"""
if not name:
return [config.filename for config in [self.mbedtls_configfile, self.crypto_configfile]]
return self._get_configfile(name).filename
if __name__ == '__main__': if __name__ == '__main__':
#pylint: disable=too-many-statements
def main(): def main():
"""Command line mbedtls_config.h manipulation tool.""" """Command line mbedtls_config.h manipulation tool."""
parser = argparse.ArgumentParser(description=""" parser = argparse.ArgumentParser(description="""
@ -498,7 +749,11 @@ if __name__ == '__main__':
parser.add_argument('--file', '-f', parser.add_argument('--file', '-f',
help="""File to read (and modify if requested). help="""File to read (and modify if requested).
Default: {}. Default: {}.
""".format(ConfigFile.default_path)) """.format(MbedTLSConfigFile.default_path))
parser.add_argument('--cryptofile', '-c',
help="""Crypto file to read (and modify if requested).
Default: {}.
""".format(CryptoConfigFile.default_path))
parser.add_argument('--force', '-o', parser.add_argument('--force', '-o',
action='store_true', action='store_true',
help="""For the set command, if SYMBOL is not help="""For the set command, if SYMBOL is not
@ -576,7 +831,7 @@ if __name__ == '__main__':
excluding X.509 and TLS.""") excluding X.509 and TLS.""")
args = parser.parse_args() args = parser.parse_args()
config = ConfigFile(args.file) config = CombinedConfig(MbedTLSConfigFile(args.file), CryptoConfigFile(args.cryptofile))
if args.command is None: if args.command is None:
parser.print_help() parser.print_help()
return 1 return 1
@ -590,7 +845,7 @@ if __name__ == '__main__':
if not args.force and args.symbol not in config.settings: if not args.force and args.symbol not in config.settings:
sys.stderr.write("A #define for the symbol {} " sys.stderr.write("A #define for the symbol {} "
"was not found in {}\n" "was not found in {}\n"
.format(args.symbol, config.filename)) .format(args.symbol, config.filename(args.symbol)))
return 1 return 1
config.set(args.symbol, value=args.value) config.set(args.symbol, value=args.value)
elif args.command == 'set-all': elif args.command == 'set-all':

View File

@ -541,7 +541,7 @@ def main():
default=True) default=True)
options = parser.parse_args() options = parser.parse_args()
os.chdir(options.directory) os.chdir(options.directory)
conf = config.ConfigFile(options.config) conf = config.MbedTLSConfig(options.config)
domain_data = DomainData(options, conf) domain_data = DomainData(options, conf)
if options.tasks is True: if options.tasks is True: