Add tls13 compat tests
Signed-off-by: Jerry Yu <jerry.h.yu@arm.com>
diff --git a/tests/scripts/generate_tls13_compat_tests.py b/tests/scripts/generate_tls13_compat_tests.py
new file mode 100755
index 0000000..5b27f61
--- /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())