quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

test_17_ssl_use.py (26614B)


      1 #!/usr/bin/env python3
      2 # -*- coding: utf-8 -*-
      3 #***************************************************************************
      4 #                                  _   _ ____  _
      5 #  Project                     ___| | | |  _ \| |
      6 #                             / __| | | | |_) | |
      7 #                            | (__| |_| |  _ <| |___
      8 #                             \___|\___/|_| \_\_____|
      9 #
     10 # Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
     11 #
     12 # This software is licensed as described in the file COPYING, which
     13 # you should have received as part of this distribution. The terms
     14 # are also available at https://curl.se/docs/copyright.html.
     15 #
     16 # You may opt to use, copy, modify, merge, publish, distribute and/or sell
     17 # copies of the Software, and permit persons to whom the Software is
     18 # furnished to do so, under the terms of the COPYING file.
     19 #
     20 # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     21 # KIND, either express or implied.
     22 #
     23 # SPDX-License-Identifier: curl
     24 #
     25 ###########################################################################
     26 #
     27 import json
     28 import logging
     29 import os
     30 import re
     31 import pytest
     32 
     33 from testenv import Env, CurlClient, LocalClient
     34 
     35 
     36 log = logging.getLogger(__name__)
     37 
     38 
     39 class TestSSLUse:
     40 
     41     @pytest.fixture(autouse=True, scope='class')
     42     def _class_scope(self, env, httpd, nghttpx):
     43         env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10*1024)
     44 
     45     def test_17_01_sslinfo_plain(self, env: Env, httpd):
     46         proto = 'http/1.1'
     47         curl = CurlClient(env=env)
     48         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
     49         r = curl.http_get(url=url, alpn_proto=proto)
     50         assert r.json['HTTPS'] == 'on', f'{r.json}'
     51         assert 'SSL_SESSION_ID' in r.json, f'{r.json}'
     52         assert 'SSL_SESSION_RESUMED' in r.json, f'{r.json}'
     53         assert r.json['SSL_SESSION_RESUMED'] == 'Initial', f'{r.json}'
     54 
     55     @pytest.mark.parametrize("tls_max", ['1.2', '1.3'])
     56     def test_17_02_sslinfo_reconnect(self, env: Env, tls_max, httpd):
     57         proto = 'http/1.1'
     58         count = 3
     59         exp_resumed = 'Resumed'
     60         xargs = ['--sessionid', '--tls-max', tls_max, f'--tlsv{tls_max}']
     61         if env.curl_uses_lib('libressl'):
     62             if tls_max == '1.3':
     63                 exp_resumed = 'Initial'  # 1.2 works in LibreSSL, but 1.3 does not, TODO
     64         if env.curl_uses_lib('rustls-ffi'):
     65             exp_resumed = 'Initial'  # Rustls does not support sessions, TODO
     66         if env.curl_uses_lib('mbedtls') and tls_max == '1.3' and \
     67            not env.curl_lib_version_at_least('mbedtls', '3.6.0'):
     68             pytest.skip('mbedtls TLSv1.3 session resume not working in 3.6.0')
     69 
     70         run_env = os.environ.copy()
     71         run_env['CURL_DEBUG'] = 'ssl'
     72         curl = CurlClient(env=env, run_env=run_env)
     73         # tell the server to close the connection after each request
     74         urln = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo?'\
     75             f'id=[0-{count-1}]&close'
     76         r = curl.http_download(urls=[urln], alpn_proto=proto, with_stats=True,
     77                                extra_args=xargs)
     78         r.check_response(count=count, http_status=200)
     79         # should have used one connection for each request, sessions after
     80         # first should have been resumed
     81         assert r.total_connects == count, r.dump_logs()
     82         for i in range(count):
     83             dfile = curl.download_file(i)
     84             assert os.path.exists(dfile)
     85             with open(dfile) as f:
     86                 djson = json.load(f)
     87             assert djson['HTTPS'] == 'on', f'{i}: {djson}'
     88             if i == 0:
     89                 assert djson['SSL_SESSION_RESUMED'] == 'Initial', f'{i}: {djson}\n{r.dump_logs()}'
     90             else:
     91                 assert djson['SSL_SESSION_RESUMED'] == exp_resumed, f'{i}: {djson}\n{r.dump_logs()}'
     92 
     93     # use host name with trailing dot, verify handshake
     94     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
     95     def test_17_03_trailing_dot(self, env: Env, proto, httpd, nghttpx):
     96         if proto == 'h3' and not env.have_h3():
     97             pytest.skip("h3 not supported")
     98         curl = CurlClient(env=env)
     99         domain = f'{env.domain1}.'
    100         url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
    101         r = curl.http_get(url=url, alpn_proto=proto)
    102         assert r.exit_code == 0, f'{r}'
    103         assert r.json, f'{r}'
    104         if proto != 'h3':  # we proxy h3
    105             # the SNI the server received is without trailing dot
    106             assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}'
    107 
    108     # use host name with double trailing dot, verify handshake
    109     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    110     def test_17_04_double_dot(self, env: Env, proto, httpd, nghttpx):
    111         if proto == 'h3' and not env.have_h3():
    112             pytest.skip("h3 not supported")
    113         curl = CurlClient(env=env)
    114         domain = f'{env.domain1}..'
    115         url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
    116         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    117             '-H', f'Host: {env.domain1}',
    118         ])
    119         if r.exit_code == 0:
    120             assert r.json, f'{r.stdout}'
    121             # the SNI the server received is without trailing dot
    122             if proto != 'h3':  # we proxy h3
    123                 assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}'
    124             assert False, f'should not have succeeded: {r.json}'
    125         # 7 - Rustls rejects a servername with .. during setup
    126         # 35 - LibreSSL rejects setting an SNI name with trailing dot
    127         # 60 - peer name matching failed against certificate
    128         assert r.exit_code in [7, 35, 60], f'{r}'
    129 
    130     # use ip address for connect
    131     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    132     def test_17_05_good_ip_addr(self, env: Env, proto, httpd, nghttpx):
    133         if env.curl_uses_lib('mbedtls'):
    134             pytest.skip("mbedTLS does use IP addresses in SNI")
    135         if proto == 'h3' and not env.have_h3():
    136             pytest.skip("h3 not supported")
    137         curl = CurlClient(env=env)
    138         domain = '127.0.0.1'
    139         url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
    140         r = curl.http_get(url=url, alpn_proto=proto)
    141         assert r.exit_code == 0, f'{r}'
    142         assert r.json, f'{r}'
    143         if proto != 'h3':  # we proxy h3
    144             # the SNI should not have been used
    145             assert 'SSL_TLS_SNI' not in r.json, f'{r.json}'
    146 
    147     # use IP address that is not in cert
    148     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    149     def test_17_05_bad_ip_addr(self, env: Env, proto,
    150                                httpd, configures_httpd,
    151                                nghttpx, configures_nghttpx):
    152         if proto == 'h3' and not env.have_h3():
    153             pytest.skip("h3 not supported")
    154         httpd.set_domain1_cred_name('domain1-no-ip')
    155         httpd.reload_if_config_changed()
    156         if proto == 'h3':
    157             nghttpx.set_cred_name('domain1-no-ip')
    158             nghttpx.reload_if_config_changed()
    159         curl = CurlClient(env=env)
    160         url = f'https://127.0.0.1:{env.port_for(proto)}/curltest/sslinfo'
    161         r = curl.http_get(url=url, alpn_proto=proto)
    162         assert r.exit_code == 60, f'{r}'
    163 
    164     # use localhost for connect
    165     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    166     def test_17_06_localhost(self, env: Env, proto, httpd, nghttpx):
    167         if proto == 'h3' and not env.have_h3():
    168             pytest.skip("h3 not supported")
    169         curl = CurlClient(env=env)
    170         domain = 'localhost'
    171         url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
    172         r = curl.http_get(url=url, alpn_proto=proto)
    173         assert r.exit_code == 0, f'{r}'
    174         assert r.json, f'{r}'
    175         if proto != 'h3':  # we proxy h3
    176             assert r.json['SSL_TLS_SNI'] == domain, f'{r.json}'
    177 
    178     @staticmethod
    179     def gen_test_17_07_list():
    180         tls13_tests = [
    181             ['def', None, True],
    182             ['AES128SHA256', ['TLS_AES_128_GCM_SHA256'], True],
    183             ['AES128SHA384', ['TLS_AES_256_GCM_SHA384'], False],
    184             ['CHACHA20SHA256', ['TLS_CHACHA20_POLY1305_SHA256'], True],
    185             ['AES128SHA384+CHACHA20SHA256', ['TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256'], True],
    186         ]
    187         tls12_tests = [
    188             ['def', None, True],
    189             ['AES128ish', ['ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256'], True],
    190             ['AES256ish', ['ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384'], False],
    191             ['CHACHA20ish', ['ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305'], True],
    192             ['AES256ish+CHACHA20ish', ['ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384',
    193               'ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305'], True],
    194         ]
    195         ret = []
    196         for tls_id, tls_proto in {
    197                 'TLSv1.2+3': 'TLSv1.3 +TLSv1.2',
    198                 'TLSv1.3': 'TLSv1.3',
    199                 'TLSv1.2': 'TLSv1.2'}.items():
    200             for [cid13, ciphers13, succeed13] in tls13_tests:
    201                 for [cid12, ciphers12, succeed12] in tls12_tests:
    202                     id = f'{tls_id}-{cid13}-{cid12}'
    203                     ret.append(pytest.param(tls_proto, ciphers13, ciphers12, succeed13, succeed12, id=id))
    204         return ret
    205 
    206     @pytest.mark.parametrize(
    207         "tls_proto, ciphers13, ciphers12, succeed13, succeed12",
    208         gen_test_17_07_list())
    209     def test_17_07_ssl_ciphers(self, env: Env, httpd, configures_httpd,
    210                                tls_proto, ciphers13, ciphers12,
    211                                succeed13, succeed12):
    212         # to test setting cipher suites, the AES 256 ciphers are disabled in the test server
    213         httpd.set_extra_config('base', [
    214             'SSLCipherSuite SSL'
    215             ' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'
    216             ':ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305',
    217             'SSLCipherSuite TLSv1.3'
    218             ' TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',
    219             f'SSLProtocol {tls_proto}'
    220         ])
    221         httpd.reload_if_config_changed()
    222         proto = 'http/1.1'
    223         curl = CurlClient(env=env)
    224         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    225         # SSL backend specifics
    226         if env.curl_uses_lib('gnutls'):
    227             pytest.skip('GnuTLS does not support setting ciphers')
    228         elif env.curl_uses_lib('boringssl'):
    229             if ciphers13 is not None:
    230                 pytest.skip('BoringSSL does not support setting TLSv1.3 ciphers')
    231         elif env.curl_uses_lib('schannel'):  # not in CI, so untested
    232             if ciphers12 is not None:
    233                 pytest.skip('Schannel does not support setting TLSv1.2 ciphers by name')
    234         elif env.curl_uses_lib('mbedtls') and not env.curl_lib_version_at_least('mbedtls', '3.6.0'):
    235             if tls_proto == 'TLSv1.3':
    236                 pytest.skip('mbedTLS < 3.6.0 does not support TLSv1.3')
    237         # test
    238         extra_args = ['--tls13-ciphers', ':'.join(ciphers13)] if ciphers13 else []
    239         extra_args += ['--ciphers', ':'.join(ciphers12)] if ciphers12 else []
    240         r = curl.http_get(url=url, alpn_proto=proto, extra_args=extra_args)
    241         if tls_proto != 'TLSv1.2' and succeed13:
    242             assert r.exit_code == 0, r.dump_logs()
    243             assert r.json['HTTPS'] == 'on', r.dump_logs()
    244             assert r.json['SSL_PROTOCOL'] == 'TLSv1.3', r.dump_logs()
    245             assert ciphers13 is None or r.json['SSL_CIPHER'] in ciphers13, r.dump_logs()
    246         elif tls_proto == 'TLSv1.2' and succeed12:
    247             assert r.exit_code == 0, r.dump_logs()
    248             assert r.json['HTTPS'] == 'on', r.dump_logs()
    249             assert r.json['SSL_PROTOCOL'] == 'TLSv1.2', r.dump_logs()
    250             assert ciphers12 is None or r.json['SSL_CIPHER'] in ciphers12, r.dump_logs()
    251         else:
    252             assert r.exit_code != 0, r.dump_logs()
    253 
    254     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    255     def test_17_08_cert_status(self, env: Env, proto, httpd, nghttpx):
    256         if proto == 'h3' and not env.have_h3():
    257             pytest.skip("h3 not supported")
    258         if not env.curl_uses_lib('openssl') and \
    259            not env.curl_uses_lib('gnutls') and \
    260            not env.curl_uses_lib('quictls'):
    261             pytest.skip("TLS library does not support --cert-status")
    262         curl = CurlClient(env=env)
    263         domain = 'localhost'
    264         url = f'https://{env.authority_for(domain, proto)}/'
    265         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    266             '--cert-status'
    267         ])
    268         # CURLE_SSL_INVALIDCERTSTATUS, our certs have no OCSP info
    269         assert r.exit_code == 91, f'{r}'
    270 
    271     @staticmethod
    272     def gen_test_17_09_list():
    273         return [[tls_proto, max_ver, min_ver]
    274                 for tls_proto in ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
    275                 for max_ver in range(5)
    276                 for min_ver in range(-2, 4)]
    277 
    278     @pytest.mark.parametrize("tls_proto, max_ver, min_ver", gen_test_17_09_list())
    279     def test_17_09_ssl_min_max(self, env: Env, httpd, configures_httpd, tls_proto, max_ver, min_ver):
    280         httpd.set_extra_config('base', [
    281             f'SSLProtocol {tls_proto}',
    282             'SSLCipherSuite ALL:@SECLEVEL=0',
    283         ])
    284         httpd.reload_if_config_changed()
    285         proto = 'http/1.1'
    286         run_env = os.environ.copy()
    287         if env.curl_uses_lib('gnutls'):
    288             # we need to override any default system configuration since
    289             # we want to test all protocol versions. Ubuntu (or the GH image)
    290             # disable TSL1.0 and TLS1.1 system wide. We do not want.
    291             our_config = os.path.join(env.gen_dir, 'gnutls_config')
    292             if not os.path.exists(our_config):
    293                 with open(our_config, 'w') as fd:
    294                     fd.write('# empty\n')
    295             run_env['GNUTLS_SYSTEM_PRIORITY_FILE'] = our_config
    296         curl = CurlClient(env=env, run_env=run_env)
    297         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    298         # SSL backend specifics
    299         if env.curl_uses_lib('gnutls'):
    300             supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
    301         elif env.curl_uses_lib('quiche'):
    302             supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
    303         elif env.curl_uses_lib('aws-lc'):
    304             supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
    305         elif env.curl_uses_lib('openssl') and \
    306             env.curl_lib_version_before('openssl', '3.0.0'):
    307             supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
    308         else:  # most SSL backends dropped support for TLSv1.0, TLSv1.1
    309             supported = [None, None, 'TLSv1.2', 'TLSv1.3']
    310         # test
    311         extra_args = [[], ['--tlsv1'], ['--tlsv1.0'], ['--tlsv1.1'], ['--tlsv1.2'], ['--tlsv1.3']][min_ver+2] + \
    312             [['--tls-max', '1.0'], ['--tls-max', '1.1'], ['--tls-max', '1.2'], ['--tls-max', '1.3'], []][max_ver]
    313         extra_args.extend(['--trace-config', 'ssl'])
    314         r = curl.http_get(url=url, alpn_proto=proto, extra_args=extra_args)
    315         if max_ver >= min_ver and tls_proto in supported[max(0, min_ver):min(max_ver, 3)+1]:
    316             assert r.exit_code == 0, f'extra_args={extra_args}\n{r.dump_logs()}'
    317             assert r.json['HTTPS'] == 'on', r.dump_logs()
    318             assert r.json['SSL_PROTOCOL'] == tls_proto, r.dump_logs()
    319         else:
    320             assert r.exit_code != 0, f'extra_args={extra_args}\n{r.dump_logs()}'
    321 
    322     def test_17_10_h3_session_reuse(self, env: Env, httpd, nghttpx):
    323         if not env.have_h3():
    324             pytest.skip("h3 not supported")
    325         if not env.curl_uses_lib('quictls') and \
    326            not (env.curl_uses_lib('openssl') and env.curl_uses_lib('ngtcp2')) and \
    327            not env.curl_uses_lib('gnutls') and \
    328            not env.curl_uses_lib('wolfssl'):
    329             pytest.skip("QUIC session reuse not implemented")
    330         count = 2
    331         docname = 'data-10k'
    332         url = f'https://localhost:{env.https_port}/{docname}'
    333         client = LocalClient(name='hx_download', env=env)
    334         if not client.exists():
    335             pytest.skip(f'example client not built: {client.name}')
    336         r = client.run(args=[
    337              '-n', f'{count}',
    338              '-f',  # forbid reuse of connections
    339              '-r', f'{env.domain1}:{env.port_for("h3")}:127.0.0.1',
    340              '-V', 'h3', url
    341         ])
    342         r.check_exit_code(0)
    343         # check that TLS session was reused as expected
    344         reused_session = False
    345         for line in r.trace_lines:
    346             if re.match(r'.*\[1-1] (\* )?SSL reusing session.*', line):
    347                 reused_session = True
    348         assert reused_session, f'{r}\n{r.dump_logs()}'
    349 
    350     # use host name server has no certificate for
    351     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    352     def test_17_11_wrong_host(self, env: Env, proto, httpd, nghttpx):
    353         if proto == 'h3' and not env.have_h3():
    354             pytest.skip("h3 not supported")
    355         curl = CurlClient(env=env)
    356         domain = f'insecure.{env.tld}'
    357         url = f'https://{domain}:{env.port_for(proto)}/curltest/sslinfo'
    358         r = curl.http_get(url=url, alpn_proto=proto)
    359         assert r.exit_code == 60, f'{r}'
    360 
    361     # use host name server has no cert for with --insecure
    362     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    363     def test_17_12_insecure(self, env: Env, proto, httpd, nghttpx):
    364         if proto == 'h3' and not env.have_h3():
    365             pytest.skip("h3 not supported")
    366         curl = CurlClient(env=env)
    367         domain = f'insecure.{env.tld}'
    368         url = f'https://{domain}:{env.port_for(proto)}/curltest/sslinfo'
    369         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    370             '--insecure'
    371         ])
    372         assert r.exit_code == 0, f'{r}'
    373         assert r.json, f'{r}'
    374 
    375     # connect to an expired certificate
    376     @pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
    377     def test_17_14_expired_cert(self, env: Env, proto, httpd):
    378         if proto == 'h3' and not env.have_h3():
    379             pytest.skip("h3 not supported")
    380         curl = CurlClient(env=env)
    381         url = f'https://{env.expired_domain}:{env.port_for(proto)}/'
    382         r = curl.http_get(url=url, alpn_proto=proto)
    383         assert r.exit_code == 60, f'{r}'  # peer failed verification
    384         exp_trace = None
    385         match_trace = None
    386         if env.curl_uses_lib('openssl') or env.curl_uses_lib('quictls'):
    387             exp_trace = r'.*SSL certificate problem: certificate has expired$'
    388         elif env.curl_uses_lib('gnutls'):
    389             exp_trace = r'.*server verification failed: certificate has expired\..*'
    390         elif env.curl_uses_lib('wolfssl'):
    391             exp_trace = r'.*server verification failed: certificate has expired\.$'
    392         if exp_trace is not None:
    393             for line in r.trace_lines:
    394                 if re.match(exp_trace, line):
    395                     match_trace = line
    396                     break
    397             assert match_trace, f'Did not find "{exp_trace}" in trace\n{r.dump_logs()}'
    398 
    399     @pytest.mark.skipif(condition=not Env.curl_has_feature('SSLS-EXPORT'),
    400                         reason='curl lacks SSL session export support')
    401     def test_17_15_session_export(self, env: Env, httpd):
    402         proto = 'http/1.1'
    403         if env.curl_uses_lib('libressl'):
    404             pytest.skip('Libressl resumption does not work inTLSv1.3')
    405         if env.curl_uses_lib('rustls-ffi'):
    406             pytest.skip('rustsls does not expose sessions')
    407         if env.curl_uses_lib('mbedtls') and \
    408            not env.curl_lib_version_at_least('mbedtls', '3.6.0'):
    409             pytest.skip('mbedtls TLSv1.3 session resume not working before 3.6.0')
    410         run_env = os.environ.copy()
    411         run_env['CURL_DEBUG'] = 'ssl,ssls'
    412         # clean session file first, then reuse
    413         session_file = os.path.join(env.gen_dir, 'test_17_15.sessions')
    414         if os.path.exists(session_file):
    415             return os.remove(session_file)
    416         xargs = ['--tls-max', '1.3', '--tlsv1.3', '--ssl-sessions', session_file]
    417         curl = CurlClient(env=env, run_env=run_env)
    418         # tell the server to close the connection after each request
    419         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    420         r = curl.http_get(url=url, alpn_proto=proto, extra_args=xargs)
    421         assert r.exit_code == 0, f'{r}'
    422         assert r.json['HTTPS'] == 'on', f'{r.json}'
    423         assert r.json['SSL_SESSION_RESUMED'] == 'Initial', f'{r.json}\n{r.dump_logs()}'
    424         # ok, run again, sessions should be imported
    425         run_dir2 = os.path.join(env.gen_dir, 'curl2')
    426         curl = CurlClient(env=env, run_env=run_env, run_dir=run_dir2)
    427         r = curl.http_get(url=url, alpn_proto=proto, extra_args=xargs)
    428         assert r.exit_code == 0, f'{r}'
    429         assert r.json['SSL_SESSION_RESUMED'] == 'Resumed', f'{r.json}\n{r.dump_logs()}'
    430 
    431     # verify the ciphers are ignored when talking TLSv1.3 only
    432     # see issue #16232
    433     def test_17_16_h3_ignore_ciphers12(self, env: Env, httpd, nghttpx):
    434         proto = 'h3'
    435         if proto == 'h3' and not env.have_h3():
    436             pytest.skip("h3 not supported")
    437         if env.curl_uses_lib('gnutls'):
    438             pytest.skip("gnutls does not ignore --ciphers on TLSv1.3")
    439         curl = CurlClient(env=env)
    440         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    441         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    442             '--ciphers', 'NONSENSE'
    443         ])
    444         assert r.exit_code == 0, f'{r}'
    445 
    446     def test_17_17_h1_ignore_ciphers13(self, env: Env, httpd):
    447         proto = 'http/1.1'
    448         curl = CurlClient(env=env)
    449         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    450         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    451             '--tls13-ciphers', 'NONSENSE', '--tls-max', '1.2'
    452         ])
    453         assert r.exit_code == 0, f'{r}'
    454 
    455     @pytest.mark.parametrize("priority, tls_proto, ciphers, success", [
    456         pytest.param("", "", [], False, id='prio-empty'),
    457         pytest.param("NONSENSE", "", [], False, id='nonsense'),
    458         pytest.param("+NONSENSE", "", [], False, id='+nonsense'),
    459         pytest.param("NORMAL:-VERS-ALL:+VERS-TLS1.2", "TLSv1.2", ['ECDHE-RSA-CHACHA20-POLY1305'], True, id='TLSv1.2-normal-only'),
    460         pytest.param("-VERS-ALL:+VERS-TLS1.2", "TLSv1.2", ['ECDHE-RSA-CHACHA20-POLY1305'], True, id='TLSv1.2-only'),
    461         pytest.param("NORMAL", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-normal'),
    462         pytest.param("NORMAL:-VERS-ALL:+VERS-TLS1.3", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-normal-only'),
    463         pytest.param("-VERS-ALL:+VERS-TLS1.3", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-only'),
    464         pytest.param("!CHACHA20-POLY1305", "TLSv1.3", ['TLS_AES_128_GCM_SHA256'], True, id='TLSv1.3-no-chacha'),
    465         pytest.param("-CIPHER-ALL:+CHACHA20-POLY1305", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-only-chacha'),
    466         pytest.param("-CIPHER-ALL:+AES-256-GCM", "", [], False, id='only-AES256'),
    467         pytest.param("-CIPHER-ALL:+AES-128-GCM", "TLSv1.3", ['TLS_AES_128_GCM_SHA256'], True, id='TLSv1.3-only-AES128'),
    468         pytest.param("SECURE:-CIPHER-ALL:+AES-128-GCM:-VERS-ALL:+VERS-TLS1.2", "TLSv1.2", ['ECDHE-RSA-AES128-GCM-SHA256'], True, id='TLSv1.2-secure'),
    469         pytest.param("-MAC-ALL:+SHA256", "", [], False, id='MAC-only-SHA256'),
    470         pytest.param("-MAC-ALL:+AEAD", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-MAC-only-AEAD'),
    471         pytest.param("-GROUP-ALL:+GROUP-X25519", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-group-only-X25519'),
    472         pytest.param("-GROUP-ALL:+GROUP-SECP192R1", "", [], False, id='group-only-SECP192R1'),
    473         ])
    474     def test_17_18_gnutls_priority(self, env: Env, httpd, configures_httpd, priority, tls_proto, ciphers, success):
    475         # to test setting cipher suites, the AES 256 ciphers are disabled in the test server
    476         httpd.set_extra_config('base', [
    477             'SSLCipherSuite SSL'
    478             ' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'
    479             ':ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305',
    480             'SSLCipherSuite TLSv1.3'
    481             ' TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',
    482         ])
    483         httpd.reload_if_config_changed()
    484         proto = 'http/1.1'
    485         curl = CurlClient(env=env)
    486         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    487         # SSL backend specifics
    488         if not env.curl_uses_lib('gnutls'):
    489             pytest.skip('curl not build with GnuTLS')
    490         # test
    491         extra_args = ['--ciphers', f'{priority}']
    492         r = curl.http_get(url=url, alpn_proto=proto, extra_args=extra_args)
    493         if success:
    494             assert r.exit_code == 0, r.dump_logs()
    495             assert r.json['HTTPS'] == 'on', r.dump_logs()
    496             if tls_proto:
    497                 assert r.json['SSL_PROTOCOL'] == tls_proto, r.dump_logs()
    498             assert r.json['SSL_CIPHER'] in ciphers, r.dump_logs()
    499         else:
    500             assert r.exit_code != 0, r.dump_logs()
    501 
    502     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    503     def test_17_19_wrong_pin(self, env: Env, proto, httpd):
    504         if proto == 'h3' and not env.have_h3():
    505             pytest.skip("h3 not supported")
    506         if env.curl_uses_lib('rustls-ffi'):
    507             pytest.skip('TLS backend ignores --pinnedpubkey')
    508         curl = CurlClient(env=env)
    509         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    510         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    511             '--pinnedpubkey', 'sha256//ffff'
    512         ])
    513         # expect NOT_IMPLEMENTED or CURLE_SSL_PINNEDPUBKEYNOTMATCH
    514         assert r.exit_code in [2, 90], f'{r.dump_logs()}'
    515 
    516     @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
    517     def test_17_20_correct_pin(self, env: Env, proto, httpd):
    518         if proto == 'h3' and not env.have_h3():
    519             pytest.skip("h3 not supported")
    520         curl = CurlClient(env=env)
    521         creds = env.get_credentials(env.domain1)
    522         assert creds
    523         url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
    524         r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
    525             '--pinnedpubkey', f'sha256//{creds.pub_sha256_b64()}'
    526         ])
    527         # expect NOT_IMPLEMENTED or OK
    528         assert r.exit_code in [0, 2], f'{r.dump_logs()}'