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.
This commit is contained in:
committed by
Nick Zitzmann
parent
c6d5f80d8b
commit
cd2cedf002
@@ -28,6 +28,9 @@
|
|||||||
|
|
||||||
#include "curl_setup.h"
|
#include "curl_setup.h"
|
||||||
|
|
||||||
|
#include "urldata.h" /* for the SessionHandle definition */
|
||||||
|
#include "curl_base64.h"
|
||||||
|
|
||||||
#ifdef USE_DARWINSSL
|
#ifdef USE_DARWINSSL
|
||||||
|
|
||||||
#ifdef HAVE_LIMITS_H
|
#ifdef HAVE_LIMITS_H
|
||||||
@@ -1298,9 +1301,11 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
|
|||||||
#else
|
#else
|
||||||
if(SSLSetSessionOption != NULL) {
|
if(SSLSetSessionOption != NULL) {
|
||||||
#endif /* CURL_BUILD_MAC */
|
#endif /* CURL_BUILD_MAC */
|
||||||
|
bool break_on_auth = !data->set.ssl.verifypeer ||
|
||||||
|
data->set.str[STRING_SSL_CAFILE];
|
||||||
err = SSLSetSessionOption(connssl->ssl_ctx,
|
err = SSLSetSessionOption(connssl->ssl_ctx,
|
||||||
kSSLSessionOptionBreakOnServerAuth,
|
kSSLSessionOptionBreakOnServerAuth,
|
||||||
data->set.ssl.verifypeer?false:true);
|
break_on_auth);
|
||||||
if(err != noErr) {
|
if(err != noErr) {
|
||||||
failf(data, "SSL: SSLSetSessionOption() failed: OSStatus %d", err);
|
failf(data, "SSL: SSLSetSessionOption() failed: OSStatus %d", err);
|
||||||
return CURLE_SSL_CONNECT_ERROR;
|
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 */
|
#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.
|
/* Configure hostname check. SNI is used if available.
|
||||||
* Both hostname check and SNI require SSLSetPeerDomainName().
|
* Both hostname check and SNI require SSLSetPeerDomainName().
|
||||||
* Also: the verifyhost setting influences SNI usage */
|
* Also: the verifyhost setting influences SNI usage */
|
||||||
@@ -1505,6 +1524,211 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
|
|||||||
return CURLE_OK;
|
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
|
static CURLcode
|
||||||
darwinssl_connect_step2(struct connectdata *conn, int sockindex)
|
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
|
/* The below is errSSLServerAuthCompleted; it's not defined in
|
||||||
Leopard's headers */
|
Leopard's headers */
|
||||||
case -9841:
|
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 */
|
/* the documentation says we need to call SSLHandshake() again */
|
||||||
return darwinssl_connect_step2(conn, sockindex);
|
return darwinssl_connect_step2(conn, sockindex);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user