Unverified Commit e178fbd4 authored by Travis Burtrum's avatar Travis Burtrum Committed by Daniel Stenberg
Browse files

SChannel/WinSSL: Implement public key pinning

Closes #1429
parent e25025b9
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -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
+140 −3
Original line number Diff line number Diff line
@@ -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 */
+1 −0
Original line number Diff line number Diff line
@@ -2772,6 +2772,7 @@ sub checksystem {
            }
           if ($libcurl =~ /winssl/i) {
               $has_winssl=1;
               $has_sslpinning=1;
               $ssllib="WinSSL";
           }
           elsif ($libcurl =~ /openssl/i) {