Merge pull request #311 from Mbed-TLS/framework_mergeback

MbedTLS 4.2.0 & TF-PSA-Crypto 1.2.0 Framework mergeback
This commit is contained in:
Gilles Peskine
2026-07-07 22:04:12 +02:00
committed by GitHub
10 changed files with 419 additions and 9 deletions
+67 -9
View File
@@ -469,6 +469,22 @@ server5-selfsigned.crt: server5.key
-out $@
all_final += server5-selfsigned.crt
server5-selfsigned.crt.der: server5-selfsigned.crt
$(OPENSSL) x509 -inform PEM -in $< -outform DER -out $@
all_final += server5-selfsigned.crt.der
server5-ca.der: server5.key
openssl req -x509 -key server5.key \
-sha256 -days 3650 -nodes \
-addext basicConstraints=critical,CA:TRUE \
-addext keyUsage=critical,digitalSignature \
-addext subjectKeyIdentifier=hash \
-addext authorityKeyIdentifier=none \
-set_serial 0x53a2cb4b124ead837da894b2 \
-subj "/CN=selfsigned/OU=testing/O=PolarSSL/C=NL" \
-outform DER -out $@
all_final += server5-ca.der
# Create a certificate which is almost identical to "server3.crt", i.e.
# it contains a public EC key and it is signed with RSA. The main difference
# compared to "server3.crt" is that in this case we use a secp256r1 key ("server5.key")
@@ -2294,6 +2310,40 @@ parse_input/server4.crt server4.crt: server4.key
md=SHA256 version=3 output_file=$@
all_final += server4.crt
# Negative tests
# Replace a well-formed basicConstraints extension containing CA:TRUE with
# a malformed extension that is empty with trailing junk. The trailing
# junk is a boolean with the value true, thus it would be interpreted as
# CA:TRUE if it was properly inside the basicConstraints extension.
parse_input/server5.basic-constraints-sequence-overflow.badsign.crt: server5-ca.der
perl -0777 -pe '$$n = s/\x04\x05\x30\x03\x01\x01\xff/\x04\x05\x30\x00\x01\x01\xff/g; die "More substitutions than expected" if $$n > 1; die "Substitution not found" if $$n < 1' <$< >$@.tmp
mv $@.tmp $@
all_final += parse_input/server5.basic-constraints-sequence-overflow.badsign.crt
parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt: parse_input/server5.basic-constraints-sequence-overflow.badsign.crt server5.key
$(OPENSSL) x509 -subj '/CN=Eve/' \
-key server5.key \
-days 3653 \
-inform DER -in $< -outform DER -out $@
all_final += parse_input/server5.basic-constraints-sequence-overflow.selfsigned.crt
# Replace a well-formed basicConstraints extension containing CA:TRUE with
# a malformed extension that is empty with trailing junk. The trailing
# junk is a boolean with the value true, thus it would be interpreted as
# CA:TRUE if it was properly inside the basicConstraints extension.
parse_input/server5.basic-constraints-integer-first.badsign.crt: server5-ca.der
perl -0777 -pe '$$n = s/\x04\x05\x30\x03\x01\x01\xff/\x04\x05\x30\x03\x02\x01\x01/g; die "More substitutions than expected" if $$n > 1; die "Substitution not found" if $$n < 1' <$< >$@.tmp
mv $@.tmp $@
all_final += parse_input/server5.basic-constraints-integer-first.badsign.crt
parse_input/server5.basic-constraints-integer-first.selfsigned.crt: parse_input/server5.basic-constraints-integer-first.badsign.crt server5.key
$(OPENSSL) x509 -subj '/CN=Eve/' \
-key server5.key \
-days 3653 \
-inform DER -in $< -outform DER -out $@
all_final += parse_input/server5.basic-constraints-integer-first.selfsigned.crt
# MD5 test certificate
cert_md_test_key = $(cli_crt_key_file_rsa)
@@ -2388,7 +2438,7 @@ all_final += pkcs7-rsa-expired.crt
# File with an otherwise valid signature signed with an expired cert
pkcs7_data_rsa_expired.der: pkcs7-rsa-expired.key pkcs7-rsa-expired.crt pkcs7_data.bin
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -inkey pkcs7-rsa-expired.key -signer pkcs7-rsa-expired.crt -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -inkey pkcs7-rsa-expired.key -signer pkcs7-rsa-expired.crt -noattr -outform DER -out $@
all_final += pkcs7_data_rsa_expired.der
# Convert signing certs to DER for testing PEM-free builds
@@ -2411,42 +2461,50 @@ all_final += pkcs7_zerolendata_detached.der
# pkcs7 signature file with CERT
pkcs7_data_cert_signed_sha256.der: $(pkcs7_test_file) $(pkcs7_test_cert_1)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
all_final += pkcs7_data_cert_signed_sha256.der
# pkcs7 signature file with CERT and sha1
pkcs7_data_cert_signed_sha1.der: $(pkcs7_test_file) $(pkcs7_test_cert_1)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha1 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha1 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
all_final += pkcs7_data_cert_signed_sha1.der
pkcs7_data_cert_signed_md5.der: $(pkcs7_test_file) $(pkcs7_test_cert_1)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md md5 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
all_final += pkcs7_data_cert_signed_md5.der
pkcs7_data_cert_signed_ripemd160.der: $(pkcs7_test_file) $(pkcs7_test_cert_1)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md ripemd160 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
all_final += pkcs7_data_cert_signed_ripemd160.der
# pkcs7 signature file with CERT and sha512
pkcs7_data_cert_signed_sha512.der: $(pkcs7_test_file) $(pkcs7_test_cert_1)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha512 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha512 -signer pkcs7-rsa-sha256-1.pem -noattr -outform DER -out $@
all_final += pkcs7_data_cert_signed_sha512.der
# pkcs7 signature file without CERT
pkcs7_data_without_cert_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -outform DER -out $@
all_final += pkcs7_data_without_cert_signed.der
# pkcs7 signature file with signature
pkcs7_data_with_signature.der: $(pkcs7_test_file) $(pkcs7_test_cert_1)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -nodetach -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -nocerts -noattr -nodetach -outform DER -out $@
all_final += pkcs7_data_with_signature.der
# pkcs7 signature file with two signers
pkcs7_data_multiple_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(pkcs7_test_cert_2)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -nocerts -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -nocerts -noattr -outform DER -out $@
all_final += pkcs7_data_multiple_signed.der
# pkcs7 signature file with three signers
pkcs7_data_3_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(pkcs7_test_cert_2) $(pkcs7_test_cert_3)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -signer pkcs7-rsa-sha256-3.pem -nocerts -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -signer pkcs7-rsa-sha256-3.pem -nocerts -noattr -outform DER -out $@
all_final += pkcs7_data_3_signed.der
# pkcs7 signature file with multiple certificates
pkcs7_data_multiple_certs_signed.der: $(pkcs7_test_file) $(pkcs7_test_cert_1) $(pkcs7_test_cert_2)
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -out $@ -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -noattr -outform DER -out $@
$(OPENSSL) smime -sign -binary -in pkcs7_data.bin -md sha256 -signer pkcs7-rsa-sha256-1.pem -signer pkcs7-rsa-sha256-2.pem -noattr -outform DER -out $@
all_final += pkcs7_data_multiple_certs_signed.der
# pkcs7 signature file with corrupted CERT
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,352 @@
"""Common code for generating the test code to validate mbedtls_ssl_session_reset().
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
import enum
import os
import re
import sys
import typing
import argparse
from typing import Dict, Iterator, List, Tuple, FrozenSet
from . import c_parsing_helper
from . import typing_util
from . import build_tree
class ResetBehavior(enum.Enum):
KEEP = 0 # Kept unchanged before/after the reset
RESET = 1 # Returned to the initial state (which is not necessarily 0)
REALLOCATE = 2 # Pointer that gets reallocated
IGNORE = 3 # Ignored field
SPECIAL = 4
class ElementType(enum.Enum):
SCALAR = 0
POINTER = 1
ARRAY = 2
NAMED_STRUCTURE = 3
IGNORE = 4
SPECIAL = 5
class FieldsInfo(typing.NamedTuple):
"""Expected reset behavior for the fields of the structure."""
rules: Dict[str, ResetBehavior]
special: Dict[str, List[str]]
# The script isn't capable to identify named structures (ex: dtls_srtp_info)
# so we keep an explicit list of them.
named_structures: FrozenSet[str]
class CField():
# pylint: disable=too-few-public-methods
"""Information about one field of a C struct."""
name: str
conditional: List[str]
element_type: ElementType
def __init__(self, name: str, conditionals: List[str], element_type: ElementType):
self.name = name
self.conditionals = conditionals.copy()
self.element_type = element_type
def check_value(self) -> List[str]:
raise Exception(f'Class {self.__class__.__name__} cannot handle entries'
f'of type {self.element_type}')
class CFieldIgnore(CField):
# pylint: disable=too-few-public-methods
"""Explicitly ignored field."""
def check_value(self) -> List[str]:
return [f'/* {self.name} is ignored */']
class CFieldKeep(CField):
# pylint: disable=too-few-public-methods
"""Field kept unchanged."""
def check_value(self) -> List[str]:
if self.element_type == ElementType.SCALAR:
return [f'TEST_EQUAL(before->{self.name}, after->{self.name});']
elif self.element_type == ElementType.POINTER:
return [f'TEST_ASSERT(before->{self.name} == after->{self.name});']
elif self.element_type == ElementType.ARRAY:
return [f'TEST_MEMORY_COMPARE(before->{self.name}, '
f'sizeof(before->{self.name}), after->{self.name}, '
f'sizeof(after->{self.name}));']
elif self.element_type == ElementType.NAMED_STRUCTURE:
return [f'TEST_MEMORY_COMPARE(&(before->{self.name}), '
f'sizeof(before->{self.name}), &(after->{self.name}), '
f'sizeof(after->{self.name}));']
return super().check_value()
class CFieldReset(CField):
# pylint: disable=too-few-public-methods
"""Field returned to the intial state."""
def check_value(self) -> List[str]:
if (self.element_type == ElementType.SCALAR) or (self.element_type == ElementType.POINTER):
return [f'TEST_ASSERT(after->{self.name} == initial.{self.name});']
elif self.element_type == ElementType.ARRAY:
return [f'TEST_MEMORY_COMPARE(after->{self.name}, '
f'sizeof(after->{self.name}), initial.{self.name}, '
f'sizeof(initial.{self.name}));']
elif self.element_type == ElementType.NAMED_STRUCTURE:
return [f'TEST_MEMORY_COMPARE(&(after->{self.name}), '
f'sizeof(after->{self.name}), &(initial.{self.name}), '
f'sizeof(initial.{self.name}));']
return super().check_value()
class CFieldReallocate(CField):
# pylint: disable=too-few-public-methods
"""Pointer (might be) reallocated during reset."""
def check_value(self) -> List[str]:
if self.element_type == ElementType.POINTER:
return [f'TEST_ASSERT(after->{self.name} != NULL);']
return super().check_value()
class CFieldSpecial(CField):
# pylint: disable=too-few-public-methods
"""Field with a custom check."""
custom_behavior: List[str]
def __init__(self, name: str, conditional: List[str], element_type: ElementType,
custom_behavior: List[str]):
self.custom_behavior = custom_behavior
super().__init__(name, conditional, element_type)
def check_value(self) -> List[str]:
return self.custom_behavior
class CStruct:
# pylint: disable=too-few-public-methods
"""Information about the fields of a C struct."""
_PREPROCESSOR_RE = re.compile(r'\s*#\s*(\w+)\s*(.*)')
_STRUCT_RE = re.compile(r'struct\s+(\w+)\s*{')
_FIELD_RE = re.compile(r'\s*([^;]+);')
_PRIVATE_FIELD_RE = re.compile(r'MBEDTLS_PRIVATE\((\w+)\)')
_BARE_FIELD_RE = re.compile(r'[\t *](\w+)\Z')
_NON_BLANK_RE = re.compile(r'.*\S')
def _get_element_type(self, name: str, declaration: str) -> ElementType:
"""Return structure field type based on either its name or the fact
that it belongs to the list of special symbols/named structures"""
# Check for fields with custom check rules
if name in self.fields_info.special:
return ElementType.SPECIAL
# Check for named structures
if name in self.fields_info.named_structures:
return ElementType.NAMED_STRUCTURE
# Check for pointer
if '*' in declaration:
return ElementType.POINTER
# Check for array
if '[' in declaration:
return ElementType.ARRAY
# If we get here then the field is a scalar
return ElementType.SCALAR
def _parse_field(self, declaration: str, conditionals: List[str]) -> CField:
"""Return the CField object describing the given field declaration."""
# Note that this simplistic parsing finds fields in inline
# sub-structs, unions and enums.
m = self._PRIVATE_FIELD_RE.search(declaration)
if not m:
m = self._BARE_FIELD_RE.search(declaration)
if not m:
raise Exception(f'Field name not found in "{declaration}"')
name = m.group(1)
# Get the expected behavior on reset
if name not in self.fields_info.rules:
raise Exception(f'Field {name} does not have an associated behavior')
behavior = self.fields_info.rules[name]
element_type = self._get_element_type(name, declaration)
if behavior == ResetBehavior.SPECIAL:
return CFieldSpecial(name, conditionals, element_type,
self.fields_info.special[name])
elif behavior == ResetBehavior.KEEP:
return CFieldKeep(name, conditionals, element_type)
elif behavior == ResetBehavior.REALLOCATE:
return CFieldReallocate(name, conditionals, element_type)
elif behavior == ResetBehavior.IGNORE:
return CFieldIgnore(name, conditionals, element_type)
elif behavior == ResetBehavior.RESET:
return CFieldReset(name, conditionals, element_type)
else:
raise Exception(f'Unhandled behavior {behavior}')
@staticmethod
def _continue_parsing_preprocessor(arguments: str,
lines: Iterator[Tuple[int, str]]) -> str:
"""Append continuation lines of preprocesssor directive."""
while True:
try:
line_content = next(lines)[1]
arguments = arguments + '\n' + line_content
except StopIteration:
raise Exception('Unexpected end of file reached while '
' parsing a C preprocessor directive')
if not line_content.endswith('\\'):
break
return arguments
def _structure_fields(self,
lines: Iterator[Tuple[int, str]],
struct_name: str) -> Iterator[CField]:
"""Yield a CField object for each field of the given structure."""
# pylint: disable=too-many-branches
found_start = False
for num, line in lines:
m = self._STRUCT_RE.match(line)
if m and m.group(1) == struct_name:
found_start = True
break
if not found_start:
raise Exception(f'Definition of struct {struct_name} not found')
conditionals: List[str] = []
for num, line in lines:
if line.startswith('}'):
return
m = self._PREPROCESSOR_RE.match(line)
if m:
# If the preprocessor directives are included in parentheses (ex:
# "(defined(AAA) && defined(BBB))") then 'line' contains the full directive
# including new lines (if present) so we can just copy that.
# If instead outer parentheses are missing, ex: "defined(AAA) && defined(BBB)"
# then parsing stops at the end of the line so we need to keep parsing
# manually if there is a "\" at the end of the line.
if line.endswith('\\'):
arguments = m.group(2)
arguments = self._continue_parsing_preprocessor(arguments, lines)
one_conditional = '#if ' + arguments
else:
one_conditional = line
one_conditional = one_conditional + '\n'
directive = m.group(1)
if directive == 'if':
conditionals.append(one_conditional)
elif directive == 'endif':
del conditionals[-1]
else:
raise Exception(f'Unsupported directive #{directive} at line {num}')
continue
m = self._FIELD_RE.match(line)
if m:
yield self._parse_field(m.group(1), conditionals)
continue
m = self._NON_BLANK_RE.match(line)
if m:
raise Exception(f'Failed to parse non-empty line {num}. Content is: {line}')
raise Exception(f'End of definition of struct {struct_name} not found')
def _check_special_fields(self):
"""Ensure that all the entries FieldsInfo.rules that are given a SPECIAL
behavior also have the corresponding entry in FieldsInfo.special
(and viceversa)"""
in_rules = frozenset(name for name in self.fields_info.rules
if self.fields_info.rules[name] == ResetBehavior.SPECIAL)
in_special = frozenset(self.fields_info.special)
if in_rules != in_special:
raise Exception(f'Fields with SPECIAL rule in FieldsInfo.rules but '
f'not listed in FieldsInfo.special: {in_rules - in_special}. '
f'Fields listed FieldsInfo.special, but not given a'
f'SPECIAL rule in FieldsInfo.rules: {in_special - in_rules}.')
def _check_rules_struct_fields_matching(self):
"""Ensure that for each field of the given FieldsInfo.rules there is
an entry in the parsed C structure and viceversa"""
given = frozenset(name for name in self.fields_info.rules)
parsed = frozenset(field.name for field in self.fields)
if given != parsed:
raise Exception(f'Fields found in the C struct but not given a '
f'reset behavior: {parsed - given}. '
f'Fields given a reset behavior but not found in '
f'the C struct: {given - parsed}')
def __init__(self, file_name: str, struct_name: str,
fields_info: FieldsInfo) -> None:
"""Parse a structure definition in a C source file."""
self.fields_info = fields_info
self._check_special_fields()
lines = c_parsing_helper.read_logical_lines(file_name)
self.fields = list(self._structure_fields(lines, struct_name))
self._check_rules_struct_fields_matching()
class SSLContextStruct(CStruct):
# pylint: disable=too-few-public-methods
"""Information about the fields of struct mbedtls_ssl_context."""
def __init__(self, fields_info: FieldsInfo) -> None:
super().__init__('include/mbedtls/ssl.h', 'mbedtls_ssl_context',
fields_info)
def write_check_function(self, out: typing_util.Writable) -> None:
"""Write the generated context-checking function to the output."""
out.write(f"""\
/*
* Copyright The Mbed TLS Contributors
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*/
/*
* The following function was automatically generated through the script
* {sys.argv[0]}.
*/
#include <test/ssl_helpers.h>
#include <test/ssl_helpers_internal.h>
#include "mbedtls/psa_util.h"
#include <test/macros.h>
#include <limits.h>
#if defined(MBEDTLS_SSL_TLS_C)
int mbedtls_test_ssl_check_context_after_session_reset(const mbedtls_ssl_context *before,
const mbedtls_ssl_context *after)
{{
mbedtls_ssl_context initial;
int ret = -1;
/* Create a freshly initialized SSL context*/
memset(&initial, 0, sizeof(initial));
mbedtls_ssl_init(&initial);
TEST_EQUAL(mbedtls_ssl_setup(&initial, after->conf), 0);
/* *INDENT-OFF* */
""")
for field in self.fields:
if len(field.conditionals) > 0:
out.write('\n'.join(field.conditionals))
for check_line in field.check_value():
out.write(f' {check_line}\n')
if len(field.conditionals) > 0:
out.write('#endif\n' * len(field.conditionals))
out.write(f"""\
/* *INDENT-ON* */
ret = 0;
exit:
mbedtls_ssl_free(&initial);
return ret;
}}
#endif /* MBEDTLS_SSL_TLS_C */
""")
def main(fields_info: FieldsInfo):
if not build_tree.looks_like_mbedtls_root(os.curdir):
raise Exception("The script must be launched from the root path of Mbed TLS")
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('-o', dest='output_file',
help='Generated output file',
default='tests/src/ssl_context_reset_verifier.c')
parsed_args = arg_parser.parse_args()
output_file = parsed_args.output_file
with open(output_file, 'wt') as out:
ssl_context = SSLContextStruct(fields_info)
ssl_context.write_check_function(out)