TLS: IP address verification, extend test
No binary (build failed)
—
(log yok)
| @@ -178,12 +178,17 @@ CURLcode Curl_vquic_tls_verify_peer(struct curl_tls_ctx *ctx, | |
| #elif defined(USE_WOLFSSL) | |
| (void)data; | |
| if(conn_config->verifyhost) { | |
| - | char *snihost = peer->sni ? peer->sni : peer->hostname; |
| WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(ctx->wssl.ssl); | |
| - | if(wolfSSL_X509_check_host(cert, snihost, strlen(snihost), 0, NULL) |
| - | == WOLFSSL_FAILURE) { |
| + | if(!cert) |
| + | result = CURLE_OUT_OF_MEMORY; |
| + | else if(peer->sni && |
| + | (wolfSSL_X509_check_host(cert, peer->sni, strlen(peer->sni), 0, NULL) |
| + | == WOLFSSL_FAILURE)) |
| + | result = CURLE_PEER_FAILED_VERIFICATION; |
| + | else if(!peer->sni && |
| + | (wolfSSL_X509_check_ip_asc(cert, peer->hostname, 0) |
| + | == WOLFSSL_FAILURE)) |
| result = CURLE_PEER_FAILED_VERIFICATION; | |
| - | } |
| wolfSSL_X509_free(cert); | |
| } | |
| if(!result) | |
| @@ -1491,11 +1491,10 @@ wssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data) | |
| } | |
| #endif | |
| - | /* Enable RFC2818 checks */ |
| - | if(conn_config->verifyhost) { |
| - | char *snihost = connssl->peer.sni ? |
| - | connssl->peer.sni : connssl->peer.hostname; |
| - | if(wolfSSL_check_domain_name(wssl->ssl, snihost) != |
| + | /* Enable RFC2818 checks on domain names. This cannot check |
| + | * IP addresses which we need to do extra after the handshake. */ |
| + | if(conn_config->verifyhost && connssl->peer.sni) { |
| + | if(wolfSSL_check_domain_name(wssl->ssl, connssl->peer.sni) != |
| WOLFSSL_SUCCESS) { | |
| return CURLE_SSL_CONNECT_ERROR; | |
| } | |
| @@ -1719,6 +1718,25 @@ static CURLcode wssl_handshake(struct Curl_cfilter *cf, | |
| detail = wolfSSL_get_error(wssl->ssl, ret); | |
| CURL_TRC_CF(data, cf, "wolfSSL_connect() -> %d, detail=%d", ret, detail); | |
| + | /* On a successful handshake with an IP address, do an extra check |
| + | * on the peer certificate */ |
| + | if(ret == WOLFSSL_SUCCESS && |
| + | conn_config->verifyhost && |
| + | !connssl->peer.sni) { |
| + | /* we have an IP address as host name. */ |
| + | WOLFSSL_X509* cert = wolfSSL_get_peer_certificate(wssl->ssl); |
| + | if(!cert) { |
| + | failf(data, "unable to get peer certificate"); |
| + | return CURLE_PEER_FAILED_VERIFICATION; |
| + | } |
| + | ret = wolfSSL_X509_check_ip_asc(cert, connssl->peer.hostname, 0); |
| + | CURL_TRC_CF(data, cf, "check peer certificate for IP match on %s -> %d", |
| + | connssl->peer.hostname, ret); |
| + | if(ret != WOLFSSL_SUCCESS) |
| + | detail = DOMAIN_NAME_MISMATCH; |
| + | wolfSSL_X509_free(cert); |
| + | } |
| + | |
| if(ret == WOLFSSL_SUCCESS) { | |
| return CURLE_OK; | |
| } | |
| @@ -610,6 +610,8 @@ class TestDownload: | |
| pytest.skip("h3 not supported") | |
| if proto != 'h3' and sys.platform.startswith('darwin') and env.ci_run: | |
| pytest.skip('failing on macOS CI runners') | |
| + | if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('wolfssl'): |
| + | pytest.skip('h3 wolfssl early data failing on macOS') |
| if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('gnutls'): | |
| pytest.skip('h3 gnutls early data failing on macOS') | |
| count = 2 | |
| @@ -712,6 +712,8 @@ class TestUpload: | |
| pytest.skip("h3 not supported") | |
| if proto != 'h3' and sys.platform.startswith('darwin') and env.ci_run: | |
| pytest.skip('failing on macOS CI runners') | |
| + | if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('wolfssl'): |
| + | pytest.skip('h3 wolfssl early data failing on macOS') |
| if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('gnutls'): | |
| pytest.skip('h3 gnutls early data failing on macOS') | |
| count = 2 | |
| @@ -186,6 +186,27 @@ class TestSSLUse: | |
| r = curl.http_get(url=url, alpn_proto=proto) | |
| assert r.exit_code == 60, f'{r}' | |
| + | # use IP address that is in cert as DNS name (not really legal) |
| + | @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) |
| + | def test_17_05_very_bad_ip_addr(self, env: Env, proto, |
| + | httpd, configures_httpd, |
| + | nghttpx, configures_nghttpx): |
| + | if proto == 'h3' and not env.have_h3(): |
| + | pytest.skip("h3 not supported") |
| + | if env.curl_uses_lib('mbedtls'): |
| + | pytest.skip("mbedtls falsely verifies a DNS: altname as IP address") |
| + | if env.curl_uses_lib('wolfssl'): |
| + | pytest.skip("wolfSSL falsely verifies a DNS: altname as IP address") |
| + | httpd.set_domain1_cred_name('domain1-very-bad') |
| + | httpd.reload_if_config_changed() |
| + | if proto == 'h3': |
| + | nghttpx.set_cred_name('domain1-very-bad') |
| + | nghttpx.reload_if_config_changed() |
| + | curl = CurlClient(env=env) |
| + | url = f'https://127.0.0.1:{env.port_for(proto)}/curltest/sslinfo' |
| + | r = curl.http_get(url=url, alpn_proto=proto) |
| + | assert r.exit_code == 60, f'{r}' |
| + | |
| # use localhost for connect | |
| @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) | |
| def test_17_06_localhost(self, env: Env, proto, httpd, nghttpx): | |
| @@ -459,11 +459,15 @@ class TestCA: | |
| def _add_leaf_usages(csr: Any, domains: List[str], issuer: Credentials) -> Any: | |
| names = [] | |
| for name in domains: | |
| - | try: |
| - | names.append(x509.IPAddress(ipaddress.ip_address(name))) |
| - | # TODO: specify specific exceptions here |
| - | except: # noqa: E722 |
| - | names.append(x509.DNSName(name)) |
| + | m = re.match(r'dns:(.+)', name) |
| + | if m: |
| + | names.append(x509.DNSName(m.group(1))) |
| + | else: |
| + | try: |
| + | names.append(x509.IPAddress(ipaddress.ip_address(name))) |
| + | # TODO: specify specific exceptions here |
| + | except: # noqa: E722 |
| + | names.append(x509.DNSName(name)) |
| return csr.add_extension( | |
| x509.BasicConstraints(ca=False, path_length=None), | |
| @@ -188,6 +188,7 @@ class EnvConfig: | |
| self.cert_specs = [ | |
| CertificateSpec(domains=[self.domain1, self.domain1brotli, 'localhost', '127.0.0.1'], key_type='rsa2048'), | |
| CertificateSpec(name='domain1-no-ip', domains=[self.domain1, self.domain1brotli], key_type='rsa2048'), | |
| + | CertificateSpec(name='domain1-very-bad', domains=[self.domain1, 'dns:127.0.0.1'], key_type='rsa2048'), |
| CertificateSpec(domains=[self.domain2], key_type='rsa2048'), | |
| CertificateSpec(domains=[self.ftp_domain], key_type='rsa2048'), | |
| CertificateSpec(domains=[self.proxy_domain, '127.0.0.1'], key_type='rsa2048'), | |