diff --git a/tests/scripts/generate_tls13_compat_tests.py b/tests/scripts/generate_tls13_compat_tests.py new file mode 100755 index 000000000..5b27f61cc --- /dev/null +++ b/tests/scripts/generate_tls13_compat_tests.py @@ -0,0 +1,425 @@ +#!/usr/bin/env python3 + +# generate_tls13_compat_tests.py +# +# 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. + +""" +Generate TLSv1.3 Compat test cases + +""" + +import sys +import abc +import argparse + +# pylint: disable=useless-super-delegation + +CERTIFICATES = { + 'ecdsa_secp256r1_sha256': ( + 'data_files/ecdsa_secp256r1_sha256.crt', + 'data_files/ecdsa_secp256r1_sha256.key'), + 'ecdsa_secp384r1_sha384': ( + 'data_files/ecdsa_secp384r1_sha384.crt', + 'data_files/ecdsa_secp384r1_sha384.key'), + 'ecdsa_secp521r1_sha512': ( + 'data_files/ecdsa_secp521r1_sha512.crt', + 'data_files/ecdsa_secp521r1_sha512.key'), +} + +CAFILE = 'data_files/test-ca2.crt' + +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, +} + +NAMED_GROUP_IANA_VALUE = { + 'secp256r1': 0x17, + 'secp384r1': 0x18, + 'secp521r1': 0x19, + 'x25519': 0x1d, + 'x448': 0x1e, +} + + +def remove_duplicates(seq): + seen = set() + seen_add = seen.add + return [x for x in seq if not (x in seen or seen_add(x))] + + +class TLSProgram(metaclass=abc.ABCMeta): + """ + Base class for generate server/client command. + """ + def __init__(self, ciphersuite, signature_algorithm, named_group): + self._cipher = ciphersuite + self._sig_alg = signature_algorithm + self._named_group = named_group + self.add_ciphersuites(ciphersuite) + self.add_named_groups(named_group) + self.add_signature_algorithms(signature_algorithm) + + @abc.abstractmethod + def add_ciphersuites(self, *ciphersuites): + pass + + @abc.abstractmethod + def add_signature_algorithms(self, *signature_algorithms): + pass + + @abc.abstractmethod + def add_named_groups(self, *named_groups): + pass + + @abc.abstractmethod + def pre_checks(self): + return [] + + @abc.abstractmethod + def cmd(self): + pass + + @abc.abstractmethod + def post_checks(self): + return [] + + +class OpenSSLServ(TLSProgram): + """ + Generate test commands for OpenSSL server. + """ + program = '$OPENSSL_NEXT' + + def __init__( + self, + ciphersuite, + signature_algorithm, + named_group): + self.ciphersuites = [] + self.named_groups = [] + self.signature_algorithms = [] + self.certificates = [] + super().__init__(ciphersuite, signature_algorithm, named_group) + + 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']} + + def add_ciphersuites(self, *ciphersuites): + self.ciphersuites.extend(ciphersuites) + + def add_signature_algorithms(self, *signature_algorithms): + self.signature_algorithms.extend(signature_algorithms) + for sig_alg in signature_algorithms: + self.certificates.append(CERTIFICATES[sig_alg]) + + NAMED_GROUP = { + 'secp256r1': 'P-256', + 'secp384r1': 'P-384', + 'secp521r1': 'P-521', + 'x25519': 'X25519', + 'x448': 'X448', + } + + def add_named_groups(self, *named_groups): + for named_group in named_groups: + self.named_groups.append(self.NAMED_GROUP[named_group]) + + def cmd(self): + ret = ['$O_NEXT_SRV_NO_CERT'] + for cert, key in self.certificates: + ret += ['-cert {cert} -key {key}'.format(cert=cert, key=key)] + ret += ['-accept $SRV_PORT'] + ciphersuites = ','.join(self.ciphersuites) + signature_algorithms = ','.join(self.signature_algorithms) + named_groups = ','.join(self.named_groups) + ret += ["-ciphersuites {ciphersuites}".format(ciphersuites=ciphersuites), + "-sigalgs {signature_algorithms}".format( + signature_algorithms=signature_algorithms), + "-groups {named_groups}".format(named_groups=named_groups)] + ret += ['-msg -tls1_3 -no_middlebox -num_tickets 0 -no_resume_ephemeral -no_cache'] + return ' '.join(ret) + + def pre_checks(self): + return ["requires_openssl_tls1_3"] + + def post_checks(self): + return [] + + +class GnuTLSServ(TLSProgram): + """ + Generate test commands for GnuTLS server. + """ + + def __init__(self, ciphersuite, signature_algorithm, named_group): + self.priority_strings = [] + self.certificates = [] + super().__init__(ciphersuite, signature_algorithm, named_group) + + 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']} + + def add_ciphersuites(self, *ciphersuites): + for cihpersuite in ciphersuites: + self.priority_strings.extend(self.CIPHER_SUITE[cihpersuite]) + + SIGNATURE_ALGORITHM = { + 'ecdsa_secp256r1_sha256': ['SIGN-ECDSA-SECP256R1-SHA256'], + 'ecdsa_secp521r1_sha512': ['SIGN-ECDSA-SECP521R1-SHA512'], + 'ecdsa_secp384r1_sha384': ['SIGN-ECDSA-SECP384R1-SHA384']} + + def add_signature_algorithms(self, *signature_algorithms): + for sig_alg in signature_algorithms: + self.priority_strings.extend(self.SIGNATURE_ALGORITHM[sig_alg]) + self.certificates.append(CERTIFICATES[sig_alg]) + + NAMED_GROUP = { + 'secp256r1': ['GROUP-SECP256R1'], + 'secp384r1': ['GROUP-SECP384R1'], + 'secp521r1': ['GROUP-SECP521R1'], + 'x25519': ['GROUP-X25519'], + 'x448': ['GROUP-X448'], + } + + def add_named_groups(self, *named_groups): + for named_group in named_groups: + self.priority_strings.extend(self.NAMED_GROUP[named_group]) + + def pre_checks(self): + return ["requires_gnutls_tls1_3", + "requires_gnutls_next_no_ticket", + "requires_gnutls_next_disable_tls13_compat", ] + + def post_checks(self): + return super().post_checks() + + def cmd(self): + ret = [ + '$G_NEXT_SRV_NO_CERT', + '--http', + '--disable-client-cert', + '--debug=4'] + for cert, key in self.certificates: + ret += ['--x509certfile {cert} --x509keyfile {key}'.format( + cert=cert, key=key)] + priority_strings = ':+'.join(['NONE'] + + list(set(self.priority_strings)) + + ['VERS-TLS1.3']) + priority_strings += ':%NO_TICKETS:%DISABLE_TLS13_COMPAT_MODE' + ret += ['--priority={priority_strings}'.format( + priority_strings=priority_strings)] + ret = ' '.join(ret) + return ret + + +class MbedTLSCli(TLSProgram): + """ + Generate test commands for mbedTLS client. + """ + def __init__(self, ciphersuite, signature_algorithm, named_group): + self.ciphersuites = [] + self.certificates = [] + self.signature_algorithms = [] + self.named_groups = [] + self.needed_named_groups = [] + super().__init__(ciphersuite, signature_algorithm, named_group) + + 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 add_ciphersuites(self, *ciphersuites): + for cihpersuite in ciphersuites: + self.ciphersuites.append(self.CIPHER_SUITE[cihpersuite]) + + def add_signature_algorithms(self, *signature_algorithms): + for sig_alg in signature_algorithms: + self.signature_algorithms.append(sig_alg) + if sig_alg == 'ecdsa_secp256r1_sha256': + self.needed_named_groups.append('secp256r1') + elif sig_alg == 'ecdsa_secp521r1_sha512': + self.needed_named_groups.append('secp521r1') + elif sig_alg == 'ecdsa_secp384r1_sha384': + self.needed_named_groups.append('secp384r1') + + self.certificates.append(CERTIFICATES[sig_alg]) + + def add_named_groups(self, *named_groups): + for named_group in named_groups: + self.named_groups.append(named_group) + + def pre_checks(self): + return ['requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL', + 'requires_config_disabled MBEDTLS_USE_PSA_CRYPTO'] + + def post_checks(self): + + check_strings = ["ECDH curve: {group}".format(group=self._named_group), + "server hello, chosen ciphersuite: ( {:04x} ) - {}".format( + CIPHER_SUITE_IANA_VALUE[self._cipher], + self.CIPHER_SUITE[self._cipher]), + "Certificate Verify: Signature algorithm ( {:04x} )".format( + SIG_ALG_IANA_VALUE[self._sig_alg]), + "Verifying peer X.509 certificate... ok", ] + return ['-c "{}"'.format(i) for i in check_strings] + + def cmd(self): + ret = ['$P_CLI'] + ret += [ + 'server_addr=127.0.0.1 server_port=$SRV_PORT', + 'debug_level=4 force_version=tls1_3'] + ret += ['ca_file={CAFILE}'.format(CAFILE=CAFILE)] + self.ciphersuites = list(set(self.ciphersuites)) + cipher = ','.join(self.ciphersuites) + if cipher: + ret += ["force_ciphersuite={cipher}".format(cipher=cipher)] + self.named_groups = remove_duplicates( + self.named_groups + self.needed_named_groups) + group = ','.join(self.named_groups) + if group: + ret += ["curves={group}".format(group=group)] + sig_alg = ','.join(self.signature_algorithms) + ret += ['sig_algs={sig_alg}'.format(sig_alg=sig_alg)] + ret = ' '.join(ret) + return ret + + +SERVER_CLS = {'OpenSSL': OpenSSLServ, 'GnuTLS': GnuTLSServ} +CLIENT_CLS = {'mbedTLS': MbedTLSCli} + + +def generate_compat_test(server=None, client=None, cipher=None, # pylint: disable=unused-argument + sig_alg=None, named_group=None, **kwargs): + """ + Generate test case with `ssl-opt.sh` format. + """ + name = 'TLS1.3 {client[0]}->{server[0]}: {cipher},{named_group},{sig_alg}'.format( + client=client, server=server, cipher=cipher, sig_alg=sig_alg, named_group=named_group) + server = SERVER_CLS[server](cipher, sig_alg, named_group) + client = CLIENT_CLS[client](cipher, sig_alg, named_group) + + cmd = ['run_test "{}"'.format(name), '"{}"'.format( + server.cmd()), '"{}"'.format(client.cmd()), '0'] + cmd += server.post_checks() + cmd += client.post_checks() + prefix = ' \\\n' + (' '*12) + cmd = prefix.join(cmd) + print('\n'.join(server.pre_checks() + client.pre_checks() + [cmd])) + return 0 +def main(): + parser = argparse.ArgumentParser() + + 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_CLS.keys(), nargs='?', + default=list(SERVER_CLS.keys())[0], + help='Choose TLS server program for test') + parser.add_argument('client', choices=CLIENT_CLS.keys(), nargs='?', + default=list(CLIENT_CLS.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() + 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_CLS.keys()) + if args.list_clients: + print(*CLIENT_CLS.keys()) + return 0 + return generate_compat_test(**vars(args)) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/ssl-opt.sh b/tests/ssl-opt.sh index 3c8248ce2..0d47fd527 100755 --- a/tests/ssl-opt.sh +++ b/tests/ssl-opt.sh @@ -80,18 +80,22 @@ fi if [ -n "${OPENSSL_NEXT:-}" ]; then O_NEXT_SRV="$OPENSSL_NEXT s_server -www -cert data_files/server5.crt -key data_files/server5.key" O_NEXT_SRV_RSA="$OPENSSL_NEXT s_server -www -cert data_files/server2-sha256.crt -key data_files/server2.key" + O_NEXT_SRV_NO_CERT="$OPENSSL_NEXT s_server -www " O_NEXT_CLI="echo 'GET / HTTP/1.0' | $OPENSSL_NEXT s_client" else O_NEXT_SRV=false O_NEXT_SRV_RSA=false + O_NEXT_SRV_NO_CERT=false O_NEXT_CLI=false fi if [ -n "${GNUTLS_NEXT_SERV:-}" ]; then G_NEXT_SRV="$GNUTLS_NEXT_SERV --x509certfile data_files/server5.crt --x509keyfile data_files/server5.key" G_NEXT_SRV_RSA="$GNUTLS_NEXT_SERV --x509certfile data_files/server2-sha256.crt --x509keyfile data_files/server2.key" + G_NEXT_SRV_NO_CERT="$GNUTLS_NEXT_SERV" else G_NEXT_SRV=false + G_NEXT_SRV_NO_CERT=false G_NEXT_SRV_RSA=false fi @@ -1289,6 +1293,26 @@ run_tests_memory_after_hanshake() run_test_memory_after_hanshake_with_mfl 512 "$MEMORY_USAGE_MFL_16K" } +run_test_tls13_compat() +{ + for server in $(scripts/generate_tls13_compat_tests.py --list-servers) + do + for client in $(scripts/generate_tls13_compat_tests.py --list-clients) + do + for cipher in $(scripts/generate_tls13_compat_tests.py --list-ciphers) + do + for sig_alg in $(scripts/generate_tls13_compat_tests.py --list-sig-algs) + do + for named_group in $(scripts/generate_tls13_compat_tests.py --list-named-groups) + do + eval "$(scripts/generate_tls13_compat_tests.py $server $client $cipher $sig_alg $named_group)" + done + done + done + done + done +} + cleanup() { rm -f $CLI_OUT $SRV_OUT $PXY_OUT $SESSION rm -f context_srv.txt @@ -1421,12 +1445,14 @@ fi if [ -n "${OPENSSL_NEXT:-}" ]; then O_NEXT_SRV="$O_NEXT_SRV -accept $SRV_PORT" O_NEXT_SRV_RSA="$O_NEXT_SRV_RSA -accept $SRV_PORT" + O_NEXT_SRV_NO_CERT="$O_NEXT_SRV_NO_CERT -accept $SRV_PORT" O_NEXT_CLI="$O_NEXT_CLI -connect 127.0.0.1:+SRV_PORT" fi if [ -n "${GNUTLS_NEXT_SERV:-}" ]; then G_NEXT_SRV="$G_NEXT_SRV -p $SRV_PORT" G_NEXT_SRV_RSA="$G_NEXT_SRV_RSA -p $SRV_PORT" + G_NEXT_SRV_NO_CERT="$G_NEXT_SRV_NO_CERT -p $SRV_PORT" fi if [ -n "${GNUTLS_NEXT_CLI:-}" ]; then @@ -9024,6 +9050,8 @@ run_test "TLS1.3: HelloRetryRequest check - gnutls" \ -c "Last error was: -0x6E00 - SSL - The handshake negotiation failed" \ -s "HELLO RETRY REQUEST was queued" +run_test_tls13_compat + # Test heap memory usage after handshake requires_config_enabled MBEDTLS_MEMORY_DEBUG requires_config_enabled MBEDTLS_MEMORY_BUFFER_ALLOC_C