curl / Mailing Lists / curl-library / Single Mail

curl-library

[PATCH] HTTPS Proxy: Implement CURLOPT_PROXY_PINNEDPUBLICKEY

From: Thomas Glanzmann <thomas_at_glanzmann.de>
Date: Thu, 24 Nov 2016 20:57:15 +0100

WARNING: I compiled and tested this code only for openssl and mbedtls

---
 docs/libcurl/opts/CURLOPT_PROXY_PINNEDPUBLICKEY.3 | 99 +++++++++++++++++++++++
 docs/libcurl/symbols-in-versions                  |  1 +
 include/curl/curl.h                               |  4 +
 lib/url.c                                         | 14 +++-
 lib/urldata.h                                     |  3 +-
 lib/vtls/cyassl.c                                 |  8 +-
 lib/vtls/gskit.c                                  |  3 +-
 lib/vtls/gtls.c                                   |  3 +-
 lib/vtls/mbedtls.c                                |  8 +-
 lib/vtls/nss.c                                    |  6 +-
 lib/vtls/openssl.c                                |  3 +-
 lib/vtls/polarssl.c                               |  8 +-
 12 files changed, 147 insertions(+), 13 deletions(-)
 create mode 100644 docs/libcurl/opts/CURLOPT_PROXY_PINNEDPUBLICKEY.3
diff --git a/docs/libcurl/opts/CURLOPT_PROXY_PINNEDPUBLICKEY.3 b/docs/libcurl/opts/CURLOPT_PROXY_PINNEDPUBLICKEY.3
new file mode 100644
index 0000000..db2cd70
--- /dev/null
+++ b/docs/libcurl/opts/CURLOPT_PROXY_PINNEDPUBLICKEY.3
@@ -0,0 +1,99 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel_at_haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_PROXY_PINNEDPUBLICKEY 3 "24 Nov 2016" "libcurl 7.52.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_PROXY_PINNEDPUBLICKEY \- set pinned public key for https proxy
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_PINNEDPUBLICKEY, char *pinnedpubkey);
+.SH DESCRIPTION
+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 separated by ";"
+
+When negotiating a TLS or SSL connection, the https proxy sends a certificate
+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.
+
+On mismatch, \fICURLE_SSL_PINNEDPUBKEYNOTMATCH\fP is returned.
+.SH DEFAULT
+NULL
+.SH PROTOCOLS
+All TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc.
+.SH EXAMPLE
+.nf
+TODO
+.fi
+.SH PUBLIC KEY EXTRACTION
+If you do not have the https proxy server's public key file you can extract it
+from the https proxy server's certificate.
+.nf
+# retrieve the server's certificate if you don't already have it
+#
+# be sure to examine the certificate to see if it is what you expected
+#
+# Windows-specific:
+# - Use NUL instead of /dev/null.
+# - OpenSSL may wait for input instead of disconnecting. Hit enter.
+# - If you don't have sed, then just copy the certificate into a file:
+#   Lines from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----.
+#
+openssl s_client -servername www.example.com -connect www.example.com:443 < /dev/null | sed -n "/-----BEGIN/,/-----END/p" > www.example.com.pem
+
+# extract public key in pem format from certificate
+openssl x509 -in www.example.com.pem -pubkey -noout > www.example.com.pubkey.pem
+
+# convert public key from pem to der
+openssl asn1parse -noout -inform pem -in www.example.com.pubkey.pem -out www.example.com.pubkey.der
+
+# sha256 hash and base64 encode der to string for use
+openssl dgst -sha256 -binary www.example.com.pubkey.der | openssl base64
+.fi
+The public key in PEM format contains a header, base64 data and a
+footer:
+.nf
+-----BEGIN PUBLIC KEY-----
+[BASE 64 DATA]
+-----END PUBLIC KEY-----
+.fi
+.SH AVAILABILITY
+PEM/DER support:
+
+  7.52.0: GSKit, GnuTLS, NSS, OpenSSL, PolarSSL, mbedtls, wolfSSL/CyaSSL
+
+sha256 support:
+
+  7.52.0: GnuTLS, NSS, OpenSSL, PolarSSL, mbedtls, 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.
+.SH "SEE ALSO"
+.BR CURLOPT_PROXY_SSL_VERIFYPEER "(3), "
+.BR CURLOPT_PROXY_SSL_VERIFYHOST "(3), "
+.BR CURLOPT_PROXY_CAINFO "(3), "
+.BR CURLOPT_PROXY_CAPATH "(3), "
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index 1d58d51..f361c25 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -486,6 +486,7 @@ CURLOPT_PROXY_SSL_VERIFYPEER    7.52.0
 CURLOPT_PROXY_TLSAUTH_PASSWORD  7.52.0
 CURLOPT_PROXY_TLSAUTH_TYPE      7.52.0
 CURLOPT_PROXY_TLSAUTH_USERNAME  7.52.0
+CURLOPT_PROXY_PINNEDPUBLICKEY   7.52.0
 CURLOPT_PROXY_TRANSFER_MODE     7.18.0
 CURLOPT_PUT                     7.1
 CURLOPT_QUOTE                   7.1
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 24762c7..41fddad 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -1770,6 +1770,10 @@ typedef enum {
      CURLPROXY_SOCKS4A and CURLPROXY_SOCKS5. */
   CINIT(SOCKS_PROXYTYPE, LONG, 263),
 
+  /* The public key in DER form used to validate the proxy public key
+     this option is used only if PROXY_SSL_VERIFYPEER is true */
+  CINIT(PROXY_PINNEDPUBLICKEY, STRINGPOINT, 264),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 
diff --git a/lib/url.c b/lib/url.c
index 384b16f..aae0bd0 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -2181,7 +2181,19 @@ CURLcode Curl_setopt(struct Curl_easy *data, CURLoption option,
      * 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],
+    result = setstropt(&data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG],
+                       va_arg(param, char *));
+#else
+    result = CURLE_NOT_BUILT_IN;
+#endif
+    break;
+  case CURLOPT_PROXY_PINNEDPUBLICKEY:
+#ifdef have_curlssl_pinnedpubkey /* only by supported backends */
+    /*
+     * 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_PROXY],
                        va_arg(param, char *));
 #else
     result = CURLE_NOT_BUILT_IN;
diff --git a/lib/urldata.h b/lib/urldata.h
index 7fed5c5..1fd94de 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1468,7 +1468,8 @@ enum dupstring {
   STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */
   STRING_SSL_CAFILE_ORIG, /* certificate file to verify peer against */
   STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */
-  STRING_SSL_PINNEDPUBLICKEY, /* public key file to verify peer against */
+  STRING_SSL_PINNEDPUBLICKEY_ORIG, /* public key file to verify peer against */
+  STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify peer against */
   STRING_SSL_CIPHER_LIST_ORIG, /* list of ciphers to use */
   STRING_SSL_CIPHER_LIST_PROXY, /* list of ciphers to use */
   STRING_SSL_EGDSOCKET,   /* path to file containing the EGD daemon socket */
diff --git a/lib/vtls/cyassl.c b/lib/vtls/cyassl.c
index 5570760..db5ce27 100644
--- a/lib/vtls/cyassl.c
+++ b/lib/vtls/cyassl.c
@@ -424,6 +424,10 @@ cyassl_connect_step2(struct connectdata *conn,
     conn->host.name;
   const char * const dispname = SSL_IS_PROXY() ?
     conn->http_proxy.host.dispname : conn->host.dispname;
+  const char * const pinnedpubkey = SSL_IS_PROXY() ?
+                        data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+                        data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
+    conn->http_proxy.host.dispname : conn->host.dispname;
 
   conn->recv[sockindex] = cyassl_recv;
   conn->send[sockindex] = cyassl_send;
@@ -497,7 +501,7 @@ cyassl_connect_step2(struct connectdata *conn,
     }
   }
 
-  if(data->set.str[STRING_SSL_PINNEDPUBLICKEY]) {
+  if(pinnedpubkey) {
 #ifdef KEEP_PEER_CERT
     X509 *x509;
     const char *x509_der;
@@ -529,7 +533,7 @@ cyassl_connect_step2(struct connectdata *conn,
     }
 
     result = Curl_pin_peer_pubkey(data,
-                                  data->set.str[STRING_SSL_PINNEDPUBLICKEY],
+                                  pinnedpubkey,
                                   (const unsigned char *)pubkey->header,
                                   (size_t)(pubkey->end - pubkey->header));
     if(result) {
diff --git a/lib/vtls/gskit.c b/lib/vtls/gskit.c
index 6cac957..fccbe50 100644
--- a/lib/vtls/gskit.c
+++ b/lib/vtls/gskit.c
@@ -1096,7 +1096,8 @@ static CURLcode gskit_connect_step3(struct connectdata *conn, int sockindex)
   }
 
   /* Check pinned public key. */
-  ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+  ptr = SSL_IS_PROXY() ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+                         data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
   if(!result && ptr) {
     curl_X509certificate x509;
     curl_asn1Element *p;
diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c
index 4c9d9fa..c5d2155 100644
--- a/lib/vtls/gtls.c
+++ b/lib/vtls/gtls.c
@@ -1228,7 +1228,8 @@ gtls_connect_step3(struct connectdata *conn,
       infof(data, "\t server certificate activation date OK\n");
   }
 
-  ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+  ptr = SSL_IS_PROXY() ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+        data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
   if(ptr) {
     result = pkp_pin_peer_pubkey(data, x509_cert, ptr);
     if(result != CURLE_OK) {
diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c
index c84e597..aebe125 100644
--- a/lib/vtls/mbedtls.c
+++ b/lib/vtls/mbedtls.c
@@ -171,7 +171,6 @@ mbed_connect_step1(struct connectdata *conn,
   const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
     conn->host.name;
   const long int port = SSL_IS_PROXY() ? conn->port : conn->remote_port;
-
   int ret = -1;
   char errorbuf[128];
   errorbuf[0]=0;
@@ -453,6 +452,9 @@ mbed_connect_step2(struct connectdata *conn,
   struct Curl_easy *data = conn->data;
   struct ssl_connect_data* connssl = &conn->ssl[sockindex];
   const mbedtls_x509_crt *peercert;
+  const char * const pinnedpubkey = SSL_IS_PROXY() ?
+        data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+        data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
 
 #ifdef HAS_ALPN
   const char* next_protocol;
@@ -524,7 +526,7 @@ mbed_connect_step2(struct connectdata *conn,
     free(buffer);
   }
 
-  if(data->set.str[STRING_SSL_PINNEDPUBLICKEY]) {
+  if(pinnedpubkey) {
     int size;
     CURLcode result;
     mbedtls_x509_crt *p;
@@ -563,7 +565,7 @@ mbed_connect_step2(struct connectdata *conn,
 
     /* mbedtls_pk_write_pubkey_der writes data at the end of the buffer. */
     result = Curl_pin_peer_pubkey(data,
-                                  data->set.str[STRING_SSL_PINNEDPUBLICKEY],
+                                  pinnedpubkey,
                                   &pubkey[PUB_DER_MAX_BYTES - size], size);
     if(result) {
       mbedtls_x509_crt_free(p);
diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
index 2d84399..bb7e496 100644
--- a/lib/vtls/nss.c
+++ b/lib/vtls/nss.c
@@ -1926,6 +1926,10 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
   PRUint32 timeout;
   long * const certverifyresult = SSL_IS_PROXY() ?
     &data->set.proxy_ssl.certverifyresult : &data->set.ssl.certverifyresult;
+  const char * const pinnedpubkey = SSL_IS_PROXY() ?
+              data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+              data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
+
 
   /* check timeout situation */
   const long time_left = Curl_timeleft(data, NULL, TRUE);
@@ -1971,7 +1975,7 @@ static CURLcode nss_do_connect(struct connectdata *conn, int sockindex)
     }
   }
 
-  result = cmp_peer_pubkey(connssl, data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+  result = cmp_peer_pubkey(connssl, pinnedpubkey);
   if(result)
     /* status already printed */
     goto error;
diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index f6dd529..7b07f1d 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -2891,7 +2891,8 @@ static CURLcode servercert(struct connectdata *conn,
     /* when not strict, we don't bother about the verify cert problems */
     result = CURLE_OK;
 
-  ptr = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+  ptr = SSL_IS_PROXY() ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+                         data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
   if(!result && ptr) {
     result = pkp_pin_peer_pubkey(data, connssl->server_cert, ptr);
     if(result)
diff --git a/lib/vtls/polarssl.c b/lib/vtls/polarssl.c
index d6b0f23..2acc5ca 100644
--- a/lib/vtls/polarssl.c
+++ b/lib/vtls/polarssl.c
@@ -397,6 +397,10 @@ polarssl_connect_step2(struct connectdata *conn,
   struct Curl_easy *data = conn->data;
   struct ssl_connect_data* connssl = &conn->ssl[sockindex];
   char buffer[1024];
+  const char * const pinnedpubkey = SSL_IS_PROXY() ?
+            data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+            data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
+
 
   char errorbuf[128];
   errorbuf[0] = 0;
@@ -458,7 +462,7 @@ polarssl_connect_step2(struct connectdata *conn,
   }
 
   /* adapted from mbedtls.c */
-  if(data->set.str[STRING_SSL_PINNEDPUBLICKEY]) {
+  if(pinnedpubkey) {
     int size;
     CURLcode result;
     x509_crt *p;
@@ -500,7 +504,7 @@ polarssl_connect_step2(struct connectdata *conn,
 
     /* pk_write_pubkey_der writes data at the end of the buffer. */
     result = Curl_pin_peer_pubkey(data,
-                                  data->set.str[STRING_SSL_PINNEDPUBLICKEY],
+                                  pinnedpubkey,
                                   &pubkey[PUB_DER_MAX_BYTES - size], size);
     if(result) {
       x509_crt_free(p);
-- 
2.1.4
-------------------------------------------------------------------
List admin: https://cool.haxx.se/list/listinfo/curl-library
Etiquette:  https://curl.haxx.se/mail/etiquette.html
Received on 2016-11-24