Hello all,

Attached is the (hopefully) final patch implementing pinned public key
hash support.

On 06/29/2015 10:14 AM, Daniel Stenberg wrote:
> First, your patch actually uses strstr() which scans for that substring.
> You want plain memcmp() or strncmp().
> 
> Then, as we're introducing a new prefix to activate this magic we can
> probably make it even less likely to be a subdir, by for example using
> double slashes or something: "sha256//".

I've implemented both suggestions.

I've also updated the documentation of that option and all flags and
such, and added 2 tests for the new functionality.

Also attached is a separate optional patch that creates a new base64
function that uses an existing memory buffer instead of allocating a new
one every time. This lets us do 1 malloc/free no matter how many hashes
are supplied, instead of 1 for each hash supplied.  It's possible this
could be of use in other places in libcurl, but I haven't looked yet.

You can also find the commits pushed to github here:
https://github.com/moparisthebest/curl/commit/4b6ff5bad3f329ab92113bd27293b8d8de68506c
https://github.com/moparisthebest/curl/commit/9f37e2bc94b485e7eff0c49ccc3ec68ca5101dd3

Thanks much!
Travis Burtrum
From 4b6ff5bad3f329ab92113bd27293b8d8de68506c Mon Sep 17 00:00:00 2001
From: moparisthebest <[email protected]>
Date: Tue, 30 Jun 2015 20:23:54 -0400
Subject: [PATCH 1/2] SSL: Pinned public key hash support

---
 docs/curl.1                                 | 11 +++--
 docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 | 19 +++++++--
 lib/vtls/cyassl.c                           | 13 ++++++
 lib/vtls/cyassl.h                           |  5 +++
 lib/vtls/gtls.c                             | 20 +++++++++
 lib/vtls/gtls.h                             |  5 +++
 lib/vtls/nss.c                              | 13 ++++++
 lib/vtls/nssg.h                             |  6 +++
 lib/vtls/openssl.c                          | 12 ++++++
 lib/vtls/openssl.h                          |  5 +++
 lib/vtls/vtls.c                             | 66 +++++++++++++++++++++++++++++
 lib/vtls/vtls.h                             |  4 ++
 src/tool_help.c                             |  4 +-
 tests/data/Makefile.inc                     |  2 +-
 tests/data/test2041                         | 58 +++++++++++++++++++++++++
 tests/data/test2042                         | 44 +++++++++++++++++++
 16 files changed, 277 insertions(+), 10 deletions(-)
 create mode 100644 tests/data/test2041
 create mode 100644 tests/data/test2042

diff --git a/docs/curl.1 b/docs/curl.1
index 11b95d4..afd4e8c 100644
--- a/docs/curl.1
+++ b/docs/curl.1
@@ -544,9 +544,11 @@ OpenSSL-powered curl to make SSL-connections much more efficiently than using
 
 If this option is set, the default capath value will be ignored, and if it is
 used several times, the last one will be used.
-.IP "--pinnedpubkey <pinned public key>"
-(SSL) Tells curl to use the specified public key file to verify the peer. The
-file must contain a single public key in PEM or DER format.
+.IP "--pinnedpubkey <pinned public key (hashes)>"
+(SSL) Tells curl to use the specified public key file (or hashes) to verify the
+peer. This can be a path to a file which contains a single public key in PEM or
+DER format, or any number of base64 encoded sha256 hashes preceded by
+\'sha256//\' and seperated by \';\'
 
 When negotiating a TLS or SSL connection, the server sends a certificate
 indicating its identity. A public key is extracted from this certificate and
@@ -554,7 +556,8 @@ if it does not exactly match the public key provided to this option, curl will
 abort the connection before sending or receiving any data.
 
 Added in 7.39.0 for OpenSSL, GnuTLS and GSKit. Added in 7.43.0 for NSS and
-wolfSSL/CyaSSL. Other SSL backends not supported.
+wolfSSL/CyaSSL. sha256 support added in 7.44.0 for OpenSSL,
+GnuTLS, NSS and wolfSSL/CyaSSL. Other SSL backends not supported.
 
 If this option is used several times, the last one will be used.
 .IP "--cert-status"
diff --git a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
index 94cad31..0d4357a 100644
--- a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
+++ b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
@@ -28,8 +28,10 @@ CURLOPT_PINNEDPUBLICKEY \- set pinned public key
 
 CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PINNEDPUBLICKEY, char *pinnedpubkey);
 .SH DESCRIPTION
-Pass a pointer to a zero terminated string as parameter. The string should be
-the file name of your pinned public key. The format expected is "PEM" or "DER".
+Pass a pointer to a zero terminated string as parameter. The string can be the
+file name of your pinned public key. The file format expected is "PEM" or "DER".
+The string can also be any number of base64 encoded sha256 hashes preceded by
+"sha256//" and seperated by ";"
 
 When negotiating a TLS or SSL connection, the server sends a certificate
 indicating its identity. A public key is extracted from this certificate and
@@ -45,6 +47,9 @@ CURL *curl = curl_easy_init();
 if(curl) {
   curl_easy_setopt(curl, CURLOPT_URL, "https://example.com";);
   curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, "/etc/publickey.der");
+  /* OR
+  curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, "sha256//YhKJKSzoTt2b5FP18fvpHo7fJYqQCjAa3HWY3tvRMwE=;sha256//t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=");
+  */
 
   /* Perform the request */
   curl_easy_perform(curl);
@@ -54,9 +59,14 @@ if(curl) {
 If you do not have the server's public key file you can extract it from the
 server's certificate.
 .nf
+# extract public key in pem format from certificate
 openssl x509 -in www.test.com.pem -pubkey -noout > www.test.com.pubkey.pem
+# convert public key from pem to der
+openssl asn1parse -noout -inform pem -in www.test.com.pubkey.pem -out www.test.com.pubkey.der
+# sha256 hash and base64 encode der to string for use
+openssl dgst -sha256 -binary www.test.com.pubkey.der | openssl base64
 .fi
-The public key is output in PEM format and contains a header, base64 data and a
+The public key in PEM format contains a header, base64 data and a
 footer:
 .nf
 -----BEGIN PUBLIC KEY-----
@@ -65,7 +75,8 @@ footer:
 .fi
 .SH AVAILABILITY
 Added in 7.39.0 for OpenSSL, GnuTLS and GSKit. Added in 7.43.0 for
-NSS and wolfSSL/CyaSSL. Other SSL backends not supported.
+NSS and wolfSSL/CyaSSL. sha256 support added in 7.44.0 for OpenSSL,
+GnuTLS, NSS and wolfSSL/CyaSSL. Other SSL backends not supported.
 .SH RETURN VALUE
 Returns CURLE_OK if TLS enabled, CURLE_UNKNOWN_OPTION if not, or
 CURLE_OUT_OF_MEMORY if there was insufficient heap space.
diff --git a/lib/vtls/cyassl.c b/lib/vtls/cyassl.c
index 40dbbe1..3ded7f1 100644
--- a/lib/vtls/cyassl.c
+++ b/lib/vtls/cyassl.c
@@ -67,6 +67,7 @@ and that's a problem since options.h hasn't been included yet. */
 #include <cyassl/error.h>
 #endif
 #include <cyassl/ctaocrypt/random.h>
+#include <cyassl/ctaocrypt/sha256.h>
 
 /* The last #include files should be: */
 #include "curl_memory.h"
@@ -770,4 +771,16 @@ int Curl_cyassl_random(struct SessionHandle *data,
   return 0;
 }
 
+void Curl_cyassl_sha256sum(const unsigned char *tmp, /* input */
+                      size_t tmplen,
+                      unsigned char *sha256sum /* output */,
+                      size_t unused)
+{
+  Sha256 SHA256pw;
+  (void)unused;
+  InitSha256(&SHA256pw);
+  Sha256Update(&SHA256pw, tmp, tmplen);
+  Sha256Final(&SHA256pw, sha256sum);
+}
+
 #endif
diff --git a/lib/vtls/cyassl.h b/lib/vtls/cyassl.h
index 12638a7..447816b 100644
--- a/lib/vtls/cyassl.h
+++ b/lib/vtls/cyassl.h
@@ -42,6 +42,10 @@ CURLcode Curl_cyassl_connect_nonblocking(struct connectdata *conn,
 int Curl_cyassl_random(struct SessionHandle *data,
                        unsigned char *entropy,
                        size_t length);
+void Curl_cyassl_sha256sum(unsigned char *tmp, /* input */
+                     size_t tmplen,
+                     unsigned char *sha256sum, /* output */
+                     size_t unused);
 
 /* Set the API backend definition to Schannel */
 #define CURL_SSL_BACKEND CURLSSLBACKEND_CYASSL
@@ -65,6 +69,7 @@ int Curl_cyassl_random(struct SessionHandle *data,
 #define curlssl_check_cxn(x) ((void)x, -1)
 #define curlssl_data_pending(x,y) Curl_cyassl_data_pending(x,y)
 #define curlssl_random(x,y,z) Curl_cyassl_random(x,y,z)
+#define curlssl_sha256sum(a,b,c,d) Curl_cyassl_sha256sum(a,b,c,d)
 
 #endif /* USE_CYASSL */
 #endif /* HEADER_CURL_CYASSL_H */
diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c
index 1db31e4..c54dfc1 100644
--- a/lib/vtls/gtls.c
+++ b/lib/vtls/gtls.c
@@ -39,6 +39,7 @@
 #ifdef USE_GNUTLS_NETTLE
 #include <gnutls/crypto.h>
 #include <nettle/md5.h>
+#include <nettle/sha2.h>
 #else
 #include <gcrypt.h>
 #endif
@@ -1557,6 +1558,25 @@ void Curl_gtls_md5sum(unsigned char *tmp, /* input */
 #endif
 }
 
+void Curl_gtls_sha256sum(const unsigned char *tmp, /* input */
+                      size_t tmplen,
+                      unsigned char *sha256sum, /* output */
+                      size_t sha256len)
+{
+#if defined(USE_GNUTLS_NETTLE)
+  struct sha256_ctx SHA256pw;
+  sha256_init(&SHA256pw);
+  sha256_update(&SHA256pw, (unsigned int)tmplen, tmp);
+  sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum);
+#elif defined(USE_GNUTLS)
+  gcry_md_hd_t SHA256pw;
+  gcry_md_open(&SHA256pw, GCRY_MD_SHA256, 0);
+  gcry_md_write(SHA256pw, tmp, tmplen);
+  memcpy(sha256sum, gcry_md_read (SHA256pw, 0), sha256len);
+  gcry_md_close(SHA256pw);
+#endif
+}
+
 bool Curl_gtls_cert_status_request(void)
 {
 #ifdef HAS_OCSP
diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h
index dcae442..0afd9b9 100644
--- a/lib/vtls/gtls.h
+++ b/lib/vtls/gtls.h
@@ -48,6 +48,10 @@ void Curl_gtls_md5sum(unsigned char *tmp, /* input */
                       size_t tmplen,
                       unsigned char *md5sum, /* output */
                       size_t md5len);
+void Curl_gtls_sha256sum(const unsigned char *tmp, /* input */
+                      size_t tmplen,
+                      unsigned char *sha256sum, /* output */
+                      size_t sha256len);
 
 bool Curl_gtls_cert_status_request(void);
 
@@ -77,6 +81,7 @@ bool Curl_gtls_cert_status_request(void);
 #define curlssl_data_pending(x,y) ((void)x, (void)y, 0)
 #define curlssl_random(x,y,z) Curl_gtls_random(x,y,z)
 #define curlssl_md5sum(a,b,c,d) Curl_gtls_md5sum(a,b,c,d)
+#define curlssl_sha256sum(a,b,c,d) Curl_gtls_sha256sum(a,b,c,d)
 #define curlssl_cert_status_request() Curl_gtls_cert_status_request()
 
 #endif /* USE_GNUTLS */
diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
index 5434ce3..91727c7 100644
--- a/lib/vtls/nss.c
+++ b/lib/vtls/nss.c
@@ -2041,6 +2041,19 @@ void Curl_nss_md5sum(unsigned char *tmp, /* input */
   PK11_DestroyContext(MD5pw, PR_TRUE);
 }
 
+void Curl_nss_sha256sum(const unsigned char *tmp, /* input */
+                     size_t tmplen,
+                     unsigned char *sha256sum, /* output */
+                     size_t sha256len)
+{
+  PK11Context *SHA256pw = PK11_CreateDigestContext(SEC_OID_SHA256);
+  unsigned int SHA256out;
+
+  PK11_DigestOp(SHA256pw, tmp, curlx_uztoui(tmplen));
+  PK11_DigestFinal(SHA256pw, sha256sum, &SHA256out, curlx_uztoui(sha256len));
+  PK11_DestroyContext(SHA256pw, PR_TRUE);
+}
+
 bool Curl_nss_cert_status_request(void)
 {
 #ifdef SSL_ENABLE_OCSP_STAPLING
diff --git a/lib/vtls/nssg.h b/lib/vtls/nssg.h
index d0e7412..5fd7275 100644
--- a/lib/vtls/nssg.h
+++ b/lib/vtls/nssg.h
@@ -56,6 +56,11 @@ void Curl_nss_md5sum(unsigned char *tmp, /* input */
                      unsigned char *md5sum, /* output */
                      size_t md5len);
 
+void Curl_nss_sha256sum(const unsigned char *tmp, /* input */
+                     size_t tmplen,
+                     unsigned char *sha256sum, /* output */
+                     size_t sha256len);
+
 bool Curl_nss_cert_status_request(void);
 
 bool Curl_nss_false_start(void);
@@ -89,6 +94,7 @@ bool Curl_nss_false_start(void);
 #define curlssl_data_pending(x,y) ((void)x, (void)y, 0)
 #define curlssl_random(x,y,z) Curl_nss_random(x,y,z)
 #define curlssl_md5sum(a,b,c,d) Curl_nss_md5sum(a,b,c,d)
+#define curlssl_sha256sum(a,b,c,d) Curl_nss_sha256sum(a,b,c,d)
 #define curlssl_cert_status_request() Curl_nss_cert_status_request()
 #define curlssl_false_start() Curl_nss_false_start()
 
diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index 37d50cb..e41499f 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -3183,6 +3183,18 @@ void Curl_ossl_md5sum(unsigned char *tmp, /* input */
   MD5_Final(md5sum, &MD5pw);
 }
 
+void Curl_ossl_sha256sum(const unsigned char *tmp, /* input */
+                      size_t tmplen,
+                      unsigned char *sha256sum /* output */,
+                      size_t unused)
+{
+  SHA256_CTX SHA256pw;
+  (void)unused;
+  SHA256_Init(&SHA256pw);
+  SHA256_Update(&SHA256pw, tmp, tmplen);
+  SHA256_Final(sha256sum, &SHA256pw);
+}
+
 bool Curl_ossl_cert_status_request(void)
 {
 #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h
index 499b4fe..ebac542 100644
--- a/lib/vtls/openssl.h
+++ b/lib/vtls/openssl.h
@@ -72,6 +72,10 @@ void Curl_ossl_md5sum(unsigned char *tmp, /* input */
                       size_t tmplen,
                       unsigned char *md5sum /* output */,
                       size_t unused);
+void Curl_ossl_sha256sum(const unsigned char *tmp, /* input */
+                      size_t tmplen,
+                      unsigned char *sha256sum /* output */,
+                      size_t unused);
 
 bool Curl_ossl_cert_status_request(void);
 
@@ -104,6 +108,7 @@ bool Curl_ossl_cert_status_request(void);
 #define curlssl_data_pending(x,y) Curl_ossl_data_pending(x,y)
 #define curlssl_random(x,y,z) Curl_ossl_random(x,y,z)
 #define curlssl_md5sum(a,b,c,d) Curl_ossl_md5sum(a,b,c,d)
+#define curlssl_sha256sum(a,b,c,d) Curl_ossl_sha256sum(a,b,c,d)
 #define curlssl_cert_status_request() Curl_ossl_cert_status_request()
 
 #define DEFAULT_CIPHER_SELECTION \
diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
index 42a2b58..01bbc61 100644
--- a/lib/vtls/vtls.c
+++ b/lib/vtls/vtls.c
@@ -774,12 +774,78 @@ CURLcode Curl_pin_peer_pubkey(const char *pinnedpubkey,
   size_t size, pem_len;
   CURLcode pem_read;
   CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+#ifdef curlssl_sha256sum
+  size_t pinkeylen;
+  char *pinkeycopy, *begin_pos, *end_pos;
+  unsigned char *sha256sumdigest = NULL, *expectedsha256sumdigest = NULL;
+#endif
 
   /* if a path wasn't specified, don't pin */
   if(!pinnedpubkey)
     return CURLE_OK;
   if(!pubkey || !pubkeylen)
     return result;
+
+#ifdef curlssl_sha256sum
+  /* only do this if pinnedpubkey starts with "sha256//", length 8 */
+  if(strncmp(pinnedpubkey, "sha256//", 8) == 0) {
+    /* compute sha256sum of public key */
+    sha256sumdigest = malloc(SHA256_DIGEST_LENGTH);
+    if(!sha256sumdigest)
+      return CURLE_OUT_OF_MEMORY;
+    curlssl_sha256sum(pubkey, pubkeylen,
+                      sha256sumdigest, SHA256_DIGEST_LENGTH);
+
+    /* it starts with sha256//, copy so we can modify it */
+    pinkeylen = strlen(pinnedpubkey) + 1;
+    pinkeycopy = malloc(pinkeylen);
+    if(!pinkeycopy) {
+      Curl_safefree(sha256sumdigest);
+      return CURLE_OUT_OF_MEMORY;
+    }
+    memcpy(pinkeycopy, pinnedpubkey, pinkeylen);
+    /* point begin_pos to the copy, and start extracting keys */
+    begin_pos = pinkeycopy;
+    do {
+      end_pos = strstr(begin_pos, ";sha256//");
+      /*
+       * if there is an end_pos, null terminate,
+       * otherwise it'll go to the end of the original string
+       */
+      if(end_pos)
+        end_pos[0] = '\0';
+
+      /* decode base64 pinnedpubkey, 8 is length of "sha256//" */
+      pem_read = Curl_base64_decode(begin_pos + 8,
+                                    &expectedsha256sumdigest, &size);
+      /* if not valid base64, don't bother comparing or freeing */
+      if(!pem_read) {
+        /* compare sha256 digests directly */
+        if(SHA256_DIGEST_LENGTH == size &&
+           !memcmp(sha256sumdigest, expectedsha256sumdigest,
+                   SHA256_DIGEST_LENGTH)) {
+          result = CURLE_OK;
+          Curl_safefree(expectedsha256sumdigest);
+          break;
+        }
+        Curl_safefree(expectedsha256sumdigest);
+      }
+
+      /*
+       * change back the null-terminator we changed earlier,
+       * and look for next begin
+       */
+      if(end_pos) {
+        end_pos[0] = ';';
+        begin_pos = strstr(end_pos, "sha256//");
+      }
+    } while(end_pos && begin_pos);
+    Curl_safefree(sha256sumdigest);
+    Curl_safefree(pinkeycopy);
+    return result;
+  }
+#endif
+
   fp = fopen(pinnedpubkey, "rb");
   if(!fp)
     return result;
diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h
index b9741d7..2349e5b 100644
--- a/lib/vtls/vtls.h
+++ b/lib/vtls/vtls.h
@@ -41,6 +41,10 @@
 #define MD5_DIGEST_LENGTH 16 /* fixed size */
 #endif
 
+#ifndef SHA256_DIGEST_LENGTH
+#define SHA256_DIGEST_LENGTH 32 /* fixed size */
+#endif
+
 /* see http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-04 */
 #define ALPN_HTTP_1_1_LENGTH 8
 #define ALPN_HTTP_1_1 "http/1.1"
diff --git a/src/tool_help.c b/src/tool_help.c
index 5927303..418980f 100644
--- a/src/tool_help.c
+++ b/src/tool_help.c
@@ -156,7 +156,9 @@ static const char *const helptext[] = {
   " -o, --output FILE   Write to FILE instead of stdout",
   "     --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 "
+  "     --pinnedpubkey FILE/HASHES  Public key (PEM/DER) file, or any number "
+  "of base64 encoded sha256 hashes preceded by \'sha256//\' and seperated by "
+  "\';\', to verify peer against "
   "(OpenSSL/GnuTLS/NSS/wolfSSL/CyaSSL/GSKit only)",
   "     --post301       "
   "Do not switch to GET after following a 301 redirect (H)",
diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 88dafa4..51823fe 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -166,4 +166,4 @@ test2008 test2009 test2010 test2011 test2012 test2013 test2014 test2015 \
 test2016 test2017 test2018 test2019 test2020 test2021 test2022 test2023 \
 test2024 test2025 test2026 test2027 test2028 test2029 test2030 test2031 \
 test2032 test2033 test2034 test2035 test2036 test2037 test2038 test2039 \
-test2040
+test2040 test2041 test2042
diff --git a/tests/data/test2041 b/tests/data/test2041
new file mode 100644
index 0000000..dcad2fd
--- /dev/null
+++ b/tests/data/test2041
@@ -0,0 +1,58 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+HTTP GET
+PEM certificate
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 7
+
+MooMoo
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+SSL
+SSLpinning
+</features>
+<server>
+https Server-localhost-sv.pem
+</server>
+ <name>
+simple HTTPS GET with base64-sha256 public key pinning
+ </name>
+ <command>
+--cacert %SRCDIR/certs/EdelCurlRoot-ca.crt --pinnedpubkey sha256//pyh+fICi9M8MFEZvherIT0cs3MN+cXNGoU9Giwyx1so= https://localhost:%HTTPSPORT/2041
+</command>
+# Ensure that we're running on localhost because we're checking the host name
+<precheck>
+perl -e "print 'Test requires default test server host' if ( '%HOSTIP' ne '127.0.0.1' );"
+</precheck>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+</strip>
+<protocol>
+GET /2041 HTTP/1.1
+Host: localhost:%HTTPSPORT
+Accept: */*
+
+</protocol>
+</verify>
+</testcase>
diff --git a/tests/data/test2042 b/tests/data/test2042
new file mode 100644
index 0000000..2181e53
--- /dev/null
+++ b/tests/data/test2042
@@ -0,0 +1,44 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+HTTP GET
+PEM certificate
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+SSL
+SSLpinning
+</features>
+<server>
+https Server-localhost-sv.pem
+</server>
+ <name>
+HTTPS wrong base64-sha256 pinnedpubkey but right CN
+ </name>
+ <command>
+--cacert %SRCDIR/certs/EdelCurlRoot-ca.crt --pinnedpubkey sha256//bSIggTf+ikMG0CtmDlpMVBd7yi7H1md4URogRPqerso= https://localhost:%HTTPSPORT/2042
+</command>
+# Ensure that we're running on localhost because we're checking the host name
+<precheck>
+perl -e "print 'Test requires default test server host' if ( '%HOSTIP' ne '127.0.0.1' );"
+</precheck>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<errorcode>
+90
+</errorcode>
+</verify>
+</testcase>
-- 
1.9.2

From 9f37e2bc94b485e7eff0c49ccc3ec68ca5101dd3 Mon Sep 17 00:00:00 2001
From: moparisthebest <[email protected]>
Date: Tue, 30 Jun 2015 20:39:49 -0400
Subject: [PATCH 2/2] Add new Curl_base64_decode_nomalloc function

---
 lib/base64.c      | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/curl_base64.h |  2 ++
 lib/vtls/vtls.c   | 30 ++++++++++++----------
 3 files changed, 95 insertions(+), 13 deletions(-)

diff --git a/lib/base64.c b/lib/base64.c
index 6b87eed..ce2df37 100644
--- a/lib/base64.c
+++ b/lib/base64.c
@@ -168,6 +168,82 @@ CURLcode Curl_base64_decode(const char *src,
   return CURLE_OK;
 }
 
+/*
+ * Curl_base64_decode()
+ *
+ * Given a base64 NUL-terminated string at src, decode it and write it
+ * to *out, if it will be less than out_size. Exact size of decoded
+ * data is returned in variable pointed by outlen.
+ *
+ * Returns CURLE_OK on success, otherwise specific error code. Function
+ * output shall not be considered valid unless CURLE_OK is returned.
+ *
+ * @unittest: 1302
+ */
+CURLcode Curl_base64_decode_nomalloc(const char *src, size_t out_size,
+                            unsigned char *out, size_t *outlen)
+{
+  size_t srclen = 0;
+  size_t length = 0;
+  size_t padding = 0;
+  size_t i;
+  size_t numQuantums;
+  size_t rawlen = 0;
+  unsigned char *pos;
+
+  *outlen = 0;
+  srclen = strlen(src);
+
+  /* Check the length of the input string is valid */
+  if(!srclen || srclen % 4)
+    return CURLE_BAD_CONTENT_ENCODING;
+
+  /* Find the position of any = padding characters */
+  while((src[length] != '=') && src[length])
+    length++;
+
+  /* A maximum of two = padding characters is allowed */
+  if(src[length] == '=') {
+    padding++;
+    if(src[length + 1] == '=')
+      padding++;
+  }
+
+  /* Check the = padding characters weren't part way through the input */
+  if(length + padding != srclen)
+    return CURLE_BAD_CONTENT_ENCODING;
+
+  /* Calculate the number of quantums */
+  numQuantums = srclen / 4;
+
+  /* Calculate the size of the decoded string */
+  rawlen = (numQuantums * 3) - padding;
+
+  /* If there is not enough room in our buffer, bail out */
+  if(rawlen >= out_size)
+    return CURLE_BAD_CONTENT_ENCODING;
+
+  pos = out;
+
+  /* Decode the quantums */
+  for(i = 0; i < numQuantums; i++) {
+    size_t result = decodeQuantum(pos, src);
+    if(!result)
+      return CURLE_BAD_CONTENT_ENCODING;
+
+    pos += result;
+    src += 4;
+  }
+
+  /* Zero terminate */
+  *pos = '\0';
+
+  /* Return the decoded data */
+  *outlen = rawlen;
+
+  return CURLE_OK;
+}
+
 static CURLcode base64_encode(const char *table64,
                               struct SessionHandle *data,
                               const char *inputbuff, size_t insize,
diff --git a/lib/curl_base64.h b/lib/curl_base64.h
index 92896fe..e9f5b24 100644
--- a/lib/curl_base64.h
+++ b/lib/curl_base64.h
@@ -31,5 +31,7 @@ CURLcode Curl_base64url_encode(struct SessionHandle *data,
 
 CURLcode Curl_base64_decode(const char *src,
                             unsigned char **outptr, size_t *outlen);
+CURLcode Curl_base64_decode_nomalloc(const char *src, size_t out_size,
+                            unsigned char *out, size_t *outlen);
 
 #endif /* HEADER_CURL_BASE64_H */
diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
index 01bbc61..7860d95 100644
--- a/lib/vtls/vtls.c
+++ b/lib/vtls/vtls.c
@@ -796,11 +796,18 @@ CURLcode Curl_pin_peer_pubkey(const char *pinnedpubkey,
     curlssl_sha256sum(pubkey, pubkeylen,
                       sha256sumdigest, SHA256_DIGEST_LENGTH);
 
+    expectedsha256sumdigest = malloc(SHA256_DIGEST_LENGTH + 1);
+    if(!expectedsha256sumdigest) {
+      Curl_safefree(sha256sumdigest);
+      return CURLE_OUT_OF_MEMORY;
+    }
+
     /* it starts with sha256//, copy so we can modify it */
     pinkeylen = strlen(pinnedpubkey) + 1;
     pinkeycopy = malloc(pinkeylen);
     if(!pinkeycopy) {
       Curl_safefree(sha256sumdigest);
+      Curl_safefree(expectedsha256sumdigest);
       return CURLE_OUT_OF_MEMORY;
     }
     memcpy(pinkeycopy, pinnedpubkey, pinkeylen);
@@ -816,19 +823,15 @@ CURLcode Curl_pin_peer_pubkey(const char *pinnedpubkey,
         end_pos[0] = '\0';
 
       /* decode base64 pinnedpubkey, 8 is length of "sha256//" */
-      pem_read = Curl_base64_decode(begin_pos + 8,
-                                    &expectedsha256sumdigest, &size);
-      /* if not valid base64, don't bother comparing or freeing */
-      if(!pem_read) {
-        /* compare sha256 digests directly */
-        if(SHA256_DIGEST_LENGTH == size &&
-           !memcmp(sha256sumdigest, expectedsha256sumdigest,
-                   SHA256_DIGEST_LENGTH)) {
-          result = CURLE_OK;
-          Curl_safefree(expectedsha256sumdigest);
-          break;
-        }
-        Curl_safefree(expectedsha256sumdigest);
+      pem_read = Curl_base64_decode_nomalloc(begin_pos + 8,
+                                    SHA256_DIGEST_LENGTH + 1,
+                                    expectedsha256sumdigest, &size);
+      /* if valid base64, compare sha256 digests directly */
+      if(!pem_read && SHA256_DIGEST_LENGTH == size &&
+         !memcmp(sha256sumdigest, expectedsha256sumdigest,
+                 SHA256_DIGEST_LENGTH)) {
+        result = CURLE_OK;
+        break;
       }
 
       /*
@@ -841,6 +844,7 @@ CURLcode Curl_pin_peer_pubkey(const char *pinnedpubkey,
       }
     } while(end_pos && begin_pos);
     Curl_safefree(sha256sumdigest);
+    Curl_safefree(expectedsha256sumdigest);
     Curl_safefree(pinkeycopy);
     return result;
   }
-- 
1.9.2

-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html

Reply via email to