aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Dudka <kdudka@redhat.com>2015-03-25 13:48:41 +0100
committerKamil Dudka <kdudka@redhat.com>2015-04-22 13:21:31 +0200
commitb47c17d67c9b5c9e985375b090f0140bf43cb146 (patch)
treeeae985fca8853c0dcbf72e5556d070637f86db56
parent1fd33e3ec8fcfe6f5797b212980ececd6bdb9d03 (diff)
nss: implement public key pinning for NSS backend
Bug: https://bugzilla.redhat.com/1195771
-rw-r--r--docs/curl.13
-rw-r--r--docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.32
-rw-r--r--lib/vtls/nss.c53
-rw-r--r--src/tool_help.c2
-rwxr-xr-xtests/runtests.pl1
5 files changed, 58 insertions, 3 deletions
diff --git a/docs/curl.1 b/docs/curl.1
index 908f64899..0e567159e 100644
--- a/docs/curl.1
+++ b/docs/curl.1
@@ -548,7 +548,8 @@ indicating its identity. A public key is extracted from this certificate and
if it does not exactly match the public key provided to this option, curl will
abort the connection before sending or receiving any data.
-This is currently only implemented in the OpenSSL, GnuTLS and GSKit backends.
+This is currently only implemented in the OpenSSL, GnuTLS, NSS and GSKit
+backends.
If this option is used several times, the last one will be used.
(Added in 7.39.0)
diff --git a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
index 2d8639275..4cc68b1d3 100644
--- a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
+++ b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
@@ -52,7 +52,7 @@ if(curl) {
.fi
.SH AVAILABILITY
If built TLS enabled. This is currently only implemented in the OpenSSL,
-GnuTLS and GSKit backends.
+GnuTLS, NSS and GSKit backends.
Added in libcurl 7.39.0
.SH RETURN VALUE
diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
index feb00ca81..daf12a9d7 100644
--- a/lib/vtls/nss.c
+++ b/lib/vtls/nss.c
@@ -56,6 +56,7 @@
#include <base64.h>
#include <cert.h>
#include <prerror.h>
+#include <keyhi.h> /* for SECKEY_DestroyPublicKey() */
#define NSSVERNUM ((NSS_VMAJOR<<16)|(NSS_VMINOR<<8)|NSS_VPATCH)
@@ -943,6 +944,53 @@ static SECStatus check_issuer_cert(PRFileDesc *sock,
return res;
}
+static CURLcode cmp_peer_pubkey(struct ssl_connect_data *connssl,
+ const char *pinnedpubkey)
+{
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ struct SessionHandle *data = connssl->data;
+ CERTCertificate *cert;
+
+ if(!pinnedpubkey)
+ /* no pinned public key specified */
+ return CURLE_OK;
+
+ /* get peer certificate */
+ cert = SSL_PeerCertificate(connssl->handle);
+ if(cert) {
+ /* extract public key from peer certificate */
+ SECKEYPublicKey *pubkey = CERT_ExtractPublicKey(cert);
+ if(pubkey) {
+ /* encode the public key as DER */
+ SECItem *cert_der = PK11_DEREncodePublicKey(pubkey);
+ if(cert_der) {
+ /* compare the public key with the pinned public key */
+ result = Curl_pin_peer_pubkey(pinnedpubkey,
+ cert_der->data,
+ cert_der->len);
+ SECITEM_FreeItem(cert_der, PR_TRUE);
+ }
+ SECKEY_DestroyPublicKey(pubkey);
+ }
+ CERT_DestroyCertificate(cert);
+ }
+
+ /* report the resulting status */
+ switch(result) {
+ case CURLE_OK:
+ infof(data, "pinned public key verified successfully!\n");
+ break;
+ case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
+ failf(data, "failed to verify pinned public key");
+ break;
+ default:
+ /* OOM, etc. */
+ break;
+ }
+
+ return result;
+}
+
/**
*
* Callback to pick the SSL client certificate.
@@ -1806,6 +1854,11 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
}
}
+ result = cmp_peer_pubkey(connssl, data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+ if(result)
+ /* status already printed */
+ goto error;
+
return CURLE_OK;
error:
diff --git a/src/tool_help.c b/src/tool_help.c
index bb7aa7c3a..27638ef16 100644
--- a/src/tool_help.c
+++ b/src/tool_help.c
@@ -156,7 +156,7 @@ static const char *const helptext[] = {
" --pass PASS Pass phrase for the private key (SSL/SSH)",
" --path-as-is Do not squash .. sequences in URL path",
" --pinnedpubkey FILE Public key (PEM/DER) to verify peer against "
- "(OpenSSL/GnuTLS/GSKit only)",
+ "(OpenSSL/GnuTLS/NSS/GSKit only)",
" --post301 "
"Do not switch to GET after following a 301 redirect (H)",
" --post302 "
diff --git a/tests/runtests.pl b/tests/runtests.pl
index ef9d3c8d4..b64c42373 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -2346,6 +2346,7 @@ sub checksystem {
}
elsif ($libcurl =~ /nss/i) {
$has_nss=1;
+ $has_sslpinning=1;
$ssllib="NSS";
}
elsif ($libcurl =~ /(yassl|wolfssl)/i) {