]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
tests: Add https-mtls server to force client auth
authorYedaya Katsman <yedaya.ka@gmail.com>
Thu, 3 Apr 2025 18:51:32 +0000 (21:51 +0300)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 7 Apr 2025 06:46:56 +0000 (08:46 +0200)
- 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

tests/FILEFORMAT.md
tests/certs/Makefile.inc
tests/certs/test-client-cert.prm [new file with mode: 0644]
tests/data/Makefile.am
tests/data/test2088 [new file with mode: 0644]
tests/runtests.pl
tests/secureserver.pl
tests/serverhelp.pm
tests/servers.pm

index 88db1440a58433276e9598ba2f0020bf5820a52c..9932aa02f700d103dc5a10fa32621cf72e0e938d 100644 (file)
@@ -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`
index 02e5e99ff79684c87001fea912663ff904ee1d76..e2ecb1924b4a6bae520a43d2eddfeb25005879d9 100644 (file)
@@ -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 (file)
index 0000000..1b3bc43
--- /dev/null
@@ -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
index 00f5b659c7e50b808445bb44630f6f3f17400958..bb316e38d49255279b128fc65ecb46ac4b3aec1c 100644 (file)
@@ -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 (file)
index 0000000..9a584fd
--- /dev/null
@@ -0,0 +1,55 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+HTTP GET
+Client Auth
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 7
+
+MooMoo
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+SSL
+!Schannel
+!sectransp
+!bearssl
+local-http
+</features>
+<server>
+https-mtls
+</server>
+<name>
+HTTPS GET with client authentication (mtls)
+</name>
+<command>
+--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
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /%TESTNUMBER HTTP/1.1\r
+Host: localhost:%HTTPS-MTLSPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+</verify>
+</testcase>
index 947e5b4790fb671a153dfc4595ff171425c4eb99..7d9d6440e78e595179bc49c545ba385ba8d79a21 100755 (executable)
@@ -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';
 }
index e672c6c03cc3150a14313f4f0b3c95e86d745748..930845e971d77b322229daa6bc2f11ec1e3bbe3f 100755 (executable)
@@ -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";
index 30abd13b6881c1a6ec035d32b53657fbb2af45a4..9df95c53e6010191509952208dbccd92a35ec63f 100644 (file)
@@ -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 &&
index a3e5edb0de0fdd54e293d87d1a6e15908f79dadc..c83460fa1abefa1c35a36b4a190b4208fab152fc 100644 (file)
@@ -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',