From: Yedaya Katsman Date: Thu, 3 Apr 2025 18:51:32 +0000 (+0300) Subject: tests: Add https-mtls server to force client auth X-Git-Tag: curl-8_14_0~343 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8988f33f622dd70870b525cab60279eee81ac960;p=thirdparty%2Fcurl.git tests: Add https-mtls server to force client auth - test2088 verifies that mutual tls works This adds a new certificate to generate which has the clientAuth key usage enabled, and uses it to connect to a https-mtls server. Closes #16923 --- diff --git a/tests/FILEFORMAT.md b/tests/FILEFORMAT.md index 88db1440a5..9932aa02f7 100644 --- a/tests/FILEFORMAT.md +++ b/tests/FILEFORMAT.md @@ -397,6 +397,7 @@ What server(s) this test case requires/uses. Available servers: - `http-proxy` - `https` - `https-proxy` +- `https-mtls` - `httptls+srp` - `httptls+srp-ipv6` - `http-unix` diff --git a/tests/certs/Makefile.inc b/tests/certs/Makefile.inc index 02e5e99ff7..e2ecb1924b 100644 --- a/tests/certs/Makefile.inc +++ b/tests/certs/Makefile.inc @@ -30,7 +30,8 @@ CERTCONFIGS = \ test-localhost.nn.prm \ test-localhost0h.prm \ test-localhost-san-first.prm \ - test-localhost-san-last.prm + test-localhost-san-last.prm \ + test-client-cert.prm GENERATEDCERTS = \ test-ca.cacert \ @@ -65,7 +66,13 @@ GENERATEDCERTS = \ test-localhost-san-last.key \ test-localhost-san-last.pem \ test-localhost-san-last.pub.der \ - test-localhost-san-last.pub.pem + test-localhost-san-last.pub.pem \ + test-client-cert.crl \ + test-client-cert.crt \ + test-client-cert.key \ + test-client-cert.pem \ + test-client-cert.pub.der \ + test-client-cert.pub.pem SRPFILES = \ srp-verifier-conf \ diff --git a/tests/certs/test-client-cert.prm b/tests/certs/test-client-cert.prm new file mode 100644 index 0000000000..1b3bc43c35 --- /dev/null +++ b/tests/certs/test-client-cert.prm @@ -0,0 +1,35 @@ +extensions = x509v3 + +[ x509v3 ] +subjectAltName = DNS:localhost +keyUsage = keyEncipherment,digitalSignature,keyAgreement +extendedKeyUsage = serverAuth,clientAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid +basicConstraints = CA:false +authorityInfoAccess = @issuer_info +crlDistributionPoints = @crl_info + +[ crl_ext ] +authorityKeyIdentifier = keyid:always +authorityInfoAccess = @issuer_info + +[ issuer_info ] +caIssuers;URI.0 = http://test.curl.se/ca/EdelCurlRoot.cer + +[ crl_info ] +URI.0 = http://test.curl.se/ca/EdelCurlRoot.crl + +[ req ] +default_bits = 2048 +distinguished_name = req_DN +default_md = sha256 +string_mask = utf8only + +[ req_DN ] +countryName = "Country Name is Northern Nowhere" +countryName_value = NN +organizationName = "Organization Name" +organizationName_value = Edel Curl Arctic Illudium Research Cloud +commonName = "Common Name" +commonName_value = localhost diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 00f5b659c7..bb316e38d4 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -253,7 +253,7 @@ test2056 test2057 test2058 test2059 test2060 test2061 test2062 test2063 \ test2064 test2065 test2066 test2067 test2068 test2069 test2070 test2071 \ test2072 test2073 test2074 test2075 test2076 test2077 test2078 test2079 \ test2080 test2081 test2082 test2083 test2084 test2085 test2086 test2087 \ -\ +test2088 \ test2100 test2101 \ \ test2200 test2201 test2202 test2203 test2204 test2205 \ diff --git a/tests/data/test2088 b/tests/data/test2088 new file mode 100644 index 0000000000..9a584fd63f --- /dev/null +++ b/tests/data/test2088 @@ -0,0 +1,55 @@ + + + +HTTPS +HTTP GET +Client Auth + + + +# +# Server-side + + +HTTP/1.1 200 OK +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 7 + +MooMoo + + + +# +# Client-side + + +SSL +!Schannel +!sectransp +!bearssl +local-http + + +https-mtls + + +HTTPS GET with client authentication (mtls) + + +--cacert %CERTDIR/certs/test-ca.crt --cert %CERTDIR/certs/test-client-cert.crt --key %CERTDIR/certs/test-client-cert.key https://localhost:%HTTPS-MTLSPORT/%TESTNUMBER + + + +# +# Verify data after the test has been "shot" + + +GET /%TESTNUMBER HTTP/1.1 +Host: localhost:%HTTPS-MTLSPORT +User-Agent: curl/%VERSION +Accept: */* + + + + diff --git a/tests/runtests.pl b/tests/runtests.pl index 947e5b4790..7d9d6440e7 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -476,6 +476,9 @@ sub parseprotocols { # 'http-proxy' is used in test cases to do CONNECT through push @protocols, 'http-proxy'; + # 'https-mtls' is used for client certificate auth testing + push @protocols, 'https-mtls'; + # 'none' is used in test cases to mean no server push @protocols, 'none'; } diff --git a/tests/secureserver.pl b/tests/secureserver.pl index e672c6c03c..930845e971 100755 --- a/tests/secureserver.pl +++ b/tests/secureserver.pl @@ -70,7 +70,9 @@ my $ipvnum = 4; # default IP version of stunneled server my $idnum = 1; # default stunneled server instance number my $proto = 'https'; # default secure server protocol my $conffile; # stunnel configuration file +my $cafile; # certificate CA PEM file my $certfile; # certificate chain PEM file +my $mtls = 0; # Whether to verify client certificates #*************************************************************************** # stunnel requires full path specification for several files. @@ -170,6 +172,9 @@ while(@ARGV) { shift @ARGV; } } + elsif($ARGV[0] eq '--mtls') { + $mtls = 1; + } else { print STDERR "\nWarning: secureserver.pl unknown parameter: $ARGV[0]\n"; } @@ -194,6 +199,7 @@ if(!$logfile) { $conffile = "$piddir/${proto}_stunnel.conf"; +$cafile = abs_path("$path/certs/test-ca.cacert"); $certfile = $stuncert ? "certs/$stuncert" : "certs/test-localhost.pem"; $certfile = abs_path($certfile); @@ -244,6 +250,7 @@ if($stunnel =~ /tstunnel(\.exe)?$/) { $tstunnel_windows = 1; # convert Cygwin/MinGW paths to Windows format + $cafile = pathhelp::sys_native_abs_path($cafile); $certfile = pathhelp::sys_native_abs_path($certfile); } @@ -292,6 +299,10 @@ if($stunnel_version >= 400) { print $stunconf "cert = $certfile\n"; print $stunconf "debug = $loglevel\n"; print $stunconf "socket = $socketopt\n"; + if($mtls) { + print $stunconf "CAfile = $cafile\n"; + print $stunconf "verifyChain = yes\n"; + } if($fips_support) { # disable fips in case OpenSSL doesn't support it print $stunconf "fips = no\n"; diff --git a/tests/serverhelp.pm b/tests/serverhelp.pm index 30abd13b68..9df95c53e6 100644 --- a/tests/serverhelp.pm +++ b/tests/serverhelp.pm @@ -142,7 +142,7 @@ sub servername_str { $proto = uc($proto) if($proto); die "unsupported protocol: '$proto'" unless($proto && - ($proto =~ /^(((FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT))$/)); + ($proto =~ /^(((FTP|HTTP|HTTP\/2|HTTP\/3|IMAP|POP3|GOPHER|SMTP|HTTPS-MTLS)S?)|(TFTP|SFTP|SOCKS|SSH|RTSP|HTTPTLS|DICT|SMB|SMBS|TELNET|MQTT))$/)); $ipver = (not $ipver) ? 'ipv4' : lc($ipver); die "unsupported IP version: '$ipver'" unless($ipver && diff --git a/tests/servers.pm b/tests/servers.pm index a3e5edb0de..c83460fa1a 100644 --- a/tests/servers.pm +++ b/tests/servers.pm @@ -235,7 +235,7 @@ sub init_serverpidfile_hash { } } for my $proto (('tftp', 'sftp', 'socks', 'ssh', 'rtsp', 'httptls', - 'dict', 'smb', 'smbs', 'telnet', 'mqtt')) { + 'dict', 'smb', 'smbs', 'telnet', 'mqtt', 'https-mtls')) { for my $ipvnum ((4, 6)) { for my $idnum ((1, 2)) { my $serv = servername_id($proto, $ipvnum, $idnum); @@ -1362,6 +1362,9 @@ sub runhttpsserver { $flags .= "--ipv$ipvnum --proto $proto "; $flags .= "--certfile \"$certfile\" " if($certfile ne 'certs/test-localhost.pem'); $flags .= "--stunnel \"$stunnel\" --srcdir \"$srcdir\" "; + if($proto eq "https-mtls") { + $flags .= "--mtls "; + } if($proto eq "gophers") { $flags .= "--connect " . protoport("gopher"); } @@ -2545,14 +2548,14 @@ sub startservers { elsif($what eq "file") { # we support it but have no server! } - elsif($what eq "https") { + elsif($what eq "https" || $what eq "https-mtls") { if(!$stunnel) { # we can't run https tests without stunnel return ("no stunnel", 4); } - if($runcert{'https'} && ($runcert{'https'} ne $certfile)) { + if($runcert{$what} && ($runcert{$what} ne $certfile)) { # stop server when running and using a different cert - if(stopserver('https')) { + if(stopserver($what)) { return ("failed stopping HTTPS server with different cert", 3); } # also stop http server, we do not know which state it is in @@ -2560,10 +2563,10 @@ sub startservers { return ("failed stopping HTTP server", 3); } } - if($run{'https'} && - !responsive_http_server("https", $verbose, 0, - protoport('https'))) { - if(stopserver('https')) { + if($run{$what} && + !responsive_http_server($what, $verbose, 0, + protoport($what))) { + if(stopserver($what)) { return ("failed stopping unresponsive HTTPS server", 3); } # also stop http server, we do not know which state it is in @@ -2572,7 +2575,7 @@ sub startservers { } } # check a running http server if we not already checked https - if($run{'http'} && !$run{'https'} && + if($run{'http'} && !$run{$what} && !responsive_http_server("http", $verbose, 0, protoport('http'))) { if(stopserver('http')) { @@ -2588,15 +2591,15 @@ sub startservers { logmsg sprintf("* pid http => %d %d\n", $pid, $pid2) if($verbose); $run{'http'}="$pid $pid2"; } - if(!$run{'https'}) { - ($serr, $pid, $pid2, $PORT{'https'}) = - runhttpsserver($verbose, "https", "", $certfile); + if(!$run{$what}) { + ($serr, $pid, $pid2, $PORT{$what}) = + runhttpsserver($verbose, $what, "", $certfile); if($pid <= 0) { return ("failed starting HTTPS server (stunnel)", $serr); } - logmsg sprintf("* pid https => %d %d\n", $pid, $pid2) + logmsg sprintf("* pid $what => %d %d\n", $pid, $pid2) if($verbose); - $run{'https'}="$pid $pid2"; + $run{$what}="$pid $pid2"; } } elsif($what eq "http/2") { @@ -3017,7 +3020,7 @@ sub subvariables { foreach my $proto ('DICT', 'FTP', 'FTP6', 'FTPS', 'GOPHER', 'GOPHER6', 'GOPHERS', - 'HTTP', 'HTTP6', 'HTTPS', + 'HTTP', 'HTTP6', 'HTTPS', 'HTTPS-MTLS', 'HTTPSPROXY', 'HTTPTLS', 'HTTPTLS6', 'HTTP2', 'HTTP2TLS', 'HTTP3',