From db4e79037d2e98e4d99df538eeca4158ce90b496 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Mon, 17 Apr 2017 19:47:51 -0400 Subject: [PATCH] SecureTransport/DarwinSSL: Implement public key pinning --- docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 | 4 + lib/vtls/darwinssl.c | 156 ++++++++++++++++++++ lib/vtls/darwinssl.h | 24 +++ tests/runtests.pl | 1 + 4 files changed, 185 insertions(+) diff --git a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 index 1951c88a6..0dc1f7295 100644 --- a/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 +++ b/docs/libcurl/opts/CURLOPT_PINNEDPUBLICKEY.3 @@ -103,6 +103,8 @@ PEM/DER support: 7.49.0: PolarSSL + 7.55.0: SecureTransport/DarwinSSL on macOS 10.7+/iOS 10+ + sha256 support: 7.44.0: OpenSSL, GnuTLS, NSS and wolfSSL/CyaSSL @@ -111,6 +113,8 @@ sha256 support: 7.49.0: PolarSSL + 7.55.0: SecureTransport/DarwinSSL on macOS 10.7+/iOS 10+ + 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/darwinssl.c b/lib/vtls/darwinssl.c index 5533dfe2f..270b3ddfb 100644 --- a/lib/vtls/darwinssl.c +++ b/lib/vtls/darwinssl.c @@ -113,6 +113,36 @@ #define ioErr -36 #define paramErr -50 +#ifdef DARWIN_SSL_PINNEDPUBKEY +/* both new and old APIs return rsa keys missing the spki header (not DER) */ +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}; +#ifdef DARWIN_SSL_PINNEDPUBKEY_V1 +/* the *new* version doesn't return DER encoded ecdsa certs like the old... */ +static const unsigned char ecDsaSecp256r1SpkiHeader[] = { + 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, + 0x42, 0x00}; + +static const unsigned char ecDsaSecp384r1SpkiHeader[] = { + 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, + 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, + 0x00, 0x22, 0x03, 0x62, 0x00}; +#endif /* DARWIN_SSL_PINNEDPUBKEY_V1 */ +#endif /* DARWIN_SSL_PINNEDPUBKEY */ + /* The following two functions were ripped from Apple sample code, * with some modifications: */ static OSStatus SocketRead(SSLConnectionRef connection, @@ -1996,6 +2026,112 @@ static int verify_cert(const char *cafile, struct Curl_easy *data, } } +#ifdef DARWIN_SSL_PINNEDPUBKEY +static CURLcode pkp_pin_peer_pubkey(struct SessionHandle *data, + SSLContextRef ctx, + const char *pinnedpubkey) +{ /* Scratch */ + size_t pubkeylen, realpubkeylen, spkiHeaderLength = 24; + unsigned char *pubkey = NULL, *realpubkey = NULL, *spkiHeader = NULL; + CFDataRef publicKeyBits = 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; + + + if(!ctx) + return result; + + do { + SecTrustRef trust; + OSStatus ret = SSLCopyPeerTrust(ctx, &trust); + if(ret != noErr || trust == NULL) + break; + + SecKeyRef keyRef = SecTrustCopyPublicKey(trust); + CFRelease(trust); + if(keyRef == NULL) + break; + +#ifdef DARWIN_SSL_PINNEDPUBKEY_V1 + + publicKeyBits = SecKeyCopyExternalRepresentation(keyRef, NULL); + CFRelease(keyRef); + if(publicKeyBits == NULL) + break; + +#elif DARWIN_SSL_PINNEDPUBKEY_V2 + + OSStatus success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, + &publicKeyBits); + CFRelease(keyRef); + if(success != errSecSuccess || publicKeyBits == NULL) + break; + +#endif /* DARWIN_SSL_PINNEDPUBKEY_V2 */ + + pubkeylen = CFDataGetLength(publicKeyBits); + pubkey = CFDataGetBytePtr(publicKeyBits); + + switch(pubkeylen) { + case 526: + /* 4096 bit RSA pubkeylen == 526 */ + spkiHeader = rsa4096SpkiHeader; + break; + case 270: + /* 2048 bit RSA pubkeylen == 270 */ + spkiHeader = rsa2048SpkiHeader; + break; +#ifdef DARWIN_SSL_PINNEDPUBKEY_V1 + case 65: + /* ecDSA secp256r1 pubkeylen == 65 */ + spkiHeader = ecDsaSecp256r1SpkiHeader; + spkiHeaderLength = 26; + break; + case 97: + /* ecDSA secp384r1 pubkeylen == 97 */ + spkiHeader = ecDsaSecp384r1SpkiHeader; + spkiHeaderLength = 23; + break; + default: + infof(data, "SSL: unhandled public key length: %d\n", pubkeylen); +#elif DARWIN_SSL_PINNEDPUBKEY_V2 + default: + /* ecDSA secp256r1 pubkeylen == 91 header already included? + * ecDSA secp384r1 header already included too + * we assume rest of algorithms do same, so do nothing + */ + result = Curl_pin_peer_pubkey(data, pinnedpubkey, pubkey, + pubkeylen); +#endif /* DARWIN_SSL_PINNEDPUBKEY_V2 */ + continue; /* break from loop */ + } + + realpubkeylen = pubkeylen + spkiHeaderLength; + realpubkey = malloc(realpubkeylen); + if(!realpubkey) + break; + + memcpy(realpubkey, spkiHeader, spkiHeaderLength); + memcpy(realpubkey + spkiHeaderLength, pubkey, pubkeylen); + + result = Curl_pin_peer_pubkey(data, pinnedpubkey, realpubkey, + realpubkeylen); + + } while(0); + + Curl_safefree(realpubkey); + if(publicKeyBits != NULL) + CFRelease(publicKeyBits); + + return result; +} +#endif /* DARWIN_SSL_PINNEDPUBKEY */ + static CURLcode darwinssl_connect_step2(struct connectdata *conn, int sockindex) { @@ -2102,6 +2238,17 @@ darwinssl_connect_step2(struct connectdata *conn, int sockindex) /* we have been connected fine, we're not waiting for anything else. */ connssl->connecting_state = ssl_connect_3; +#ifdef DARWIN_SSL_PINNEDPUBKEY + if(data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]) { + CURLcode result = pkp_pin_peer_pubkey(data, connssl->ssl_ctx, + data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG]); + if(result) { + failf(data, "SSL: public key does not match pinned public key!"); + return result; + } + } +#endif /* DARWIN_SSL_PINNEDPUBKEY */ + /* Informational message */ (void)SSLGetNegotiatedCipher(connssl->ssl_ctx, &cipher); (void)SSLGetNegotiatedProtocolVersion(connssl->ssl_ctx, &protocol); @@ -2573,6 +2720,15 @@ void Curl_darwinssl_md5sum(unsigned char *tmp, /* input */ (void)CC_MD5(tmp, (CC_LONG)tmplen, md5sum); } +void Curl_darwinssl_sha256sum(unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum, /* output */ + size_t sha256len) +{ + assert(sha256len >= SHA256_DIGEST_LENGTH); + (void)CC_SHA256(tmp, (CC_LONG)tmplen, sha256sum); +} + bool Curl_darwinssl_false_start(void) { #if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 diff --git a/lib/vtls/darwinssl.h b/lib/vtls/darwinssl.h index 4bd41ca47..fd372ffa0 100644 --- a/lib/vtls/darwinssl.h +++ b/lib/vtls/darwinssl.h @@ -48,11 +48,34 @@ void Curl_darwinssl_md5sum(unsigned char *tmp, /* input */ size_t tmplen, unsigned char *md5sum, /* output */ size_t md5len); +void Curl_darwinssl_sha256sum(unsigned char *tmp, /* input */ + size_t tmplen, + unsigned char *sha256sum, /* output */ + size_t sha256len); bool Curl_darwinssl_false_start(void); /* Set the API backend definition to SecureTransport */ #define CURL_SSL_BACKEND CURLSSLBACKEND_DARWINSSL +/* pinned public key support tests */ + +/* version 1 supports macOS 10.12+ and iOS 10+ */ +#if ((TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000) || \ + (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)) +#define DARWIN_SSL_PINNEDPUBKEY_V1 1 +#endif + +/* version 2 supports MacOSX 10.7+ */ +#if (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) +#define DARWIN_SSL_PINNEDPUBKEY_V2 1 +#endif + +#if defined(DARWIN_SSL_PINNEDPUBKEY_V1) || defined(DARWIN_SSL_PINNEDPUBKEY_V2) +/* this backend supports CURLOPT_PINNEDPUBLICKEY */ +#define DARWIN_SSL_PINNEDPUBKEY 1 +#define have_curlssl_pinnedpubkey 1 +#endif /* DARWIN_SSL_PINNEDPUBKEY */ + /* API setup for SecureTransport */ #define curlssl_init() (1) #define curlssl_cleanup() Curl_nop_stmt @@ -70,6 +93,7 @@ bool Curl_darwinssl_false_start(void); #define curlssl_data_pending(x,y) Curl_darwinssl_data_pending(x, y) #define curlssl_random(x,y,z) ((void)x, Curl_darwinssl_random(y,z)) #define curlssl_md5sum(a,b,c,d) Curl_darwinssl_md5sum(a,b,c,d) +#define curlssl_sha256sum(a,b,c,d) Curl_darwinssl_sha256sum(a,b,c,d) #define curlssl_false_start() Curl_darwinssl_false_start() #endif /* USE_DARWINSSL */ diff --git a/tests/runtests.pl b/tests/runtests.pl index 90a29573a..b04fd378d 100755 --- a/tests/runtests.pl +++ b/tests/runtests.pl @@ -2412,6 +2412,7 @@ sub checksystem { } elsif ($libcurl =~ /securetransport/i) { $has_darwinssl=1; + $has_sslpinning=1; $ssllib="DarwinSSL"; } elsif ($libcurl =~ /BoringSSL/i) {