aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormoparisthebest <admin@moparisthebest.com>2017-04-19 00:31:23 -0400
committerDaniel Stenberg <daniel@haxx.se>2018-01-25 22:14:39 +0100
commite178fbd40a896f2098278ae61e1166c88e7b31d0 (patch)
treec6cd43cc1d41283994426dd2950b20cf57aaf77d
parente25025b9f8f7f2cb37e584ed5c7b7a53f1392142 (diff)
SChannel/WinSSL: Implement public key pinning
Closes #1429
-rw-r--r--docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.36
-rw-r--r--lib/vtls/schannel.c143
-rwxr-xr-xtests/runtests.pl1
3 files changed, 146 insertions, 4 deletions
diff --git a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
index 47646474e..dd8ed4ffa 100644
--- a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
+++ b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3
@@ -5,7 +5,7 @@
.\" * | (__| |_| | _ <| |___
.\" * \___|\___/|_| \_\_____|
.\" *
-.\" * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@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
@@ -105,6 +105,8 @@ PEM/DER support:
7.54.1: SecureTransport/DarwinSSL on macOS 10.7+/iOS 10+
+ 7.58.1: SChannel/WinSSL
+
sha256 support:
7.44.0: OpenSSL, GnuTLS, NSS and wolfSSL/CyaSSL
@@ -115,6 +117,8 @@ sha256 support:
7.54.1: SecureTransport/DarwinSSL on macOS 10.7+/iOS 10+
+ 7.58.1: SChannel/WinSSL Windows XP SP3+
+
Other SSL backends not supported.
.SH RETURN VALUE
Returns CURLE_OK if TLS enabled, CURLE_UNKNOWN_OPTION if not, or
diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c
index 85c64cf44..6ae834c72 100644
--- a/lib/vtls/schannel.c
+++ b/lib/vtls/schannel.c
@@ -7,7 +7,7 @@
*
* Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
* Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
- * Copyright (C) 2012 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2012 - 2018, Daniel Stenberg, <daniel@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
@@ -129,6 +129,10 @@
* #define failf(x, y, ...) printf(y, __VA_ARGS__)
*/
+#ifndef CALG_SHA_256
+# define CALG_SHA_256 0x0000800c
+#endif
+
/* Structs to store Schannel handles */
struct curl_schannel_cred {
CredHandle cred_handle;
@@ -165,6 +169,9 @@ struct ssl_backend_data {
static Curl_recv schannel_recv;
static Curl_send schannel_send;
+static CURLcode pkp_pin_peer_pubkey(struct connectdata *conn, int sockindex,
+ const char *pinnedpubkey);
+
#ifdef _WIN32_WCE
static CURLcode verify_certificate(struct connectdata *conn, int sockindex);
#endif
@@ -542,6 +549,7 @@ schannel_connect_step2(struct connectdata *conn, int sockindex)
bool doread;
char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
+ const char *pubkey_ptr;
doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
@@ -761,6 +769,17 @@ schannel_connect_step2(struct connectdata *conn, int sockindex)
infof(data, "schannel: SSL/TLS handshake complete\n");
}
+ pubkey_ptr = SSL_IS_PROXY() ?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
+ if(pubkey_ptr) {
+ result = pkp_pin_peer_pubkey(conn, sockindex, pubkey_ptr);
+ if(result) {
+ failf(data, "SSL: public key does not match pinned public key!");
+ return result;
+ }
+ }
+
#ifdef _WIN32_WCE
/* Windows CE doesn't do any server certificate validation.
We have to do it manually. */
@@ -1669,6 +1688,68 @@ static CURLcode Curl_schannel_random(struct Curl_easy *data UNUSED_PARAM,
return CURLE_OK;
}
+static CURLcode pkp_pin_peer_pubkey(struct connectdata *conn, int sockindex,
+ const char *pinnedpubkey)
+{
+ SECURITY_STATUS status;
+ struct Curl_easy *data = conn->data;
+ struct ssl_connect_data *connssl = &conn->ssl[sockindex];
+ CERT_CONTEXT *pCertContextServer = NULL;
+ const char *x509_der;
+ int x509_der_len;
+ curl_X509certificate x509_parsed;
+ curl_asn1Element *pubkey;
+
+ /* Result is returned to caller */
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+ /* if a path wasn't specified, don't pin */
+ if(!pinnedpubkey)
+ return CURLE_OK;
+
+ do {
+ status = s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
+ SECPKG_ATTR_REMOTE_CERT_CONTEXT,
+ &pCertContextServer);
+
+ if((status != SEC_E_OK) || (pCertContextServer == NULL)) {
+ failf(data, "schannel: Failed to read remote certificate context: %s",
+ Curl_sspi_strerror(conn, status));
+ break; /* failed */
+ }
+
+
+ if(!(((pCertContextServer->dwCertEncodingType & X509_ASN_ENCODING) != 0) &&
+ (pCertContextServer->cbCertEncoded > 0)))
+ break;
+
+ x509_der = pCertContextServer->pbCertEncoded;
+ x509_der_len = pCertContextServer->cbCertEncoded;
+ memset(&x509_parsed, 0, sizeof x509_parsed);
+ if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len))
+ break;
+
+ pubkey = &x509_parsed.subjectPublicKeyInfo;
+ if(!pubkey->header || pubkey->end <= pubkey->header) {
+ failf(data, "SSL: failed retrieving public key from server certificate");
+ break;
+ }
+
+ result = Curl_pin_peer_pubkey(data,
+ pinnedpubkey,
+ (const unsigned char *)pubkey->header,
+ (size_t)(pubkey->end - pubkey->header));
+ if(result) {
+ failf(data, "SSL: public key does not match pinned public key!");
+ }
+ } while(0);
+
+ if(pCertContextServer)
+ CertFreeCertificateContext(pCertContextServer);
+
+ return result;
+}
+
#ifdef _WIN32_WCE
static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
{
@@ -1809,6 +1890,62 @@ static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
}
#endif /* _WIN32_WCE */
+static void Curl_schannel_checksum(const unsigned char *input,
+ size_t inputlen,
+ unsigned char *checksum,
+ size_t checksumlen,
+ const unsigned char *pszProvider,
+ const unsigned int algId)
+{
+ HCRYPTPROV hProv = 0;
+ HCRYPTHASH hHash = 0;
+ size_t cbHashSize = 0, dwCount = sizeof(size_t);
+
+ /* since this can fail in multiple ways, zero memory first so we never
+ * return old data
+ */
+ memset(checksum, 0, checksumlen);
+
+ if(!CryptAcquireContext(&hProv, NULL, NULL, pszProvider,
+ CRYPT_VERIFYCONTEXT))
+ return; /* failed */
+
+ do {
+ if(!CryptCreateHash(hProv, algId, 0, 0, &hHash))
+ break; /* failed */
+
+ if(!CryptHashData(hHash, (const BYTE*) input, inputlen, 0))
+ break; /* failed */
+
+ /* get hash size */
+ if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize,
+ &dwCount, 0))
+ break; /* failed */
+
+ /* check hash size */
+ if(checksumlen < cbHashSize)
+ break; /* failed */
+
+ if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &checksumlen, 0))
+ break; /* failed */
+ } while(0);
+
+ if(hHash)
+ CryptDestroyHash(hHash);
+
+ if(hProv)
+ CryptReleaseContext(hProv, 0);
+}
+
+void Curl_schannel_sha256sum(unsigned char *input,
+ size_t inputlen,
+ unsigned char *sha256sum,
+ size_t sha256len)
+{
+ Curl_schannel_checksum(input, inputlen, sha256sum, sha256len,
+ PROV_RSA_AES, CALG_SHA_256);
+}
+
static void *Curl_schannel_get_internals(struct ssl_connect_data *connssl,
CURLINFO info UNUSED_PARAM)
{
@@ -1821,7 +1958,7 @@ const struct Curl_ssl Curl_ssl_schannel = {
0, /* have_ca_path */
1, /* have_certinfo */
- 0, /* have_pinnedpubkey */
+ 1, /* have_pinnedpubkey */
0, /* have_ssl_ctx */
0, /* support_https_proxy */
@@ -1846,7 +1983,7 @@ const struct Curl_ssl Curl_ssl_schannel = {
Curl_none_engines_list, /* engines_list */
Curl_none_false_start, /* false_start */
Curl_none_md5sum, /* md5sum */
- NULL /* sha256sum */
+ Curl_schannel_sha256sum /* sha256sum */
};
#endif /* USE_SCHANNEL */
diff --git a/tests/runtests.pl b/tests/runtests.pl
index d68c20f19..d6aa5cac6 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -2772,6 +2772,7 @@ sub checksystem {
}
if ($libcurl =~ /winssl/i) {
$has_winssl=1;
+ $has_sslpinning=1;
$ssllib="WinSSL";
}
elsif ($libcurl =~ /openssl/i) {