curl: Backport upstream fix for SecureTransport on macOS

Backport upstream curl commit `16bb32e104d` (sectransp: fix for
incomplete read/writes, 2023-01-05) to fix TLS support on macOS.

Fixes: #24398
This commit is contained in:
Brad King
2023-02-09 16:54:25 -05:00
parent 29bcbcab4f
commit 0d312728eb
+88 -40
View File
@@ -136,6 +136,15 @@
/* The last #include file should be: */ /* The last #include file should be: */
#include "memdebug.h" #include "memdebug.h"
#define DEBUG_CF 0
#if DEBUG_CF
#define CF_DEBUGF(x) x
#else
#define CF_DEBUGF(x) do { } while(0)
#endif
/* From MacTypes.h (which we can't include because it isn't present in iOS: */ /* From MacTypes.h (which we can't include because it isn't present in iOS: */
#define ioErr -36 #define ioErr -36
#define paramErr -50 #define paramErr -50
@@ -838,6 +847,8 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection,
DEBUGASSERT(data); DEBUGASSERT(data);
nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result); nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result);
CF_DEBUGF(infof(data, CFMSG(cf, "bio_read(len=%zu) -> %zd, result=%d"),
*dataLength, nread, result));
if(nread < 0) { if(nread < 0) {
switch(result) { switch(result) {
case CURLE_OK: case CURLE_OK:
@@ -851,6 +862,9 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection,
} }
nread = 0; nread = 0;
} }
else if((size_t)nread < *dataLength) {
rtn = errSSLWouldBlock;
}
*dataLength = nread; *dataLength = nread;
return rtn; return rtn;
} }
@@ -865,22 +879,27 @@ static OSStatus bio_cf_out_write(SSLConnectionRef connection,
struct Curl_easy *data = connssl->call_data; struct Curl_easy *data = connssl->call_data;
ssize_t nwritten; ssize_t nwritten;
CURLcode result; CURLcode result;
OSStatus ortn = noErr; OSStatus rtn = noErr;
DEBUGASSERT(data); DEBUGASSERT(data);
nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result); nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result);
CF_DEBUGF(infof(data, CFMSG(cf, "bio_send(len=%zu) -> %zd, result=%d"),
*dataLength, nwritten, result));
if(nwritten <= 0) { if(nwritten <= 0) {
if(result == CURLE_AGAIN) { if(result == CURLE_AGAIN) {
ortn = errSSLWouldBlock; rtn = errSSLWouldBlock;
backend->ssl_direction = true; backend->ssl_direction = true;
} }
else { else {
ortn = ioErr; rtn = ioErr;
} }
nwritten = 0; nwritten = 0;
} }
else if((size_t)nwritten < *dataLength) {
rtn = errSSLWouldBlock;
}
*dataLength = nwritten; *dataLength = nwritten;
return ortn; return rtn;
} }
#ifndef CURL_DISABLE_VERBOSE_STRINGS #ifndef CURL_DISABLE_VERBOSE_STRINGS
@@ -1638,6 +1657,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
DEBUGASSERT(backend); DEBUGASSERT(backend);
CF_DEBUGF(infof(data, CFMSG(cf, "connect_step1")));
GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); GetDarwinVersionNumber(&darwinver_maj, &darwinver_min);
#endif /* CURL_BUILD_MAC */ #endif /* CURL_BUILD_MAC */
@@ -2231,7 +2251,8 @@ static int append_cert_to_array(struct Curl_easy *data,
return CURLE_OK; return CURLE_OK;
} }
static CURLcode verify_cert_buf(struct Curl_easy *data, static CURLcode verify_cert_buf(struct Curl_cfilter *cf,
struct Curl_easy *data,
const unsigned char *certbuf, size_t buflen, const unsigned char *certbuf, size_t buflen,
SSLContextRef ctx) SSLContextRef ctx)
{ {
@@ -2239,7 +2260,12 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
long res; long res;
unsigned char *der; unsigned char *der;
size_t derlen, offset = 0; size_t derlen, offset = 0;
OSStatus ret;
SecTrustResultType trust_eval;
CFMutableArrayRef array = NULL;
SecTrustRef trust = NULL;
CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
(void)cf;
/* /*
* Certbuf now contains the contents of the certificate file, which can be * Certbuf now contains the contents of the certificate file, which can be
* - a single DER certificate, * - a single DER certificate,
@@ -2249,11 +2275,11 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
* Go through certbuf, and convert any PEM certificate in it into DER * Go through certbuf, and convert any PEM certificate in it into DER
* format. * format.
*/ */
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
&kCFTypeArrayCallBacks);
if(!array) { if(!array) {
failf(data, "SSL: out of memory creating CA certificate array"); failf(data, "SSL: out of memory creating CA certificate array");
return CURLE_OUT_OF_MEMORY; result = CURLE_OUT_OF_MEMORY;
goto out;
} }
while(offset < buflen) { while(offset < buflen) {
@@ -2265,10 +2291,10 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
*/ */
res = pem_to_der((const char *)certbuf + offset, &der, &derlen); res = pem_to_der((const char *)certbuf + offset, &der, &derlen);
if(res < 0) { if(res < 0) {
CFRelease(array);
failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle", failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle",
n, offset); n, offset);
return CURLE_SSL_CACERT_BADFILE; result = CURLE_SSL_CACERT_BADFILE;
goto out;
} }
offset += res; offset += res;
@@ -2276,8 +2302,9 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
/* This is not a PEM file, probably a certificate in DER format. */ /* This is not a PEM file, probably a certificate in DER format. */
rc = append_cert_to_array(data, certbuf, buflen, array); rc = append_cert_to_array(data, certbuf, buflen, array);
if(rc != CURLE_OK) { if(rc != CURLE_OK) {
CFRelease(array); CF_DEBUGF(infof(data, CFMSG(cf, "append_cert for CA failed")));
return rc; result = rc;
goto out;
} }
break; break;
} }
@@ -2289,63 +2316,73 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
rc = append_cert_to_array(data, der, derlen, array); rc = append_cert_to_array(data, der, derlen, array);
free(der); free(der);
if(rc != CURLE_OK) { if(rc != CURLE_OK) {
CFRelease(array); CF_DEBUGF(infof(data, CFMSG(cf, "append_cert for CA failed")));
return rc; result = rc;
goto out;
} }
} }
SecTrustRef trust; ret = SSLCopyPeerTrust(ctx, &trust);
OSStatus ret = SSLCopyPeerTrust(ctx, &trust);
if(!trust) { if(!trust) {
failf(data, "SSL: error getting certificate chain"); failf(data, "SSL: error getting certificate chain");
CFRelease(array); goto out;
return CURLE_PEER_FAILED_VERIFICATION;
} }
else if(ret != noErr) { else if(ret != noErr) {
CFRelease(array);
failf(data, "SSLCopyPeerTrust() returned error %d", ret); failf(data, "SSLCopyPeerTrust() returned error %d", ret);
return CURLE_PEER_FAILED_VERIFICATION; goto out;
} }
CF_DEBUGF(infof(data, CFMSG(cf, "setting %d trust anchors"), n));
ret = SecTrustSetAnchorCertificates(trust, array); ret = SecTrustSetAnchorCertificates(trust, array);
if(ret != noErr) { if(ret != noErr) {
CFRelease(array);
CFRelease(trust);
failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret); failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret);
return CURLE_PEER_FAILED_VERIFICATION; goto out;
} }
ret = SecTrustSetAnchorCertificatesOnly(trust, true); ret = SecTrustSetAnchorCertificatesOnly(trust, true);
if(ret != noErr) { if(ret != noErr) {
CFRelease(array);
CFRelease(trust);
failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret); failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret);
return CURLE_PEER_FAILED_VERIFICATION; goto out;
} }
SecTrustResultType trust_eval = 0; trust_eval = 0;
ret = SecTrustEvaluate(trust, &trust_eval); ret = SecTrustEvaluate(trust, &trust_eval);
CFRelease(array);
CFRelease(trust);
if(ret != noErr) { if(ret != noErr) {
failf(data, "SecTrustEvaluate() returned error %d", ret); failf(data, "SecTrustEvaluate() returned error %d", ret);
return CURLE_PEER_FAILED_VERIFICATION; goto out;
} }
switch(trust_eval) { switch(trust_eval) {
case kSecTrustResultUnspecified: case kSecTrustResultUnspecified:
/* what does this really mean? */
CF_DEBUGF(infof(data, CFMSG(cf, "trust result: Unspecified")));
result = CURLE_OK;
goto out;
case kSecTrustResultProceed: case kSecTrustResultProceed:
return CURLE_OK; CF_DEBUGF(infof(data, CFMSG(cf, "trust result: Proceed")));
result = CURLE_OK;
goto out;
case kSecTrustResultRecoverableTrustFailure: case kSecTrustResultRecoverableTrustFailure:
failf(data, "SSL: peer not verified: RecoverableTrustFailure");
goto out;
case kSecTrustResultDeny: case kSecTrustResultDeny:
failf(data, "SSL: peer not verified: Deny");
goto out;
default: default:
failf(data, "SSL: certificate verification failed (result: %d)", failf(data, "SSL: perr not verified: result=%d", trust_eval);
trust_eval); goto out;
return CURLE_PEER_FAILED_VERIFICATION;
} }
out:
if(trust)
CFRelease(trust);
if(array)
CFRelease(array);
return result;
} }
static CURLcode verify_cert(struct Curl_easy *data, const char *cafile, static CURLcode verify_cert(struct Curl_cfilter *cf,
struct Curl_easy *data, const char *cafile,
const struct curl_blob *ca_info_blob, const struct curl_blob *ca_info_blob,
SSLContextRef ctx) SSLContextRef ctx)
{ {
@@ -2354,6 +2391,7 @@ static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
size_t buflen; size_t buflen;
if(ca_info_blob) { if(ca_info_blob) {
CF_DEBUGF(infof(data, CFMSG(cf, "verify_peer, CA from config blob")));
certbuf = (unsigned char *)malloc(ca_info_blob->len + 1); certbuf = (unsigned char *)malloc(ca_info_blob->len + 1);
if(!certbuf) { if(!certbuf) {
return CURLE_OUT_OF_MEMORY; return CURLE_OUT_OF_MEMORY;
@@ -2363,6 +2401,8 @@ static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
certbuf[ca_info_blob->len]='\0'; certbuf[ca_info_blob->len]='\0';
} }
else if(cafile) { else if(cafile) {
CF_DEBUGF(infof(data, CFMSG(cf, "verify_peer, CA from file '%s'"),
cafile));
if(read_cert(cafile, &certbuf, &buflen) < 0) { if(read_cert(cafile, &certbuf, &buflen) < 0) {
failf(data, "SSL: failed to read or invalid CA certificate"); failf(data, "SSL: failed to read or invalid CA certificate");
return CURLE_SSL_CACERT_BADFILE; return CURLE_SSL_CACERT_BADFILE;
@@ -2371,7 +2411,7 @@ static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
else else
return CURLE_SSL_CACERT_BADFILE; return CURLE_SSL_CACERT_BADFILE;
result = verify_cert_buf(data, certbuf, buflen, ctx); result = verify_cert_buf(cf, data, certbuf, buflen, ctx);
free(certbuf); free(certbuf);
return result; return result;
} }
@@ -2498,8 +2538,10 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf,
|| ssl_connect_2_reading == connssl->connecting_state || ssl_connect_2_reading == connssl->connecting_state
|| ssl_connect_2_writing == connssl->connecting_state); || ssl_connect_2_writing == connssl->connecting_state);
DEBUGASSERT(backend); DEBUGASSERT(backend);
CF_DEBUGF(infof(data, CFMSG(cf, "connect_step2")));
/* Here goes nothing: */ /* Here goes nothing: */
check_handshake:
err = SSLHandshake(backend->ssl_ctx); err = SSLHandshake(backend->ssl_ctx);
if(err != noErr) { if(err != noErr) {
@@ -2514,14 +2556,14 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf,
case -9841: case -9841:
if((conn_config->CAfile || conn_config->ca_info_blob) && if((conn_config->CAfile || conn_config->ca_info_blob) &&
conn_config->verifypeer) { conn_config->verifypeer) {
CURLcode result = verify_cert(data, conn_config->CAfile, CURLcode result = verify_cert(cf, data, conn_config->CAfile,
conn_config->ca_info_blob, conn_config->ca_info_blob,
backend->ssl_ctx); backend->ssl_ctx);
if(result) if(result)
return result; return result;
} }
/* the documentation says we need to call SSLHandshake() again */ /* the documentation says we need to call SSLHandshake() again */
return sectransp_connect_step2(cf, data); goto check_handshake;
/* Problem with encrypt / decrypt */ /* Problem with encrypt / decrypt */
case errSSLPeerDecodeError: case errSSLPeerDecodeError:
@@ -2961,6 +3003,7 @@ static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf,
{ {
struct ssl_connect_data *connssl = cf->ctx; struct ssl_connect_data *connssl = cf->ctx;
CF_DEBUGF(infof(data, CFMSG(cf, "connect_step3")));
/* There is no step 3! /* There is no step 3!
* Well, okay, let's collect server certificates, and if verbose mode is on, * Well, okay, let's collect server certificates, and if verbose mode is on,
* let's print the details of the server certificates. */ * let's print the details of the server certificates. */
@@ -3069,6 +3112,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data,
} }
if(ssl_connect_done == connssl->connecting_state) { if(ssl_connect_done == connssl->connecting_state) {
CF_DEBUGF(infof(data, CFMSG(cf, "connected")));
connssl->state = ssl_connection_complete; connssl->state = ssl_connection_complete;
*done = TRUE; *done = TRUE;
} }
@@ -3114,6 +3158,7 @@ static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data)
DEBUGASSERT(backend); DEBUGASSERT(backend);
if(backend->ssl_ctx) { if(backend->ssl_ctx) {
CF_DEBUGF(infof(data, CFMSG(cf, "close")));
(void)SSLClose(backend->ssl_ctx); (void)SSLClose(backend->ssl_ctx);
#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
if(SSLCreateContext) if(SSLCreateContext)
@@ -3157,6 +3202,7 @@ static int sectransp_shutdown(struct Curl_cfilter *cf,
what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], SSL_SHUTDOWN_TIMEOUT); what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], SSL_SHUTDOWN_TIMEOUT);
CF_DEBUGF(infof(data, CFMSG(cf, "shutdown")));
while(loop--) { while(loop--) {
if(what < 0) { if(what < 0) {
/* anything that gets here is fatally bad */ /* anything that gets here is fatally bad */
@@ -3225,6 +3271,7 @@ static int sectransp_check_cxn(struct Curl_cfilter *cf,
DEBUGASSERT(backend); DEBUGASSERT(backend);
if(backend->ssl_ctx) { if(backend->ssl_ctx) {
CF_DEBUGF(infof(data, CFMSG(cf, "check connection")));
err = SSLGetSessionState(backend->ssl_ctx, &state); err = SSLGetSessionState(backend->ssl_ctx, &state);
if(err == noErr) if(err == noErr)
return state == kSSLConnected || state == kSSLHandshake; return state == kSSLConnected || state == kSSLHandshake;
@@ -3245,6 +3292,7 @@ static bool sectransp_data_pending(struct Curl_cfilter *cf,
DEBUGASSERT(backend); DEBUGASSERT(backend);
if(backend->ssl_ctx) { /* SSL is in use */ if(backend->ssl_ctx) { /* SSL is in use */
CF_DEBUGF(infof(data, CFMSG(cf, "data_pending")));
err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer); err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer);
if(err == noErr) if(err == noErr)
return buffer > 0UL; return buffer > 0UL;
@@ -3402,7 +3450,7 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf,
case -9841: case -9841:
if((conn_config->CAfile || conn_config->ca_info_blob) && if((conn_config->CAfile || conn_config->ca_info_blob) &&
conn_config->verifypeer) { conn_config->verifypeer) {
CURLcode result = verify_cert(data, conn_config->CAfile, CURLcode result = verify_cert(cf, data, conn_config->CAfile,
conn_config->ca_info_blob, conn_config->ca_info_blob,
backend->ssl_ctx); backend->ssl_ctx);
if(result) if(result)