SSL: support in-memory CA certs for some backends

- New options CURLOPT_CAINFO_BLOB and CURLOPT_PROXY_CAINFO_BLOB to
  specify in-memory PEM certificates for OpenSSL, Schannel (Windows)
  and Secure Transport (Apple) SSL backends.

Prior to this change PEM certificates could only be imported from a file
and not from memory.

Co-authored-by: moparisthebest@users.noreply.github.com

Ref: https://github.com/curl/curl/pull/4679
Ref: https://github.com/curl/curl/pull/5677
Ref: https://github.com/curl/curl/pull/6109

Closes https://github.com/curl/curl/pull/6662
This commit is contained in:
Gilles Vollant 2020-07-13 03:17:56 +02:00 committed by Jay Satiro
parent 70cf50fb4a
commit 77fc3859b2
24 changed files with 697 additions and 142 deletions

View file

@ -313,6 +313,10 @@ static CURLcode dohprobe(struct Curl_easy *data,
ERROR_CHECK_SETOPT(CURLOPT_CAINFO,
data->set.str[STRING_SSL_CAFILE]);
}
if(data->set.blobs[BLOB_CAINFO]) {
ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB,
data->set.blobs[BLOB_CAINFO]);
}
if(data->set.str[STRING_SSL_CAPATH]) {
ERROR_CHECK_SETOPT(CURLOPT_CAPATH,
data->set.str[STRING_SSL_CAPATH]);

View file

@ -38,6 +38,7 @@ struct curl_easyoption Curl_easyopts[] = {
{"AWS_SIGV4", CURLOPT_AWS_SIGV4, CURLOT_STRING, 0},
{"BUFFERSIZE", CURLOPT_BUFFERSIZE, CURLOT_LONG, 0},
{"CAINFO", CURLOPT_CAINFO, CURLOT_STRING, 0},
{"CAINFO_BLOB", CURLOPT_CAINFO_BLOB, CURLOT_BLOB, 0},
{"CAPATH", CURLOPT_CAPATH, CURLOT_STRING, 0},
{"CERTINFO", CURLOPT_CERTINFO, CURLOT_LONG, 0},
{"CHUNK_BGN_FUNCTION", CURLOPT_CHUNK_BGN_FUNCTION, CURLOT_FUNCTION, 0},
@ -205,6 +206,7 @@ struct curl_easyoption Curl_easyopts[] = {
{"PROXYUSERNAME", CURLOPT_PROXYUSERNAME, CURLOT_STRING, 0},
{"PROXYUSERPWD", CURLOPT_PROXYUSERPWD, CURLOT_STRING, 0},
{"PROXY_CAINFO", CURLOPT_PROXY_CAINFO, CURLOT_STRING, 0},
{"PROXY_CAINFO_BLOB", CURLOPT_PROXY_CAINFO_BLOB, CURLOT_BLOB, 0},
{"PROXY_CAPATH", CURLOPT_PROXY_CAPATH, CURLOT_STRING, 0},
{"PROXY_CRLFILE", CURLOPT_PROXY_CRLFILE, CURLOT_STRING, 0},
{"PROXY_ISSUERCERT", CURLOPT_PROXY_ISSUERCERT, CURLOT_STRING, 0},
@ -352,6 +354,6 @@ struct curl_easyoption Curl_easyopts[] = {
*/
int Curl_easyopts_check(void)
{
return ((CURLOPT_LASTENTRY%10000) != (308 + 1));
return ((CURLOPT_LASTENTRY%10000) != (310 + 1));
}
#endif

View file

@ -2041,6 +2041,20 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE],
va_arg(param, char *));
break;
case CURLOPT_CAINFO_BLOB:
/*
* Blob that holds CA info for SSL connection.
* Specify entire PEM of the CA certificate
*/
#ifdef USE_SSL
if(Curl_ssl->supports & SSLSUPP_CAINFO_BLOB)
result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO],
va_arg(param, struct curl_blob *));
else
#endif
return CURLE_NOT_BUILT_IN;
break;
#ifndef CURL_DISABLE_PROXY
case CURLOPT_PROXY_CAINFO:
/*
@ -2050,6 +2064,19 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY],
va_arg(param, char *));
break;
case CURLOPT_PROXY_CAINFO_BLOB:
/*
* Blob that holds CA info for SSL connection proxy.
* Specify entire PEM of the CA certificate
*/
#ifdef USE_SSL
if(Curl_ssl->supports & SSLSUPP_CAINFO_BLOB)
result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO_PROXY],
va_arg(param, struct curl_blob *));
else
#endif
return CURLE_NOT_BUILT_IN;
break;
#endif
case CURLOPT_CAPATH:
/*

View file

@ -3738,6 +3738,7 @@ static CURLcode create_conn(struct Curl_easy *data,
data->set.ssl.primary.pinned_key =
data->set.str[STRING_SSL_PINNEDPUBLICKEY];
data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT];
data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO];
data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES];
#ifndef CURL_DISABLE_PROXY
@ -3753,6 +3754,8 @@ static CURLcode create_conn(struct Curl_easy *data,
data->set.proxy_ssl.primary.pinned_key =
data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY];
data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY];
data->set.proxy_ssl.primary.ca_info_blob =
data->set.blobs[BLOB_CAINFO_PROXY];
data->set.proxy_ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY];
data->set.proxy_ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY];
data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY];

View file

@ -253,6 +253,7 @@ struct ssl_primary_config {
char *cipher_list13; /* list of TLS 1.3 cipher suites to use */
char *pinned_key;
struct curl_blob *cert_blob;
struct curl_blob *ca_info_blob;
char *curves; /* list of curves to use */
BIT(verifypeer); /* set TRUE if this is desired */
BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */
@ -1604,6 +1605,8 @@ enum dupblob {
BLOB_KEY_PROXY,
BLOB_SSL_ISSUERCERT,
BLOB_SSL_ISSUERCERT_PROXY,
BLOB_CAINFO,
BLOB_CAINFO_PROXY,
BLOB_LAST
};

View file

@ -2510,6 +2510,67 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
return res;
}
static CURLcode load_cacert_from_memory(SSL_CTX *ctx,
const struct curl_blob *ca_info_blob)
{
/* these need freed at the end */
BIO *cbio = NULL;
STACK_OF(X509_INFO) *inf = NULL;
/* everything else is just a reference */
int i, count = 0;
X509_STORE *cts = NULL;
X509_INFO *itmp = NULL;
if(ca_info_blob->len > (size_t)INT_MAX)
return CURLE_SSL_CACERT_BADFILE;
cts = SSL_CTX_get_cert_store(ctx);
if(!cts)
return CURLE_OUT_OF_MEMORY;
cbio = BIO_new_mem_buf(ca_info_blob->data, (int)ca_info_blob->len);
if(!cbio)
return CURLE_OUT_OF_MEMORY;
inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if(!inf) {
BIO_free(cbio);
return CURLE_SSL_CACERT_BADFILE;
}
/* add each entry from PEM file to x509_store */
for(i = 0; i < (int)sk_X509_INFO_num(inf); ++i) {
itmp = sk_X509_INFO_value(inf, i);
if(itmp->x509) {
if(X509_STORE_add_cert(cts, itmp->x509)) {
++count;
}
else {
/* set count to 0 to return an error */
count = 0;
break;
}
}
if(itmp->crl) {
if(X509_STORE_add_crl(cts, itmp->crl)) {
++count;
}
else {
/* set count to 0 to return an error */
count = 0;
break;
}
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free(cbio);
/* if we didn't end up importing anything, treat that as an error */
return (count > 0 ? CURLE_OK : CURLE_SSL_CACERT_BADFILE);
}
static CURLcode ossl_connect_step1(struct Curl_easy *data,
struct connectdata *conn, int sockindex)
{
@ -2537,8 +2598,11 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data,
#endif
char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
const char * const ssl_cert_type = SSL_SET_OPTION(cert_type);
const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile);
const char * const ssl_cafile =
/* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
(ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile));
const char * const ssl_capath = SSL_CONN_CONFIG(CApath);
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile);
@ -2962,6 +3026,19 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data,
}
#endif
if(ca_info_blob) {
result = load_cacert_from_memory(backend->ctx, ca_info_blob);
if(result) {
if(result == CURLE_OUT_OF_MEMORY ||
(verifypeer && !imported_native_ca)) {
failf(data, "error importing CA certificate blob");
return result;
}
/* Only warning if no certificate verification is required. */
infof(data, "error importing CA certificate blob, continuing anyway\n");
}
}
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
{
@ -3018,7 +3095,8 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data,
#endif
#ifdef CURL_CA_FALLBACK
if(verifypeer && !ssl_cafile && !ssl_capath && !imported_native_ca) {
if(verifypeer &&
!ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
/* verifying the peer without any CA certificates won't
work so use openssl's built in default as fallback */
SSL_CTX_set_default_verify_paths(backend->ctx);
@ -4425,6 +4503,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
{ CURLSSLBACKEND_OPENSSL, "openssl" }, /* info */
SSLSUPP_CA_PATH |
SSLSUPP_CAINFO_BLOB |
SSLSUPP_CERTINFO |
SSLSUPP_PINNEDPUBKEY |
SSLSUPP_SSL_CTX |

View file

@ -473,7 +473,7 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
#endif
#else
#ifdef HAS_MANUAL_VERIFY_API
if(SSL_CONN_CONFIG(CAfile)) {
if(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) {
if(curlx_verify_windows_version(6, 1, PLATFORM_WINNT,
VERSION_GREATER_THAN_EQUAL)) {
BACKEND->use_manual_cred_validation = true;
@ -487,7 +487,7 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
else
BACKEND->use_manual_cred_validation = false;
#else
if(SSL_CONN_CONFIG(CAfile)) {
if(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) {
failf(data, "schannel: CA cert support not built in");
return CURLE_NOT_BUILT_IN;
}
@ -2403,6 +2403,9 @@ const struct Curl_ssl Curl_ssl_schannel = {
{ CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */
SSLSUPP_CERTINFO |
#ifdef HAS_MANUAL_VERIFY_API
SSLSUPP_CAINFO_BLOB |
#endif
SSLSUPP_PINNEDPUBKEY,
sizeof(struct ssl_backend_data),

View file

@ -77,21 +77,156 @@ static int is_cr_or_lf(char c)
return c == '\r' || c == '\n';
}
static CURLcode add_certs_to_store(HCERTSTORE trust_store,
const char *ca_file,
struct Curl_easy *data)
/* Search the substring needle,needlelen into string haystack,haystacklen
* Strings don't need to be terminated by a '\0'.
* Similar of OSX/Linux memmem (not available on Visual Studio).
* Return position of beginning of first occurence or NULL if not found
*/
static const char *c_memmem(const void *haystack, size_t haystacklen,
const void *needle, size_t needlelen)
{
const char *p;
char first;
const char *str_limit = (const char *)haystack + haystacklen;
if(!needlelen || needlelen > haystacklen)
return NULL;
first = *(const char *)needle;
for(p = (const char *)haystack; p <= (str_limit - needlelen); p++)
if(((*p) == first) && (memcmp(p, needle, needlelen) == 0))
return p;
return NULL;
}
static CURLcode add_certs_data_to_store(HCERTSTORE trust_store,
const char *ca_buffer,
size_t ca_buffer_size,
const char *ca_file_text,
struct Curl_easy *data)
{
const size_t begin_cert_len = strlen(BEGIN_CERT);
const size_t end_cert_len = strlen(END_CERT);
CURLcode result = CURLE_OK;
int num_certs = 0;
bool more_certs = 1;
const char *current_ca_file_ptr = ca_buffer;
const char *ca_buffer_limit = ca_buffer + ca_buffer_size;
while(more_certs && (current_ca_file_ptr<ca_buffer_limit)) {
const char *begin_cert_ptr = c_memmem(current_ca_file_ptr,
ca_buffer_limit-current_ca_file_ptr,
BEGIN_CERT,
begin_cert_len);
if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[begin_cert_len])) {
more_certs = 0;
}
else {
const char *end_cert_ptr = c_memmem(begin_cert_ptr,
ca_buffer_limit-begin_cert_ptr,
END_CERT,
end_cert_len);
if(!end_cert_ptr) {
failf(data,
"schannel: CA file '%s' is not correctly formatted",
ca_file_text);
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
CERT_BLOB cert_blob;
CERT_CONTEXT *cert_context = NULL;
BOOL add_cert_result = FALSE;
DWORD actual_content_type = 0;
DWORD cert_size = (DWORD)
((end_cert_ptr + end_cert_len) - begin_cert_ptr);
cert_blob.pbData = (BYTE *)begin_cert_ptr;
cert_blob.cbData = cert_size;
if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
&cert_blob,
CERT_QUERY_CONTENT_FLAG_CERT,
CERT_QUERY_FORMAT_FLAG_ALL,
0,
NULL,
&actual_content_type,
NULL,
NULL,
NULL,
(const void **)&cert_context)) {
char buffer[STRERROR_LEN];
failf(data,
"schannel: failed to extract certificate from CA file "
"'%s': %s",
ca_file_text,
Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
current_ca_file_ptr = begin_cert_ptr + cert_size;
/* Sanity check that the cert_context object is the right type */
if(CERT_QUERY_CONTENT_CERT != actual_content_type) {
failf(data,
"schannel: unexpected content type '%d' when extracting "
"certificate from CA file '%s'",
actual_content_type, ca_file_text);
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
add_cert_result =
CertAddCertificateContextToStore(trust_store,
cert_context,
CERT_STORE_ADD_ALWAYS,
NULL);
CertFreeCertificateContext(cert_context);
if(!add_cert_result) {
char buffer[STRERROR_LEN];
failf(data,
"schannel: failed to add certificate from CA file '%s' "
"to certificate store: %s",
ca_file_text,
Curl_winapi_strerror(GetLastError(), buffer,
sizeof(buffer)));
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
num_certs++;
}
}
}
}
}
}
if(result == CURLE_OK) {
if(!num_certs) {
infof(data,
"schannel: did not add any certificates from CA file '%s'\n",
ca_file_text);
}
else {
infof(data,
"schannel: added %d certificate(s) from CA file '%s'\n",
num_certs, ca_file_text);
}
}
return result;
}
static CURLcode add_certs_file_to_store(HCERTSTORE trust_store,
const char *ca_file,
struct Curl_easy *data)
{
CURLcode result;
HANDLE ca_file_handle = INVALID_HANDLE_VALUE;
LARGE_INTEGER file_size;
char *ca_file_buffer = NULL;
char *current_ca_file_ptr = NULL;
TCHAR *ca_file_tstr = NULL;
size_t ca_file_bufsize = 0;
DWORD total_bytes_read = 0;
bool more_certs = 0;
int num_certs = 0;
size_t END_CERT_LEN;
ca_file_tstr = curlx_convert_UTF8_to_tchar((char *)ca_file);
if(!ca_file_tstr) {
@ -181,106 +316,10 @@ static CURLcode add_certs_to_store(HCERTSTORE trust_store,
if(result != CURLE_OK) {
goto cleanup;
}
END_CERT_LEN = strlen(END_CERT);
more_certs = 1;
current_ca_file_ptr = ca_file_buffer;
while(more_certs && *current_ca_file_ptr != '\0') {
char *begin_cert_ptr = strstr(current_ca_file_ptr, BEGIN_CERT);
if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[strlen(BEGIN_CERT)])) {
more_certs = 0;
}
else {
char *end_cert_ptr = strstr(begin_cert_ptr, END_CERT);
if(!end_cert_ptr) {
failf(data,
"schannel: CA file '%s' is not correctly formatted",
ca_file);
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
CERT_BLOB cert_blob;
CERT_CONTEXT *cert_context = NULL;
BOOL add_cert_result = FALSE;
DWORD actual_content_type = 0;
DWORD cert_size = (DWORD)
((end_cert_ptr + END_CERT_LEN) - begin_cert_ptr);
cert_blob.pbData = (BYTE *)begin_cert_ptr;
cert_blob.cbData = cert_size;
if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
&cert_blob,
CERT_QUERY_CONTENT_FLAG_CERT,
CERT_QUERY_FORMAT_FLAG_ALL,
0,
NULL,
&actual_content_type,
NULL,
NULL,
NULL,
(const void **)&cert_context)) {
char buffer[STRERROR_LEN];
failf(data,
"schannel: failed to extract certificate from CA file "
"'%s': %s",
ca_file,
Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
current_ca_file_ptr = begin_cert_ptr + cert_size;
/* Sanity check that the cert_context object is the right type */
if(CERT_QUERY_CONTENT_CERT != actual_content_type) {
failf(data,
"schannel: unexpected content type '%d' when extracting "
"certificate from CA file '%s'",
actual_content_type, ca_file);
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
add_cert_result =
CertAddCertificateContextToStore(trust_store,
cert_context,
CERT_STORE_ADD_ALWAYS,
NULL);
CertFreeCertificateContext(cert_context);
if(!add_cert_result) {
char buffer[STRERROR_LEN];
failf(data,
"schannel: failed to add certificate from CA file '%s' "
"to certificate store: %s",
ca_file,
Curl_winapi_strerror(GetLastError(), buffer,
sizeof(buffer)));
result = CURLE_SSL_CACERT_BADFILE;
more_certs = 0;
}
else {
num_certs++;
}
}
}
}
}
}
if(result == CURLE_OK) {
if(!num_certs) {
infof(data,
"schannel: did not add any certificates from CA file '%s'\n",
ca_file);
}
else {
infof(data,
"schannel: added %d certificate(s) from CA file '%s'\n",
num_certs, ca_file);
}
}
result = add_certs_data_to_store(trust_store,
ca_file_buffer, ca_file_bufsize,
ca_file,
data);
cleanup:
if(ca_file_handle != INVALID_HANDLE_VALUE) {
@ -550,7 +589,8 @@ CURLcode Curl_verify_certificate(struct Curl_easy *data,
result = CURLE_PEER_FAILED_VERIFICATION;
}
if(result == CURLE_OK && SSL_CONN_CONFIG(CAfile) &&
if(result == CURLE_OK &&
(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
BACKEND->use_manual_cred_validation) {
/*
* Create a chain engine that uses the certificates in the CA file as
@ -576,8 +616,19 @@ CURLcode Curl_verify_certificate(struct Curl_easy *data,
result = CURLE_SSL_CACERT_BADFILE;
}
else {
result = add_certs_to_store(trust_store, SSL_CONN_CONFIG(CAfile),
data);
const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
if(ca_info_blob) {
result = add_certs_data_to_store(trust_store,
(const char *)ca_info_blob->data,
ca_info_blob->len,
"(memory blob)",
data);
}
else {
result = add_certs_file_to_store(trust_store,
SSL_CONN_CONFIG(CAfile),
data);
}
}
}

View file

@ -1659,8 +1659,10 @@ static CURLcode sectransp_connect_step1(struct Curl_easy *data,
curl_socket_t sockfd = conn->sock[sockindex];
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
struct ssl_backend_data *backend = connssl->backend;
const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile);
const struct curl_blob *ssl_cablob = NULL;
const struct curl_blob *ssl_cablob = SSL_CONN_CONFIG(ca_info_blob);
const char * const ssl_cafile =
/* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
(ssl_cablob ? NULL : SSL_CONN_CONFIG(CAfile));
const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
@ -2007,7 +2009,8 @@ static CURLcode sectransp_connect_step1(struct Curl_easy *data,
bool is_cert_file = (!is_cert_data) && is_file(ssl_cafile);
if(!(is_cert_file || is_cert_data)) {
failf(data, "SSL: can't load CA certificate file %s", ssl_cafile);
failf(data, "SSL: can't load CA certificate file %s",
ssl_cafile ? ssl_cafile : "(blob memory)");
return CURLE_SSL_CACERT_BADFILE;
}
}
@ -2084,7 +2087,8 @@ static CURLcode sectransp_connect_step1(struct Curl_easy *data,
else {
CURLcode result;
ssl_sessionid =
aprintf("%s:%d:%d:%s:%ld", ssl_cafile,
aprintf("%s:%d:%d:%s:%ld",
ssl_cafile ? ssl_cafile : "(blob memory)",
verifypeer, SSL_CONN_CONFIG(verifyhost), hostname, port);
ssl_sessionid_len = strlen(ssl_sessionid);
@ -2224,7 +2228,7 @@ static int read_cert(const char *file, unsigned char **out, size_t *outlen)
}
static int append_cert_to_array(struct Curl_easy *data,
unsigned char *buf, size_t buflen,
const unsigned char *buf, size_t buflen,
CFMutableArrayRef array)
{
CFDataRef certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen);
@ -2262,18 +2266,14 @@ static int append_cert_to_array(struct Curl_easy *data,
return CURLE_OK;
}
static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
SSLContextRef ctx)
static CURLcode verify_cert_buf(struct Curl_easy *data,
const unsigned char *certbuf, size_t buflen,
SSLContextRef ctx)
{
int n = 0, rc;
long res;
unsigned char *certbuf, *der;
size_t buflen, derlen, offset = 0;
if(read_cert(cafile, &certbuf, &buflen) < 0) {
failf(data, "SSL: failed to read or invalid CA certificate");
return CURLE_SSL_CACERT_BADFILE;
}
unsigned char *der;
size_t derlen, offset = 0;
/*
* Certbuf now contains the contents of the certificate file, which can be
@ -2287,7 +2287,6 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if(!array) {
free(certbuf);
failf(data, "SSL: out of memory creating CA certificate array");
return CURLE_OUT_OF_MEMORY;
}
@ -2301,7 +2300,6 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
*/
res = pem_to_der((const char *)certbuf + offset, &der, &derlen);
if(res < 0) {
free(certbuf);
CFRelease(array);
failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle",
n, offset);
@ -2312,7 +2310,6 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
if(res == 0 && offset == 0) {
/* This is not a PEM file, probably a certificate in DER format. */
rc = append_cert_to_array(data, certbuf, buflen, array);
free(certbuf);
if(rc != CURLE_OK) {
CFRelease(array);
return rc;
@ -2321,14 +2318,12 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
}
else if(res == 0) {
/* No more certificates in the bundle. */
free(certbuf);
break;
}
rc = append_cert_to_array(data, der, derlen, array);
free(der);
if(rc != CURLE_OK) {
free(certbuf);
CFRelease(array);
return rc;
}
@ -2385,6 +2380,38 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
}
}
static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
const struct curl_blob *ca_info_blob,
SSLContextRef ctx)
{
int result;
unsigned char *certbuf;
size_t buflen;
if(ca_info_blob) {
certbuf = (unsigned char *)malloc(ca_info_blob->len + 1);
if(!certbuf) {
return CURLE_OUT_OF_MEMORY;
}
buflen = ca_info_blob->len;
memcpy(certbuf, ca_info_blob->data, ca_info_blob->len);
certbuf[ca_info_blob->len]='\0';
}
else if(cafile) {
if(read_cert(cafile, &certbuf, &buflen) < 0) {
failf(data, "SSL: failed to read or invalid CA certificate");
return CURLE_SSL_CACERT_BADFILE;
}
}
else
return CURLE_SSL_CACERT_BADFILE;
result = verify_cert_buf(data, certbuf, buflen, ctx);
free(certbuf);
return result;
}
#ifdef SECTRANSP_PINNEDPUBKEY
static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data,
SSLContextRef ctx,
@ -2520,8 +2547,10 @@ sectransp_connect_step2(struct Curl_easy *data, struct connectdata *conn,
/* The below is errSSLServerAuthCompleted; it's not defined in
Leopard's headers */
case -9841:
if(SSL_CONN_CONFIG(CAfile) && SSL_CONN_CONFIG(verifypeer)) {
CURLcode result = verify_cert(SSL_CONN_CONFIG(CAfile), data,
if((SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
SSL_CONN_CONFIG(verifypeer)) {
CURLcode result = verify_cert(data, SSL_CONN_CONFIG(CAfile),
SSL_CONN_CONFIG(ca_info_blob),
backend->ssl_ctx);
if(result)
return result;
@ -3365,8 +3394,10 @@ static ssize_t sectransp_recv(struct Curl_easy *data,
/* The below is errSSLPeerAuthCompleted; it's not defined in
Leopard's headers */
case -9841:
if(SSL_CONN_CONFIG(CAfile) && SSL_CONN_CONFIG(verifypeer)) {
CURLcode result = verify_cert(SSL_CONN_CONFIG(CAfile), data,
if((SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
SSL_CONN_CONFIG(verifypeer)) {
CURLcode result = verify_cert(data, SSL_CONN_CONFIG(CAfile),
SSL_CONN_CONFIG(ca_info_blob),
backend->ssl_ctx);
if(result)
return result;
@ -3393,6 +3424,7 @@ static void *sectransp_get_internals(struct ssl_connect_data *connssl,
const struct Curl_ssl Curl_ssl_sectransp = {
{ CURLSSLBACKEND_SECURETRANSPORT, "secure-transport" }, /* info */
SSLSUPP_CAINFO_BLOB |
#ifdef SECTRANSP_PINNEDPUBKEY
SSLSUPP_PINNEDPUBKEY,
#else

View file

@ -135,6 +135,7 @@ Curl_ssl_config_matches(struct ssl_primary_config *data,
(data->verifyhost == needle->verifyhost) &&
(data->verifystatus == needle->verifystatus) &&
blobcmp(data->cert_blob, needle->cert_blob) &&
blobcmp(data->ca_info_blob, needle->ca_info_blob) &&
Curl_safe_strcasecompare(data->CApath, needle->CApath) &&
Curl_safe_strcasecompare(data->CAfile, needle->CAfile) &&
Curl_safe_strcasecompare(data->clientcert, needle->clientcert) &&
@ -161,6 +162,7 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source,
dest->sessionid = source->sessionid;
CLONE_BLOB(cert_blob);
CLONE_BLOB(ca_info_blob);
CLONE_STRING(CApath);
CLONE_STRING(CAfile);
CLONE_STRING(clientcert);
@ -185,6 +187,7 @@ void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc)
Curl_safefree(sslc->cipher_list13);
Curl_safefree(sslc->pinned_key);
Curl_safefree(sslc->cert_blob);
Curl_safefree(sslc->ca_info_blob);
Curl_safefree(sslc->curves);
}

View file

@ -32,6 +32,7 @@ struct ssl_connect_data;
#define SSLSUPP_SSL_CTX (1<<3) /* supports CURLOPT_SSL_CTX */
#define SSLSUPP_HTTPS_PROXY (1<<4) /* supports access via HTTPS proxies */
#define SSLSUPP_TLS13_CIPHERSUITES (1<<5) /* supports TLS 1.3 ciphersuites */
#define SSLSUPP_CAINFO_BLOB (1<<6)
struct Curl_ssl {
/*