From cd2cedf002a7639fbb6295a2f9838bc99d4a0bf7 Mon Sep 17 00:00:00 2001 From: Vilmos Nebehaj Date: Thu, 17 Apr 2014 07:03:05 -0700 Subject: Add support for --cacert in DarwinSSL. Security Framework on OS X makes it possible to supply extra anchor (CA) certificates via the Certificate, Key, and Trust Services API. This commit makes the '--cacert' option work using this API. More information: https://developer.apple.com/library/mac/documentation/security/Reference/certifkeytrustservices/Reference/reference.html The HTTPS tests now pass on OS X except 314, which requires the '--crl' option to work. --- lib/vtls/curl_darwinssl.c | 232 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 231 insertions(+), 1 deletion(-) (limited to 'lib/vtls') diff --git a/lib/vtls/curl_darwinssl.c b/lib/vtls/curl_darwinssl.c index 5be6a2ead..cc19a7c61 100644 --- a/lib/vtls/curl_darwinssl.c +++ b/lib/vtls/curl_darwinssl.c @@ -28,6 +28,9 @@ #include "curl_setup.h" +#include "urldata.h" /* for the SessionHandle definition */ +#include "curl_base64.h" + #ifdef USE_DARWINSSL #ifdef HAVE_LIMITS_H @@ -1298,9 +1301,11 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn, #else if(SSLSetSessionOption != NULL) { #endif /* CURL_BUILD_MAC */ + bool break_on_auth = !data->set.ssl.verifypeer || + data->set.str[STRING_SSL_CAFILE]; err = SSLSetSessionOption(connssl->ssl_ctx, kSSLSessionOptionBreakOnServerAuth, - data->set.ssl.verifypeer?false:true); + break_on_auth); if(err != noErr) { failf(data, "SSL: SSLSetSessionOption() failed: OSStatus %d", err); return CURLE_SSL_CONNECT_ERROR; @@ -1325,6 +1330,20 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn, } #endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ + if(data->set.str[STRING_SSL_CAFILE]) { + bool is_cert_file = is_file(data->set.str[STRING_SSL_CAFILE]); + if (!is_cert_file) { + failf(data, "SSL: can't load CA certificate file %s", + data->set.str[STRING_SSL_CAFILE]); + return CURLE_SSL_CACERT_BADFILE; + } + if (!data->set.ssl.verifypeer) { + failf(data, "SSL: CA certificate set, but certificate verification " + "is disabled"); + return CURLE_SSL_CONNECT_ERROR; + } + } + /* Configure hostname check. SNI is used if available. * Both hostname check and SNI require SSLSetPeerDomainName(). * Also: the verifyhost setting influences SNI usage */ @@ -1505,6 +1524,211 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn, return CURLE_OK; } +static int pem_to_der(const char *in, unsigned char **out, size_t *outlen) +{ + char *sep, *start, *end; + int i, j, err; + size_t len; + unsigned char *b64; + + /* Jump through the separators in the first line. */ + sep = strstr(in, "-----"); + if (sep == NULL) + return -1; + sep = strstr(sep + 1, "-----"); + if (sep == NULL) + return -1; + + start = sep + 5; + + /* Find beginning of last line separator. */ + end = strstr(start, "-----"); + if (end == NULL) + return -1; + + len = end - start; + *out = malloc(len); + if (!*out) + return -1; + + b64 = malloc(len + 1); + if (!b64) { + free(*out); + return -1; + } + + /* Create base64 string without linefeeds. */ + for (i = 0, j = 0; i < len; i++) { + if (start[i] != '\r' && start[i] != '\n') + b64[j++] = start[i]; + } + b64[j] = '\0'; + + err = (int)Curl_base64_decode((const char *)b64, out, outlen); + free(b64); + if (err) { + free(*out); + return -1; + } + + return 0; +} + +static int read_cert(const char *file, unsigned char **out, size_t *outlen) +{ + int fd, ret, n, len = 0, cap = 512; + size_t derlen; + unsigned char buf[cap], *data, *der; + + fd = open(file, 0); + if (fd < 0) + return -1; + + data = malloc(cap); + if (!data) { + close(fd); + return -1; + } + + for (;;) { + n = read(fd, buf, sizeof(buf)); + if (n < 0) { + close(fd); + free(data); + return -1; + } else if (n == 0) { + close(fd); + break; + } + + if (len + n >= cap) { + cap *= 2; + data = realloc(data, cap); + if (!data) { + close(fd); + return -1; + } + } + + memcpy(data + len, buf, n); + len += n; + } + data[len] = '\0'; + + /* + * Check if the certificate is in PEM format, and convert it to DER. If this + * fails, we assume the certificate is in DER format. + */ + if (pem_to_der((const char *)data, &der, &derlen) == 0) { + free(data); + data = der; + len = derlen; + } + + *out = data; + *outlen = len; + + return 0; +} + +static int sslerr_to_curlerr(struct SessionHandle *data, int err) +{ + switch(err) { + case errSSLXCertChainInvalid: + failf(data, "SSL certificate problem: Invalid certificate chain"); + return CURLE_SSL_CACERT; + case errSSLUnknownRootCert: + failf(data, "SSL certificate problem: Untrusted root certificate"); + return CURLE_SSL_CACERT; + case errSSLNoRootCert: + failf(data, "SSL certificate problem: No root certificate"); + return CURLE_SSL_CACERT; + case errSSLCertExpired: + failf(data, "SSL certificate problem: Certificate chain had an " + "expired certificate"); + return CURLE_SSL_CACERT; + case errSSLBadCert: + failf(data, "SSL certificate problem: Couldn't understand the server " + "certificate format"); + return CURLE_SSL_CONNECT_ERROR; + case errSSLHostNameMismatch: + failf(data, "SSL certificate peer hostname mismatch"); + return CURLE_PEER_FAILED_VERIFICATION; + default: + failf(data, "SSL unexpected certificate error %d", err); + return CURLE_SSL_CACERT; + } +} + +static int verify_cert(const char *cafile, struct SessionHandle *data, + SSLContextRef ctx) +{ + unsigned char *certbuf; + size_t buflen; + if (read_cert(cafile, &certbuf, &buflen) < 0) { + failf(data, "SSL: failed to read or invalid CA certificate"); + return CURLE_SSL_CACERT; + } + + CFDataRef certdata = CFDataCreate(kCFAllocatorDefault, certbuf, buflen); + free(certbuf); + if (!certdata) { + failf(data, "SSL: failed to allocate array for CA certificate"); + return CURLE_OUT_OF_MEMORY; + } + + SecCertificateRef cacert = SecCertificateCreateWithData(kCFAllocatorDefault, + certdata); + CFRelease(certdata); + if (!cacert) { + failf(data, "SSL: failed to create SecCertificate from CA certificate"); + return CURLE_SSL_CACERT; + } + + SecTrustRef trust; + OSStatus ret = SSLCopyPeerTrust(ctx, &trust); + if (trust == NULL) { + failf(data, "SSL: error getting certificate chain"); + return CURLE_OUT_OF_MEMORY; + } else if (ret != noErr) { + return sslerr_to_curlerr(data, ret); + } + + CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeArrayCallBacks); + CFArrayAppendValue(array, cacert); + CFRelease(cacert); + + ret = SecTrustSetAnchorCertificates(trust, array); + if (ret != noErr) { + CFRelease(trust); + return sslerr_to_curlerr(data, ret); + } + + SecTrustResultType trust_eval = 0; + ret = SecTrustEvaluate(trust, &trust_eval); + CFRelease(array); + CFRelease(trust); + if (ret != noErr) { + return sslerr_to_curlerr(data, ret); + } + + switch (trust_eval) { + case kSecTrustResultUnspecified: + case kSecTrustResultProceed: + infof(data, "SSL: certificate verification succeeded (result: %d)", + trust_eval); + return CURLE_OK; + + case kSecTrustResultRecoverableTrustFailure: + case kSecTrustResultDeny: + default: + failf(data, "SSL: certificate verification failed (result: %d)", + trust_eval); + return CURLE_PEER_FAILED_VERIFICATION; + } +} + static CURLcode darwinssl_connect_step2(struct connectdata *conn, int sockindex) { @@ -1531,6 +1755,12 @@ darwinssl_connect_step2(struct connectdata *conn, int sockindex) /* The below is errSSLServerAuthCompleted; it's not defined in Leopard's headers */ case -9841: + if(data->set.str[STRING_SSL_CAFILE]) { + int res = verify_cert(data->set.str[STRING_SSL_CAFILE], data, + connssl->ssl_ctx); + if (res != CURLE_OK) + return res; + } /* the documentation says we need to call SSLHandshake() again */ return darwinssl_connect_step2(conn, sockindex); -- cgit v1.2.3