Hello all,

The patch attached is a first go at supporting pinning public keys with
sha256 hashes, continuing on the road to RFC-7469 [1] support.
Currently this only works for OpenSSL (others are trivial, I'll probably
only be able to test GnuTLS though) and I haven't updated documentation
or tests until I get confirmation that I am going in the right direction.

I am re-using the current option/curlopt for pinnedpubkey, which
currently supports a file path to a der or pem encoded public key, this
adds support for a list of sha256 hashes formatted like so:

sha256/rVYsrz3N5qYAwyCH110Ph/c4PxbH+ChdaGEuh81+lPI=
or
sha256/rVYsrz3N5qYAwyCH110Ph/c4PxbH+ChdaGEuh81+lPI=;sha256/t62CeU2tQiqkexU74Gxa2eg7fRbEgoChTociMee9wno=

And so on, any number of them.  They are base64 encoded sha256 digests,
which is what the RFC supports.

To test this, get the sha256 hash of a public key by looking in the
Public-Key-Pins header sent by the server, or this openssl command:

openssl s_client -connect www.google.com:443 2>&1 < /dev/null | sed -n
'/-----BEGIN/,/-----END/p' | openssl x509 -noout -pubkey | openssl rsa
-pubin -outform DER | openssl dgst -sha256 -binary | openssl enc -base64

Then send it in to curl for example like this:

curl https://www.google.com --pinnedpubkey
'sha256/rVYsrz3N5qYAwyCH110Ph/c4PxbH+ChdaGEuh81+lPI='

Basically my questions are these:

1. Is it safe to re-use the existing curlopt, the code treats it as a
hash only if it starts with "sha256/" and nothing else, and then will
not look on the filesystem for a file at all.  I suppose this could
break systems where a der/pem is in a folder named 'sha256/' with no
leading path parts, but I feel like that's a safe bet?  (And by 'break'
I mean fail-closed, it'll fail to connect with 'curl: (90) SSL: public
key does not match pinned public key!')
2. General code questions, does this look like a decent way to handle
the string etc?

Thanks for any comments I can get!
Travis Burtrum

[1]: https://www.rfc-editor.org/rfc/rfc7469.txt
From 1250db8a529fcda4d7d21c7b8144ca84a16483c4 Mon Sep 17 00:00:00 2001
From: moparisthebest <[email protected]>
Date: Fri, 29 May 2015 01:07:32 -0400
Subject: [PATCH] SSL: Pinned public key hash support, for comments

---
 lib/vtls/openssl.c | 12 +++++++++++
 lib/vtls/openssl.h |  5 +++++
 lib/vtls/vtls.c    | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 79 insertions(+)

diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index 96a7d6e..8931e2c 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -3237,6 +3237,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..94aff35 100644
--- a/lib/vtls/vtls.c
+++ b/lib/vtls/vtls.c
@@ -774,12 +774,74 @@ 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[SHA256_DIGEST_LENGTH];
+  unsigned char *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/" */
+  begin_pos = strstr(pinnedpubkey, "sha256/");
+  if(begin_pos && 0 == (begin_pos - pinnedpubkey)) {
+    /* compute sha256sum of public key */
+    curlssl_sha256sum(pubkey, pubkeylen,
+                      sha256sumdigest, SHA256_DIGEST_LENGTH);
+
+    /* it starts with sha256/, copy so we can modify it */
+    pinkeylen = strlen(pinnedpubkey);
+    pinkeycopy = malloc(pinkeylen);
+    if(!pinkeycopy)
+      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, 7 is length of "sha256/" */
+      pem_read = Curl_base64_decode(begin_pos + 7,
+                                    &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(pinkeycopy);
+    return result;
+  }
+#endif
+
   fp = fopen(pinnedpubkey, "rb");
   if(!fp)
     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