summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authormoparisthebest <admin@moparisthebest.com>2014-09-30 22:31:17 -0400
committerDaniel Stenberg <daniel@haxx.se>2014-10-07 14:44:19 +0200
commit93e450793ce289925dfd1d5e3b2d14e781f8dfd4 (patch)
tree3ceea898922e067a4a692204f6388ab633deebef /lib
parentd1b56d00439ab26d7fc43e37ab18ae331ddc400d (diff)
downloadcurl-93e450793ce289925dfd1d5e3b2d14e781f8dfd4.tar.gz
SSL: implement public key pinning
Option --pinnedpubkey takes a path to a public key in DER format and only connect if it matches (currently only implemented with OpenSSL). Provides CURLOPT_PINNEDPUBLICKEY for curl_easy_setopt(). 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
Diffstat (limited to 'lib')
-rw-r--r--lib/strerror.c3
-rw-r--r--lib/url.c8
-rw-r--r--lib/urldata.h1
-rw-r--r--lib/vtls/openssl.c108
4 files changed, 120 insertions, 0 deletions
diff --git a/lib/strerror.c b/lib/strerror.c
index 66033f219..1a1360607 100644
--- a/lib/strerror.c
+++ b/lib/strerror.c
@@ -298,6 +298,9 @@ curl_easy_strerror(CURLcode error)
case CURLE_NO_CONNECTION_AVAILABLE:
return "The max connection limit is reached";
+ case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
+ return "SSL public key does not matched pinned public key";
+
/* error codes not used by current libcurl */
case CURLE_OBSOLETE20:
case CURLE_OBSOLETE24:
diff --git a/lib/url.c b/lib/url.c
index da67edf78..6db79deb2 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -1991,6 +1991,14 @@ CURLcode Curl_setopt(struct SessionHandle *data, CURLoption option,
result = CURLE_NOT_BUILT_IN;
#endif
break;
+ 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
diff --git a/lib/urldata.h b/lib/urldata.h
index 8594c2f7d..fd59d781d 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1385,6 +1385,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 2d1fa5bd3..aacd2778f 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -2363,6 +2363,107 @@ static CURLcode get_cert_chain(struct connectdata *conn,
}
/*
+ * Heavily modified from:
+ * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL
+ */
+static int pkp_pin_peer_pubkey(X509* cert, char *pinnedpubkey)
+{
+ /* Scratch */
+ FILE* fp = NULL;
+ int len1 = 0, len2 = 0;
+ unsigned char *buff1 = NULL, *buff2 = NULL, *temp = NULL;
+ long size = 0;
+
+ /* Result is returned to caller */
+ int ret = 0, result = FALSE;
+
+ /* if a path wasn't specified, don't pin */
+ if(NULL == pinnedpubkey) return TRUE;
+ if(NULL == cert) return 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 < 1)
+ break; /* failed */
+
+ /* http://www.openssl.org/docs/crypto/buffer.html */
+ buff1 = temp = OPENSSL_malloc(len1);
+ if(NULL == buff1)
+ 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, "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 */
+ size = ftell(fp);
+
+ /*
+ * if the size of our certificate doesn't match the size of
+ * the file, they can't be the same, don't bother reading it
+ */
+ if(len2 != size)
+ break; /* failed */
+
+ /* Rewind to beginning to perform the read */
+ ret = fseek(fp, 0, SEEK_SET);
+ if(0 != ret)
+ break; /* failed */
+
+ /* http://www.openssl.org/docs/crypto/buffer.html */
+ buff2 = OPENSSL_malloc(len2);
+ if(NULL == buff2)
+ break; /* failed */
+
+ /* Returns number of elements read, which should be 1 */
+ ret = (int)fread(buff2, (size_t)len2, 1, fp);
+ if(1 != ret)
+ break; /* failed */
+
+ /* The one good exit point */
+ result = (0 == memcmp(buff1, buff2, (size_t)len2));
+
+ } while(0);
+
+ if(NULL != fp)
+ fclose(fp);
+
+ /* http://www.openssl.org/docs/crypto/buffer.html */
+ if(NULL != buff2)
+ OPENSSL_free(buff2);
+
+ 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!
@@ -2485,6 +2586,13 @@ static CURLcode servercert(struct connectdata *conn,
infof(data, "\t SSL certificate verify ok.\n");
}
+ if(data->set.str[STRING_SSL_PINNEDPUBLICKEY] != NULL &&
+ 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;