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