curl-library
[PATCH] Implement --pinnedpubkey option which will take a path to a public key in DER format and only connect if it matches (currently only implemented with OpenSSL). Extract a public RSA key from a website like so: openssl s_client -connect google.com:443 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' | openssl x509 -noout -pubkey | openssl rsa -pubin -outform DER > google.com.der
From: moparisthebest <android_at_moparisthebest.org>
Date: Mon, 25 Aug 2014 19:47:07 -0400
Date: Mon, 25 Aug 2014 19:47:07 -0400
--- include/curl/curl.h | 6 +++ lib/url.c | 9 ++++ lib/urldata.h | 2 + lib/vtls/openssl.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tool_cfgable.c | 1 + src/tool_cfgable.h | 1 + src/tool_getparam.c | 6 +++ src/tool_help.c | 1 + src/tool_operate.c | 3 ++ 9 files changed, 159 insertions(+) diff --git a/include/curl/curl.h b/include/curl/curl.h index d40b2db..ccd9c3b 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -521,6 +521,8 @@ typedef enum { CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */ CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the session will be queued */ + CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not + match */ CURL_LAST /* never use! */ } CURLcode; @@ -1611,6 +1613,10 @@ typedef enum { /* Pass in a bitmask of "header options" */ CINIT(HEADEROPT, LONG, 229), + /* The public key in DER form used to validate the peer public key + this option is used only if SSL_VERIFYPEER is true */ + CINIT(PINNEDPUBLICKEY, OBJECTPOINT, 230), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/lib/url.c b/lib/url.c index 89c3fd5..78870c9 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1983,6 +1983,14 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option, data->set.ssl.certinfo = (0 != va_arg(param, long))?TRUE:FALSE; break; #endif + case CURLOPT_PINNEDPUBLICKEY: + /* + * Set pinned public key for SSL connection. + * Specify file name of the public key in DER format. + */ + result = setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY], + va_arg(param, char *)); + break; case CURLOPT_CAINFO: /* * Set CA info for SSL connection. Specify file name of the CA certificate @@ -5473,6 +5481,7 @@ static CURLcode create_conn(struct SessionHandle *data, */ data->set.ssl.CApath = data->set.str[STRING_SSL_CAPATH]; data->set.ssl.CAfile = data->set.str[STRING_SSL_CAFILE]; + data->set.ssl.pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; data->set.ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE]; data->set.ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT]; data->set.ssl.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; diff --git a/lib/urldata.h b/lib/urldata.h index 8594c2f..16ec349 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -378,6 +378,7 @@ struct ssl_config_data { void *fsslctxp; /* parameter for call back */ bool sessionid; /* cache session IDs or not */ bool certinfo; /* gather lots of certificate info */ + char *pinnedpubkey; /* public key to verify peer against */ #ifdef USE_TLS_SRP char *username; /* TLS username (for, e.g., SRP) */ @@ -1385,6 +1386,7 @@ enum dupstring { STRING_SET_URL, /* what original URL to work on */ STRING_SSL_CAPATH, /* CA directory name (doesn't work on windows) */ STRING_SSL_CAFILE, /* certificate file to verify peer against */ + STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */ STRING_SSL_CIPHER_LIST, /* list of ciphers to use */ STRING_SSL_EGDSOCKET, /* path to file containing the EGD daemon socket */ STRING_SSL_RANDOM_FILE, /* path to file containing "random" data */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index da92854..3570581 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -2357,6 +2357,130 @@ static CURLcode get_cert_chain(struct connectdata *conn, } /* + * Modified from: + * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL + */ +static int pkp_pin_peer_pubkey(X509* cert, char *pinnedpubkey) +{ + // if a path wasn't specified, don't pin + if(NULL == pinnedpubkey) return TRUE; + if(NULL == cert) return FALSE; + + FILE* fp = NULL; + + /* Scratch */ + int len1 = 0, len2 = 0; + unsigned char *buff1 = NULL, *buff2 = NULL; + + /* Result is returned to caller */ + int ret = 0, result = FALSE; + + do + { + + /* Begin Gyrations to get the subjectPublicKeyInfo */ + /* Thanks to Viktor Dukhovni on the OpenSSL mailing list */ + + /* http://groups.google.com/group/mailing.openssl.users/browse_thread + /thread/d61858dae102c6c7 */ + len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL); + if(!(len1 > 0)) + break; /* failed */ + + /* scratch */ + unsigned char* temp = NULL; + + /* http://www.openssl.org/docs/crypto/buffer.html */ + buff1 = temp = OPENSSL_malloc(len1); + if(!(buff1 != NULL)) + break; /* failed */ + + /* http://www.openssl.org/docs/crypto/d2i_X509.html */ + len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp); + + /* + * These checks are verifying we got back the same values as when we + * sized the buffer.Its pretty weak since they should always be the + * same. But it gives us something to test. + */ + if(!((len1 == len2) && (temp != NULL) && ((temp - buff1) == len1))) + break; /* failed */ + + /* End Gyrations */ + + /* See the warning above!!! */ + fp = fopen(pinnedpubkey, "rx"); + if(NULL ==fp) + fp = fopen(pinnedpubkey, "r"); + + if(!(NULL != fp)) + break; /* failed */ + + /* Seek to eof to determine the file's size */ + ret = fseek(fp, 0, SEEK_END); + if(!(0 == ret)) + break; /* failed */ + + /* Fetch the file's size */ + long size = ftell(fp); + + /* + * Arbitrary size, but should be relatively small + * (less than 1K or 2K) + */ + if(!(size != -1 && size > 0 && size < 2048)) + break; /* failed */ + + /* Rewind to beginning to perform the read */ + ret = fseek(fp, 0, SEEK_SET); + if(!(0 == ret)) + break; /* failed */ + + /* Re-use buff2 and len2 */ + buff2 = NULL; len2 = (int)size; + + /* http://www.openssl.org/docs/crypto/buffer.html */ + buff2 = OPENSSL_malloc(len2); + if(!(buff2 != NULL)) + break; /* failed */ + + /* Returns number of elements read, which should be 1 */ + ret = (int)fread(buff2, (size_t)len2, 1, fp); + if(!(ret == 1)) + break; /* failed */ + + /* Re-use size. MIN and MAX macro below... */ + size = len1 < len2 ? len1 : len2; + + /*************************/ + /***** PAYDIRT *****/ + /*************************/ + if(len1 != (int)size + || len2 != (int)size + || 0 != memcmp(buff1, buff2, (size_t)size) + ) + break; /* failed */ + + /* The one good exit point */ + result = TRUE; + + } while(0); + + if(fp != NULL) + fclose(fp); + + /* http://www.openssl.org/docs/crypto/buffer.html */ + if(NULL != buff2) + OPENSSL_free(buff2); + + /* http://www.openssl.org/docs/crypto/buffer.html */ + if(NULL != buff1) + OPENSSL_free(buff1); + + return result; +} + +/* * Get the server cert, verify it and show it etc, only call failf() if the * 'strict' argument is TRUE as otherwise all this is for informational * purposes only! @@ -2479,6 +2603,12 @@ static CURLcode servercert(struct connectdata *conn, infof(data, "\t SSL certificate verify ok.\n"); } + if(TRUE != pkp_pin_peer_pubkey(connssl->server_cert, + data->set.str[STRING_SSL_PINNEDPUBLICKEY])) { + failf(data, "SSL: public key does not matched pinned public key!"); + return CURLE_SSL_PINNEDPUBKEYNOTMATCH; + } + X509_free(connssl->server_cert); connssl->server_cert = NULL; connssl->connecting_state = ssl_connect_done; diff --git a/src/tool_cfgable.c b/src/tool_cfgable.c index 2fdae07..bd8707e 100644 --- a/src/tool_cfgable.c +++ b/src/tool_cfgable.c @@ -101,6 +101,7 @@ static void free_config_fields(struct OperationConfig *config) Curl_safefree(config->cacert); Curl_safefree(config->capath); Curl_safefree(config->crlfile); + Curl_safefree(config->pinnedpubkey); Curl_safefree(config->key); Curl_safefree(config->key_type); Curl_safefree(config->key_passwd); diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index 4ef2690..11a6a98 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -110,6 +110,7 @@ struct OperationConfig { char *cacert; char *capath; char *crlfile; + char *pinnedpubkey; char *key; char *key_type; char *key_passwd; diff --git a/src/tool_getparam.c b/src/tool_getparam.c index 180878b..242a2d8 100644 --- a/src/tool_getparam.c +++ b/src/tool_getparam.c @@ -215,6 +215,7 @@ static const struct LongShort aliases[]= { {"Em", "tlsauthtype", TRUE}, {"En", "ssl-allow-beast", FALSE}, {"Eo", "login-options", TRUE}, + {"Ep", "pinnedpubkey", TRUE}, {"f", "fail", FALSE}, {"F", "form", TRUE}, {"Fs", "form-string", TRUE}, @@ -1358,6 +1359,11 @@ ParameterError getparameter(char *flag, /* f or -long-flag */ GetStr(&config->login_options, nextarg); break; + case 'p': /* Pinned public key DER file */ + /* Pinned public key DER file */ + GetStr(&config->pinnedpubkey, nextarg); + break; + default: /* certificate file */ { char *certname, *passphrase; diff --git a/src/tool_help.c b/src/tool_help.c index c255be0..cad12a3 100644 --- a/src/tool_help.c +++ b/src/tool_help.c @@ -51,6 +51,7 @@ static const char *const helptext[] = { " --basic Use HTTP Basic Authentication (H)", " --cacert FILE CA certificate to verify peer against (SSL)", " --capath DIR CA directory to verify peer against (SSL)", + " --pinnedpubkey FILE Public key (DER) to verify peer against (SSL)", " -E, --cert CERT[:PASSWD] Client certificate file and password (SSL)", " --cert-type TYPE Certificate file type (DER/PEM/ENG) (SSL)", " --ciphers LIST SSL ciphers to use (SSL)", diff --git a/src/tool_operate.c b/src/tool_operate.c index fd2fd6d..488fb08 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -1025,6 +1025,9 @@ static CURLcode operate_do(struct GlobalConfig *global, if(config->crlfile) my_setopt_str(curl, CURLOPT_CRLFILE, config->crlfile); + if(config->pinnedpubkey) + my_setopt_str(curl, CURLOPT_PINNEDPUBLICKEY, config->pinnedpubkey); + if(curlinfo->features & CURL_VERSION_SSL) { if(config->insecure_ok) { my_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); -- 1.9.2 --------------080406020909040506070407 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: base64 Content-Disposition: inline LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLQpMaXN0IGFkbWluOiBodHRwOi8vY29vbC5oYXh4LnNlL2xpc3QvbGlzdGluZm8v Y3VybC1saWJyYXJ5CkV0aXF1ZXR0ZTogIGh0dHA6Ly9jdXJsLmhheHguc2UvbWFpbC9ldGlxdWV0 dGUuaHRtbA== --------------080406020909040506070407--Received on 2001-09-17