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:
parent
c04664ad35
commit
b06619d0a3
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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}'
|
||||
|
||||
|
||||
@ -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=[
|
||||
|
||||
@ -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 */
|
||||
|
||||
Loading…
Reference in New Issue
Block a user