1a8e1175bSopenharmony_ci#!/usr/bin/env python3 2a8e1175bSopenharmony_ci 3a8e1175bSopenharmony_ci# generate_tls13_compat_tests.py 4a8e1175bSopenharmony_ci# 5a8e1175bSopenharmony_ci# Copyright The Mbed TLS Contributors 6a8e1175bSopenharmony_ci# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 7a8e1175bSopenharmony_ci 8a8e1175bSopenharmony_ci""" 9a8e1175bSopenharmony_ciGenerate TLSv1.3 Compat test cases 10a8e1175bSopenharmony_ci 11a8e1175bSopenharmony_ci""" 12a8e1175bSopenharmony_ci 13a8e1175bSopenharmony_ciimport sys 14a8e1175bSopenharmony_ciimport os 15a8e1175bSopenharmony_ciimport argparse 16a8e1175bSopenharmony_ciimport itertools 17a8e1175bSopenharmony_cifrom collections import namedtuple 18a8e1175bSopenharmony_ci 19a8e1175bSopenharmony_ci# define certificates configuration entry 20a8e1175bSopenharmony_ciCertificate = namedtuple("Certificate", ['cafile', 'certfile', 'keyfile']) 21a8e1175bSopenharmony_ci# define the certificate parameters for signature algorithms 22a8e1175bSopenharmony_ciCERTIFICATES = { 23a8e1175bSopenharmony_ci 'ecdsa_secp256r1_sha256': Certificate('data_files/test-ca2.crt', 24a8e1175bSopenharmony_ci 'data_files/ecdsa_secp256r1.crt', 25a8e1175bSopenharmony_ci 'data_files/ecdsa_secp256r1.key'), 26a8e1175bSopenharmony_ci 'ecdsa_secp384r1_sha384': Certificate('data_files/test-ca2.crt', 27a8e1175bSopenharmony_ci 'data_files/ecdsa_secp384r1.crt', 28a8e1175bSopenharmony_ci 'data_files/ecdsa_secp384r1.key'), 29a8e1175bSopenharmony_ci 'ecdsa_secp521r1_sha512': Certificate('data_files/test-ca2.crt', 30a8e1175bSopenharmony_ci 'data_files/ecdsa_secp521r1.crt', 31a8e1175bSopenharmony_ci 'data_files/ecdsa_secp521r1.key'), 32a8e1175bSopenharmony_ci 'rsa_pss_rsae_sha256': Certificate('data_files/test-ca_cat12.crt', 33a8e1175bSopenharmony_ci 'data_files/server2-sha256.crt', 'data_files/server2.key' 34a8e1175bSopenharmony_ci ) 35a8e1175bSopenharmony_ci} 36a8e1175bSopenharmony_ci 37a8e1175bSopenharmony_ciCIPHER_SUITE_IANA_VALUE = { 38a8e1175bSopenharmony_ci "TLS_AES_128_GCM_SHA256": 0x1301, 39a8e1175bSopenharmony_ci "TLS_AES_256_GCM_SHA384": 0x1302, 40a8e1175bSopenharmony_ci "TLS_CHACHA20_POLY1305_SHA256": 0x1303, 41a8e1175bSopenharmony_ci "TLS_AES_128_CCM_SHA256": 0x1304, 42a8e1175bSopenharmony_ci "TLS_AES_128_CCM_8_SHA256": 0x1305 43a8e1175bSopenharmony_ci} 44a8e1175bSopenharmony_ci 45a8e1175bSopenharmony_ciSIG_ALG_IANA_VALUE = { 46a8e1175bSopenharmony_ci "ecdsa_secp256r1_sha256": 0x0403, 47a8e1175bSopenharmony_ci "ecdsa_secp384r1_sha384": 0x0503, 48a8e1175bSopenharmony_ci "ecdsa_secp521r1_sha512": 0x0603, 49a8e1175bSopenharmony_ci 'rsa_pss_rsae_sha256': 0x0804, 50a8e1175bSopenharmony_ci} 51a8e1175bSopenharmony_ci 52a8e1175bSopenharmony_ciNAMED_GROUP_IANA_VALUE = { 53a8e1175bSopenharmony_ci 'secp256r1': 0x17, 54a8e1175bSopenharmony_ci 'secp384r1': 0x18, 55a8e1175bSopenharmony_ci 'secp521r1': 0x19, 56a8e1175bSopenharmony_ci 'x25519': 0x1d, 57a8e1175bSopenharmony_ci 'x448': 0x1e, 58a8e1175bSopenharmony_ci # Only one finite field group to keep testing time within reasonable bounds. 59a8e1175bSopenharmony_ci 'ffdhe2048': 0x100, 60a8e1175bSopenharmony_ci} 61a8e1175bSopenharmony_ci 62a8e1175bSopenharmony_ciclass TLSProgram: 63a8e1175bSopenharmony_ci """ 64a8e1175bSopenharmony_ci Base class for generate server/client command. 65a8e1175bSopenharmony_ci """ 66a8e1175bSopenharmony_ci 67a8e1175bSopenharmony_ci # pylint: disable=too-many-arguments 68a8e1175bSopenharmony_ci def __init__(self, ciphersuite=None, signature_algorithm=None, named_group=None, 69a8e1175bSopenharmony_ci cert_sig_alg=None, compat_mode=True): 70a8e1175bSopenharmony_ci self._ciphers = [] 71a8e1175bSopenharmony_ci self._sig_algs = [] 72a8e1175bSopenharmony_ci self._named_groups = [] 73a8e1175bSopenharmony_ci self._cert_sig_algs = [] 74a8e1175bSopenharmony_ci if ciphersuite: 75a8e1175bSopenharmony_ci self.add_ciphersuites(ciphersuite) 76a8e1175bSopenharmony_ci if named_group: 77a8e1175bSopenharmony_ci self.add_named_groups(named_group) 78a8e1175bSopenharmony_ci if signature_algorithm: 79a8e1175bSopenharmony_ci self.add_signature_algorithms(signature_algorithm) 80a8e1175bSopenharmony_ci if cert_sig_alg: 81a8e1175bSopenharmony_ci self.add_cert_signature_algorithms(cert_sig_alg) 82a8e1175bSopenharmony_ci self._compat_mode = compat_mode 83a8e1175bSopenharmony_ci 84a8e1175bSopenharmony_ci # add_ciphersuites should not override by sub class 85a8e1175bSopenharmony_ci def add_ciphersuites(self, *ciphersuites): 86a8e1175bSopenharmony_ci self._ciphers.extend( 87a8e1175bSopenharmony_ci [cipher for cipher in ciphersuites if cipher not in self._ciphers]) 88a8e1175bSopenharmony_ci 89a8e1175bSopenharmony_ci # add_signature_algorithms should not override by sub class 90a8e1175bSopenharmony_ci def add_signature_algorithms(self, *signature_algorithms): 91a8e1175bSopenharmony_ci self._sig_algs.extend( 92a8e1175bSopenharmony_ci [sig_alg for sig_alg in signature_algorithms if sig_alg not in self._sig_algs]) 93a8e1175bSopenharmony_ci 94a8e1175bSopenharmony_ci # add_named_groups should not override by sub class 95a8e1175bSopenharmony_ci def add_named_groups(self, *named_groups): 96a8e1175bSopenharmony_ci self._named_groups.extend( 97a8e1175bSopenharmony_ci [named_group for named_group in named_groups if named_group not in self._named_groups]) 98a8e1175bSopenharmony_ci 99a8e1175bSopenharmony_ci # add_cert_signature_algorithms should not override by sub class 100a8e1175bSopenharmony_ci def add_cert_signature_algorithms(self, *signature_algorithms): 101a8e1175bSopenharmony_ci self._cert_sig_algs.extend( 102a8e1175bSopenharmony_ci [sig_alg for sig_alg in signature_algorithms if sig_alg not in self._cert_sig_algs]) 103a8e1175bSopenharmony_ci 104a8e1175bSopenharmony_ci # pylint: disable=no-self-use 105a8e1175bSopenharmony_ci def pre_checks(self): 106a8e1175bSopenharmony_ci return [] 107a8e1175bSopenharmony_ci 108a8e1175bSopenharmony_ci # pylint: disable=no-self-use 109a8e1175bSopenharmony_ci def cmd(self): 110a8e1175bSopenharmony_ci if not self._cert_sig_algs: 111a8e1175bSopenharmony_ci self._cert_sig_algs = list(CERTIFICATES.keys()) 112a8e1175bSopenharmony_ci return self.pre_cmd() 113a8e1175bSopenharmony_ci 114a8e1175bSopenharmony_ci # pylint: disable=no-self-use 115a8e1175bSopenharmony_ci def post_checks(self): 116a8e1175bSopenharmony_ci return [] 117a8e1175bSopenharmony_ci 118a8e1175bSopenharmony_ci # pylint: disable=no-self-use 119a8e1175bSopenharmony_ci def pre_cmd(self): 120a8e1175bSopenharmony_ci return ['false'] 121a8e1175bSopenharmony_ci 122a8e1175bSopenharmony_ci # pylint: disable=unused-argument,no-self-use 123a8e1175bSopenharmony_ci def hrr_post_checks(self, named_group): 124a8e1175bSopenharmony_ci return [] 125a8e1175bSopenharmony_ci 126a8e1175bSopenharmony_ci 127a8e1175bSopenharmony_ciclass OpenSSLBase(TLSProgram): 128a8e1175bSopenharmony_ci """ 129a8e1175bSopenharmony_ci Generate base test commands for OpenSSL. 130a8e1175bSopenharmony_ci """ 131a8e1175bSopenharmony_ci 132a8e1175bSopenharmony_ci NAMED_GROUP = { 133a8e1175bSopenharmony_ci 'secp256r1': 'P-256', 134a8e1175bSopenharmony_ci 'secp384r1': 'P-384', 135a8e1175bSopenharmony_ci 'secp521r1': 'P-521', 136a8e1175bSopenharmony_ci 'x25519': 'X25519', 137a8e1175bSopenharmony_ci 'x448': 'X448', 138a8e1175bSopenharmony_ci 'ffdhe2048': 'ffdhe2048', 139a8e1175bSopenharmony_ci } 140a8e1175bSopenharmony_ci 141a8e1175bSopenharmony_ci def cmd(self): 142a8e1175bSopenharmony_ci ret = super().cmd() 143a8e1175bSopenharmony_ci 144a8e1175bSopenharmony_ci if self._ciphers: 145a8e1175bSopenharmony_ci ciphersuites = ':'.join(self._ciphers) 146a8e1175bSopenharmony_ci ret += ["-ciphersuites {ciphersuites}".format(ciphersuites=ciphersuites)] 147a8e1175bSopenharmony_ci 148a8e1175bSopenharmony_ci if self._sig_algs: 149a8e1175bSopenharmony_ci signature_algorithms = set(self._sig_algs + self._cert_sig_algs) 150a8e1175bSopenharmony_ci signature_algorithms = ':'.join(signature_algorithms) 151a8e1175bSopenharmony_ci ret += ["-sigalgs {signature_algorithms}".format( 152a8e1175bSopenharmony_ci signature_algorithms=signature_algorithms)] 153a8e1175bSopenharmony_ci 154a8e1175bSopenharmony_ci if self._named_groups: 155a8e1175bSopenharmony_ci named_groups = ':'.join( 156a8e1175bSopenharmony_ci map(lambda named_group: self.NAMED_GROUP[named_group], self._named_groups)) 157a8e1175bSopenharmony_ci ret += ["-groups {named_groups}".format(named_groups=named_groups)] 158a8e1175bSopenharmony_ci 159a8e1175bSopenharmony_ci ret += ['-msg -tls1_3'] 160a8e1175bSopenharmony_ci if not self._compat_mode: 161a8e1175bSopenharmony_ci ret += ['-no_middlebox'] 162a8e1175bSopenharmony_ci 163a8e1175bSopenharmony_ci return ret 164a8e1175bSopenharmony_ci 165a8e1175bSopenharmony_ci def pre_checks(self): 166a8e1175bSopenharmony_ci ret = ["requires_openssl_tls1_3"] 167a8e1175bSopenharmony_ci 168a8e1175bSopenharmony_ci # ffdh groups require at least openssl 3.0 169a8e1175bSopenharmony_ci ffdh_groups = ['ffdhe2048'] 170a8e1175bSopenharmony_ci 171a8e1175bSopenharmony_ci if any(x in ffdh_groups for x in self._named_groups): 172a8e1175bSopenharmony_ci ret = ["requires_openssl_tls1_3_with_ffdh"] 173a8e1175bSopenharmony_ci 174a8e1175bSopenharmony_ci return ret 175a8e1175bSopenharmony_ci 176a8e1175bSopenharmony_ci 177a8e1175bSopenharmony_ciclass OpenSSLServ(OpenSSLBase): 178a8e1175bSopenharmony_ci """ 179a8e1175bSopenharmony_ci Generate test commands for OpenSSL server. 180a8e1175bSopenharmony_ci """ 181a8e1175bSopenharmony_ci 182a8e1175bSopenharmony_ci def cmd(self): 183a8e1175bSopenharmony_ci ret = super().cmd() 184a8e1175bSopenharmony_ci ret += ['-num_tickets 0 -no_resume_ephemeral -no_cache'] 185a8e1175bSopenharmony_ci return ret 186a8e1175bSopenharmony_ci 187a8e1175bSopenharmony_ci def post_checks(self): 188a8e1175bSopenharmony_ci return ['-c "HTTP/1.0 200 ok"'] 189a8e1175bSopenharmony_ci 190a8e1175bSopenharmony_ci def pre_cmd(self): 191a8e1175bSopenharmony_ci ret = ['$O_NEXT_SRV_NO_CERT'] 192a8e1175bSopenharmony_ci for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs): 193a8e1175bSopenharmony_ci ret += ['-cert {cert} -key {key}'.format(cert=cert, key=key)] 194a8e1175bSopenharmony_ci return ret 195a8e1175bSopenharmony_ci 196a8e1175bSopenharmony_ci 197a8e1175bSopenharmony_ciclass OpenSSLCli(OpenSSLBase): 198a8e1175bSopenharmony_ci """ 199a8e1175bSopenharmony_ci Generate test commands for OpenSSL client. 200a8e1175bSopenharmony_ci """ 201a8e1175bSopenharmony_ci 202a8e1175bSopenharmony_ci def pre_cmd(self): 203a8e1175bSopenharmony_ci return ['$O_NEXT_CLI_NO_CERT', 204a8e1175bSopenharmony_ci '-CAfile {cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)] 205a8e1175bSopenharmony_ci 206a8e1175bSopenharmony_ci 207a8e1175bSopenharmony_ciclass GnuTLSBase(TLSProgram): 208a8e1175bSopenharmony_ci """ 209a8e1175bSopenharmony_ci Generate base test commands for GnuTLS. 210a8e1175bSopenharmony_ci """ 211a8e1175bSopenharmony_ci 212a8e1175bSopenharmony_ci CIPHER_SUITE = { 213a8e1175bSopenharmony_ci 'TLS_AES_256_GCM_SHA384': [ 214a8e1175bSopenharmony_ci 'AES-256-GCM', 215a8e1175bSopenharmony_ci 'SHA384', 216a8e1175bSopenharmony_ci 'AEAD'], 217a8e1175bSopenharmony_ci 'TLS_AES_128_GCM_SHA256': [ 218a8e1175bSopenharmony_ci 'AES-128-GCM', 219a8e1175bSopenharmony_ci 'SHA256', 220a8e1175bSopenharmony_ci 'AEAD'], 221a8e1175bSopenharmony_ci 'TLS_CHACHA20_POLY1305_SHA256': [ 222a8e1175bSopenharmony_ci 'CHACHA20-POLY1305', 223a8e1175bSopenharmony_ci 'SHA256', 224a8e1175bSopenharmony_ci 'AEAD'], 225a8e1175bSopenharmony_ci 'TLS_AES_128_CCM_SHA256': [ 226a8e1175bSopenharmony_ci 'AES-128-CCM', 227a8e1175bSopenharmony_ci 'SHA256', 228a8e1175bSopenharmony_ci 'AEAD'], 229a8e1175bSopenharmony_ci 'TLS_AES_128_CCM_8_SHA256': [ 230a8e1175bSopenharmony_ci 'AES-128-CCM-8', 231a8e1175bSopenharmony_ci 'SHA256', 232a8e1175bSopenharmony_ci 'AEAD']} 233a8e1175bSopenharmony_ci 234a8e1175bSopenharmony_ci SIGNATURE_ALGORITHM = { 235a8e1175bSopenharmony_ci 'ecdsa_secp256r1_sha256': ['SIGN-ECDSA-SECP256R1-SHA256'], 236a8e1175bSopenharmony_ci 'ecdsa_secp521r1_sha512': ['SIGN-ECDSA-SECP521R1-SHA512'], 237a8e1175bSopenharmony_ci 'ecdsa_secp384r1_sha384': ['SIGN-ECDSA-SECP384R1-SHA384'], 238a8e1175bSopenharmony_ci 'rsa_pss_rsae_sha256': ['SIGN-RSA-PSS-RSAE-SHA256']} 239a8e1175bSopenharmony_ci 240a8e1175bSopenharmony_ci NAMED_GROUP = { 241a8e1175bSopenharmony_ci 'secp256r1': ['GROUP-SECP256R1'], 242a8e1175bSopenharmony_ci 'secp384r1': ['GROUP-SECP384R1'], 243a8e1175bSopenharmony_ci 'secp521r1': ['GROUP-SECP521R1'], 244a8e1175bSopenharmony_ci 'x25519': ['GROUP-X25519'], 245a8e1175bSopenharmony_ci 'x448': ['GROUP-X448'], 246a8e1175bSopenharmony_ci 'ffdhe2048': ['GROUP-FFDHE2048'], 247a8e1175bSopenharmony_ci } 248a8e1175bSopenharmony_ci 249a8e1175bSopenharmony_ci def pre_checks(self): 250a8e1175bSopenharmony_ci return ["requires_gnutls_tls1_3", 251a8e1175bSopenharmony_ci "requires_gnutls_next_no_ticket", 252a8e1175bSopenharmony_ci "requires_gnutls_next_disable_tls13_compat", ] 253a8e1175bSopenharmony_ci 254a8e1175bSopenharmony_ci def cmd(self): 255a8e1175bSopenharmony_ci ret = super().cmd() 256a8e1175bSopenharmony_ci 257a8e1175bSopenharmony_ci priority_string_list = [] 258a8e1175bSopenharmony_ci 259a8e1175bSopenharmony_ci def update_priority_string_list(items, map_table): 260a8e1175bSopenharmony_ci for item in items: 261a8e1175bSopenharmony_ci for i in map_table[item]: 262a8e1175bSopenharmony_ci if i not in priority_string_list: 263a8e1175bSopenharmony_ci yield i 264a8e1175bSopenharmony_ci 265a8e1175bSopenharmony_ci if self._ciphers: 266a8e1175bSopenharmony_ci priority_string_list.extend(update_priority_string_list( 267a8e1175bSopenharmony_ci self._ciphers, self.CIPHER_SUITE)) 268a8e1175bSopenharmony_ci else: 269a8e1175bSopenharmony_ci priority_string_list.extend(['CIPHER-ALL', 'MAC-ALL']) 270a8e1175bSopenharmony_ci 271a8e1175bSopenharmony_ci if self._sig_algs: 272a8e1175bSopenharmony_ci signature_algorithms = set(self._sig_algs + self._cert_sig_algs) 273a8e1175bSopenharmony_ci priority_string_list.extend(update_priority_string_list( 274a8e1175bSopenharmony_ci signature_algorithms, self.SIGNATURE_ALGORITHM)) 275a8e1175bSopenharmony_ci else: 276a8e1175bSopenharmony_ci priority_string_list.append('SIGN-ALL') 277a8e1175bSopenharmony_ci 278a8e1175bSopenharmony_ci 279a8e1175bSopenharmony_ci if self._named_groups: 280a8e1175bSopenharmony_ci priority_string_list.extend(update_priority_string_list( 281a8e1175bSopenharmony_ci self._named_groups, self.NAMED_GROUP)) 282a8e1175bSopenharmony_ci else: 283a8e1175bSopenharmony_ci priority_string_list.append('GROUP-ALL') 284a8e1175bSopenharmony_ci 285a8e1175bSopenharmony_ci priority_string_list = ['NONE'] + \ 286a8e1175bSopenharmony_ci priority_string_list + ['VERS-TLS1.3'] 287a8e1175bSopenharmony_ci 288a8e1175bSopenharmony_ci priority_string = ':+'.join(priority_string_list) 289a8e1175bSopenharmony_ci priority_string += ':%NO_TICKETS' 290a8e1175bSopenharmony_ci 291a8e1175bSopenharmony_ci if not self._compat_mode: 292a8e1175bSopenharmony_ci priority_string += [':%DISABLE_TLS13_COMPAT_MODE'] 293a8e1175bSopenharmony_ci 294a8e1175bSopenharmony_ci ret += ['--priority={priority_string}'.format( 295a8e1175bSopenharmony_ci priority_string=priority_string)] 296a8e1175bSopenharmony_ci return ret 297a8e1175bSopenharmony_ci 298a8e1175bSopenharmony_ciclass GnuTLSServ(GnuTLSBase): 299a8e1175bSopenharmony_ci """ 300a8e1175bSopenharmony_ci Generate test commands for GnuTLS server. 301a8e1175bSopenharmony_ci """ 302a8e1175bSopenharmony_ci 303a8e1175bSopenharmony_ci def pre_cmd(self): 304a8e1175bSopenharmony_ci ret = ['$G_NEXT_SRV_NO_CERT', '--http', '--disable-client-cert', '--debug=4'] 305a8e1175bSopenharmony_ci 306a8e1175bSopenharmony_ci for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs): 307a8e1175bSopenharmony_ci ret += ['--x509certfile {cert} --x509keyfile {key}'.format( 308a8e1175bSopenharmony_ci cert=cert, key=key)] 309a8e1175bSopenharmony_ci return ret 310a8e1175bSopenharmony_ci 311a8e1175bSopenharmony_ci def post_checks(self): 312a8e1175bSopenharmony_ci return ['-c "HTTP/1.0 200 OK"'] 313a8e1175bSopenharmony_ci 314a8e1175bSopenharmony_ci 315a8e1175bSopenharmony_ciclass GnuTLSCli(GnuTLSBase): 316a8e1175bSopenharmony_ci """ 317a8e1175bSopenharmony_ci Generate test commands for GnuTLS client. 318a8e1175bSopenharmony_ci """ 319a8e1175bSopenharmony_ci 320a8e1175bSopenharmony_ci def pre_cmd(self): 321a8e1175bSopenharmony_ci return ['$G_NEXT_CLI_NO_CERT', '--debug=4', '--single-key-share', 322a8e1175bSopenharmony_ci '--x509cafile {cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)] 323a8e1175bSopenharmony_ci 324a8e1175bSopenharmony_ci 325a8e1175bSopenharmony_ciclass MbedTLSBase(TLSProgram): 326a8e1175bSopenharmony_ci """ 327a8e1175bSopenharmony_ci Generate base test commands for mbedTLS. 328a8e1175bSopenharmony_ci """ 329a8e1175bSopenharmony_ci 330a8e1175bSopenharmony_ci CIPHER_SUITE = { 331a8e1175bSopenharmony_ci 'TLS_AES_256_GCM_SHA384': 'TLS1-3-AES-256-GCM-SHA384', 332a8e1175bSopenharmony_ci 'TLS_AES_128_GCM_SHA256': 'TLS1-3-AES-128-GCM-SHA256', 333a8e1175bSopenharmony_ci 'TLS_CHACHA20_POLY1305_SHA256': 'TLS1-3-CHACHA20-POLY1305-SHA256', 334a8e1175bSopenharmony_ci 'TLS_AES_128_CCM_SHA256': 'TLS1-3-AES-128-CCM-SHA256', 335a8e1175bSopenharmony_ci 'TLS_AES_128_CCM_8_SHA256': 'TLS1-3-AES-128-CCM-8-SHA256'} 336a8e1175bSopenharmony_ci 337a8e1175bSopenharmony_ci def cmd(self): 338a8e1175bSopenharmony_ci ret = super().cmd() 339a8e1175bSopenharmony_ci ret += ['debug_level=4'] 340a8e1175bSopenharmony_ci 341a8e1175bSopenharmony_ci 342a8e1175bSopenharmony_ci if self._ciphers: 343a8e1175bSopenharmony_ci ciphers = ','.join( 344a8e1175bSopenharmony_ci map(lambda cipher: self.CIPHER_SUITE[cipher], self._ciphers)) 345a8e1175bSopenharmony_ci ret += ["force_ciphersuite={ciphers}".format(ciphers=ciphers)] 346a8e1175bSopenharmony_ci 347a8e1175bSopenharmony_ci if self._sig_algs + self._cert_sig_algs: 348a8e1175bSopenharmony_ci ret += ['sig_algs={sig_algs}'.format( 349a8e1175bSopenharmony_ci sig_algs=','.join(set(self._sig_algs + self._cert_sig_algs)))] 350a8e1175bSopenharmony_ci 351a8e1175bSopenharmony_ci if self._named_groups: 352a8e1175bSopenharmony_ci named_groups = ','.join(self._named_groups) 353a8e1175bSopenharmony_ci ret += ["groups={named_groups}".format(named_groups=named_groups)] 354a8e1175bSopenharmony_ci return ret 355a8e1175bSopenharmony_ci 356a8e1175bSopenharmony_ci #pylint: disable=missing-function-docstring 357a8e1175bSopenharmony_ci def add_ffdh_group_requirements(self, requirement_list): 358a8e1175bSopenharmony_ci if 'ffdhe2048' in self._named_groups: 359a8e1175bSopenharmony_ci requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048') 360a8e1175bSopenharmony_ci if 'ffdhe3072' in self._named_groups: 361a8e1175bSopenharmony_ci requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048') 362a8e1175bSopenharmony_ci if 'ffdhe4096' in self._named_groups: 363a8e1175bSopenharmony_ci requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048') 364a8e1175bSopenharmony_ci if 'ffdhe6144' in self._named_groups: 365a8e1175bSopenharmony_ci requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048') 366a8e1175bSopenharmony_ci if 'ffdhe8192' in self._named_groups: 367a8e1175bSopenharmony_ci requirement_list.append('requires_config_enabled PSA_WANT_DH_RFC7919_2048') 368a8e1175bSopenharmony_ci 369a8e1175bSopenharmony_ci def pre_checks(self): 370a8e1175bSopenharmony_ci ret = ['requires_config_enabled MBEDTLS_DEBUG_C', 371a8e1175bSopenharmony_ci 'requires_config_enabled MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_EPHEMERAL_ENABLED'] 372a8e1175bSopenharmony_ci 373a8e1175bSopenharmony_ci if self._compat_mode: 374a8e1175bSopenharmony_ci ret += ['requires_config_enabled MBEDTLS_SSL_TLS1_3_COMPATIBILITY_MODE'] 375a8e1175bSopenharmony_ci 376a8e1175bSopenharmony_ci if 'rsa_pss_rsae_sha256' in self._sig_algs + self._cert_sig_algs: 377a8e1175bSopenharmony_ci ret.append( 378a8e1175bSopenharmony_ci 'requires_config_enabled MBEDTLS_X509_RSASSA_PSS_SUPPORT') 379a8e1175bSopenharmony_ci 380a8e1175bSopenharmony_ci ec_groups = ['secp256r1', 'secp384r1', 'secp521r1', 'x25519', 'x448'] 381a8e1175bSopenharmony_ci ffdh_groups = ['ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192'] 382a8e1175bSopenharmony_ci 383a8e1175bSopenharmony_ci if any(x in ec_groups for x in self._named_groups): 384a8e1175bSopenharmony_ci ret.append('requires_config_enabled PSA_WANT_ALG_ECDH') 385a8e1175bSopenharmony_ci 386a8e1175bSopenharmony_ci if any(x in ffdh_groups for x in self._named_groups): 387a8e1175bSopenharmony_ci ret.append('requires_config_enabled PSA_WANT_ALG_FFDH') 388a8e1175bSopenharmony_ci self.add_ffdh_group_requirements(ret) 389a8e1175bSopenharmony_ci 390a8e1175bSopenharmony_ci return ret 391a8e1175bSopenharmony_ci 392a8e1175bSopenharmony_ci 393a8e1175bSopenharmony_ciclass MbedTLSServ(MbedTLSBase): 394a8e1175bSopenharmony_ci """ 395a8e1175bSopenharmony_ci Generate test commands for mbedTLS server. 396a8e1175bSopenharmony_ci """ 397a8e1175bSopenharmony_ci 398a8e1175bSopenharmony_ci def cmd(self): 399a8e1175bSopenharmony_ci ret = super().cmd() 400a8e1175bSopenharmony_ci ret += ['tls13_kex_modes=ephemeral cookies=0 tickets=0'] 401a8e1175bSopenharmony_ci return ret 402a8e1175bSopenharmony_ci 403a8e1175bSopenharmony_ci def pre_checks(self): 404a8e1175bSopenharmony_ci return ['requires_config_enabled MBEDTLS_SSL_SRV_C'] + super().pre_checks() 405a8e1175bSopenharmony_ci 406a8e1175bSopenharmony_ci def post_checks(self): 407a8e1175bSopenharmony_ci check_strings = ["Protocol is TLSv1.3"] 408a8e1175bSopenharmony_ci if self._ciphers: 409a8e1175bSopenharmony_ci check_strings.append( 410a8e1175bSopenharmony_ci "server hello, chosen ciphersuite: {} ( id={:04d} )".format( 411a8e1175bSopenharmony_ci self.CIPHER_SUITE[self._ciphers[0]], 412a8e1175bSopenharmony_ci CIPHER_SUITE_IANA_VALUE[self._ciphers[0]])) 413a8e1175bSopenharmony_ci if self._sig_algs: 414a8e1175bSopenharmony_ci check_strings.append( 415a8e1175bSopenharmony_ci "received signature algorithm: 0x{:x}".format( 416a8e1175bSopenharmony_ci SIG_ALG_IANA_VALUE[self._sig_algs[0]])) 417a8e1175bSopenharmony_ci 418a8e1175bSopenharmony_ci for named_group in self._named_groups: 419a8e1175bSopenharmony_ci check_strings += ['got named group: {named_group}({iana_value:04x})'.format( 420a8e1175bSopenharmony_ci named_group=named_group, 421a8e1175bSopenharmony_ci iana_value=NAMED_GROUP_IANA_VALUE[named_group])] 422a8e1175bSopenharmony_ci 423a8e1175bSopenharmony_ci check_strings.append("Certificate verification was skipped") 424a8e1175bSopenharmony_ci return ['-s "{}"'.format(i) for i in check_strings] 425a8e1175bSopenharmony_ci 426a8e1175bSopenharmony_ci def pre_cmd(self): 427a8e1175bSopenharmony_ci ret = ['$P_SRV'] 428a8e1175bSopenharmony_ci for _, cert, key in map(lambda sig_alg: CERTIFICATES[sig_alg], self._cert_sig_algs): 429a8e1175bSopenharmony_ci ret += ['crt_file={cert} key_file={key}'.format(cert=cert, key=key)] 430a8e1175bSopenharmony_ci return ret 431a8e1175bSopenharmony_ci 432a8e1175bSopenharmony_ci def hrr_post_checks(self, named_group): 433a8e1175bSopenharmony_ci return ['-s "HRR selected_group: {:s}"'.format(named_group)] 434a8e1175bSopenharmony_ci 435a8e1175bSopenharmony_ci 436a8e1175bSopenharmony_ciclass MbedTLSCli(MbedTLSBase): 437a8e1175bSopenharmony_ci """ 438a8e1175bSopenharmony_ci Generate test commands for mbedTLS client. 439a8e1175bSopenharmony_ci """ 440a8e1175bSopenharmony_ci 441a8e1175bSopenharmony_ci def pre_cmd(self): 442a8e1175bSopenharmony_ci return ['$P_CLI', 443a8e1175bSopenharmony_ci 'ca_file={cafile}'.format(cafile=CERTIFICATES[self._cert_sig_algs[0]].cafile)] 444a8e1175bSopenharmony_ci 445a8e1175bSopenharmony_ci def pre_checks(self): 446a8e1175bSopenharmony_ci return ['requires_config_enabled MBEDTLS_SSL_CLI_C'] + super().pre_checks() 447a8e1175bSopenharmony_ci 448a8e1175bSopenharmony_ci def hrr_post_checks(self, named_group): 449a8e1175bSopenharmony_ci ret = ['-c "received HelloRetryRequest message"'] 450a8e1175bSopenharmony_ci ret += ['-c "selected_group ( {:d} )"'.format(NAMED_GROUP_IANA_VALUE[named_group])] 451a8e1175bSopenharmony_ci return ret 452a8e1175bSopenharmony_ci 453a8e1175bSopenharmony_ci def post_checks(self): 454a8e1175bSopenharmony_ci check_strings = ["Protocol is TLSv1.3"] 455a8e1175bSopenharmony_ci if self._ciphers: 456a8e1175bSopenharmony_ci check_strings.append( 457a8e1175bSopenharmony_ci "server hello, chosen ciphersuite: ( {:04x} ) - {}".format( 458a8e1175bSopenharmony_ci CIPHER_SUITE_IANA_VALUE[self._ciphers[0]], 459a8e1175bSopenharmony_ci self.CIPHER_SUITE[self._ciphers[0]])) 460a8e1175bSopenharmony_ci if self._sig_algs: 461a8e1175bSopenharmony_ci check_strings.append( 462a8e1175bSopenharmony_ci "Certificate Verify: Signature algorithm ( {:04x} )".format( 463a8e1175bSopenharmony_ci SIG_ALG_IANA_VALUE[self._sig_algs[0]])) 464a8e1175bSopenharmony_ci 465a8e1175bSopenharmony_ci for named_group in self._named_groups: 466a8e1175bSopenharmony_ci check_strings += ['NamedGroup: {named_group} ( {iana_value:x} )'.format( 467a8e1175bSopenharmony_ci named_group=named_group, 468a8e1175bSopenharmony_ci iana_value=NAMED_GROUP_IANA_VALUE[named_group])] 469a8e1175bSopenharmony_ci 470a8e1175bSopenharmony_ci check_strings.append("Verifying peer X.509 certificate... ok") 471a8e1175bSopenharmony_ci return ['-c "{}"'.format(i) for i in check_strings] 472a8e1175bSopenharmony_ci 473a8e1175bSopenharmony_ci 474a8e1175bSopenharmony_ciSERVER_CLASSES = {'OpenSSL': OpenSSLServ, 'GnuTLS': GnuTLSServ, 'mbedTLS': MbedTLSServ} 475a8e1175bSopenharmony_ciCLIENT_CLASSES = {'OpenSSL': OpenSSLCli, 'GnuTLS': GnuTLSCli, 'mbedTLS': MbedTLSCli} 476a8e1175bSopenharmony_ci 477a8e1175bSopenharmony_ci 478a8e1175bSopenharmony_cidef generate_compat_test(client=None, server=None, cipher=None, named_group=None, sig_alg=None): 479a8e1175bSopenharmony_ci """ 480a8e1175bSopenharmony_ci Generate test case with `ssl-opt.sh` format. 481a8e1175bSopenharmony_ci """ 482a8e1175bSopenharmony_ci name = 'TLS 1.3 {client[0]}->{server[0]}: {cipher},{named_group},{sig_alg}'.format( 483a8e1175bSopenharmony_ci client=client, server=server, cipher=cipher[4:], sig_alg=sig_alg, named_group=named_group) 484a8e1175bSopenharmony_ci 485a8e1175bSopenharmony_ci server_object = SERVER_CLASSES[server](ciphersuite=cipher, 486a8e1175bSopenharmony_ci named_group=named_group, 487a8e1175bSopenharmony_ci signature_algorithm=sig_alg, 488a8e1175bSopenharmony_ci cert_sig_alg=sig_alg) 489a8e1175bSopenharmony_ci client_object = CLIENT_CLASSES[client](ciphersuite=cipher, 490a8e1175bSopenharmony_ci named_group=named_group, 491a8e1175bSopenharmony_ci signature_algorithm=sig_alg, 492a8e1175bSopenharmony_ci cert_sig_alg=sig_alg) 493a8e1175bSopenharmony_ci 494a8e1175bSopenharmony_ci cmd = ['run_test "{}"'.format(name), 495a8e1175bSopenharmony_ci '"{}"'.format(' '.join(server_object.cmd())), 496a8e1175bSopenharmony_ci '"{}"'.format(' '.join(client_object.cmd())), 497a8e1175bSopenharmony_ci '0'] 498a8e1175bSopenharmony_ci cmd += server_object.post_checks() 499a8e1175bSopenharmony_ci cmd += client_object.post_checks() 500a8e1175bSopenharmony_ci cmd += ['-C "received HelloRetryRequest message"'] 501a8e1175bSopenharmony_ci prefix = ' \\\n' + (' '*9) 502a8e1175bSopenharmony_ci cmd = prefix.join(cmd) 503a8e1175bSopenharmony_ci return '\n'.join(server_object.pre_checks() + client_object.pre_checks() + [cmd]) 504a8e1175bSopenharmony_ci 505a8e1175bSopenharmony_ci 506a8e1175bSopenharmony_cidef generate_hrr_compat_test(client=None, server=None, 507a8e1175bSopenharmony_ci client_named_group=None, server_named_group=None, 508a8e1175bSopenharmony_ci cert_sig_alg=None): 509a8e1175bSopenharmony_ci """ 510a8e1175bSopenharmony_ci Generate Hello Retry Request test case with `ssl-opt.sh` format. 511a8e1175bSopenharmony_ci """ 512a8e1175bSopenharmony_ci name = 'TLS 1.3 {client[0]}->{server[0]}: HRR {c_named_group} -> {s_named_group}'.format( 513a8e1175bSopenharmony_ci client=client, server=server, c_named_group=client_named_group, 514a8e1175bSopenharmony_ci s_named_group=server_named_group) 515a8e1175bSopenharmony_ci server_object = SERVER_CLASSES[server](named_group=server_named_group, 516a8e1175bSopenharmony_ci cert_sig_alg=cert_sig_alg) 517a8e1175bSopenharmony_ci 518a8e1175bSopenharmony_ci client_object = CLIENT_CLASSES[client](named_group=client_named_group, 519a8e1175bSopenharmony_ci cert_sig_alg=cert_sig_alg) 520a8e1175bSopenharmony_ci client_object.add_named_groups(server_named_group) 521a8e1175bSopenharmony_ci 522a8e1175bSopenharmony_ci cmd = ['run_test "{}"'.format(name), 523a8e1175bSopenharmony_ci '"{}"'.format(' '.join(server_object.cmd())), 524a8e1175bSopenharmony_ci '"{}"'.format(' '.join(client_object.cmd())), 525a8e1175bSopenharmony_ci '0'] 526a8e1175bSopenharmony_ci cmd += server_object.post_checks() 527a8e1175bSopenharmony_ci cmd += client_object.post_checks() 528a8e1175bSopenharmony_ci cmd += server_object.hrr_post_checks(server_named_group) 529a8e1175bSopenharmony_ci cmd += client_object.hrr_post_checks(server_named_group) 530a8e1175bSopenharmony_ci prefix = ' \\\n' + (' '*9) 531a8e1175bSopenharmony_ci cmd = prefix.join(cmd) 532a8e1175bSopenharmony_ci return '\n'.join(server_object.pre_checks() + 533a8e1175bSopenharmony_ci client_object.pre_checks() + 534a8e1175bSopenharmony_ci [cmd]) 535a8e1175bSopenharmony_ci 536a8e1175bSopenharmony_ciSSL_OUTPUT_HEADER = '''#!/bin/sh 537a8e1175bSopenharmony_ci 538a8e1175bSopenharmony_ci# {filename} 539a8e1175bSopenharmony_ci# 540a8e1175bSopenharmony_ci# Copyright The Mbed TLS Contributors 541a8e1175bSopenharmony_ci# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 542a8e1175bSopenharmony_ci# 543a8e1175bSopenharmony_ci# Purpose 544a8e1175bSopenharmony_ci# 545a8e1175bSopenharmony_ci# List TLS1.3 compat test cases. They are generated by 546a8e1175bSopenharmony_ci# `{cmd}`. 547a8e1175bSopenharmony_ci# 548a8e1175bSopenharmony_ci# PLEASE DO NOT EDIT THIS FILE. IF NEEDED, PLEASE MODIFY `generate_tls13_compat_tests.py` 549a8e1175bSopenharmony_ci# AND REGENERATE THIS FILE. 550a8e1175bSopenharmony_ci# 551a8e1175bSopenharmony_ci''' 552a8e1175bSopenharmony_ci 553a8e1175bSopenharmony_cidef main(): 554a8e1175bSopenharmony_ci """ 555a8e1175bSopenharmony_ci Main function of this program 556a8e1175bSopenharmony_ci """ 557a8e1175bSopenharmony_ci parser = argparse.ArgumentParser() 558a8e1175bSopenharmony_ci 559a8e1175bSopenharmony_ci parser.add_argument('-o', '--output', nargs='?', 560a8e1175bSopenharmony_ci default=None, help='Output file path if `-a` was set') 561a8e1175bSopenharmony_ci 562a8e1175bSopenharmony_ci parser.add_argument('-a', '--generate-all-tls13-compat-tests', action='store_true', 563a8e1175bSopenharmony_ci default=False, help='Generate all available tls13 compat tests') 564a8e1175bSopenharmony_ci 565a8e1175bSopenharmony_ci parser.add_argument('--list-ciphers', action='store_true', 566a8e1175bSopenharmony_ci default=False, help='List supported ciphersuites') 567a8e1175bSopenharmony_ci 568a8e1175bSopenharmony_ci parser.add_argument('--list-sig-algs', action='store_true', 569a8e1175bSopenharmony_ci default=False, help='List supported signature algorithms') 570a8e1175bSopenharmony_ci 571a8e1175bSopenharmony_ci parser.add_argument('--list-named-groups', action='store_true', 572a8e1175bSopenharmony_ci default=False, help='List supported named groups') 573a8e1175bSopenharmony_ci 574a8e1175bSopenharmony_ci parser.add_argument('--list-servers', action='store_true', 575a8e1175bSopenharmony_ci default=False, help='List supported TLS servers') 576a8e1175bSopenharmony_ci 577a8e1175bSopenharmony_ci parser.add_argument('--list-clients', action='store_true', 578a8e1175bSopenharmony_ci default=False, help='List supported TLS Clients') 579a8e1175bSopenharmony_ci 580a8e1175bSopenharmony_ci parser.add_argument('server', choices=SERVER_CLASSES.keys(), nargs='?', 581a8e1175bSopenharmony_ci default=list(SERVER_CLASSES.keys())[0], 582a8e1175bSopenharmony_ci help='Choose TLS server program for test') 583a8e1175bSopenharmony_ci parser.add_argument('client', choices=CLIENT_CLASSES.keys(), nargs='?', 584a8e1175bSopenharmony_ci default=list(CLIENT_CLASSES.keys())[0], 585a8e1175bSopenharmony_ci help='Choose TLS client program for test') 586a8e1175bSopenharmony_ci parser.add_argument('cipher', choices=CIPHER_SUITE_IANA_VALUE.keys(), nargs='?', 587a8e1175bSopenharmony_ci default=list(CIPHER_SUITE_IANA_VALUE.keys())[0], 588a8e1175bSopenharmony_ci help='Choose cipher suite for test') 589a8e1175bSopenharmony_ci parser.add_argument('sig_alg', choices=SIG_ALG_IANA_VALUE.keys(), nargs='?', 590a8e1175bSopenharmony_ci default=list(SIG_ALG_IANA_VALUE.keys())[0], 591a8e1175bSopenharmony_ci help='Choose cipher suite for test') 592a8e1175bSopenharmony_ci parser.add_argument('named_group', choices=NAMED_GROUP_IANA_VALUE.keys(), nargs='?', 593a8e1175bSopenharmony_ci default=list(NAMED_GROUP_IANA_VALUE.keys())[0], 594a8e1175bSopenharmony_ci help='Choose cipher suite for test') 595a8e1175bSopenharmony_ci 596a8e1175bSopenharmony_ci args = parser.parse_args() 597a8e1175bSopenharmony_ci 598a8e1175bSopenharmony_ci def get_all_test_cases(): 599a8e1175bSopenharmony_ci # Generate normal compat test cases 600a8e1175bSopenharmony_ci for client, server, cipher, named_group, sig_alg in \ 601a8e1175bSopenharmony_ci itertools.product(CLIENT_CLASSES.keys(), 602a8e1175bSopenharmony_ci SERVER_CLASSES.keys(), 603a8e1175bSopenharmony_ci CIPHER_SUITE_IANA_VALUE.keys(), 604a8e1175bSopenharmony_ci NAMED_GROUP_IANA_VALUE.keys(), 605a8e1175bSopenharmony_ci SIG_ALG_IANA_VALUE.keys()): 606a8e1175bSopenharmony_ci if server == 'mbedTLS' or client == 'mbedTLS': 607a8e1175bSopenharmony_ci yield generate_compat_test(client=client, server=server, 608a8e1175bSopenharmony_ci cipher=cipher, named_group=named_group, 609a8e1175bSopenharmony_ci sig_alg=sig_alg) 610a8e1175bSopenharmony_ci 611a8e1175bSopenharmony_ci 612a8e1175bSopenharmony_ci # Generate Hello Retry Request compat test cases 613a8e1175bSopenharmony_ci for client, server, client_named_group, server_named_group in \ 614a8e1175bSopenharmony_ci itertools.product(CLIENT_CLASSES.keys(), 615a8e1175bSopenharmony_ci SERVER_CLASSES.keys(), 616a8e1175bSopenharmony_ci NAMED_GROUP_IANA_VALUE.keys(), 617a8e1175bSopenharmony_ci NAMED_GROUP_IANA_VALUE.keys()): 618a8e1175bSopenharmony_ci 619a8e1175bSopenharmony_ci if (client == 'mbedTLS' or server == 'mbedTLS') and \ 620a8e1175bSopenharmony_ci client_named_group != server_named_group: 621a8e1175bSopenharmony_ci yield generate_hrr_compat_test(client=client, server=server, 622a8e1175bSopenharmony_ci client_named_group=client_named_group, 623a8e1175bSopenharmony_ci server_named_group=server_named_group, 624a8e1175bSopenharmony_ci cert_sig_alg="ecdsa_secp256r1_sha256") 625a8e1175bSopenharmony_ci 626a8e1175bSopenharmony_ci if args.generate_all_tls13_compat_tests: 627a8e1175bSopenharmony_ci if args.output: 628a8e1175bSopenharmony_ci with open(args.output, 'w', encoding="utf-8") as f: 629a8e1175bSopenharmony_ci f.write(SSL_OUTPUT_HEADER.format( 630a8e1175bSopenharmony_ci filename=os.path.basename(args.output), cmd=' '.join(sys.argv))) 631a8e1175bSopenharmony_ci f.write('\n\n'.join(get_all_test_cases())) 632a8e1175bSopenharmony_ci f.write('\n') 633a8e1175bSopenharmony_ci else: 634a8e1175bSopenharmony_ci print('\n\n'.join(get_all_test_cases())) 635a8e1175bSopenharmony_ci return 0 636a8e1175bSopenharmony_ci 637a8e1175bSopenharmony_ci if args.list_ciphers or args.list_sig_algs or args.list_named_groups \ 638a8e1175bSopenharmony_ci or args.list_servers or args.list_clients: 639a8e1175bSopenharmony_ci if args.list_ciphers: 640a8e1175bSopenharmony_ci print(*CIPHER_SUITE_IANA_VALUE.keys()) 641a8e1175bSopenharmony_ci if args.list_sig_algs: 642a8e1175bSopenharmony_ci print(*SIG_ALG_IANA_VALUE.keys()) 643a8e1175bSopenharmony_ci if args.list_named_groups: 644a8e1175bSopenharmony_ci print(*NAMED_GROUP_IANA_VALUE.keys()) 645a8e1175bSopenharmony_ci if args.list_servers: 646a8e1175bSopenharmony_ci print(*SERVER_CLASSES.keys()) 647a8e1175bSopenharmony_ci if args.list_clients: 648a8e1175bSopenharmony_ci print(*CLIENT_CLASSES.keys()) 649a8e1175bSopenharmony_ci return 0 650a8e1175bSopenharmony_ci 651a8e1175bSopenharmony_ci print(generate_compat_test(server=args.server, client=args.client, sig_alg=args.sig_alg, 652a8e1175bSopenharmony_ci cipher=args.cipher, named_group=args.named_group)) 653a8e1175bSopenharmony_ci return 0 654a8e1175bSopenharmony_ci 655a8e1175bSopenharmony_ci 656a8e1175bSopenharmony_ciif __name__ == "__main__": 657a8e1175bSopenharmony_ci sys.exit(main()) 658