tests: add SNI and peer name checks

- connect to DNS names with trailing dot
- connect to DNS names with double trailing dot
- rustls, always give `peer->hostname` and let it
  figure out SNI itself
- add SNI tests for ip address and localhost
- document in code and TODO that QUIC with ngtcp2+wolfssl
  does not do proper peer verification of the certificate
- mbedtls, skip tests with ip address verification as not
  supported by the library

Closes #13486
This commit is contained in:
Stefan Eissing 2024-04-26 14:13:23 +02:00 committed by Daniel Stenberg
parent c04664ad35
commit b06619d0a3
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
6 changed files with 88 additions and 9 deletions

View File

@ -126,6 +126,7 @@
13.13 Make sure we forbid TLS 1.3 post-handshake authentication
13.14 Support the clienthello extension
13.15 Select signature algorithms
13.16 QUIC peer verification with wolfSSL
14. GnuTLS
14.2 check connection
@ -921,6 +922,11 @@
https://github.com/curl/curl/issues/12982
13.16 QUIC peer verification with wolfSSL
Peer certificate verification is missing in the QUIC (ngtcp2) implementation
using wolfSSL.
14. GnuTLS
14.2 check connection

View File

@ -324,7 +324,11 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx,
#elif defined(USE_WOLFSSL)
(void)data;
if(conn_config->verifyhost) {
if(!peer->sni ||
/* TODO: this does not really verify the peer certificate.
* On TCP connection this works as it is wired into the wolfSSL
* connect() implementation and gives a special return code on
* such a fail. */
if(peer->sni &&
wolfSSL_check_domain_name(ctx->ssl, peer->sni) == SSL_FAILURE)
return CURLE_PEER_FAILED_VERIFICATION;
}

View File

@ -479,13 +479,8 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
backend->config = rustls_client_config_builder_build(config_builder);
DEBUGASSERT(rconn == NULL);
{
/* rustls claims to manage ip address hostnames as well here. So,
* if we have an SNI, we use it, otherwise we pass the hostname */
char *server = connssl->peer.sni?
connssl->peer.sni : connssl->peer.hostname;
result = rustls_client_connection_new(backend->config, server, &rconn);
}
result = rustls_client_connection_new(backend->config,
connssl->peer.hostname, &rconn);
if(result != RUSTLS_RESULT_OK) {
rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen);
failf(data, "rustls_client_connection_new: %.*s", (int)errorlen, errorbuf);

View File

@ -101,4 +101,77 @@ class TestSSLUse:
else:
assert djson['SSL_SESSION_RESUMED'] == exp_resumed, f'{i}: {djson}'
# use host name with trailing dot, verify handshake
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_17_03_trailing_dot(self, env: Env, httpd, nghttpx, repeat, proto):
if env.curl_uses_lib('gnutls'):
pytest.skip("gnutls does not match hostnames with trailing dot")
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
curl = CurlClient(env=env)
domain = f'{env.domain1}.'
url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
r = curl.http_get(url=url, alpn_proto=proto)
assert r.exit_code == 0, f'{r}'
assert r.json, f'{r}'
if proto != 'h3': # we proxy h3
# the SNI the server received is without trailing dot
assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}'
# use host name with double trailing dot, verify handshake
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_17_04_double_dot(self, env: Env, httpd, nghttpx, repeat, proto):
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
if proto == 'h3' and env.curl_uses_lib('wolfssl'):
pytest.skip("wolfSSL HTTP/3 peer verification does not properly check")
curl = CurlClient(env=env)
domain = f'{env.domain1}..'
url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
'-H', f'Host: {env.domain1}',
])
if r.exit_code == 0:
assert r.json, f'{r.stdout}'
# the SNI the server received is without trailing dot
if proto != 'h3': # we proxy h3
assert r.json['SSL_TLS_SNI'] == env.domain1, f'{r.json}'
assert False, f'should not have succeeded: {r.json}'
# 7 - rustls rejects a servername with .. during setup
# 35 - libressl rejects setting an SNI name with trailing dot
# 60 - peer name matching failed against certificate
assert r.exit_code in [7, 35, 60], f'{r}'
# use ip address for connect
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_17_05_ip_addr(self, env: Env, httpd, nghttpx, repeat, proto):
if env.curl_uses_lib('bearssl'):
pytest.skip("bearssl does not support cert verification with IP addresses")
if env.curl_uses_lib('mbedtls'):
pytest.skip("mbedtls does not support cert verification with IP addresses")
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
curl = CurlClient(env=env)
domain = f'127.0.0.1'
url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
r = curl.http_get(url=url, alpn_proto=proto)
assert r.exit_code == 0, f'{r}'
assert r.json, f'{r}'
if proto != 'h3': # we proxy h3
# the SNI should not have been used
assert 'SSL_TLS_SNI' not in r.json, f'{r.json}'
# use localhost for connect
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_17_06_localhost(self, env: Env, httpd, nghttpx, repeat, proto):
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
curl = CurlClient(env=env)
domain = f'localhost'
url = f'https://{env.authority_for(domain, proto)}/curltest/sslinfo'
r = curl.http_get(url=url, alpn_proto=proto)
assert r.exit_code == 0, f'{r}'
assert r.json, f'{r}'
if proto != 'h3': # we proxy h3
assert r.json['SSL_TLS_SNI'] == domain, f'{r.json}'

View File

@ -133,7 +133,7 @@ class EnvConfig:
self.domain2 = f"two.{self.tld}"
self.proxy_domain = f"proxy.{self.tld}"
self.cert_specs = [
CertificateSpec(domains=[self.domain1, self.domain1brotli, 'localhost'], key_type='rsa2048'),
CertificateSpec(domains=[self.domain1, self.domain1brotli, 'localhost', '127.0.0.1'], key_type='rsa2048'),
CertificateSpec(domains=[self.domain2], key_type='rsa2048'),
CertificateSpec(domains=[self.proxy_domain, '127.0.0.1'], key_type='rsa2048'),
CertificateSpec(name="clientsX", sub_specs=[

View File

@ -709,6 +709,7 @@ static int curltest_sslinfo_handler(request_rec *r)
brigade_env_var(r, bb, "SSL_SESSION_RESUMED");
brigade_env_var(r, bb, "SSL_SRP_USER");
brigade_env_var(r, bb, "SSL_SRP_USERINFO");
brigade_env_var(r, bb, "SSL_TLS_SNI");
apr_brigade_puts(bb, NULL, NULL, "}\n");
/* flush response */