]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
writeout: add %{certs} and %{num_certs}
authorDaniel Stenberg <daniel@haxx.se>
Tue, 27 Dec 2022 11:00:12 +0000 (12:00 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 27 Dec 2022 21:41:17 +0000 (22:41 +0100)
Let users get the server certificate chain using the command line

Closes #10019

docs/cmdline-opts/write-out.d
src/tool_operate.c
src/tool_operate.h
src/tool_writeout.c
src/tool_writeout.h
tests/data/test970
tests/data/test972

index 8ca1746d08266d9acfd45f4fbaa4b4eaf07329c1..309c78d32f98b258a8ca719adfc4280a4e769c39 100644 (file)
@@ -37,6 +37,10 @@ occurrences of % must be doubled when using this option.
 The variables available are:
 .RS
 .TP 15
+.B certs
+Output the certificate chain with details. Supported only by the OpenSSL,
+GnuTLS, Schannel, NSS, GSKit and Secure Transport backends (Added in 7.88.0)
+.TP
 .B content_type
 The Content-Type of the requested document, if there was any.
 .TP
@@ -89,6 +93,11 @@ The local port number of the most recently done connection. (Added in 7.29.0)
 .B method
 The http method used in the most recent HTTP request. (Added in 7.72.0)
 .TP
+.B num_certs
+Number of server certificates received in the TLS handshake. Supported only by
+the OpenSSL, GnuTLS, Schannel, NSS, GSKit and Secure Transport backends (Added
+in 7.88.0)
+.TP
 .B num_connects
 Number of new connects made in the recent transfer. (Added in 7.12.3)
 .TP
index 2b0cc083c0cc772f2b620b05b7b69845d5993783..6d0ba68cd92f4415cae6020dff5f9af5ea229208 100644 (file)
@@ -1553,6 +1553,9 @@ static CURLcode single_transfer(struct GlobalConfig *global,
         if(config->ssl_ec_curves)
           my_setopt_str(curl, CURLOPT_SSL_EC_CURVES, config->ssl_ec_curves);
 
+        if(config->writeout)
+          my_setopt_str(curl, CURLOPT_CERTINFO, 1L);
+
         if(feature_ssl) {
           /* Check if config->cert is a PKCS#11 URI and set the
            * config->cert_type if necessary */
index 5c08b99aec46dc6c7469b4a0bd276059e75a4fb9..ce3304cb127dad4617e05b596ce13b1a1d3f6ef6 100644 (file)
@@ -33,6 +33,7 @@ struct per_transfer {
   struct per_transfer *next;
   struct per_transfer *prev;
   struct OperationConfig *config; /* for this transfer */
+  struct curl_certinfo *certinfo;
   CURL *curl;
   long retry_numretries;
   long retry_sleep_default;
index 2789ee20bf44a451386b03dc3a26d454d01499cb..e99f1fc136898238b487b176bef6e58456350e3c 100644 (file)
@@ -28,6 +28,7 @@
 #include "tool_cfgable.h"
 #include "tool_writeout.h"
 #include "tool_writeout_json.h"
+#include "dynbuf.h"
 
 #include "memdebug.h" /* keep this as LAST include */
 
@@ -72,6 +73,7 @@ static const struct httpmap http_version[] = {
    Variable names should be in alphabetical order.
    */
 static const struct writeoutvar variables[] = {
+  {"certs", VAR_CERT, CURLINFO_NONE, writeString},
   {"content_type", VAR_CONTENT_TYPE, CURLINFO_CONTENT_TYPE, writeString},
   {"errormsg", VAR_ERRORMSG, CURLINFO_NONE, writeString},
   {"exitcode", VAR_EXITCODE, CURLINFO_NONE, writeLong},
@@ -85,6 +87,7 @@ static const struct writeoutvar variables[] = {
   {"local_ip", VAR_LOCAL_IP, CURLINFO_LOCAL_IP, writeString},
   {"local_port", VAR_LOCAL_PORT, CURLINFO_LOCAL_PORT, writeLong},
   {"method", VAR_EFFECTIVE_METHOD, CURLINFO_EFFECTIVE_METHOD, writeString},
+  {"num_certs", VAR_NUM_CERTS, CURLINFO_NONE, writeLong},
   {"num_connects", VAR_NUM_CONNECTS, CURLINFO_NUM_CONNECTS, writeLong},
   {"num_headers", VAR_NUM_HEADERS, CURLINFO_NONE, writeLong},
   {"num_redirects", VAR_REDIRECT_COUNT, CURLINFO_REDIRECT_COUNT, writeLong},
@@ -168,6 +171,8 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar,
 {
   bool valid = false;
   const char *strinfo = NULL;
+  struct dynbuf buf;
+  curlx_dyn_init(&buf, 256*1024);
 
   DEBUGASSERT(wovar->writefunc == writeString);
 
@@ -193,6 +198,51 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar,
   }
   else {
     switch(wovar->id) {
+    case VAR_CERT:
+      if(per->certinfo) {
+        int i;
+        bool error = FALSE;
+        for(i = 0; (i < per->certinfo->num_of_certs) && !error; i++) {
+          struct curl_slist *slist;
+
+          for(slist = per->certinfo->certinfo[i]; slist; slist = slist->next) {
+            size_t len;
+            if(curl_strnequal(slist->data, "cert:", 5)) {
+              if(curlx_dyn_add(&buf, &slist->data[5])) {
+                error = TRUE;
+                break;
+              }
+            }
+            else {
+              if(curlx_dyn_add(&buf, slist->data)) {
+                error = TRUE;
+                break;
+              }
+            }
+            len = curlx_dyn_len(&buf);
+            if(len) {
+              char *ptr = curlx_dyn_ptr(&buf);
+              if(ptr[len -1] != '\n') {
+                /* add a newline to make things look better */
+                if(curlx_dyn_addn(&buf, "\n", 1)) {
+                  error = TRUE;
+                  break;
+                }
+              }
+            }
+          }
+        }
+        if(!error) {
+          strinfo = curlx_dyn_ptr(&buf);
+          if(!strinfo)
+            /* maybe not a TLS protocol */
+            strinfo = "";
+          valid = true;
+        }
+      }
+      else
+        strinfo = ""; /* no cert info */
+      break;
     case VAR_ERRORMSG:
       if(per_result) {
         strinfo = (per->errorbuffer && per->errorbuffer[0]) ?
@@ -232,6 +282,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar,
       fprintf(stream, "\"%s\":null", wovar->name);
   }
 
+  curlx_dyn_free(&buf);
   return 1; /* return 1 if anything was written */
 }
 
@@ -250,6 +301,10 @@ static int writeLong(FILE *stream, const struct writeoutvar *wovar,
   }
   else {
     switch(wovar->id) {
+    case VAR_NUM_CERTS:
+      longinfo = per->certinfo ? per->certinfo->num_of_certs : 0;
+      valid = true;
+      break;
     case VAR_NUM_HEADERS:
       longinfo = per->num_headers;
       valid = true;
@@ -327,6 +382,11 @@ void ourWriteOut(const char *writeinfo, struct per_transfer *per,
   FILE *stream = stdout;
   const char *ptr = writeinfo;
   bool done = FALSE;
+  struct curl_certinfo *certinfo;
+  CURLcode res = curl_easy_getinfo(per->curl, CURLINFO_CERTINFO, &certinfo);
+
+  if(!res && certinfo)
+    per->certinfo = certinfo;
 
   while(ptr && *ptr && !done) {
     if('%' == *ptr && ptr[1]) {
index c7cdb9771c7bb0fa4fbfab7bc18c3a4e97d04115..1d6572020ac9261533e8782af9cec5a337030646 100644 (file)
@@ -29,6 +29,7 @@
 typedef enum {
   VAR_NONE,       /* must be the first */
   VAR_APPCONNECT_TIME,
+  VAR_CERT,
   VAR_CONNECT_TIME,
   VAR_CONTENT_TYPE,
   VAR_EFFECTIVE_FILENAME,
@@ -47,6 +48,7 @@ typedef enum {
   VAR_LOCAL_IP,
   VAR_LOCAL_PORT,
   VAR_NAMELOOKUP_TIME,
+  VAR_NUM_CERTS,
   VAR_NUM_CONNECTS,
   VAR_NUM_HEADERS,
   VAR_ONERROR,
index ee0a3e9aea0a64573e71fc3fb07517918d53c426..37945269a19cd0021e5e0298d8bdf34d9bd93f4b 100644 (file)
@@ -59,7 +59,7 @@ Accept: */*
 \r
 </protocol>
 <stdout nonewline="yes">
-{"content_type":"text/html","errormsg":null,"exitcode":0,"filename_effective":"log/out%TESTNUMBER","ftp_entry_path":null,"http_code":200,"http_connect":0,"http_version":"1.1","local_ip":"127.0.0.1","local_port":13,"method":"GET","num_connects":1,"num_headers":9,"num_redirects":0,"proxy_ssl_verify_result":0,"redirect_url":null,"referer":null,"remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"response_code":200,"scheme":"HTTP","size_download":445,"size_header":4019,"size_request":4019,"size_upload":0,"speed_download":13,"speed_upload":13,"ssl_verify_result":0,"time_appconnect":0.000013,"time_connect":0.000013,"time_namelookup":0.000013,"time_pretransfer":0.000013,"time_redirect":0.000013,"time_starttransfer":0.000013,"time_total":0.000013,"url":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","url_effective":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","urlnum":0,"curl_version":"curl-unit-test-fake-version"}
+{"certs":"","content_type":"text/html","errormsg":null,"exitcode":0,"filename_effective":"log/out%TESTNUMBER","ftp_entry_path":null,"http_code":200,"http_connect":0,"http_version":"1.1","local_ip":"127.0.0.1","local_port":13,"method":"GET","num_certs":0,"num_connects":1,"num_headers":9,"num_redirects":0,"proxy_ssl_verify_result":0,"redirect_url":null,"referer":null,"remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"response_code":200,"scheme":"HTTP","size_download":445,"size_header":4019,"size_request":4019,"size_upload":0,"speed_download":13,"speed_upload":13,"ssl_verify_result":0,"time_appconnect":0.000013,"time_connect":0.000013,"time_namelookup":0.000013,"time_pretransfer":0.000013,"time_redirect":0.000013,"time_starttransfer":0.000013,"time_total":0.000013,"url":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","url_effective":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","urlnum":0,"curl_version":"curl-unit-test-fake-version"}
 </stdout>
 </verify>
 </testcase>
index 0bcf04e39c8b6392ddd817a88c66c38ef60af0ee..b62eb5bde8e7940534c51e15f1ab0110a9948bed 100644 (file)
@@ -60,7 +60,7 @@ Accept: */*
 \r
 </protocol>
 <stdout mode="text">
-{"content_type":"text/html","errormsg":null,"exitcode":0,"filename_effective":"log/out972","ftp_entry_path":null,"http_code":200,"http_connect":0,"http_version":"1.1","local_ip":"%HOSTIP","local_port":13,"method":"GET","num_connects":1,"num_headers":9,"num_redirects":0,"proxy_ssl_verify_result":0,"redirect_url":null,"referer":null,"remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"response_code":200,"scheme":"HTTP","size_download":445,"size_header":4019,"size_request":4019,"size_upload":0,"speed_download":13,"speed_upload":13,"ssl_verify_result":0,"time_appconnect":0.000013,"time_connect":0.000013,"time_namelookup":0.000013,"time_pretransfer":0.000013,"time_redirect":0.000013,"time_starttransfer":0.000013,"time_total":0.000013,"url":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","url_effective":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","urlnum":0,"curl_version":"curl-unit-test-fake-version"}
+{"certs":"","content_type":"text/html","errormsg":null,"exitcode":0,"filename_effective":"log/out972","ftp_entry_path":null,"http_code":200,"http_connect":0,"http_version":"1.1","local_ip":"%HOSTIP","local_port":13,"method":"GET","num_certs":0,"num_connects":1,"num_headers":9,"num_redirects":0,"proxy_ssl_verify_result":0,"redirect_url":null,"referer":null,"remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"response_code":200,"scheme":"HTTP","size_download":445,"size_header":4019,"size_request":4019,"size_upload":0,"speed_download":13,"speed_upload":13,"ssl_verify_result":0,"time_appconnect":0.000013,"time_connect":0.000013,"time_namelookup":0.000013,"time_pretransfer":0.000013,"time_redirect":0.000013,"time_starttransfer":0.000013,"time_total":0.000013,"url":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","url_effective":"http://%HOSTIP:%HTTPPORT/%TESTNUMBER","urlnum":0,"curl_version":"curl-unit-test-fake-version"}
 </stdout>
 </verify>
 </testcase>