Path: blob/main/external/curl/tests/http/test_17_ssl_use.py
2066 views
#!/usr/bin/env python31# -*- coding: utf-8 -*-2#***************************************************************************3# _ _ ____ _4# Project ___| | | | _ \| |5# / __| | | | |_) | |6# | (__| |_| | _ <| |___7# \___|\___/|_| \_\_____|8#9# Copyright (C) Daniel Stenberg, <[email protected]>, et al.10#11# This software is licensed as described in the file COPYING, which12# you should have received as part of this distribution. The terms13# are also available at https://curl.se/docs/copyright.html.14#15# You may opt to use, copy, modify, merge, publish, distribute and/or sell16# copies of the Software, and permit persons to whom the Software is17# furnished to do so, under the terms of the COPYING file.18#19# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY20# KIND, either express or implied.21#22# SPDX-License-Identifier: curl23#24###########################################################################25#26import json27import logging28import os29import re30import pytest3132from testenv import Env, CurlClient, LocalClient333435log = logging.getLogger(__name__)363738class TestSSLUse:3940@pytest.fixture(autouse=True, scope='class')41def _class_scope(self, env, httpd, nghttpx):42env.make_data_file(indir=httpd.docs_dir, fname="data-10k", fsize=10*1024)4344def test_17_01_sslinfo_plain(self, env: Env, httpd):45proto = 'http/1.1'46curl = CurlClient(env=env)47url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'48r = curl.http_get(url=url, alpn_proto=proto)49assert r.json['HTTPS'] == 'on', f'{r.json}'50assert 'SSL_SESSION_ID' in r.json, f'{r.json}'51assert 'SSL_SESSION_RESUMED' in r.json, f'{r.json}'52assert r.json['SSL_SESSION_RESUMED'] == 'Initial', f'{r.json}'5354@pytest.mark.parametrize("tls_max", ['1.2', '1.3'])55def test_17_02_sslinfo_reconnect(self, env: Env, tls_max, httpd):56proto = 'http/1.1'57count = 358exp_resumed = 'Resumed'59xargs = ['--sessionid', '--tls-max', tls_max, f'--tlsv{tls_max}']60if env.curl_uses_lib('libressl'):61if tls_max == '1.3':62exp_resumed = 'Initial' # 1.2 works in LibreSSL, but 1.3 does not, TODO63if env.curl_uses_lib('rustls-ffi'):64exp_resumed = 'Initial' # Rustls does not support sessions, TODO65if env.curl_uses_lib('bearssl') and tls_max == '1.3':66pytest.skip('BearSSL does not support TLSv1.3')67if env.curl_uses_lib('mbedtls') and tls_max == '1.3' and \68not env.curl_lib_version_at_least('mbedtls', '3.6.0'):69pytest.skip('mbedtls TLSv1.3 session resume not working in 3.6.0')7071run_env = os.environ.copy()72run_env['CURL_DEBUG'] = 'ssl'73curl = CurlClient(env=env, run_env=run_env)74# tell the server to close the connection after each request75urln = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo?'\76f'id=[0-{count-1}]&close'77r = curl.http_download(urls=[urln], alpn_proto=proto, with_stats=True,78extra_args=xargs)79r.check_response(count=count, http_status=200)80# should have used one connection for each request, sessions after81# first should have been resumed82assert r.total_connects == count, r.dump_logs()83for i in range(count):84dfile = curl.download_file(i)85assert os.path.exists(dfile)86with open(dfile) as f:87djson = json.load(f)88assert djson['HTTPS'] == 'on', f'{i}: {djson}'89if i == 0:90assert djson['SSL_SESSION_RESUMED'] == 'Initial', f'{i}: {djson}\n{r.dump_logs()}'91else:92assert djson['SSL_SESSION_RESUMED'] == exp_resumed, f'{i}: {djson}\n{r.dump_logs()}'9394# use host name with trailing dot, verify handshake95@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])96def test_17_03_trailing_dot(self, env: Env, proto, httpd, nghttpx):97if proto == 'h3' and not env.have_h3():98pytest.skip("h3 not supported")99curl = CurlClient(env=env)100domain = f'{env.domain1}.'101url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'102r = curl.http_get(url=url, alpn_proto=proto)103assert r.exit_code == 0, f'{r}'104assert r.json, f'{r}'105if proto != 'h3': # we proxy h3106# the SNI the server received is without trailing dot107assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}'108109# use host name with double trailing dot, verify handshake110@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])111def test_17_04_double_dot(self, env: Env, proto, httpd, nghttpx):112if proto == 'h3' and not env.have_h3():113pytest.skip("h3 not supported")114curl = CurlClient(env=env)115domain = f'{env.domain1}..'116url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'117r = curl.http_get(url=url, alpn_proto=proto, extra_args=[118'-H', f'Host: {env.domain1}',119])120if r.exit_code == 0:121assert r.json, f'{r.stdout}'122# the SNI the server received is without trailing dot123if proto != 'h3': # we proxy h3124assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}'125assert False, f'should not have succeeded: {r.json}'126# 7 - Rustls rejects a servername with .. during setup127# 35 - LibreSSL rejects setting an SNI name with trailing dot128# 60 - peer name matching failed against certificate129assert r.exit_code in [7, 35, 60], f'{r}'130131# use ip address for connect132@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])133def test_17_05_good_ip_addr(self, env: Env, proto, httpd, nghttpx):134if env.curl_uses_lib('bearssl'):135pytest.skip("BearSSL does not support cert verification with IP addresses")136if env.curl_uses_lib('mbedtls'):137pytest.skip("mbedTLS does use IP addresses in SNI")138if proto == 'h3' and not env.have_h3():139pytest.skip("h3 not supported")140curl = CurlClient(env=env)141domain = '127.0.0.1'142url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'143r = curl.http_get(url=url, alpn_proto=proto)144assert r.exit_code == 0, f'{r}'145assert r.json, f'{r}'146if proto != 'h3': # we proxy h3147# the SNI should not have been used148assert 'SSL_TLS_SNI' not in r.json, f'{r.json}'149150# use IP address that is not in cert151@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])152def test_17_05_bad_ip_addr(self, env: Env, proto,153httpd, configures_httpd,154nghttpx, configures_nghttpx):155if proto == 'h3' and not env.have_h3():156pytest.skip("h3 not supported")157httpd.set_domain1_cred_name('domain1-no-ip')158httpd.reload_if_config_changed()159if proto == 'h3':160nghttpx.set_cred_name('domain1-no-ip')161nghttpx.reload_if_config_changed()162curl = CurlClient(env=env)163url = f'https://127.0.0.1:{env.port_for(proto)}/curltest/sslinfo'164r = curl.http_get(url=url, alpn_proto=proto)165assert r.exit_code == 60, f'{r}'166167# use localhost for connect168@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])169def test_17_06_localhost(self, env: Env, proto, httpd, nghttpx):170if proto == 'h3' and not env.have_h3():171pytest.skip("h3 not supported")172curl = CurlClient(env=env)173domain = 'localhost'174url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'175r = curl.http_get(url=url, alpn_proto=proto)176assert r.exit_code == 0, f'{r}'177assert r.json, f'{r}'178if proto != 'h3': # we proxy h3179assert r.json['SSL_TLS_SNI'] == domain, f'{r.json}'180181@staticmethod182def gen_test_17_07_list():183tls13_tests = [184['def', None, True],185['AES128SHA256', ['TLS_AES_128_GCM_SHA256'], True],186['AES128SHA384', ['TLS_AES_256_GCM_SHA384'], False],187['CHACHA20SHA256', ['TLS_CHACHA20_POLY1305_SHA256'], True],188['AES128SHA384+CHACHA20SHA256', ['TLS_AES_256_GCM_SHA384', 'TLS_CHACHA20_POLY1305_SHA256'], True],189]190tls12_tests = [191['def', None, True],192['AES128ish', ['ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256'], True],193['AES256ish', ['ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384'], False],194['CHACHA20ish', ['ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305'], True],195['AES256ish+CHACHA20ish', ['ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384',196'ECDHE-ECDSA-CHACHA20-POLY1305', 'ECDHE-RSA-CHACHA20-POLY1305'], True],197]198ret = []199for tls_id, tls_proto in {200'TLSv1.2+3': 'TLSv1.3 +TLSv1.2',201'TLSv1.3': 'TLSv1.3',202'TLSv1.2': 'TLSv1.2'}.items():203for [cid13, ciphers13, succeed13] in tls13_tests:204for [cid12, ciphers12, succeed12] in tls12_tests:205id = f'{tls_id}-{cid13}-{cid12}'206ret.append(pytest.param(tls_proto, ciphers13, ciphers12, succeed13, succeed12, id=id))207return ret208209@pytest.mark.parametrize(210"tls_proto, ciphers13, ciphers12, succeed13, succeed12",211gen_test_17_07_list())212def test_17_07_ssl_ciphers(self, env: Env, httpd, configures_httpd,213tls_proto, ciphers13, ciphers12,214succeed13, succeed12):215# to test setting cipher suites, the AES 256 ciphers are disabled in the test server216httpd.set_extra_config('base', [217'SSLCipherSuite SSL'218' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'219':ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305',220'SSLCipherSuite TLSv1.3'221' TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',222f'SSLProtocol {tls_proto}'223])224httpd.reload_if_config_changed()225proto = 'http/1.1'226curl = CurlClient(env=env)227url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'228# SSL backend specifics229if env.curl_uses_lib('gnutls'):230pytest.skip('GnuTLS does not support setting ciphers')231elif env.curl_uses_lib('boringssl'):232if ciphers13 is not None:233pytest.skip('BoringSSL does not support setting TLSv1.3 ciphers')234elif env.curl_uses_lib('schannel'): # not in CI, so untested235if ciphers12 is not None:236pytest.skip('Schannel does not support setting TLSv1.2 ciphers by name')237elif env.curl_uses_lib('bearssl'):238if tls_proto == 'TLSv1.3':239pytest.skip('BearSSL does not support TLSv1.3')240tls_proto = 'TLSv1.2'241elif env.curl_uses_lib('mbedtls') and not env.curl_lib_version_at_least('mbedtls', '3.6.0'):242if tls_proto == 'TLSv1.3':243pytest.skip('mbedTLS < 3.6.0 does not support TLSv1.3')244elif env.curl_uses_lib('sectransp'): # not in CI, so untested245if tls_proto == 'TLSv1.3':246pytest.skip('Secure Transport does not support TLSv1.3')247tls_proto = 'TLSv1.2'248# test249extra_args = ['--tls13-ciphers', ':'.join(ciphers13)] if ciphers13 else []250extra_args += ['--ciphers', ':'.join(ciphers12)] if ciphers12 else []251r = curl.http_get(url=url, alpn_proto=proto, extra_args=extra_args)252if tls_proto != 'TLSv1.2' and succeed13:253assert r.exit_code == 0, r.dump_logs()254assert r.json['HTTPS'] == 'on', r.dump_logs()255assert r.json['SSL_PROTOCOL'] == 'TLSv1.3', r.dump_logs()256assert ciphers13 is None or r.json['SSL_CIPHER'] in ciphers13, r.dump_logs()257elif tls_proto == 'TLSv1.2' and succeed12:258assert r.exit_code == 0, r.dump_logs()259assert r.json['HTTPS'] == 'on', r.dump_logs()260assert r.json['SSL_PROTOCOL'] == 'TLSv1.2', r.dump_logs()261assert ciphers12 is None or r.json['SSL_CIPHER'] in ciphers12, r.dump_logs()262else:263assert r.exit_code != 0, r.dump_logs()264265@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])266def test_17_08_cert_status(self, env: Env, proto, httpd, nghttpx):267if proto == 'h3' and not env.have_h3():268pytest.skip("h3 not supported")269if not env.curl_uses_lib('openssl') and \270not env.curl_uses_lib('gnutls') and \271not env.curl_uses_lib('quictls'):272pytest.skip("TLS library does not support --cert-status")273curl = CurlClient(env=env)274domain = 'localhost'275url = f'https://{env.authority_for(domain, proto)}/'276r = curl.http_get(url=url, alpn_proto=proto, extra_args=[277'--cert-status'278])279# CURLE_SSL_INVALIDCERTSTATUS, our certs have no OCSP info280assert r.exit_code == 91, f'{r}'281282@staticmethod283def gen_test_17_09_list():284return [[tls_proto, max_ver, min_ver]285for tls_proto in ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']286for max_ver in range(5)287for min_ver in range(-2, 4)]288289@pytest.mark.parametrize("tls_proto, max_ver, min_ver", gen_test_17_09_list())290def test_17_09_ssl_min_max(self, env: Env, httpd, configures_httpd, tls_proto, max_ver, min_ver):291httpd.set_extra_config('base', [292f'SSLProtocol {tls_proto}',293'SSLCipherSuite ALL:@SECLEVEL=0',294])295httpd.reload_if_config_changed()296proto = 'http/1.1'297run_env = os.environ.copy()298if env.curl_uses_lib('gnutls'):299# we need to override any default system configuration since300# we want to test all protocol versions. Ubuntu (or the GH image)301# disable TSL1.0 and TLS1.1 system wide. We do not want.302our_config = os.path.join(env.gen_dir, 'gnutls_config')303if not os.path.exists(our_config):304with open(our_config, 'w') as fd:305fd.write('# empty\n')306run_env['GNUTLS_SYSTEM_PRIORITY_FILE'] = our_config307curl = CurlClient(env=env, run_env=run_env)308url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'309# SSL backend specifics310if env.curl_uses_lib('bearssl'):311supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', None]312elif env.curl_uses_lib('sectransp'): # not in CI, so untested313supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', None]314elif env.curl_uses_lib('gnutls'):315supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']316elif env.curl_uses_lib('quiche'):317supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']318elif env.curl_uses_lib('aws-lc'):319supported = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']320else: # most SSL backends dropped support for TLSv1.0, TLSv1.1321supported = [None, None, 'TLSv1.2', 'TLSv1.3']322# test323extra_args = [[], ['--tlsv1'], ['--tlsv1.0'], ['--tlsv1.1'], ['--tlsv1.2'], ['--tlsv1.3']][min_ver+2] + \324[['--tls-max', '1.0'], ['--tls-max', '1.1'], ['--tls-max', '1.2'], ['--tls-max', '1.3'], []][max_ver]325extra_args.extend(['--trace-config', 'ssl'])326r = curl.http_get(url=url, alpn_proto=proto, extra_args=extra_args)327if max_ver >= min_ver and tls_proto in supported[max(0, min_ver):min(max_ver, 3)+1]:328assert r.exit_code == 0, f'extra_args={extra_args}\n{r.dump_logs()}'329assert r.json['HTTPS'] == 'on', r.dump_logs()330assert r.json['SSL_PROTOCOL'] == tls_proto, r.dump_logs()331else:332assert r.exit_code != 0, f'extra_args={extra_args}\n{r.dump_logs()}'333334def test_17_10_h3_session_reuse(self, env: Env, httpd, nghttpx):335if not env.have_h3():336pytest.skip("h3 not supported")337if not env.curl_uses_lib('quictls') and \338not (env.curl_uses_lib('openssl') and env.curl_uses_lib('ngtcp2')) and \339not env.curl_uses_lib('gnutls') and \340not env.curl_uses_lib('wolfssl'):341pytest.skip("QUIC session reuse not implemented")342count = 2343docname = 'data-10k'344url = f'https://localhost:{env.https_port}/{docname}'345client = LocalClient(name='hx-download', env=env)346if not client.exists():347pytest.skip(f'example client not built: {client.name}')348r = client.run(args=[349'-n', f'{count}',350'-f', # forbid reuse of connections351'-r', f'{env.domain1}:{env.port_for("h3")}:127.0.0.1',352'-V', 'h3', url353])354r.check_exit_code(0)355# check that TLS session was reused as expected356reused_session = False357for line in r.trace_lines:358if re.match(r'.*\[1-1] (\* )?SSL reusing session.*', line):359reused_session = True360assert reused_session, f'{r}\n{r.dump_logs()}'361362# use host name server has no certificate for363@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])364def test_17_11_wrong_host(self, env: Env, proto, httpd, nghttpx):365if proto == 'h3' and not env.have_h3():366pytest.skip("h3 not supported")367curl = CurlClient(env=env)368domain = f'insecure.{env.tld}'369url = f'https://{domain}:{env.port_for(proto)}/curltest/sslinfo'370r = curl.http_get(url=url, alpn_proto=proto)371assert r.exit_code == 60, f'{r}'372373# use host name server has no cert for with --insecure374@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])375def test_17_12_insecure(self, env: Env, proto, httpd, nghttpx):376if proto == 'h3' and not env.have_h3():377pytest.skip("h3 not supported")378curl = CurlClient(env=env)379domain = f'insecure.{env.tld}'380url = f'https://{domain}:{env.port_for(proto)}/curltest/sslinfo'381r = curl.http_get(url=url, alpn_proto=proto, extra_args=[382'--insecure'383])384assert r.exit_code == 0, f'{r}'385assert r.json, f'{r}'386387# connect to an expired certificate388@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])389def test_17_14_expired_cert(self, env: Env, proto, httpd):390if proto == 'h3' and not env.have_h3():391pytest.skip("h3 not supported")392curl = CurlClient(env=env)393url = f'https://{env.expired_domain}:{env.port_for(proto)}/'394r = curl.http_get(url=url, alpn_proto=proto)395assert r.exit_code == 60, f'{r}' # peer failed verification396exp_trace = None397match_trace = None398if env.curl_uses_lib('openssl') or env.curl_uses_lib('quictls'):399exp_trace = r'.*SSL certificate problem: certificate has expired$'400elif env.curl_uses_lib('gnutls'):401exp_trace = r'.*server verification failed: certificate has expired\..*'402elif env.curl_uses_lib('wolfssl'):403exp_trace = r'.*server verification failed: certificate has expired\.$'404if exp_trace is not None:405for line in r.trace_lines:406if re.match(exp_trace, line):407match_trace = line408break409assert match_trace, f'Did not find "{exp_trace}" in trace\n{r.dump_logs()}'410411@pytest.mark.skipif(condition=not Env.curl_has_feature('SSLS-EXPORT'),412reason='curl lacks SSL session export support')413def test_17_15_session_export(self, env: Env, httpd):414proto = 'http/1.1'415if env.curl_uses_lib('libressl'):416pytest.skip('Libressl resumption does not work inTLSv1.3')417if env.curl_uses_lib('rustls-ffi'):418pytest.skip('rustsls does not expose sessions')419if env.curl_uses_lib('bearssl'):420pytest.skip('BearSSL does not support TLSv1.3')421if env.curl_uses_lib('mbedtls') and \422not env.curl_lib_version_at_least('mbedtls', '3.6.0'):423pytest.skip('mbedtls TLSv1.3 session resume not working before 3.6.0')424run_env = os.environ.copy()425run_env['CURL_DEBUG'] = 'ssl,ssls'426# clean session file first, then reuse427session_file = os.path.join(env.gen_dir, 'test_17_15.sessions')428if os.path.exists(session_file):429return os.remove(session_file)430xargs = ['--tls-max', '1.3', '--tlsv1.3', '--ssl-sessions', session_file]431curl = CurlClient(env=env, run_env=run_env)432# tell the server to close the connection after each request433url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'434r = curl.http_get(url=url, alpn_proto=proto, extra_args=xargs)435assert r.exit_code == 0, f'{r}'436assert r.json['HTTPS'] == 'on', f'{r.json}'437assert r.json['SSL_SESSION_RESUMED'] == 'Initial', f'{r.json}\n{r.dump_logs()}'438# ok, run again, sessions should be imported439run_dir2 = os.path.join(env.gen_dir, 'curl2')440curl = CurlClient(env=env, run_env=run_env, run_dir=run_dir2)441r = curl.http_get(url=url, alpn_proto=proto, extra_args=xargs)442assert r.exit_code == 0, f'{r}'443assert r.json['SSL_SESSION_RESUMED'] == 'Resumed', f'{r.json}\n{r.dump_logs()}'444445# verify the ciphers are ignored when talking TLSv1.3 only446# see issue #16232447def test_17_16_h3_ignore_ciphers12(self, env: Env, httpd, nghttpx):448proto = 'h3'449if proto == 'h3' and not env.have_h3():450pytest.skip("h3 not supported")451if env.curl_uses_lib('gnutls'):452pytest.skip("gnutls does not ignore --ciphers on TLSv1.3")453curl = CurlClient(env=env)454url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'455r = curl.http_get(url=url, alpn_proto=proto, extra_args=[456'--ciphers', 'NONSENSE'457])458assert r.exit_code == 0, f'{r}'459460def test_17_17_h1_ignore_ciphers13(self, env: Env, httpd):461proto = 'http/1.1'462curl = CurlClient(env=env)463url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'464r = curl.http_get(url=url, alpn_proto=proto, extra_args=[465'--tls13-ciphers', 'NONSENSE', '--tls-max', '1.2'466])467assert r.exit_code == 0, f'{r}'468469@pytest.mark.parametrize("priority, tls_proto, ciphers, success", [470pytest.param("", "", [], False, id='prio-empty'),471pytest.param("NONSENSE", "", [], False, id='nonsense'),472pytest.param("+NONSENSE", "", [], False, id='+nonsense'),473pytest.param("NORMAL:-VERS-ALL:+VERS-TLS1.2", "TLSv1.2", ['ECDHE-RSA-CHACHA20-POLY1305'], True, id='TLSv1.2-normal-only'),474pytest.param("-VERS-ALL:+VERS-TLS1.2", "TLSv1.2", ['ECDHE-RSA-CHACHA20-POLY1305'], True, id='TLSv1.2-only'),475pytest.param("NORMAL", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-normal'),476pytest.param("NORMAL:-VERS-ALL:+VERS-TLS1.3", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-normal-only'),477pytest.param("-VERS-ALL:+VERS-TLS1.3", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-only'),478pytest.param("!CHACHA20-POLY1305", "TLSv1.3", ['TLS_AES_128_GCM_SHA256'], True, id='TLSv1.3-no-chacha'),479pytest.param("-CIPHER-ALL:+CHACHA20-POLY1305", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-only-chacha'),480pytest.param("-CIPHER-ALL:+AES-256-GCM", "", [], False, id='only-AES256'),481pytest.param("-CIPHER-ALL:+AES-128-GCM", "TLSv1.3", ['TLS_AES_128_GCM_SHA256'], True, id='TLSv1.3-only-AES128'),482pytest.param("SECURE:-CIPHER-ALL:+AES-128-GCM:-VERS-ALL:+VERS-TLS1.2", "TLSv1.2", ['ECDHE-RSA-AES128-GCM-SHA256'], True, id='TLSv1.2-secure'),483pytest.param("-MAC-ALL:+SHA256", "", [], False, id='MAC-only-SHA256'),484pytest.param("-MAC-ALL:+AEAD", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-MAC-only-AEAD'),485pytest.param("-GROUP-ALL:+GROUP-X25519", "TLSv1.3", ['TLS_CHACHA20_POLY1305_SHA256'], True, id='TLSv1.3-group-only-X25519'),486pytest.param("-GROUP-ALL:+GROUP-SECP192R1", "", [], False, id='group-only-SECP192R1'),487])488def test_17_18_gnutls_priority(self, env: Env, httpd, configures_httpd, priority, tls_proto, ciphers, success):489# to test setting cipher suites, the AES 256 ciphers are disabled in the test server490httpd.set_extra_config('base', [491'SSLCipherSuite SSL'492' ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'493':ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305',494'SSLCipherSuite TLSv1.3'495' TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',496])497httpd.reload_if_config_changed()498proto = 'http/1.1'499curl = CurlClient(env=env)500url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'501# SSL backend specifics502if not env.curl_uses_lib('gnutls'):503pytest.skip('curl not build with GnuTLS')504# test505extra_args = ['--ciphers', f'{priority}']506r = curl.http_get(url=url, alpn_proto=proto, extra_args=extra_args)507if success:508assert r.exit_code == 0, r.dump_logs()509assert r.json['HTTPS'] == 'on', r.dump_logs()510if tls_proto:511assert r.json['SSL_PROTOCOL'] == tls_proto, r.dump_logs()512assert r.json['SSL_CIPHER'] in ciphers, r.dump_logs()513else:514assert r.exit_code != 0, r.dump_logs()515516@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])517def test_17_19_wrong_pin(self, env: Env, proto, httpd):518if proto == 'h3' and not env.have_h3():519pytest.skip("h3 not supported")520if env.curl_uses_any_libs(['bearssl', 'rustls-ffi']):521pytest.skip('TLS backend ignores --pinnedpubkey')522curl = CurlClient(env=env)523url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'524r = curl.http_get(url=url, alpn_proto=proto, extra_args=[525'--pinnedpubkey', 'sha256//ffff'526])527# expect NOT_IMPLEMENTED or CURLE_SSL_PINNEDPUBKEYNOTMATCH528assert r.exit_code in [2, 90], f'{r.dump_logs()}'529530@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])531def test_17_20_correct_pin(self, env: Env, proto, httpd):532if proto == 'h3' and not env.have_h3():533pytest.skip("h3 not supported")534curl = CurlClient(env=env)535creds = env.get_credentials(env.domain1)536assert creds537url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'538r = curl.http_get(url=url, alpn_proto=proto, extra_args=[539'--pinnedpubkey', f'sha256//{creds.pub_sha256_b64()}'540])541# expect NOT_IMPLEMENTED or OK542assert r.exit_code in [0, 2], f'{r.dump_logs()}'543544545