SChannel/WinSSL: Implement public key pinning

This commit is contained in:
Travis Burtrum 2017-04-19 00:31:23 -04:00
parent 33cfcfd9f0
commit 1d49e25d26
4 changed files with 265 additions and 0 deletions

View File

@ -103,6 +103,8 @@ PEM/DER support:
7.49.0: PolarSSL
7.55.0: SChannel/WinSSL
sha256 support:
7.44.0: OpenSSL, GnuTLS, NSS and wolfSSL/CyaSSL
@ -111,6 +113,8 @@ sha256 support:
7.49.0: PolarSSL
7.55.0: 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

View File

@ -79,9 +79,32 @@
* #define failf(x, y, ...) printf(y, __VA_ARGS__)
*/
/* APIs return rsa keys missing the spki header (not DER) */
static const size_t spkiHeaderLength = 24;
static const unsigned char rsa4096SpkiHeader[] = {
0x30, 0x82, 0x02, 0x22, 0x30, 0x0d,
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
0x00, 0x03, 0x82, 0x02, 0x0f, 0x00};
static const unsigned char rsa2048SpkiHeader[] = {
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
0x00, 0x03, 0x82, 0x01, 0x0f, 0x00};
/* define these here if they aren't already */
#ifndef CALG_SHA_256
#define CALG_SHA_256 0x0000800c
#endif
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
@ -458,6 +481,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;
@ -672,6 +696,16 @@ 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. */
@ -1576,6 +1610,159 @@ CURLcode Curl_schannel_random(unsigned char *entropy, size_t length)
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 CERT_CHAIN_CONTEXT *pChainContext = NULL;
HCRYPTPROV hCryptProv = 0;
HCRYPTKEY hCertPubKey = NULL;
size_t bloblen, pubkeylen, realpubkeylen;
unsigned char *blob = NULL, *pubkey = NULL, *realpubkey = NULL,
*spkiHeader = NULL;
/* 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(&connssl->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(!CryptAcquireContext(&hCryptProv, NULL, NULL,
/*PROV_RSA_FULL,*/
PROV_RSA_AES,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
failf(data, "schannel: Failed to acquire crypto context: %s",
Curl_sspi_strerror(conn, GetLastError()));
break; /* failed */
}
/* Get the public key information for the certificate.
This works for RSA keys, but secp256r1 and secp384r1 gives
this error:
Unknown error (0x8009310B) - ASN1 bad tag value met.
*/
if(!CryptImportPublicKeyInfo(
hCryptProv,
X509_ASN_ENCODING,
&pCertContextServer->pCertInfo->SubjectPublicKeyInfo,
&hCertPubKey)) {
failf(data, "schannel: Failed to import public key info: %s",
Curl_sspi_strerror(conn, GetLastError()));
break; /* failed */
}
/* export to blob */
if(!CryptExportKey(hCertPubKey, 0, PUBLICKEYBLOB, 0, 0, &bloblen)) {
failf(data, "schannel: Failed to get public key blob length: %s",
Curl_sspi_strerror(conn, GetLastError()));
break; /* failed */
}
blob = malloc(bloblen);
if(!blob)
break; /* failed */
if(!CryptExportKey(hCertPubKey, 0, PUBLICKEYBLOB, 0, blob, &bloblen)) {
failf(data, "schannel: Failed to export public key to blob: %s",
Curl_sspi_strerror(conn, GetLastError()));
break; /* failed */
}
/* export to der
not sure what second arg should be,
X509_PUBLIC_KEY_INFO -- fails
RSA_CSP_PUBLICKEYBLOB -- not quite der, missing spki header bytes
CNG_RSA_PUBLIC_KEY_BLOB -- fails
PKCS_CONTENT_INFO -- fails
X509_SEQUENCE_OF_ANY - fails
CRYPT_STRING_BASE64HEADER - fails
*/
if(!CryptEncodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, blob, 0,
NULL, NULL, &pubkeylen)) {
failf(data, "schannel: Failed to get public key der length: %s",
Curl_sspi_strerror(conn, GetLastError()));
break;
}
pubkey = malloc(pubkeylen);
if(!pubkey)
break;
if(!CryptEncodeObjectEx(X509_ASN_ENCODING, RSA_CSP_PUBLICKEYBLOB, blob, 0,
NULL, pubkey, &pubkeylen)) {
failf(data, "schannel: Failed to export public key to der: %s",
Curl_sspi_strerror(conn, GetLastError()));
break;
}
FILE *fp;
fp=fopen("windows.key", "wb");
fwrite(pubkey, sizeof(pubkey[0]), pubkeylen, fp);
fclose(fp);
switch(pubkeylen) {
case 526:
/* 4096 bit RSA pubkeylen == 526 */
spkiHeader = rsa4096SpkiHeader;
break;
case 270:
/* 2048 bit RSA pubkeylen == 270 */
spkiHeader = rsa2048SpkiHeader;
break;
default:
infof(data, "SSL: unhandled public key length: %d\n", pubkeylen);
continue; /* break from loop */
}
realpubkeylen = pubkeylen + spkiHeaderLength;
realpubkey = malloc(realpubkeylen);
if(!realpubkey)
break;
memcpy(realpubkey, spkiHeader, spkiHeaderLength);
memcpy(realpubkey + spkiHeaderLength, pubkey, pubkeylen);
fp=fopen("windows.real.key", "wb");
fwrite(realpubkey, sizeof(realpubkey[0]), realpubkeylen, fp);
fclose(fp);
/* The one good exit point */
result = Curl_pin_peer_pubkey(data, pinnedpubkey, realpubkey,
realpubkeylen);
} while(0);
if(pChainContext)
CertFreeCertificateChain(pChainContext);
if(pCertContextServer)
CertFreeCertificateContext(pCertContextServer);
if(hCryptProv != 0)
CryptReleaseContext(hCryptProv, 0UL);
Curl_safefree(blob);
Curl_safefree(pubkey);
Curl_safefree(realpubkey);
return result;
}
#ifdef _WIN32_WCE
static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
{
@ -1716,4 +1903,69 @@ 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_md5sum(unsigned char *input,
size_t inputlen,
unsigned char *md5sum,
size_t md5len)
{
Curl_schannel_checksum(input, inputlen, md5sum, md5len,
PROV_RSA_FULL, CALG_MD5);
}
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);
}
#endif /* USE_SCHANNEL */

View File

@ -93,6 +93,10 @@ void Curl_schannel_cleanup(void);
size_t Curl_schannel_version(char *buffer, size_t size);
CURLcode Curl_schannel_random(unsigned char *entropy, size_t length);
void Curl_schannel_sha256sum(unsigned char *tmp, /* input */
size_t tmplen,
unsigned char *sha256sum, /* output */
size_t sha256len);
/* Set the API backend definition to Schannel */
#define CURL_SSL_BACKEND CURLSSLBACKEND_SCHANNEL
@ -100,6 +104,9 @@ CURLcode Curl_schannel_random(unsigned char *entropy, size_t length);
/* this backend supports CURLOPT_CERTINFO */
#define have_curlssl_certinfo 1
/* this backend supports CURLOPT_PINNEDPUBLICKEY */
#define have_curlssl_pinnedpubkey 1
/* API setup for Schannel */
#define curlssl_init Curl_schannel_init
#define curlssl_cleanup Curl_schannel_cleanup
@ -116,6 +123,7 @@ CURLcode Curl_schannel_random(unsigned char *entropy, size_t length);
#define curlssl_check_cxn(x) ((void)x, -1)
#define curlssl_data_pending Curl_schannel_data_pending
#define curlssl_random(x,y,z) ((void)x, Curl_schannel_random(y,z))
#define curlssl_sha256sum(a,b,c,d) Curl_schannel_sha256sum(a,b,c,d)
#endif /* USE_SCHANNEL */
#endif /* HEADER_CURL_SCHANNEL_H */

View File

@ -2379,6 +2379,7 @@ sub checksystem {
}
if ($libcurl =~ /winssl/i) {
$has_winssl=1;
$has_sslpinning=1;
$ssllib="WinSSL";
}
elsif ($libcurl =~ /openssl/i) {