From: Daniel Stenberg Date: Tue, 27 Dec 2022 11:00:12 +0000 (+0100) Subject: writeout: add %{certs} and %{num_certs} X-Git-Tag: curl-7_88_0~215 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c6aa19c1dadb082c00f9b693dfda0559556aeba8;p=thirdparty%2Fcurl.git writeout: add %{certs} and %{num_certs} Let users get the server certificate chain using the command line Closes #10019 --- diff --git a/docs/cmdline-opts/write-out.d b/docs/cmdline-opts/write-out.d index 8ca1746d08..309c78d32f 100644 --- a/docs/cmdline-opts/write-out.d +++ b/docs/cmdline-opts/write-out.d @@ -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 diff --git a/src/tool_operate.c b/src/tool_operate.c index 2b0cc083c0..6d0ba68cd9 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -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 */ diff --git a/src/tool_operate.h b/src/tool_operate.h index 5c08b99aec..ce3304cb12 100644 --- a/src/tool_operate.h +++ b/src/tool_operate.h @@ -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; diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 2789ee20bf..e99f1fc136 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -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]) { diff --git a/src/tool_writeout.h b/src/tool_writeout.h index c7cdb9771c..1d6572020a 100644 --- a/src/tool_writeout.h +++ b/src/tool_writeout.h @@ -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, diff --git a/tests/data/test970 b/tests/data/test970 index ee0a3e9aea..37945269a1 100644 --- a/tests/data/test970 +++ b/tests/data/test970 @@ -59,7 +59,7 @@ Accept: */* -{"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"} diff --git a/tests/data/test972 b/tests/data/test972 index 0bcf04e39c..b62eb5bde8 100644 --- a/tests/data/test972 +++ b/tests/data/test972 @@ -60,7 +60,7 @@ Accept: */* -{"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"}