Merge pull request #9673 from eleuzi01/issue-54-fw

Move TLS auxiliary test scripts to the framework
This commit is contained in:
David Horstmann 2024-10-17 17:15:46 +00:00 committed by GitHub
commit 13d2939563
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 15 additions and 1260 deletions

@ -1 +1 @@
Subproject commit 1de0641e789d3c38b3ce99d7922002992cbe816c
Subproject commit 311d8acd1c329d6417db576ba5219973c500d696

View File

@ -61,11 +61,11 @@ if(GEN_FILES)
${CMAKE_CURRENT_BINARY_DIR}/ssl_debug_helpers_generated.c
COMMAND
${MBEDTLS_PYTHON_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/../scripts/generate_ssl_debug_helpers.py
${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_ssl_debug_helpers.py
--mbedtls-root ${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/../scripts/generate_ssl_debug_helpers.py
${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_ssl_debug_helpers.py
${tls_error_headers}
)
else()

View File

@ -364,11 +364,11 @@ $(TF_PSA_CRYPTO_DRIVERS_BUILTIN_SRC_PATH)/error.c:
echo " Gen $@"
$(PERL) ../scripts/generate_errors.pl
ssl_debug_helpers_generated.c: $(gen_file_dep) ../scripts/generate_ssl_debug_helpers.py
ssl_debug_helpers_generated.c: $(gen_file_dep) ../framework/scripts/generate_ssl_debug_helpers.py
ssl_debug_helpers_generated.c: $(gen_file_dep) $(filter-out %config%,$(wildcard ../include/mbedtls/*.h))
ssl_debug_helpers_generated.c:
echo " Gen $@"
$(PYTHON) ../scripts/generate_ssl_debug_helpers.py --mbedtls-root .. .
$(PYTHON) ../framework/scripts/generate_ssl_debug_helpers.py --mbedtls-root .. .
version_features.c: $(gen_file_dep) ../scripts/generate_features.pl
version_features.c: $(gen_file_dep) ../scripts/data_files/version_features.fmt

View File

@ -1,416 +0,0 @@
#!/usr/bin/env python3
"""Generate library/ssl_debug_helpers_generated.c
The code generated by this module includes debug helper functions that can not be
implemented by fixed codes.
"""
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
import sys
import re
import os
import textwrap
import argparse
import framework_scripts_path # pylint: disable=unused-import
from mbedtls_framework import build_tree
def remove_c_comments(string):
"""
Remove C style comments from input string
"""
string_pattern = r"(?P<string>\".*?\"|\'.*?\')"
comment_pattern = r"(?P<comment>/\*.*?\*/|//[^\r\n]*$)"
pattern = re.compile(string_pattern + r'|' + comment_pattern,
re.MULTILINE | re.DOTALL)
def replacer(match):
if match.lastgroup == 'comment':
return ""
return match.group()
return pattern.sub(replacer, string)
class CondDirectiveNotMatch(Exception):
pass
def preprocess_c_source_code(source, *classes):
"""
Simple preprocessor for C source code.
Only processes condition directives without expanding them.
Yield object according to the classes input. Most match firstly
If the directive pair does not match , raise CondDirectiveNotMatch.
Assume source code does not include comments and compile pass.
"""
pattern = re.compile(r"^[ \t]*#[ \t]*" +
r"(?P<directive>(if[ \t]|ifndef[ \t]|ifdef[ \t]|else|endif))" +
r"[ \t]*(?P<param>(.*\\\n)*.*$)",
re.MULTILINE)
stack = []
def _yield_objects(s, d, p, st, end):
"""
Output matched source piece
"""
nonlocal stack
start_line, end_line = '', ''
if stack:
start_line = '#{} {}'.format(d, p)
if d == 'if':
end_line = '#endif /* {} */'.format(p)
elif d == 'ifdef':
end_line = '#endif /* defined({}) */'.format(p)
else:
end_line = '#endif /* !defined({}) */'.format(p)
has_instance = False
for cls in classes:
for instance in cls.extract(s, st, end):
if has_instance is False:
has_instance = True
yield pair_start, start_line
yield instance.span()[0], instance
if has_instance:
yield start, end_line
for match in pattern.finditer(source):
directive = match.groupdict()['directive'].strip()
param = match.groupdict()['param']
start, end = match.span()
if directive in ('if', 'ifndef', 'ifdef'):
stack.append((directive, param, start, end))
continue
if not stack:
raise CondDirectiveNotMatch()
pair_directive, pair_param, pair_start, pair_end = stack.pop()
yield from _yield_objects(source,
pair_directive,
pair_param,
pair_end,
start)
if directive == 'endif':
continue
if pair_directive == 'if':
directive = 'if'
param = "!( {} )".format(pair_param)
elif pair_directive == 'ifdef':
directive = 'ifndef'
param = pair_param
else:
directive = 'ifdef'
param = pair_param
stack.append((directive, param, start, end))
assert not stack, len(stack)
class EnumDefinition:
"""
Generate helper functions around enumeration.
Currently, it generate translation function from enum value to string.
Enum definition looks like:
[typedef] enum [prefix name] { [body] } [suffix name];
Known limitation:
- the '}' and ';' SHOULD NOT exist in different macro blocks. Like
```
enum test {
....
#if defined(A)
....
};
#else
....
};
#endif
```
"""
@classmethod
def extract(cls, source_code, start=0, end=-1):
enum_pattern = re.compile(r'enum\s*(?P<prefix_name>\w*)\s*' +
r'{\s*(?P<body>[^}]*)}' +
r'\s*(?P<suffix_name>\w*)\s*;',
re.MULTILINE | re.DOTALL)
for match in enum_pattern.finditer(source_code, start, end):
yield EnumDefinition(source_code,
span=match.span(),
group=match.groupdict())
def __init__(self, source_code, span=None, group=None):
assert isinstance(group, dict)
prefix_name = group.get('prefix_name', None)
suffix_name = group.get('suffix_name', None)
body = group.get('body', None)
assert prefix_name or suffix_name
assert body
assert span
# If suffix_name exists, it is a typedef
self._prototype = suffix_name if suffix_name else 'enum ' + prefix_name
self._name = suffix_name if suffix_name else prefix_name
self._body = body
self._source = source_code
self._span = span
def __repr__(self):
return 'Enum({},{})'.format(self._name, self._span)
def __str__(self):
return repr(self)
def span(self):
return self._span
def generate_translation_function(self):
"""
Generate function for translating value to string
"""
translation_table = []
for line in self._body.splitlines():
if line.strip().startswith('#'):
# Preprocess directive, keep it in table
translation_table.append(line.strip())
continue
if not line.strip():
continue
for field in line.strip().split(','):
if not field.strip():
continue
member = field.strip().split()[0]
translation_table.append(
'{space}case {member}:\n{space} return "{member}";'
.format(member=member, space=' '*8)
)
body = textwrap.dedent('''\
const char *{name}_str( {prototype} in )
{{
switch (in) {{
{translation_table}
default:
return "UNKNOWN_VALUE";
}}
}}
''')
body = body.format(translation_table='\n'.join(translation_table),
name=self._name,
prototype=self._prototype)
return body
class SignatureAlgorithmDefinition:
"""
Generate helper functions for signature algorithms.
It generates translation function from signature algorithm define to string.
Signature algorithm definition looks like:
#define MBEDTLS_TLS1_3_SIG_[ upper case signature algorithm ] [ value(hex) ]
Known limitation:
- the definitions SHOULD exist in same macro blocks.
"""
@classmethod
def extract(cls, source_code, start=0, end=-1):
sig_alg_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_TLS1_3_SIG_\w+)\s+' +
r'(?P<value>0[xX][0-9a-fA-F]+)$',
re.MULTILINE | re.DOTALL)
matches = list(sig_alg_pattern.finditer(source_code, start, end))
if matches:
yield SignatureAlgorithmDefinition(source_code, definitions=matches)
def __init__(self, source_code, definitions=None):
if definitions is None:
definitions = []
assert isinstance(definitions, list) and definitions
self._definitions = definitions
self._source = source_code
def __repr__(self):
return 'SigAlgs({})'.format(self._definitions[0].span())
def span(self):
return self._definitions[0].span()
def __str__(self):
"""
Generate function for translating value to string
"""
translation_table = []
for m in self._definitions:
name = m.groupdict()['name']
return_val = name[len('MBEDTLS_TLS1_3_SIG_'):].lower()
translation_table.append(
' case {}:\n return "{}";'.format(name, return_val))
body = textwrap.dedent('''\
const char *mbedtls_ssl_sig_alg_to_str( uint16_t in )
{{
switch( in )
{{
{translation_table}
}};
return "UNKNOWN";
}}''')
body = body.format(translation_table='\n'.join(translation_table))
return body
class NamedGroupDefinition:
"""
Generate helper functions for named group
It generates translation function from named group define to string.
Named group definition looks like:
#define MBEDTLS_SSL_IANA_TLS_GROUP_[ upper case named group ] [ value(hex) ]
Known limitation:
- the definitions SHOULD exist in same macro blocks.
"""
@classmethod
def extract(cls, source_code, start=0, end=-1):
named_group_pattern = re.compile(r'#define\s+(?P<name>MBEDTLS_SSL_IANA_TLS_GROUP_\w+)\s+' +
r'(?P<value>0[xX][0-9a-fA-F]+)$',
re.MULTILINE | re.DOTALL)
matches = list(named_group_pattern.finditer(source_code, start, end))
if matches:
yield NamedGroupDefinition(source_code, definitions=matches)
def __init__(self, source_code, definitions=None):
if definitions is None:
definitions = []
assert isinstance(definitions, list) and definitions
self._definitions = definitions
self._source = source_code
def __repr__(self):
return 'NamedGroup({})'.format(self._definitions[0].span())
def span(self):
return self._definitions[0].span()
def __str__(self):
"""
Generate function for translating value to string
"""
translation_table = []
for m in self._definitions:
name = m.groupdict()['name']
iana_name = name[len('MBEDTLS_SSL_IANA_TLS_GROUP_'):].lower()
translation_table.append(' case {}:\n return "{}";'.format(name, iana_name))
body = textwrap.dedent('''\
const char *mbedtls_ssl_named_group_to_str( uint16_t in )
{{
switch( in )
{{
{translation_table}
}};
return "UNKNOWN";
}}''')
body = body.format(translation_table='\n'.join(translation_table))
return body
OUTPUT_C_TEMPLATE = '''\
/* Automatically generated by generate_ssl_debug_helpers.py. DO NOT EDIT. */
/**
* \\file ssl_debug_helpers_generated.c
*
* \\brief Automatically generated helper functions for debugging
*/
/*
* Copyright The Mbed TLS Contributors
* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
*
*/
#include "ssl_misc.h"
#if defined(MBEDTLS_DEBUG_C)
#include "ssl_debug_helpers.h"
{functions}
#endif /* MBEDTLS_DEBUG_C */
/* End of automatically generated file. */
'''
def generate_ssl_debug_helpers(output_directory, mbedtls_root):
"""
Generate functions of debug helps
"""
mbedtls_root = os.path.abspath(
mbedtls_root or build_tree.guess_mbedtls_root())
with open(os.path.join(mbedtls_root, 'include/mbedtls/ssl.h')) as f:
source_code = remove_c_comments(f.read())
definitions = dict()
for start, instance in preprocess_c_source_code(source_code,
EnumDefinition,
SignatureAlgorithmDefinition,
NamedGroupDefinition):
if start in definitions:
continue
if isinstance(instance, EnumDefinition):
definition = instance.generate_translation_function()
else:
definition = instance
definitions[start] = definition
function_definitions = [str(v) for _, v in sorted(definitions.items())]
if output_directory == sys.stdout:
sys.stdout.write(OUTPUT_C_TEMPLATE.format(
functions='\n'.join(function_definitions)))
else:
with open(os.path.join(output_directory, 'ssl_debug_helpers_generated.c'), 'w') as f:
f.write(OUTPUT_C_TEMPLATE.format(
functions='\n'.join(function_definitions)))
def main():
"""
Command line entry
"""
parser = argparse.ArgumentParser()
parser.add_argument('--mbedtls-root', nargs='?', default=None,
help='root directory of mbedtls source code')
parser.add_argument('output_directory', nargs='?',
default='library', help='source/header files location')
args = parser.parse_args()
generate_ssl_debug_helpers(args.output_directory, args.mbedtls_root)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -13,7 +13,7 @@ python scripts\generate_driver_wrappers.py || exit /b 1
perl scripts\generate_errors.pl || exit /b 1
perl scripts\generate_query_config.pl || exit /b 1
perl scripts\generate_features.pl || exit /b 1
python scripts\generate_ssl_debug_helpers.py || exit /b 1
python framework\scripts\generate_ssl_debug_helpers.py || exit /b 1
@rem @@@@ Build @@@@
perl scripts\generate_visualc_files.pl || exit /b 1
@ -29,4 +29,4 @@ python framework\scripts\generate_ecp_tests.py --directory tf-psa-crypto\tests\s
python framework\scripts\generate_psa_tests.py --directory tf-psa-crypto\tests\suites || exit /b 1
python framework\scripts\generate_test_keys.py --output tests\src\test_keys.h || exit /b 1
python framework\scripts\generate_test_cert_macros.py --output tests\src\test_certs.h || exit /b 1
python tests\scripts\generate_tls13_compat_tests.py || exit /b 1
python framework\scripts\generate_tls13_compat_tests.py || exit /b 1

View File

@ -71,9 +71,9 @@ if(GEN_FILES)
${CMAKE_CURRENT_SOURCE_DIR}/..
COMMAND
"${MBEDTLS_PYTHON_EXECUTABLE}"
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_tls13_compat_tests.py"
"${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_tls13_compat_tests.py"
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_tls13_compat_tests.py
${CMAKE_CURRENT_SOURCE_DIR}/../framework/scripts/generate_tls13_compat_tests.py
)
add_custom_target(tls13-compat.sh
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/opt-testcases/tls13-compat.sh)

View File

@ -62,9 +62,9 @@ GENERATED_FILES += src/test_keys.h src/test_certs.h
# Generated files needed to (fully) run ssl-opt.sh
.PHONY: ssl-opt
opt-testcases/tls13-compat.sh: scripts/generate_tls13_compat_tests.py
opt-testcases/tls13-compat.sh: ../framework/scripts/generate_tls13_compat_tests.py
echo " Gen $@"
$(PYTHON) scripts/generate_tls13_compat_tests.py -o $@
$(PYTHON) ../framework/scripts/generate_tls13_compat_tests.py -o $@
GENERATED_FILES += opt-testcases/tls13-compat.sh
ssl-opt: opt-testcases/tls13-compat.sh

View File

@ -290,7 +290,7 @@ reset_ciphersuites()
# list of entries of the form "STANDARD_NAME=PROGRAM_NAME".
translate_ciphers()
{
ciphers=$(scripts/translate_ciphers.py "$@")
ciphers=$(../framework/scripts/translate_ciphers.py "$@")
if [ $? -ne 0 ]; then
echo "translate_ciphers.py failed with exit code $1" >&2
echo "$2" >&2

View File

@ -173,8 +173,8 @@ if in_mbedtls_repo; then
check scripts/generate_errors.pl ${builtin_drivers_dir}/error.c
check scripts/generate_query_config.pl programs/test/query_config.c
check scripts/generate_features.pl library/version_features.c
check scripts/generate_ssl_debug_helpers.py library/ssl_debug_helpers_generated.c
check tests/scripts/generate_tls13_compat_tests.py tests/opt-testcases/tls13-compat.sh
check framework/scripts/generate_ssl_debug_helpers.py library/ssl_debug_helpers_generated.c
check framework/scripts/generate_tls13_compat_tests.py tests/opt-testcases/tls13-compat.sh
check framework/scripts/generate_test_cert_macros.py tests/src/test_certs.h
# generate_visualc_files enumerates source files (library/*.c). It doesn't
# care about their content, but the files must exist. So it must run after

View File

@ -160,6 +160,6 @@ component_check_test_helpers () {
./framework/scripts/test_generate_test_code.py 2>&1
msg "unit test: translate_ciphers.py"
python3 -m unittest tests/scripts/translate_ciphers.py 2>&1
python3 -m unittest framework/scripts/translate_ciphers.py 2>&1
}

View File

@ -1,649 +0,0 @@
#!/usr/bin/env python3
# generate_tls13_compat_tests.py
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
"""
Generate TLSv1.3 Compat test cases
"""
import sys
import os
import argparse
import itertools
from collections import namedtuple
# define certificates configuration entry
Certificate = namedtuple("Certificate", ['cafile', 'certfile', 'keyfile'])
# define the certificate parameters for signature algorithms
CERTIFICATES = {
'ecdsa_secp256r1_sha256': Certificate('$DATA_FILES_PATH/test-ca2.crt',
'$DATA_FILES_PATH/ecdsa_secp256r1.crt',
'$DATA_FILES_PATH/ecdsa_secp256r1.key'),
'ecdsa_secp384r1_sha384': Certificate('$DATA_FILES_PATH/test-ca2.crt',
'$DATA_FILES_PATH/ecdsa_secp384r1.crt',
'$DATA_FILES_PATH/ecdsa_secp384r1.key'),
'ecdsa_secp521r1_sha512': Certificate('$DATA_FILES_PATH/test-ca2.crt',
'$DATA_FILES_PATH/ecdsa_secp521r1.crt',
'$DATA_FILES_PATH/ecdsa_secp521r1.key'),
'rsa_pss_rsae_sha256': Certificate('$DATA_FILES_PATH/test-ca_cat12.crt',
'$DATA_FILES_PATH/server2-sha256.crt',
'$DATA_FILES_PATH/server2.key')
}
CIPHER_SUITE_IANA_VALUE = {
"TLS_AES_128_GCM_SHA256": 0x1301,
"TLS_AES_256_GCM_SHA384": 0x1302,
"TLS_CHACHA20_POLY1305_SHA256": 0x1303,
"TLS_AES_128_CCM_SHA256": 0x1304,
"TLS_AES_128_CCM_8_SHA256": 0x1305
}
SIG_ALG_IANA_VALUE = {
"ecdsa_secp256r1_sha256": 0x0403,
"ecdsa_secp384r1_sha384": 0x0503,
"ecdsa_secp521r1_sha512": 0x0603,
'rsa_pss_rsae_sha256': 0x0804,
}
NAMED_GROUP_IANA_VALUE = {
'secp256r1': 0x17,
'secp384r1': 0x18,
'secp521r1': 0x19,
'x25519': 0x1d,
'x448': 0x1e,
# Only one finite field group to keep testing time within reasonable bounds.
'ffdhe2048': 0x100,
}
class TLSProgram:
"""
Base class for generate server/client command.
"""
# pylint: disable=too-many-arguments
def __init__(self, ciphersuite=None, signature_algorithm=None, named_group=None,
cert_sig_alg=None):
self._ciphers = []
self._sig_algs = []
self._named_groups = []
self._cert_sig_algs = []
if ciphersuite:
self.add_ciphersuites(ciphersuite)
if named_group:
self.add_named_groups(named_group)
if signature_algorithm:
self.add_signature_algorithms(signature_algorithm)
if cert_sig_alg:
self.add_cert_signature_algorithms(cert_sig_alg)
# add_ciphersuites should not override by sub class
def add_ciphersuites(self, *ciphersuites):
self._ciphers.extend(
[cipher for cipher in ciphersuites if cipher not in self._ciphers])
# add_signature_algorithms should not override by sub class
def add_signature_algorithms(self, *signature_algorithms):
self._sig_algs.extend(
[sig_alg for sig_alg in signature_algorithms if sig_alg not in self._sig_algs])
# add_named_groups should not override by sub class
def add_named_groups(self, *named_groups):
self._named_groups.extend(
[named_group for named_group in named_groups if named_group not in self._named_groups])
# add_cert_signature_algorithms should not override by sub class
def add_cert_signature_algorithms(self, *signature_algorithms):
self._cert_sig_algs.extend(
[sig_alg for sig_alg in signature_algorithms if sig_alg not in self._cert_sig_algs])
# pylint: disable=no-self-use
def pre_checks(self):
return []
# pylint: disable=no-self-use
def cmd(self):
if not self._cert_sig_algs:
self._cert_sig_algs = list(CERTIFICATES.keys())
return self.pre_cmd()
# pylint: disable=no-self-use
def post_checks(self):
return []
# pylint: disable=no-self-use
def pre_cmd(self):
return ['false']
# pylint: disable=unused-argument,no-self-use
def hrr_post_checks(self, named_group):
return []
class OpenSSLBase(TLSProgram):
"""
Generate base test commands for OpenSSL.
"""
NAMED_GROUP = {
'secp256r1': 'P-256',
'secp384r1': 'P-384',
'secp521r1': 'P-521',
'x25519': 'X25519',
'x448': 'X448',
'ffdhe2048': 'ffdhe2048',
}
def cmd(self):
ret = super().cmd()
if self._ciphers:
ciphersuites = ':'.join(self._ciphers)
ret += ["-ciphersuites {ciphersuites}".format(ciphersuites=ciphersuites)]
if self._sig_algs:
signature_algorithms = set(self._sig_algs + self._cert_sig_algs)
signature_algorithms = ':'.join(signature_algorithms)
ret += ["-sigalgs {signature_algorithms}".format(
signature_algorithms=signature_algorithms)]
if self._named_groups:
named_groups = ':'.join(
map(lambda named_group: self.NAMED_GROUP[named_group], self._named_groups))
ret += ["-groups {named_groups}".format(named_groups=named_groups)]
ret += ['-msg -tls1_3']
return ret
def pre_checks(self):
ret = ["requires_openssl_tls1_3"]
# ffdh groups require at least openssl 3.0
ffdh_groups = ['ffdhe2048']
if any(x in ffdh_groups for x in self._named_groups):
ret = ["requires_openssl_tls1_3_with_ffdh"]
return ret
class OpenSSLServ(OpenSSLBase):
"""
Generate test commands for OpenSSL server.
"""
def cmd(self):
ret = super().cmd()
ret += ['-num_tickets 0 -no_resume_ephemeral -no_cache']
return ret
def post_checks(self):
return ['-c "HTTP/1.0 200 ok"']
def pre_cmd(self):
ret = ['$O_NEXT_SRV_NO_CERT']
for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs):
ret += ['-cert {cert} -key {key}'.format(cert=cert, key=key)]
return ret
class OpenSSLCli(OpenSSLBase):
"""
Generate test commands for OpenSSL client.
"""
def pre_cmd(self):
return ['$O_NEXT_CLI_NO_CERT',
'-CAfile {cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)]
class GnuTLSBase(TLSProgram):
"""
Generate base test commands for GnuTLS.
"""
CIPHER_SUITE = {
'TLS_AES_256_GCM_SHA384': [
'AES-256-GCM',
'SHA384',
'AEAD'],
'TLS_AES_128_GCM_SHA256': [
'AES-128-GCM',
'SHA256',
'AEAD'],
'TLS_CHACHA20_POLY1305_SHA256': [
'CHACHA20-POLY1305',
'SHA256',
'AEAD'],
'TLS_AES_128_CCM_SHA256': [
'AES-128-CCM',
'SHA256',
'AEAD'],
'TLS_AES_128_CCM_8_SHA256': [
'AES-128-CCM-8',
'SHA256',
'AEAD']}
SIGNATURE_ALGORITHM = {
'ecdsa_secp256r1_sha256': ['SIGN-ECDSA-SECP256R1-SHA256'],
'ecdsa_secp521r1_sha512': ['SIGN-ECDSA-SECP521R1-SHA512'],
'ecdsa_secp384r1_sha384': ['SIGN-ECDSA-SECP384R1-SHA384'],
'rsa_pss_rsae_sha256': ['SIGN-RSA-PSS-RSAE-SHA256']}
NAMED_GROUP = {
'secp256r1': ['GROUP-SECP256R1'],
'secp384r1': ['GROUP-SECP384R1'],
'secp521r1': ['GROUP-SECP521R1'],
'x25519': ['GROUP-X25519'],
'x448': ['GROUP-X448'],
'ffdhe2048': ['GROUP-FFDHE2048'],
}
def pre_checks(self):
return ["requires_gnutls_tls1_3",
"requires_gnutls_next_no_ticket"]
def cmd(self):
ret = super().cmd()
priority_string_list = []
def update_priority_string_list(items, map_table):
for item in items:
for i in map_table[item]:
if i not in priority_string_list:
yield i
if self._ciphers:
priority_string_list.extend(update_priority_string_list(
self._ciphers, self.CIPHER_SUITE))
else:
priority_string_list.extend(['CIPHER-ALL', 'MAC-ALL'])
if self._sig_algs:
signature_algorithms = set(self._sig_algs + self._cert_sig_algs)
priority_string_list.extend(update_priority_string_list(
signature_algorithms, self.SIGNATURE_ALGORITHM))
else:
priority_string_list.append('SIGN-ALL')
if self._named_groups:
priority_string_list.extend(update_priority_string_list(
self._named_groups, self.NAMED_GROUP))
else:
priority_string_list.append('GROUP-ALL')
priority_string_list = ['NONE'] + \
priority_string_list + ['VERS-TLS1.3']
priority_string = ':+'.join(priority_string_list)
priority_string += ':%NO_TICKETS'
ret += ['--priority={priority_string}'.format(
priority_string=priority_string)]
return ret
class GnuTLSServ(GnuTLSBase):
"""
Generate test commands for GnuTLS server.
"""
def pre_cmd(self):
ret = ['$G_NEXT_SRV_NO_CERT', '--http', '--disable-client-cert', '--debug=4']
for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs):
ret += ['--x509certfile {cert} --x509keyfile {key}'.format(
cert=cert, key=key)]
return ret
def post_checks(self):
return ['-c "HTTP/1.0 200 OK"']
class GnuTLSCli(GnuTLSBase):
"""
Generate test commands for GnuTLS client.
"""
def pre_cmd(self):
return ['$G_NEXT_CLI_NO_CERT', '--debug=4', '--single-key-share',
'--x509cafile {cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)]
class MbedTLSBase(TLSProgram):
"""
Generate base test commands for mbedTLS.
"""
CIPHER_SUITE = {
'TLS_AES_256_GCM_SHA384': 'TLS1-3-AES-256-GCM-SHA384',
'TLS_AES_128_GCM_SHA256': 'TLS1-3-AES-128-GCM-SHA256',
'TLS_CHACHA20_POLY1305_SHA256': 'TLS1-3-CHACHA20-POLY1305-SHA256',
'TLS_AES_128_CCM_SHA256': 'TLS1-3-AES-128-CCM-SHA256',
'TLS_AES_128_CCM_8_SHA256': 'TLS1-3-AES-128-CCM-8-SHA256'}
def cmd(self):
ret = super().cmd()
ret += ['debug_level=4']
if self._ciphers:
ciphers = ','.join(
map(lambda cipher: self.CIPHER_SUITE[cipher], self._ciphers))
ret += ["force_ciphersuite={ciphers}".format(ciphers=ciphers)]
if self._sig_algs + self._cert_sig_algs:
ret += ['sig_algs={sig_algs}'.format(
sig_algs=','.join(set(self._sig_algs + self._cert_sig_algs)))]
if self._named_groups:
named_groups = ','.join(self._named_groups)
ret += ["groups={named_groups}".format(named_groups=named_groups)]
return ret
#pylint: disable=missing-function-docstring
def add_ffdh_group_requirements(self, requirement_list):
if 'ffdhe2048' in self._named_groups:
requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048')
if 'ffdhe3072' in self._named_groups:
requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048')
if 'ffdhe4096' in self._named_groups:
requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048')
if 'ffdhe6144' in self._named_groups:
requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048')
if 'ffdhe8192' in self._named_groups:
requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048')
def pre_checks(self):
ret = ['requires_config_enabled MBEDTLS_DEBUG_C',
'requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED']
if 'rsa_pss_rsae_sha256' in self._sig_algs + self._cert_sig_algs:
ret.append(
'requires_config_enabled MBEDTLS_X509_RSASSA_PSS_SUPPORT')
ec_groups = ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448']
ffdh_groups = ['ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192']
if any(x in ec_groups for x in self._named_groups):
ret.append('requires_config_enabled PSA_WANT_ALG_ECDH')
if any(x in ffdh_groups for x in self._named_groups):
ret.append('requires_config_enabled PSA_WANT_ALG_FFDH')
self.add_ffdh_group_requirements(ret)
return ret
class MbedTLSServ(MbedTLSBase):
"""
Generate test commands for mbedTLS server.
"""
def cmd(self):
ret = super().cmd()
ret += ['tls13_kex_modes=ephemeral cookies=0 tickets=0']
return ret
def pre_checks(self):
return ['requires_config_enabled MBEDTLS_SSL_SRV_C'] + super().pre_checks()
def post_checks(self):
check_strings = ["Protocol is TLSv1.3"]
if self._ciphers:
check_strings.append(
"server hello, chosen ciphersuite: {} ( id={:04d} )".format(
self.CIPHER_SUITE[self._ciphers[0]],
CIPHER_SUITE_IANA_VALUE[self._ciphers[0]]))
if self._sig_algs:
check_strings.append(
"received signature algorithm: 0x{:x}".format(
SIG_ALG_IANA_VALUE[self._sig_algs[0]]))
for named_group in self._named_groups:
check_strings += ['got named group: {named_group}({iana_value:04x})'.format(
named_group=named_group,
iana_value=NAMED_GROUP_IANA_VALUE[named_group])]
check_strings.append("Certificate verification was skipped")
return ['-s "{}"'.format(i) for i in check_strings]
def pre_cmd(self):
ret = ['$P_SRV']
for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs):
ret += ['crt_file={cert} key_file={key}'.format(cert=cert, key=key)]
return ret
def hrr_post_checks(self, named_group):
return ['-s "HRR selected_group: {:s}"'.format(named_group)]
class MbedTLSCli(MbedTLSBase):
"""
Generate test commands for mbedTLS client.
"""
def pre_cmd(self):
return ['$P_CLI',
'ca_file={cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)]
def pre_checks(self):
return ['requires_config_enabled MBEDTLS_SSL_CLI_C'] + super().pre_checks()
def hrr_post_checks(self, named_group):
ret = ['-c "received HelloRetryRequest message"']
ret += ['-c "selected_group ( {:d} )"'.format(NAMED_GROUP_IANA_VALUE[named_group])]
return ret
def post_checks(self):
check_strings = ["Protocol is TLSv1.3"]
if self._ciphers:
check_strings.append(
"server hello, chosen ciphersuite: ( {:04x} ) - {}".format(
CIPHER_SUITE_IANA_VALUE[self._ciphers[0]],
self.CIPHER_SUITE[self._ciphers[0]]))
if self._sig_algs:
check_strings.append(
"Certificate Verify: Signature algorithm ( {:04x} )".format(
SIG_ALG_IANA_VALUE[self._sig_algs[0]]))
for named_group in self._named_groups:
check_strings += ['NamedGroup: {named_group} ( {iana_value:x} )'.format(
named_group=named_group,
iana_value=NAMED_GROUP_IANA_VALUE[named_group])]
check_strings.append("Verifying peer X.509 certificate... ok")
return ['-c "{}"'.format(i) for i in check_strings]
SERVER_CLASSES = {'OpenSSL': OpenSSLServ, 'GnuTLS': GnuTLSServ, 'mbedTLS': MbedTLSServ}
CLIENT_CLASSES = {'OpenSSL': OpenSSLCli, 'GnuTLS': GnuTLSCli, 'mbedTLS': MbedTLSCli}
def generate_compat_test(client=None, server=None, cipher=None, named_group=None, sig_alg=None):
"""
Generate test case with `ssl-opt.sh` format.
"""
name = 'TLS 1.3 {client[0]}->{server[0]}: {cipher},{named_group},{sig_alg}'.format(
client=client, server=server, cipher=cipher[4:], sig_alg=sig_alg, named_group=named_group)
server_object = SERVER_CLASSES[server](ciphersuite=cipher,
named_group=named_group,
signature_algorithm=sig_alg,
cert_sig_alg=sig_alg)
client_object = CLIENT_CLASSES[client](ciphersuite=cipher,
named_group=named_group,
signature_algorithm=sig_alg,
cert_sig_alg=sig_alg)
cmd = ['run_test "{}"'.format(name),
'"{}"'.format(' '.join(server_object.cmd())),
'"{}"'.format(' '.join(client_object.cmd())),
'0']
cmd += server_object.post_checks()
cmd += client_object.post_checks()
cmd += ['-C "received HelloRetryRequest message"']
prefix = ' \\\n' + (' '*9)
cmd = prefix.join(cmd)
return '\n'.join(server_object.pre_checks() + client_object.pre_checks() + [cmd])
def generate_hrr_compat_test(client=None, server=None,
client_named_group=None, server_named_group=None,
cert_sig_alg=None):
"""
Generate Hello Retry Request test case with `ssl-opt.sh` format.
"""
name = 'TLS 1.3 {client[0]}->{server[0]}: HRR {c_named_group} -> {s_named_group}'.format(
client=client, server=server, c_named_group=client_named_group,
s_named_group=server_named_group)
server_object = SERVER_CLASSES[server](named_group=server_named_group,
cert_sig_alg=cert_sig_alg)
client_object = CLIENT_CLASSES[client](named_group=client_named_group,
cert_sig_alg=cert_sig_alg)
client_object.add_named_groups(server_named_group)
cmd = ['run_test "{}"'.format(name),
'"{}"'.format(' '.join(server_object.cmd())),
'"{}"'.format(' '.join(client_object.cmd())),
'0']
cmd += server_object.post_checks()
cmd += client_object.post_checks()
cmd += server_object.hrr_post_checks(server_named_group)
cmd += client_object.hrr_post_checks(server_named_group)
prefix = ' \\\n' + (' '*9)
cmd = prefix.join(cmd)
return '\n'.join(server_object.pre_checks() +
client_object.pre_checks() +
[cmd])
SSL_OUTPUT_HEADER = '''\
# TLS 1.3 interoperability test cases (equivalent of compat.sh for TLS 1.3).
#
# Automatically generated by {cmd}. Do not edit!
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
'''
DATA_FILES_PATH_VAR = '''
DATA_FILES_PATH=../framework/data_files
'''
def main():
"""
Main function of this program
"""
parser = argparse.ArgumentParser()
parser.add_argument('-o', '--output',
default='tests/opt-testcases/tls13-compat.sh',
help='Output file path (not used with -1)')
parser.add_argument('-1', '--single', action='store_true',
help='Print a single test case')
# Single mode used to be the default.
parser.add_argument('-a', '--generate-all-tls13-compat-tests',
action='store_false', dest='single',
help='Generate all test cases (negates -1) (default)')
parser.add_argument('--list-ciphers', action='store_true',
default=False, help='List supported ciphersuites')
parser.add_argument('--list-sig-algs', action='store_true',
default=False, help='List supported signature algorithms')
parser.add_argument('--list-named-groups', action='store_true',
default=False, help='List supported named groups')
parser.add_argument('--list-servers', action='store_true',
default=False, help='List supported TLS servers')
parser.add_argument('--list-clients', action='store_true',
default=False, help='List supported TLS Clients')
parser.add_argument('server', choices=SERVER_CLASSES.keys(), nargs='?',
default=list(SERVER_CLASSES.keys())[0],
help='Choose TLS server program for test')
parser.add_argument('client', choices=CLIENT_CLASSES.keys(), nargs='?',
default=list(CLIENT_CLASSES.keys())[0],
help='Choose TLS client program for test')
parser.add_argument('cipher', choices=CIPHER_SUITE_IANA_VALUE.keys(), nargs='?',
default=list(CIPHER_SUITE_IANA_VALUE.keys())[0],
help='Choose cipher suite for test')
parser.add_argument('sig_alg', choices=SIG_ALG_IANA_VALUE.keys(), nargs='?',
default=list(SIG_ALG_IANA_VALUE.keys())[0],
help='Choose cipher suite for test')
parser.add_argument('named_group', choices=NAMED_GROUP_IANA_VALUE.keys(), nargs='?',
default=list(NAMED_GROUP_IANA_VALUE.keys())[0],
help='Choose cipher suite for test')
args = parser.parse_args()
def get_all_test_cases():
# Generate normal compat test cases
for client, server, cipher, named_group, sig_alg in \
itertools.product(CLIENT_CLASSES.keys(),
SERVER_CLASSES.keys(),
CIPHER_SUITE_IANA_VALUE.keys(),
NAMED_GROUP_IANA_VALUE.keys(),
SIG_ALG_IANA_VALUE.keys()):
if server == 'mbedTLS' or client == 'mbedTLS':
yield generate_compat_test(client=client, server=server,
cipher=cipher, named_group=named_group,
sig_alg=sig_alg)
# Generate Hello Retry Request compat test cases
for client, server, client_named_group, server_named_group in \
itertools.product(CLIENT_CLASSES.keys(),
SERVER_CLASSES.keys(),
NAMED_GROUP_IANA_VALUE.keys(),
NAMED_GROUP_IANA_VALUE.keys()):
if (client == 'mbedTLS' or server == 'mbedTLS') and \
client_named_group != server_named_group:
yield generate_hrr_compat_test(client=client, server=server,
client_named_group=client_named_group,
server_named_group=server_named_group,
cert_sig_alg="ecdsa_secp256r1_sha256")
if not args.single:
if args.output:
with open(args.output, 'w', encoding="utf-8") as f:
f.write(SSL_OUTPUT_HEADER.format(
filename=os.path.basename(args.output),
cmd=os.path.basename(sys.argv[0])))
f.write(DATA_FILES_PATH_VAR)
f.write('\n\n'.join(get_all_test_cases()))
f.write('\n')
else:
print('\n\n'.join(get_all_test_cases()))
return 0
if args.list_ciphers or args.list_sig_algs or args.list_named_groups \
or args.list_servers or args.list_clients:
if args.list_ciphers:
print(*CIPHER_SUITE_IANA_VALUE.keys())
if args.list_sig_algs:
print(*SIG_ALG_IANA_VALUE.keys())
if args.list_named_groups:
print(*NAMED_GROUP_IANA_VALUE.keys())
if args.list_servers:
print(*SERVER_CLASSES.keys())
if args.list_clients:
print(*CLIENT_CLASSES.keys())
return 0
print(generate_compat_test(server=args.server, client=args.client, sig_alg=args.sig_alg,
cipher=args.cipher, named_group=args.named_group))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,180 +0,0 @@
#!/usr/bin/env python3
# translate_ciphers.py
#
# Copyright The Mbed TLS Contributors
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
"""
Translate standard ciphersuite names to GnuTLS, OpenSSL and Mbed TLS standards.
To test the translation functions run:
python3 -m unittest translate_cipher.py
"""
import re
import argparse
import unittest
class TestTranslateCiphers(unittest.TestCase):
"""
Ensure translate_ciphers.py translates and formats ciphersuite names
correctly
"""
def test_translate_all_cipher_names(self):
"""
Translate standard ciphersuite names to GnuTLS, OpenSSL and
Mbed TLS counterpart. Use only a small subset of ciphers
that exercise each step of the translation functions
"""
ciphers = [
("TLS_ECDHE_ECDSA_WITH_NULL_SHA",
"+ECDHE-ECDSA:+NULL:+SHA1",
"ECDHE-ECDSA-NULL-SHA",
"TLS-ECDHE-ECDSA-WITH-NULL-SHA"),
("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"+ECDHE-ECDSA:+AES-128-GCM:+AEAD",
"ECDHE-ECDSA-AES128-GCM-SHA256",
"TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256"),
("TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"+DHE-RSA:+3DES-CBC:+SHA1",
"EDH-RSA-DES-CBC3-SHA",
"TLS-DHE-RSA-WITH-3DES-EDE-CBC-SHA"),
("TLS_RSA_WITH_AES_256_CBC_SHA",
"+RSA:+AES-256-CBC:+SHA1",
"AES256-SHA",
"TLS-RSA-WITH-AES-256-CBC-SHA"),
("TLS_PSK_WITH_3DES_EDE_CBC_SHA",
"+PSK:+3DES-CBC:+SHA1",
"PSK-3DES-EDE-CBC-SHA",
"TLS-PSK-WITH-3DES-EDE-CBC-SHA"),
("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
None,
"ECDHE-ECDSA-CHACHA20-POLY1305",
"TLS-ECDHE-ECDSA-WITH-CHACHA20-POLY1305-SHA256"),
("TLS_ECDHE_ECDSA_WITH_AES_128_CCM",
"+ECDHE-ECDSA:+AES-128-CCM:+AEAD",
None,
"TLS-ECDHE-ECDSA-WITH-AES-128-CCM"),
("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384",
None,
"ECDHE-ARIA256-GCM-SHA384",
"TLS-ECDHE-RSA-WITH-ARIA-256-GCM-SHA384"),
]
for s, g_exp, o_exp, m_exp in ciphers:
if g_exp is not None:
g = translate_gnutls(s)
self.assertEqual(g, g_exp)
if o_exp is not None:
o = translate_ossl(s)
self.assertEqual(o, o_exp)
if m_exp is not None:
m = translate_mbedtls(s)
self.assertEqual(m, m_exp)
def translate_gnutls(s_cipher):
"""
Translate s_cipher from standard ciphersuite naming convention
and return the GnuTLS naming convention
"""
# Replace "_" with "-" to handle ciphersuite names based on Mbed TLS
# naming convention
s_cipher = s_cipher.replace("_", "-")
s_cipher = re.sub(r'\ATLS-', '+', s_cipher)
s_cipher = s_cipher.replace("-WITH-", ":+")
s_cipher = s_cipher.replace("-EDE", "")
# SHA in Mbed TLS == SHA1 GnuTLS,
# if the last 3 chars are SHA append 1
if s_cipher[-3:] == "SHA":
s_cipher = s_cipher+"1"
# CCM or CCM-8 should be followed by ":+AEAD"
# Replace "GCM:+SHAxyz" with "GCM:+AEAD"
if "CCM" in s_cipher or "GCM" in s_cipher:
s_cipher = re.sub(r"GCM-SHA\d\d\d", "GCM", s_cipher)
s_cipher = s_cipher+":+AEAD"
# Replace the last "-" with ":+"
else:
index = s_cipher.rindex("-")
s_cipher = s_cipher[:index] + ":+" + s_cipher[index+1:]
return s_cipher
def translate_ossl(s_cipher):
"""
Translate s_cipher from standard ciphersuite naming convention
and return the OpenSSL naming convention
"""
# Replace "_" with "-" to handle ciphersuite names based on Mbed TLS
# naming convention
s_cipher = s_cipher.replace("_", "-")
s_cipher = re.sub(r'^TLS-', '', s_cipher)
s_cipher = s_cipher.replace("-WITH", "")
# Remove the "-" from "ABC-xyz"
s_cipher = s_cipher.replace("AES-", "AES")
s_cipher = s_cipher.replace("CAMELLIA-", "CAMELLIA")
s_cipher = s_cipher.replace("ARIA-", "ARIA")
# Remove "RSA" if it is at the beginning
s_cipher = re.sub(r'^RSA-', r'', s_cipher)
# For all circumstances outside of PSK
if "PSK" not in s_cipher:
s_cipher = s_cipher.replace("-EDE", "")
s_cipher = s_cipher.replace("3DES-CBC", "DES-CBC3")
# Remove "CBC" if it is not prefixed by DES
s_cipher = re.sub(r'(?<!DES-)CBC-', r'', s_cipher)
# ECDHE-RSA-ARIA does not exist in OpenSSL
s_cipher = s_cipher.replace("ECDHE-RSA-ARIA", "ECDHE-ARIA")
# POLY1305 should not be followed by anything
if "POLY1305" in s_cipher:
index = s_cipher.rindex("POLY1305")
s_cipher = s_cipher[:index+8]
# If DES is being used, Replace DHE with EDH
if "DES" in s_cipher and "DHE" in s_cipher and "ECDHE" not in s_cipher:
s_cipher = s_cipher.replace("DHE", "EDH")
return s_cipher
def translate_mbedtls(s_cipher):
"""
Translate s_cipher from standard ciphersuite naming convention
and return Mbed TLS ciphersuite naming convention
"""
# Replace "_" with "-"
s_cipher = s_cipher.replace("_", "-")
return s_cipher
def format_ciphersuite_names(mode, names):
t = {"g": translate_gnutls,
"o": translate_ossl,
"m": translate_mbedtls
}[mode]
return " ".join(c + '=' + t(c) for c in names)
def main(target, names):
print(format_ciphersuite_names(target, names))
if __name__ == "__main__":
PARSER = argparse.ArgumentParser()
PARSER.add_argument('target', metavar='TARGET', choices=['o', 'g', 'm'])
PARSER.add_argument('names', metavar='NAMES', nargs='+')
ARGS = PARSER.parse_args()
main(ARGS.target, ARGS.names)