aboutsummaryrefslogtreecommitdiff
path: root/lib/vtls/curl_darwinssl.c
diff options
context:
space:
mode:
authorVilmos Nebehaj <v.nebehaj@gmail.com>2014-04-17 07:03:05 -0700
committerNick Zitzmann <nickzman@gmail.com>2014-05-21 18:48:14 -0500
commitcd2cedf002a7639fbb6295a2f9838bc99d4a0bf7 (patch)
treed5018777c241a4ae26ef9455fe01569df14bef87 /lib/vtls/curl_darwinssl.c
parentc6d5f80d8b6ec795a3f88833d6f7c471fe8f2b4c (diff)
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.
Diffstat (limited to 'lib/vtls/curl_darwinssl.c')
-rw-r--r--lib/vtls/curl_darwinssl.c232
1 files changed, 231 insertions, 1 deletions
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);