blob: 5b27f61cc97845fa0bebdbd955905404e07c3f20 [file] [log] [blame]
Jerry Yu305bfc32021-11-24 16:04:47 +08001#!/usr/bin/env python3
2
3# generate_tls13_compat_tests.py
4#
5# Copyright The Mbed TLS Contributors
6# SPDX-License-Identifier: Apache-2.0
7#
8# Licensed under the Apache License, Version 2.0 (the "License"); you may
9# not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12# http://www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19
20"""
21Generate TLSv1.3 Compat test cases
22
23"""
24
25import sys
26import abc
27import argparse
28
29# pylint: disable=useless-super-delegation
30
31CERTIFICATES = {
32 'ecdsa_secp256r1_sha256': (
33 'data_files/ecdsa_secp256r1_sha256.crt',
34 'data_files/ecdsa_secp256r1_sha256.key'),
35 'ecdsa_secp384r1_sha384': (
36 'data_files/ecdsa_secp384r1_sha384.crt',
37 'data_files/ecdsa_secp384r1_sha384.key'),
38 'ecdsa_secp521r1_sha512': (
39 'data_files/ecdsa_secp521r1_sha512.crt',
40 'data_files/ecdsa_secp521r1_sha512.key'),
41}
42
43CAFILE = 'data_files/test-ca2.crt'
44
45CIPHER_SUITE_IANA_VALUE = {
46 "TLS_AES_128_GCM_SHA256": 0x1301,
47 "TLS_AES_256_GCM_SHA384": 0x1302,
48 "TLS_CHACHA20_POLY1305_SHA256": 0x1303,
49 "TLS_AES_128_CCM_SHA256": 0x1304,
50 "TLS_AES_128_CCM_8_SHA256": 0x1305
51}
52
53SIG_ALG_IANA_VALUE = {
54 "ecdsa_secp256r1_sha256": 0x0403,
55 "ecdsa_secp384r1_sha384": 0x0503,
56 "ecdsa_secp521r1_sha512": 0x0603,
57}
58
59NAMED_GROUP_IANA_VALUE = {
60 'secp256r1': 0x17,
61 'secp384r1': 0x18,
62 'secp521r1': 0x19,
63 'x25519': 0x1d,
64 'x448': 0x1e,
65}
66
67
68def remove_duplicates(seq):
69 seen = set()
70 seen_add = seen.add
71 return [x for x in seq if not (x in seen or seen_add(x))]
72
73
74class TLSProgram(metaclass=abc.ABCMeta):
75 """
76 Base class for generate server/client command.
77 """
78 def __init__(self, ciphersuite, signature_algorithm, named_group):
79 self._cipher = ciphersuite
80 self._sig_alg = signature_algorithm
81 self._named_group = named_group
82 self.add_ciphersuites(ciphersuite)
83 self.add_named_groups(named_group)
84 self.add_signature_algorithms(signature_algorithm)
85
86 @abc.abstractmethod
87 def add_ciphersuites(self, *ciphersuites):
88 pass
89
90 @abc.abstractmethod
91 def add_signature_algorithms(self, *signature_algorithms):
92 pass
93
94 @abc.abstractmethod
95 def add_named_groups(self, *named_groups):
96 pass
97
98 @abc.abstractmethod
99 def pre_checks(self):
100 return []
101
102 @abc.abstractmethod
103 def cmd(self):
104 pass
105
106 @abc.abstractmethod
107 def post_checks(self):
108 return []
109
110
111class OpenSSLServ(TLSProgram):
112 """
113 Generate test commands for OpenSSL server.
114 """
115 program = '$OPENSSL_NEXT'
116
117 def __init__(
118 self,
119 ciphersuite,
120 signature_algorithm,
121 named_group):
122 self.ciphersuites = []
123 self.named_groups = []
124 self.signature_algorithms = []
125 self.certificates = []
126 super().__init__(ciphersuite, signature_algorithm, named_group)
127
128 CIPHER_SUITE = {
129 'TLS_AES_256_GCM_SHA384': [
130 'AES-256-GCM',
131 'SHA384',
132 'AEAD'],
133 'TLS_AES_128_GCM_SHA256': [
134 'AES-128-GCM',
135 'SHA256',
136 'AEAD'],
137 'TLS_CHACHA20_POLY1305_SHA256': [
138 'CHACHA20-POLY1305',
139 'SHA256',
140 'AEAD'],
141 'TLS_AES_128_CCM_SHA256': [
142 'AES-128-CCM',
143 'SHA256',
144 'AEAD'],
145 'TLS_AES_128_CCM_8_SHA256': [
146 'AES-128-CCM-8',
147 'SHA256',
148 'AEAD']}
149
150 def add_ciphersuites(self, *ciphersuites):
151 self.ciphersuites.extend(ciphersuites)
152
153 def add_signature_algorithms(self, *signature_algorithms):
154 self.signature_algorithms.extend(signature_algorithms)
155 for sig_alg in signature_algorithms:
156 self.certificates.append(CERTIFICATES[sig_alg])
157
158 NAMED_GROUP = {
159 'secp256r1': 'P-256',
160 'secp384r1': 'P-384',
161 'secp521r1': 'P-521',
162 'x25519': 'X25519',
163 'x448': 'X448',
164 }
165
166 def add_named_groups(self, *named_groups):
167 for named_group in named_groups:
168 self.named_groups.append(self.NAMED_GROUP[named_group])
169
170 def cmd(self):
171 ret = ['$O_NEXT_SRV_NO_CERT']
172 for cert, key in self.certificates:
173 ret += ['-cert {cert} -key {key}'.format(cert=cert, key=key)]
174 ret += ['-accept $SRV_PORT']
175 ciphersuites = ','.join(self.ciphersuites)
176 signature_algorithms = ','.join(self.signature_algorithms)
177 named_groups = ','.join(self.named_groups)
178 ret += ["-ciphersuites {ciphersuites}".format(ciphersuites=ciphersuites),
179 "-sigalgs {signature_algorithms}".format(
180 signature_algorithms=signature_algorithms),
181 "-groups {named_groups}".format(named_groups=named_groups)]
182 ret += ['-msg -tls1_3 -no_middlebox -num_tickets 0 -no_resume_ephemeral -no_cache']
183 return ' '.join(ret)
184
185 def pre_checks(self):
186 return ["requires_openssl_tls1_3"]
187
188 def post_checks(self):
189 return []
190
191
192class GnuTLSServ(TLSProgram):
193 """
194 Generate test commands for GnuTLS server.
195 """
196
197 def __init__(self, ciphersuite, signature_algorithm, named_group):
198 self.priority_strings = []
199 self.certificates = []
200 super().__init__(ciphersuite, signature_algorithm, named_group)
201
202 CIPHER_SUITE = {
203 'TLS_AES_256_GCM_SHA384': [
204 'AES-256-GCM',
205 'SHA384',
206 'AEAD'],
207 'TLS_AES_128_GCM_SHA256': [
208 'AES-128-GCM',
209 'SHA256',
210 'AEAD'],
211 'TLS_CHACHA20_POLY1305_SHA256': [
212 'CHACHA20-POLY1305',
213 'SHA256',
214 'AEAD'],
215 'TLS_AES_128_CCM_SHA256': [
216 'AES-128-CCM',
217 'SHA256',
218 'AEAD'],
219 'TLS_AES_128_CCM_8_SHA256': [
220 'AES-128-CCM-8',
221 'SHA256',
222 'AEAD']}
223
224 def add_ciphersuites(self, *ciphersuites):
225 for cihpersuite in ciphersuites:
226 self.priority_strings.extend(self.CIPHER_SUITE[cihpersuite])
227
228 SIGNATURE_ALGORITHM = {
229 'ecdsa_secp256r1_sha256': ['SIGN-ECDSA-SECP256R1-SHA256'],
230 'ecdsa_secp521r1_sha512': ['SIGN-ECDSA-SECP521R1-SHA512'],
231 'ecdsa_secp384r1_sha384': ['SIGN-ECDSA-SECP384R1-SHA384']}
232
233 def add_signature_algorithms(self, *signature_algorithms):
234 for sig_alg in signature_algorithms:
235 self.priority_strings.extend(self.SIGNATURE_ALGORITHM[sig_alg])
236 self.certificates.append(CERTIFICATES[sig_alg])
237
238 NAMED_GROUP = {
239 'secp256r1': ['GROUP-SECP256R1'],
240 'secp384r1': ['GROUP-SECP384R1'],
241 'secp521r1': ['GROUP-SECP521R1'],
242 'x25519': ['GROUP-X25519'],
243 'x448': ['GROUP-X448'],
244 }
245
246 def add_named_groups(self, *named_groups):
247 for named_group in named_groups:
248 self.priority_strings.extend(self.NAMED_GROUP[named_group])
249
250 def pre_checks(self):
251 return ["requires_gnutls_tls1_3",
252 "requires_gnutls_next_no_ticket",
253 "requires_gnutls_next_disable_tls13_compat", ]
254
255 def post_checks(self):
256 return super().post_checks()
257
258 def cmd(self):
259 ret = [
260 '$G_NEXT_SRV_NO_CERT',
261 '--http',
262 '--disable-client-cert',
263 '--debug=4']
264 for cert, key in self.certificates:
265 ret += ['--x509certfile {cert} --x509keyfile {key}'.format(
266 cert=cert, key=key)]
267 priority_strings = ':+'.join(['NONE'] +
268 list(set(self.priority_strings)) +
269 ['VERS-TLS1.3'])
270 priority_strings += ':%NO_TICKETS:%DISABLE_TLS13_COMPAT_MODE'
271 ret += ['--priority={priority_strings}'.format(
272 priority_strings=priority_strings)]
273 ret = ' '.join(ret)
274 return ret
275
276
277class MbedTLSCli(TLSProgram):
278 """
279 Generate test commands for mbedTLS client.
280 """
281 def __init__(self, ciphersuite, signature_algorithm, named_group):
282 self.ciphersuites = []
283 self.certificates = []
284 self.signature_algorithms = []
285 self.named_groups = []
286 self.needed_named_groups = []
287 super().__init__(ciphersuite, signature_algorithm, named_group)
288
289 CIPHER_SUITE = {
290 'TLS_AES_256_GCM_SHA384': 'TLS1-3-AES-256-GCM-SHA384',
291 'TLS_AES_128_GCM_SHA256': 'TLS1-3-AES-128-GCM-SHA256',
292 'TLS_CHACHA20_POLY1305_SHA256': 'TLS1-3-CHACHA20-POLY1305-SHA256',
293 'TLS_AES_128_CCM_SHA256': 'TLS1-3-AES-128-CCM-SHA256',
294 'TLS_AES_128_CCM_8_SHA256': 'TLS1-3-AES-128-CCM-8-SHA256'}
295
296 def add_ciphersuites(self, *ciphersuites):
297 for cihpersuite in ciphersuites:
298 self.ciphersuites.append(self.CIPHER_SUITE[cihpersuite])
299
300 def add_signature_algorithms(self, *signature_algorithms):
301 for sig_alg in signature_algorithms:
302 self.signature_algorithms.append(sig_alg)
303 if sig_alg == 'ecdsa_secp256r1_sha256':
304 self.needed_named_groups.append('secp256r1')
305 elif sig_alg == 'ecdsa_secp521r1_sha512':
306 self.needed_named_groups.append('secp521r1')
307 elif sig_alg == 'ecdsa_secp384r1_sha384':
308 self.needed_named_groups.append('secp384r1')
309
310 self.certificates.append(CERTIFICATES[sig_alg])
311
312 def add_named_groups(self, *named_groups):
313 for named_group in named_groups:
314 self.named_groups.append(named_group)
315
316 def pre_checks(self):
317 return ['requires_config_enabled MBEDTLS_SSL_PROTO_TLS1_3_EXPERIMENTAL',
318 'requires_config_disabled MBEDTLS_USE_PSA_CRYPTO']
319
320 def post_checks(self):
321
322 check_strings = ["ECDH curve: {group}".format(group=self._named_group),
323 "server hello, chosen ciphersuite: ( {:04x} ) - {}".format(
324 CIPHER_SUITE_IANA_VALUE[self._cipher],
325 self.CIPHER_SUITE[self._cipher]),
326 "Certificate Verify: Signature algorithm ( {:04x} )".format(
327 SIG_ALG_IANA_VALUE[self._sig_alg]),
328 "Verifying peer X.509 certificate... ok", ]
329 return ['-c "{}"'.format(i) for i in check_strings]
330
331 def cmd(self):
332 ret = ['$P_CLI']
333 ret += [
334 'server_addr=127.0.0.1 server_port=$SRV_PORT',
335 'debug_level=4 force_version=tls1_3']
336 ret += ['ca_file={CAFILE}'.format(CAFILE=CAFILE)]
337 self.ciphersuites = list(set(self.ciphersuites))
338 cipher = ','.join(self.ciphersuites)
339 if cipher:
340 ret += ["force_ciphersuite={cipher}".format(cipher=cipher)]
341 self.named_groups = remove_duplicates(
342 self.named_groups + self.needed_named_groups)
343 group = ','.join(self.named_groups)
344 if group:
345 ret += ["curves={group}".format(group=group)]
346 sig_alg = ','.join(self.signature_algorithms)
347 ret += ['sig_algs={sig_alg}'.format(sig_alg=sig_alg)]
348 ret = ' '.join(ret)
349 return ret
350
351
352SERVER_CLS = {'OpenSSL': OpenSSLServ, 'GnuTLS': GnuTLSServ}
353CLIENT_CLS = {'mbedTLS': MbedTLSCli}
354
355
356def generate_compat_test(server=None, client=None, cipher=None, # pylint: disable=unused-argument
357 sig_alg=None, named_group=None, **kwargs):
358 """
359 Generate test case with `ssl-opt.sh` format.
360 """
361 name = 'TLS1.3 {client[0]}->{server[0]}: {cipher},{named_group},{sig_alg}'.format(
362 client=client, server=server, cipher=cipher, sig_alg=sig_alg, named_group=named_group)
363 server = SERVER_CLS[server](cipher, sig_alg, named_group)
364 client = CLIENT_CLS[client](cipher, sig_alg, named_group)
365
366 cmd = ['run_test "{}"'.format(name), '"{}"'.format(
367 server.cmd()), '"{}"'.format(client.cmd()), '0']
368 cmd += server.post_checks()
369 cmd += client.post_checks()
370 prefix = ' \\\n' + (' '*12)
371 cmd = prefix.join(cmd)
372 print('\n'.join(server.pre_checks() + client.pre_checks() + [cmd]))
373 return 0
374def main():
375 parser = argparse.ArgumentParser()
376
377 parser.add_argument('--list-ciphers', action='store_true',
378 default=False, help='List supported ciphersuites')
379
380 parser.add_argument('--list-sig-algs', action='store_true',
381 default=False, help='List supported signature algorithms')
382
383 parser.add_argument('--list-named-groups', action='store_true',
384 default=False, help='List supported named groups')
385
386 parser.add_argument('--list-servers', action='store_true',
387 default=False, help='List supported TLS servers')
388
389 parser.add_argument('--list-clients', action='store_true',
390 default=False, help='List supported TLS Clients')
391
392 parser.add_argument('server', choices=SERVER_CLS.keys(), nargs='?',
393 default=list(SERVER_CLS.keys())[0],
394 help='Choose TLS server program for test')
395 parser.add_argument('client', choices=CLIENT_CLS.keys(), nargs='?',
396 default=list(CLIENT_CLS.keys())[0],
397 help='Choose TLS client program for test')
398 parser.add_argument('cipher', choices=CIPHER_SUITE_IANA_VALUE.keys(), nargs='?',
399 default=list(CIPHER_SUITE_IANA_VALUE.keys())[0],
400 help='Choose cipher suite for test')
401 parser.add_argument('sig_alg', choices=SIG_ALG_IANA_VALUE.keys(), nargs='?',
402 default=list(SIG_ALG_IANA_VALUE.keys())[0],
403 help='Choose cipher suite for test')
404 parser.add_argument('named_group', choices=NAMED_GROUP_IANA_VALUE.keys(), nargs='?',
405 default=list(NAMED_GROUP_IANA_VALUE.keys())[0],
406 help='Choose cipher suite for test')
407
408 args = parser.parse_args()
409 if args.list_ciphers or args.list_sig_algs or args.list_named_groups \
410 or args.list_servers or args.list_clients:
411 if args.list_ciphers:
412 print(*CIPHER_SUITE_IANA_VALUE.keys())
413 if args.list_sig_algs:
414 print(*SIG_ALG_IANA_VALUE.keys())
415 if args.list_named_groups:
416 print(*NAMED_GROUP_IANA_VALUE.keys())
417 if args.list_servers:
418 print(*SERVER_CLS.keys())
419 if args.list_clients:
420 print(*CLIENT_CLS.keys())
421 return 0
422 return generate_compat_test(**vars(args))
423
424if __name__ == "__main__":
425 sys.exit(main())