diff options
Diffstat (limited to 'libs/libcurl/src/vtls')
32 files changed, 27067 insertions, 26137 deletions
diff --git a/libs/libcurl/src/vtls/bearssl.c b/libs/libcurl/src/vtls/bearssl.c index 1221ce8c84..6d53fab53c 100644 --- a/libs/libcurl/src/vtls/bearssl.c +++ b/libs/libcurl/src/vtls/bearssl.c @@ -1,1214 +1,1206 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_BEARSSL - -#include <bearssl.h> - -#include "bearssl.h" -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "vtls.h" -#include "connect.h" -#include "select.h" -#include "multiif.h" -#include "curl_printf.h" -#include "curl_memory.h" -#include "strcase.h" - -struct x509_context { - const br_x509_class *vtable; - br_x509_minimal_context minimal; - br_x509_decoder_context decoder; - bool verifyhost; - bool verifypeer; - int cert_num; -}; - -struct ssl_backend_data { - br_ssl_client_context ctx; - struct x509_context x509; - unsigned char buf[BR_SSL_BUFSIZE_BIDI]; - br_x509_trust_anchor *anchors; - size_t anchors_len; - const char *protocols[2]; - /* SSL client context is active */ - bool active; - /* size of pending write, yet to be flushed */ - size_t pending_write; -}; - -struct cafile_parser { - CURLcode err; - bool in_cert; - br_x509_decoder_context xc; - /* array of trust anchors loaded from CAfile */ - br_x509_trust_anchor *anchors; - size_t anchors_len; - /* buffer for DN data */ - unsigned char dn[1024]; - size_t dn_len; -}; - -#define CAFILE_SOURCE_PATH 1 -#define CAFILE_SOURCE_BLOB 2 -struct cafile_source { - int type; - const char *data; - size_t len; -}; - -static void append_dn(void *ctx, const void *buf, size_t len) -{ - struct cafile_parser *ca = ctx; - - if(ca->err != CURLE_OK || !ca->in_cert) - return; - if(sizeof(ca->dn) - ca->dn_len < len) { - ca->err = CURLE_FAILED_INIT; - return; - } - memcpy(ca->dn + ca->dn_len, buf, len); - ca->dn_len += len; -} - -static void x509_push(void *ctx, const void *buf, size_t len) -{ - struct cafile_parser *ca = ctx; - - if(ca->in_cert) - br_x509_decoder_push(&ca->xc, buf, len); -} - -static CURLcode load_cafile(struct cafile_source *source, - br_x509_trust_anchor **anchors, - size_t *anchors_len) -{ - struct cafile_parser ca; - br_pem_decoder_context pc; - br_x509_trust_anchor *ta; - size_t ta_size; - br_x509_trust_anchor *new_anchors; - size_t new_anchors_len; - br_x509_pkey *pkey; - FILE *fp = 0; - unsigned char buf[BUFSIZ]; - const unsigned char *p; - const char *name; - size_t n, i, pushed; - - DEBUGASSERT(source->type == CAFILE_SOURCE_PATH - || source->type == CAFILE_SOURCE_BLOB); - - if(source->type == CAFILE_SOURCE_PATH) { - fp = fopen(source->data, "rb"); - if(!fp) - return CURLE_SSL_CACERT_BADFILE; - } - - if(source->type == CAFILE_SOURCE_BLOB && source->len > (size_t)INT_MAX) - return CURLE_SSL_CACERT_BADFILE; - - ca.err = CURLE_OK; - ca.in_cert = FALSE; - ca.anchors = NULL; - ca.anchors_len = 0; - br_pem_decoder_init(&pc); - br_pem_decoder_setdest(&pc, x509_push, &ca); - do { - if(source->type == CAFILE_SOURCE_PATH) { - n = fread(buf, 1, sizeof(buf), fp); - if(n == 0) - break; - p = buf; - } - else if(source->type == CAFILE_SOURCE_BLOB) { - n = source->len; - p = (unsigned char *) source->data; - } - while(n) { - pushed = br_pem_decoder_push(&pc, p, n); - if(ca.err) - goto fail; - p += pushed; - n -= pushed; - - switch(br_pem_decoder_event(&pc)) { - case 0: - break; - case BR_PEM_BEGIN_OBJ: - name = br_pem_decoder_name(&pc); - if(strcmp(name, "CERTIFICATE") && strcmp(name, "X509 CERTIFICATE")) - break; - br_x509_decoder_init(&ca.xc, append_dn, &ca); - ca.in_cert = TRUE; - ca.dn_len = 0; - break; - case BR_PEM_END_OBJ: - if(!ca.in_cert) - break; - ca.in_cert = FALSE; - if(br_x509_decoder_last_error(&ca.xc)) { - ca.err = CURLE_SSL_CACERT_BADFILE; - goto fail; - } - /* add trust anchor */ - if(ca.anchors_len == SIZE_MAX / sizeof(ca.anchors[0])) { - ca.err = CURLE_OUT_OF_MEMORY; - goto fail; - } - new_anchors_len = ca.anchors_len + 1; - new_anchors = realloc(ca.anchors, - new_anchors_len * sizeof(ca.anchors[0])); - if(!new_anchors) { - ca.err = CURLE_OUT_OF_MEMORY; - goto fail; - } - ca.anchors = new_anchors; - ca.anchors_len = new_anchors_len; - ta = &ca.anchors[ca.anchors_len - 1]; - ta->dn.data = NULL; - ta->flags = 0; - if(br_x509_decoder_isCA(&ca.xc)) - ta->flags |= BR_X509_TA_CA; - pkey = br_x509_decoder_get_pkey(&ca.xc); - if(!pkey) { - ca.err = CURLE_SSL_CACERT_BADFILE; - goto fail; - } - ta->pkey = *pkey; - - /* calculate space needed for trust anchor data */ - ta_size = ca.dn_len; - switch(pkey->key_type) { - case BR_KEYTYPE_RSA: - ta_size += pkey->key.rsa.nlen + pkey->key.rsa.elen; - break; - case BR_KEYTYPE_EC: - ta_size += pkey->key.ec.qlen; - break; - default: - ca.err = CURLE_FAILED_INIT; - goto fail; - } - - /* fill in trust anchor DN and public key data */ - ta->dn.data = malloc(ta_size); - if(!ta->dn.data) { - ca.err = CURLE_OUT_OF_MEMORY; - goto fail; - } - memcpy(ta->dn.data, ca.dn, ca.dn_len); - ta->dn.len = ca.dn_len; - switch(pkey->key_type) { - case BR_KEYTYPE_RSA: - ta->pkey.key.rsa.n = ta->dn.data + ta->dn.len; - memcpy(ta->pkey.key.rsa.n, pkey->key.rsa.n, pkey->key.rsa.nlen); - ta->pkey.key.rsa.e = ta->pkey.key.rsa.n + ta->pkey.key.rsa.nlen; - memcpy(ta->pkey.key.rsa.e, pkey->key.rsa.e, pkey->key.rsa.elen); - break; - case BR_KEYTYPE_EC: - ta->pkey.key.ec.q = ta->dn.data + ta->dn.len; - memcpy(ta->pkey.key.ec.q, pkey->key.ec.q, pkey->key.ec.qlen); - break; - } - break; - default: - ca.err = CURLE_SSL_CACERT_BADFILE; - goto fail; - } - } - } while(source->type != CAFILE_SOURCE_BLOB); - if(fp && ferror(fp)) - ca.err = CURLE_READ_ERROR; - else if(ca.in_cert) - ca.err = CURLE_SSL_CACERT_BADFILE; - -fail: - if(fp) - fclose(fp); - if(ca.err == CURLE_OK) { - *anchors = ca.anchors; - *anchors_len = ca.anchors_len; - } - else { - for(i = 0; i < ca.anchors_len; ++i) - free(ca.anchors[i].dn.data); - free(ca.anchors); - } - - return ca.err; -} - -static void x509_start_chain(const br_x509_class **ctx, - const char *server_name) -{ - struct x509_context *x509 = (struct x509_context *)ctx; - - if(!x509->verifypeer) { - x509->cert_num = 0; - return; - } - - if(!x509->verifyhost) - server_name = NULL; - x509->minimal.vtable->start_chain(&x509->minimal.vtable, server_name); -} - -static void x509_start_cert(const br_x509_class **ctx, uint32_t length) -{ - struct x509_context *x509 = (struct x509_context *)ctx; - - if(!x509->verifypeer) { - /* Only decode the first cert in the chain to obtain the public key */ - if(x509->cert_num == 0) - br_x509_decoder_init(&x509->decoder, NULL, NULL); - return; - } - - x509->minimal.vtable->start_cert(&x509->minimal.vtable, length); -} - -static void x509_append(const br_x509_class **ctx, const unsigned char *buf, - size_t len) -{ - struct x509_context *x509 = (struct x509_context *)ctx; - - if(!x509->verifypeer) { - if(x509->cert_num == 0) - br_x509_decoder_push(&x509->decoder, buf, len); - return; - } - - x509->minimal.vtable->append(&x509->minimal.vtable, buf, len); -} - -static void x509_end_cert(const br_x509_class **ctx) -{ - struct x509_context *x509 = (struct x509_context *)ctx; - - if(!x509->verifypeer) { - x509->cert_num++; - return; - } - - x509->minimal.vtable->end_cert(&x509->minimal.vtable); -} - -static unsigned x509_end_chain(const br_x509_class **ctx) -{ - struct x509_context *x509 = (struct x509_context *)ctx; - - if(!x509->verifypeer) { - return br_x509_decoder_last_error(&x509->decoder); - } - - return x509->minimal.vtable->end_chain(&x509->minimal.vtable); -} - -static const br_x509_pkey *x509_get_pkey(const br_x509_class *const *ctx, - unsigned *usages) -{ - struct x509_context *x509 = (struct x509_context *)ctx; - - if(!x509->verifypeer) { - /* Nothing in the chain is verified, just return the public key of the - first certificate and allow its usage for both TLS_RSA_* and - TLS_ECDHE_* */ - if(usages) - *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; - return br_x509_decoder_get_pkey(&x509->decoder); - } - - return x509->minimal.vtable->get_pkey(&x509->minimal.vtable, usages); -} - -static const br_x509_class x509_vtable = { - sizeof(struct x509_context), - x509_start_chain, - x509_start_cert, - x509_append, - x509_end_cert, - x509_end_chain, - x509_get_pkey -}; - -struct st_cipher { - const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ - const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ - uint16_t num; /* BearSSL cipher suite */ -}; - -/* Macro to initialize st_cipher data structure */ -#define CIPHER_DEF(num, alias) { #num, alias, BR_##num } - -static const struct st_cipher ciphertable[] = { - /* RFC 2246 TLS 1.0 */ - CIPHER_DEF(TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ - "DES-CBC3-SHA"), - - /* RFC 3268 TLS 1.0 AES */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ - "AES128-SHA"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ - "AES256-SHA"), - - /* RFC 5246 TLS 1.2 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ - "AES128-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ - "AES256-SHA256"), - - /* RFC 5288 TLS 1.2 AES GCM */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ - "AES128-GCM-SHA256"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ - "AES256-GCM-SHA384"), - - /* RFC 4492 TLS 1.0 ECC */ - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ - "ECDH-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ - "ECDH-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ - "ECDH-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ - "ECDHE-ECDSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ - "ECDHE-ECDSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ - "ECDHE-ECDSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ - "ECDH-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ - "ECDH-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ - "ECDH-RSA-AES256-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ - "ECDHE-RSA-DES-CBC3-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ - "ECDHE-RSA-AES128-SHA"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ - "ECDHE-RSA-AES256-SHA"), - - /* RFC 5289 TLS 1.2 ECC HMAC SHA256/384 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ - "ECDHE-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ - "ECDHE-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ - "ECDH-ECDSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ - "ECDH-ECDSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ - "ECDHE-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ - "ECDHE-RSA-AES256-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ - "ECDH-RSA-AES128-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ - "ECDH-RSA-AES256-SHA384"), - - /* RFC 5289 TLS 1.2 GCM */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ - "ECDHE-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ - "ECDHE-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ - "ECDH-ECDSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ - "ECDH-ECDSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ - "ECDHE-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ - "ECDHE-RSA-AES256-GCM-SHA384"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ - "ECDH-RSA-AES128-GCM-SHA256"), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ - "ECDH-RSA-AES256-GCM-SHA384"), -#ifdef BR_TLS_RSA_WITH_AES_128_CCM - - /* RFC 6655 TLS 1.2 CCM - Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */ - "AES128-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */ - "AES256-CCM"), - CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */ - "AES128-CCM8"), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */ - "AES256-CCM8"), - - /* RFC 7251 TLS 1.2 ECC CCM - Supported since BearSSL 0.6 */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */ - "ECDHE-ECDSA-AES128-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */ - "ECDHE-ECDSA-AES256-CCM"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */ - "ECDHE-ECDSA-AES128-CCM8"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */ - "ECDHE-ECDSA-AES256-CCM8"), -#endif - - /* RFC 7905 TLS 1.2 ChaCha20-Poly1305 - Supported since BearSSL 0.2 */ - CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ - "ECDHE-RSA-CHACHA20-POLY1305"), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ - "ECDHE-ECDSA-CHACHA20-POLY1305"), -}; - -#define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0])) -#define CIPHER_NAME_BUF_LEN 64 - -static bool is_separator(char c) -{ - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; -} - -static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data, - br_ssl_engine_context *ssl_eng, - const char *ciphers) -{ - uint16_t selected_ciphers[NUM_OF_CIPHERS]; - size_t selected_count = 0; - char cipher_name[CIPHER_NAME_BUF_LEN]; - const char *cipher_start = ciphers; - const char *cipher_end; - size_t i, j; - - if(!cipher_start) - return CURLE_SSL_CIPHER; - - while(true) { - /* Extract the next cipher name from the ciphers string */ - while(is_separator(*cipher_start)) - ++cipher_start; - if(*cipher_start == '\0') - break; - cipher_end = cipher_start; - while(*cipher_end != '\0' && !is_separator(*cipher_end)) - ++cipher_end; - j = cipher_end - cipher_start < CIPHER_NAME_BUF_LEN - 1 ? - cipher_end - cipher_start : CIPHER_NAME_BUF_LEN - 1; - strncpy(cipher_name, cipher_start, j); - cipher_name[j] = '\0'; - cipher_start = cipher_end; - - /* Lookup the cipher name in the table of available ciphers. If the cipher - name starts with "TLS_" we do the lookup by IANA name. Otherwise, we try - to match cipher name by an (OpenSSL) alias. */ - if(strncasecompare(cipher_name, "TLS_", 4)) { - for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].name); ++i); - } - else { - for(i = 0; i < NUM_OF_CIPHERS && - !strcasecompare(cipher_name, ciphertable[i].alias_name); ++i); - } - if(i == NUM_OF_CIPHERS) { - infof(data, "BearSSL: unknown cipher in list: %s", cipher_name); - continue; - } - - /* No duplicates allowed */ - for(j = 0; j < selected_count && - selected_ciphers[j] != ciphertable[i].num; j++); - if(j < selected_count) { - infof(data, "BearSSL: duplicate cipher in list: %s", cipher_name); - continue; - } - - DEBUGASSERT(selected_count < NUM_OF_CIPHERS); - selected_ciphers[selected_count] = ciphertable[i].num; - ++selected_count; - } - - if(selected_count == 0) { - failf(data, "BearSSL: no supported cipher in list"); - return CURLE_SSL_CIPHER; - } - - br_ssl_engine_set_suites(ssl_eng, selected_ciphers, selected_count); - return CURLE_OK; -} - -static CURLcode bearssl_connect_step1(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob); - const char * const ssl_cafile = - /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ - (ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile)); - const char *hostname = SSL_HOST_NAME(); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - const bool verifyhost = SSL_CONN_CONFIG(verifyhost); - CURLcode ret; - unsigned version_min, version_max; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - - DEBUGASSERT(backend); - - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_SSLv2: - failf(data, "BearSSL does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv3: - failf(data, "BearSSL does not support SSLv3"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_0: - version_min = BR_TLS10; - version_max = BR_TLS10; - break; - case CURL_SSLVERSION_TLSv1_1: - version_min = BR_TLS11; - version_max = BR_TLS11; - break; - case CURL_SSLVERSION_TLSv1_2: - version_min = BR_TLS12; - version_max = BR_TLS12; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - version_min = BR_TLS10; - version_max = BR_TLS12; - break; - default: - failf(data, "BearSSL: unknown CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(ca_info_blob) { - struct cafile_source source; - source.type = CAFILE_SOURCE_BLOB; - source.data = ca_info_blob->data; - source.len = ca_info_blob->len; - - ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); - if(ret != CURLE_OK) { - if(verifypeer) { - failf(data, "error importing CA certificate blob"); - return ret; - } - /* Only warn if no certificate verification is required. */ - infof(data, "error importing CA certificate blob, continuing anyway"); - } - } - - if(ssl_cafile) { - struct cafile_source source; - source.type = CAFILE_SOURCE_PATH; - source.data = ssl_cafile; - source.len = 0; - - ret = load_cafile(&source, &backend->anchors, &backend->anchors_len); - if(ret != CURLE_OK) { - if(verifypeer) { - failf(data, "error setting certificate verify locations." - " CAfile: %s", ssl_cafile); - return ret; - } - infof(data, "error setting certificate verify locations," - " continuing anyway:"); - } - } - - /* initialize SSL context */ - br_ssl_client_init_full(&backend->ctx, &backend->x509.minimal, - backend->anchors, backend->anchors_len); - br_ssl_engine_set_versions(&backend->ctx.eng, version_min, version_max); - br_ssl_engine_set_buffer(&backend->ctx.eng, backend->buf, - sizeof(backend->buf), 1); - - if(SSL_CONN_CONFIG(cipher_list)) { - /* Override the ciphers as specified. For the default cipher list see the - BearSSL source code of br_ssl_client_init_full() */ - ret = bearssl_set_selected_ciphers(data, &backend->ctx.eng, - SSL_CONN_CONFIG(cipher_list)); - if(ret) - return ret; - } - - /* initialize X.509 context */ - backend->x509.vtable = &x509_vtable; - backend->x509.verifypeer = verifypeer; - backend->x509.verifyhost = verifyhost; - br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable); - - if(SSL_SET_OPTION(primary.sessionid)) { - void *session; - - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE, - &session, NULL, sockindex)) { - br_ssl_engine_set_session_parameters(&backend->ctx.eng, session); - infof(data, "BearSSL: re-using session ID"); - } - Curl_ssl_sessionid_unlock(data); - } - - if(conn->bits.tls_enable_alpn) { - int cur = 0; - - /* NOTE: when adding more protocols here, increase the size of the - * protocols array in `struct ssl_backend_data`. - */ - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy) -#endif - ) { - backend->protocols[cur++] = ALPN_H2; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - backend->protocols[cur++] = ALPN_HTTP_1_1; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - br_ssl_engine_set_protocol_names(&backend->ctx.eng, - backend->protocols, cur); - } - - if((1 == Curl_inet_pton(AF_INET, hostname, &addr)) -#ifdef ENABLE_IPV6 - || (1 == Curl_inet_pton(AF_INET6, hostname, &addr)) -#endif - ) { - if(verifyhost) { - failf(data, "BearSSL: " - "host verification of IP address is not supported"); - return CURLE_PEER_FAILED_VERIFICATION; - } - hostname = NULL; - } - else { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - hostname = snihost; - } - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - ret = (*data->set.ssl.fsslctx)(data, &backend->ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(ret) { - failf(data, "BearSSL: error signaled by ssl ctx callback"); - return ret; - } - } - - if(!br_ssl_client_reset(&backend->ctx, hostname, 1)) - return CURLE_FAILED_INIT; - backend->active = TRUE; - - connssl->connecting_state = ssl_connect_2; - - return CURLE_OK; -} - -static CURLcode bearssl_run_until(struct Curl_easy *data, - struct connectdata *conn, int sockindex, - unsigned target) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - curl_socket_t sockfd = conn->sock[sockindex]; - unsigned state; - unsigned char *buf; - size_t len; - ssize_t ret; - int err; - - DEBUGASSERT(backend); - - for(;;) { - state = br_ssl_engine_current_state(&backend->ctx.eng); - if(state & BR_SSL_CLOSED) { - err = br_ssl_engine_last_error(&backend->ctx.eng); - switch(err) { - case BR_ERR_OK: - /* TLS close notify */ - if(connssl->state != ssl_connection_complete) { - failf(data, "SSL: connection closed during handshake"); - return CURLE_SSL_CONNECT_ERROR; - } - return CURLE_OK; - case BR_ERR_X509_EXPIRED: - failf(data, "SSL: X.509 verification: " - "certificate is expired or not yet valid"); - return CURLE_PEER_FAILED_VERIFICATION; - case BR_ERR_X509_BAD_SERVER_NAME: - failf(data, "SSL: X.509 verification: " - "expected server name was not found in the chain"); - return CURLE_PEER_FAILED_VERIFICATION; - case BR_ERR_X509_NOT_TRUSTED: - failf(data, "SSL: X.509 verification: " - "chain could not be linked to a trust anchor"); - return CURLE_PEER_FAILED_VERIFICATION; - } - /* X.509 errors are documented to have the range 32..63 */ - if(err >= 32 && err < 64) - return CURLE_PEER_FAILED_VERIFICATION; - return CURLE_SSL_CONNECT_ERROR; - } - if(state & target) - return CURLE_OK; - if(state & BR_SSL_SENDREC) { - buf = br_ssl_engine_sendrec_buf(&backend->ctx.eng, &len); - ret = swrite(sockfd, buf, len); - if(ret == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { - if(connssl->state != ssl_connection_complete) - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_AGAIN; - } - return CURLE_WRITE_ERROR; - } - br_ssl_engine_sendrec_ack(&backend->ctx.eng, ret); - } - else if(state & BR_SSL_RECVREC) { - buf = br_ssl_engine_recvrec_buf(&backend->ctx.eng, &len); - ret = sread(sockfd, buf, len); - if(ret == 0) { - failf(data, "SSL: EOF without close notify"); - return CURLE_READ_ERROR; - } - if(ret == -1) { - if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) { - if(connssl->state != ssl_connection_complete) - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_AGAIN; - } - return CURLE_READ_ERROR; - } - br_ssl_engine_recvrec_ack(&backend->ctx.eng, ret); - } - } -} - -static CURLcode bearssl_connect_step2(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - CURLcode ret; - - DEBUGASSERT(backend); - - ret = bearssl_run_until(data, conn, sockindex, - BR_SSL_SENDAPP | BR_SSL_RECVAPP); - if(ret == CURLE_AGAIN) - return CURLE_OK; - if(ret == CURLE_OK) { - if(br_ssl_engine_current_state(&backend->ctx.eng) == BR_SSL_CLOSED) { - failf(data, "SSL: connection closed during handshake"); - return CURLE_SSL_CONNECT_ERROR; - } - connssl->connecting_state = ssl_connect_3; - } - return ret; -} - -static CURLcode bearssl_connect_step3(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - CURLcode ret; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - DEBUGASSERT(backend); - - if(conn->bits.tls_enable_alpn) { - const char *protocol; - - protocol = br_ssl_engine_get_selected_protocol(&backend->ctx.eng); - if(protocol) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, protocol); - -#ifdef USE_HTTP2 - if(!strcmp(protocol, ALPN_H2)) - conn->alpn = CURL_HTTP_VERSION_2; - else -#endif - if(!strcmp(protocol, ALPN_HTTP_1_1)) - conn->alpn = CURL_HTTP_VERSION_1_1; - else - infof(data, "ALPN, unrecognized protocol %s", protocol); - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } - else - infof(data, VTLS_INFOF_NO_ALPN); - } - - if(SSL_SET_OPTION(primary.sessionid)) { - bool incache; - bool added = FALSE; - void *oldsession; - br_ssl_session_parameters *session; - - session = malloc(sizeof(*session)); - if(!session) - return CURLE_OUT_OF_MEMORY; - br_ssl_engine_get_session_parameters(&backend->ctx.eng, session); - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - &oldsession, NULL, sockindex)); - if(incache) - Curl_ssl_delsessionid(data, oldsession); - ret = Curl_ssl_addsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - session, 0, sockindex, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) - free(session); - if(ret) { - return CURLE_OUT_OF_MEMORY; - } - } - - connssl->connecting_state = ssl_connect_done; - - return CURLE_OK; -} - -static ssize_t bearssl_send(struct Curl_easy *data, int sockindex, - const void *buf, size_t len, CURLcode *err) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - unsigned char *app; - size_t applen; - - DEBUGASSERT(backend); - - for(;;) { - *err = bearssl_run_until(data, conn, sockindex, BR_SSL_SENDAPP); - if (*err != CURLE_OK) - return -1; - app = br_ssl_engine_sendapp_buf(&backend->ctx.eng, &applen); - if(!app) { - failf(data, "SSL: connection closed during write"); - *err = CURLE_SEND_ERROR; - return -1; - } - if(backend->pending_write) { - applen = backend->pending_write; - backend->pending_write = 0; - return applen; - } - if(applen > len) - applen = len; - memcpy(app, buf, applen); - br_ssl_engine_sendapp_ack(&backend->ctx.eng, applen); - br_ssl_engine_flush(&backend->ctx.eng, 0); - backend->pending_write = applen; - } -} - -static ssize_t bearssl_recv(struct Curl_easy *data, int sockindex, - char *buf, size_t len, CURLcode *err) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - unsigned char *app; - size_t applen; - - DEBUGASSERT(backend); - - *err = bearssl_run_until(data, conn, sockindex, BR_SSL_RECVAPP); - if(*err != CURLE_OK) - return -1; - app = br_ssl_engine_recvapp_buf(&backend->ctx.eng, &applen); - if(!app) - return 0; - if(applen > len) - applen = len; - memcpy(buf, app, applen); - br_ssl_engine_recvapp_ack(&backend->ctx.eng, applen); - - return applen; -} - -static CURLcode bearssl_connect_common(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode ret; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - timediff_t timeout_ms; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1 == connssl->connecting_state) { - ret = bearssl_connect_step1(data, conn, sockindex); - if(ret) - return ret; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ - ret = bearssl_connect_step2(data, conn, sockindex); - if(ret || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return ret; - } - - if(ssl_connect_3 == connssl->connecting_state) { - ret = bearssl_connect_step3(data, conn, sockindex); - if(ret) - return ret; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = bearssl_recv; - conn->send[sockindex] = bearssl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static size_t bearssl_version(char *buffer, size_t size) -{ - return msnprintf(buffer, size, "BearSSL"); -} - -static bool bearssl_data_pending(const struct connectdata *conn, - int connindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[connindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - return br_ssl_engine_current_state(&backend->ctx.eng) & BR_SSL_RECVAPP; -} - -static CURLcode bearssl_random(struct Curl_easy *data UNUSED_PARAM, - unsigned char *entropy, size_t length) -{ - static br_hmac_drbg_context ctx; - static bool seeded = FALSE; - - if(!seeded) { - br_prng_seeder seeder; - - br_hmac_drbg_init(&ctx, &br_sha256_vtable, NULL, 0); - seeder = br_prng_seeder_system(NULL); - if(!seeder || !seeder(&ctx.vtable)) - return CURLE_FAILED_INIT; - seeded = TRUE; - } - br_hmac_drbg_generate(&ctx, entropy, length); - - return CURLE_OK; -} - -static CURLcode bearssl_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - CURLcode ret; - bool done = FALSE; - - ret = bearssl_connect_common(data, conn, sockindex, FALSE, &done); - if(ret) - return ret; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static CURLcode bearssl_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - return bearssl_connect_common(data, conn, sockindex, TRUE, done); -} - -static void *bearssl_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - return &backend->ctx; -} - -static void bearssl_close(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - size_t i; - - DEBUGASSERT(backend); - - if(backend->active) { - br_ssl_engine_close(&backend->ctx.eng); - (void)bearssl_run_until(data, conn, sockindex, BR_SSL_CLOSED); - } - for(i = 0; i < backend->anchors_len; ++i) - free(backend->anchors[i].dn.data); - free(backend->anchors); -} - -static void bearssl_session_free(void *ptr) -{ - free(ptr); -} - -static CURLcode bearssl_sha256sum(const unsigned char *input, - size_t inputlen, - unsigned char *sha256sum, - size_t sha256len UNUSED_PARAM) -{ - br_sha256_context ctx; - - br_sha256_init(&ctx); - br_sha256_update(&ctx, input, inputlen); - br_sha256_out(&ctx, sha256sum); - return CURLE_OK; -} - -const struct Curl_ssl Curl_ssl_bearssl = { - { CURLSSLBACKEND_BEARSSL, "bearssl" }, /* info */ - SSLSUPP_CAINFO_BLOB | SSLSUPP_SSL_CTX, - sizeof(struct ssl_backend_data), - - Curl_none_init, /* init */ - Curl_none_cleanup, /* cleanup */ - bearssl_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ - bearssl_data_pending, /* data_pending */ - bearssl_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - bearssl_connect, /* connect */ - bearssl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - bearssl_get_internals, /* get_internals */ - bearssl_close, /* close_one */ - Curl_none_close_all, /* close_all */ - bearssl_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - bearssl_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif /* USE_BEARSSL */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_BEARSSL
+
+#include <bearssl.h>
+
+#include "bearssl.h"
+#include "urldata.h"
+#include "sendf.h"
+#include "inet_pton.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "connect.h"
+#include "select.h"
+#include "multiif.h"
+#include "curl_printf.h"
+#include "strcase.h"
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+struct x509_context {
+ const br_x509_class *vtable;
+ br_x509_minimal_context minimal;
+ br_x509_decoder_context decoder;
+ bool verifyhost;
+ bool verifypeer;
+ int cert_num;
+};
+
+struct ssl_backend_data {
+ br_ssl_client_context ctx;
+ struct x509_context x509;
+ unsigned char buf[BR_SSL_BUFSIZE_BIDI];
+ br_x509_trust_anchor *anchors;
+ size_t anchors_len;
+ const char *protocols[2];
+ /* SSL client context is active */
+ bool active;
+ /* size of pending write, yet to be flushed */
+ size_t pending_write;
+};
+
+struct cafile_parser {
+ CURLcode err;
+ bool in_cert;
+ br_x509_decoder_context xc;
+ /* array of trust anchors loaded from CAfile */
+ br_x509_trust_anchor *anchors;
+ size_t anchors_len;
+ /* buffer for DN data */
+ unsigned char dn[1024];
+ size_t dn_len;
+};
+
+#define CAFILE_SOURCE_PATH 1
+#define CAFILE_SOURCE_BLOB 2
+struct cafile_source {
+ int type;
+ const char *data;
+ size_t len;
+};
+
+static void append_dn(void *ctx, const void *buf, size_t len)
+{
+ struct cafile_parser *ca = ctx;
+
+ if(ca->err != CURLE_OK || !ca->in_cert)
+ return;
+ if(sizeof(ca->dn) - ca->dn_len < len) {
+ ca->err = CURLE_FAILED_INIT;
+ return;
+ }
+ memcpy(ca->dn + ca->dn_len, buf, len);
+ ca->dn_len += len;
+}
+
+static void x509_push(void *ctx, const void *buf, size_t len)
+{
+ struct cafile_parser *ca = ctx;
+
+ if(ca->in_cert)
+ br_x509_decoder_push(&ca->xc, buf, len);
+}
+
+static CURLcode load_cafile(struct cafile_source *source,
+ br_x509_trust_anchor **anchors,
+ size_t *anchors_len)
+{
+ struct cafile_parser ca;
+ br_pem_decoder_context pc;
+ br_x509_trust_anchor *ta;
+ size_t ta_size;
+ br_x509_trust_anchor *new_anchors;
+ size_t new_anchors_len;
+ br_x509_pkey *pkey;
+ FILE *fp = 0;
+ unsigned char buf[BUFSIZ];
+ const unsigned char *p;
+ const char *name;
+ size_t n, i, pushed;
+
+ DEBUGASSERT(source->type == CAFILE_SOURCE_PATH
+ || source->type == CAFILE_SOURCE_BLOB);
+
+ if(source->type == CAFILE_SOURCE_PATH) {
+ fp = fopen(source->data, "rb");
+ if(!fp)
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ if(source->type == CAFILE_SOURCE_BLOB && source->len > (size_t)INT_MAX)
+ return CURLE_SSL_CACERT_BADFILE;
+
+ ca.err = CURLE_OK;
+ ca.in_cert = FALSE;
+ ca.anchors = NULL;
+ ca.anchors_len = 0;
+ br_pem_decoder_init(&pc);
+ br_pem_decoder_setdest(&pc, x509_push, &ca);
+ do {
+ if(source->type == CAFILE_SOURCE_PATH) {
+ n = fread(buf, 1, sizeof(buf), fp);
+ if(n == 0)
+ break;
+ p = buf;
+ }
+ else if(source->type == CAFILE_SOURCE_BLOB) {
+ n = source->len;
+ p = (unsigned char *) source->data;
+ }
+ while(n) {
+ pushed = br_pem_decoder_push(&pc, p, n);
+ if(ca.err)
+ goto fail;
+ p += pushed;
+ n -= pushed;
+
+ switch(br_pem_decoder_event(&pc)) {
+ case 0:
+ break;
+ case BR_PEM_BEGIN_OBJ:
+ name = br_pem_decoder_name(&pc);
+ if(strcmp(name, "CERTIFICATE") && strcmp(name, "X509 CERTIFICATE"))
+ break;
+ br_x509_decoder_init(&ca.xc, append_dn, &ca);
+ ca.in_cert = TRUE;
+ ca.dn_len = 0;
+ break;
+ case BR_PEM_END_OBJ:
+ if(!ca.in_cert)
+ break;
+ ca.in_cert = FALSE;
+ if(br_x509_decoder_last_error(&ca.xc)) {
+ ca.err = CURLE_SSL_CACERT_BADFILE;
+ goto fail;
+ }
+ /* add trust anchor */
+ if(ca.anchors_len == SIZE_MAX / sizeof(ca.anchors[0])) {
+ ca.err = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ new_anchors_len = ca.anchors_len + 1;
+ new_anchors = realloc(ca.anchors,
+ new_anchors_len * sizeof(ca.anchors[0]));
+ if(!new_anchors) {
+ ca.err = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ ca.anchors = new_anchors;
+ ca.anchors_len = new_anchors_len;
+ ta = &ca.anchors[ca.anchors_len - 1];
+ ta->dn.data = NULL;
+ ta->flags = 0;
+ if(br_x509_decoder_isCA(&ca.xc))
+ ta->flags |= BR_X509_TA_CA;
+ pkey = br_x509_decoder_get_pkey(&ca.xc);
+ if(!pkey) {
+ ca.err = CURLE_SSL_CACERT_BADFILE;
+ goto fail;
+ }
+ ta->pkey = *pkey;
+
+ /* calculate space needed for trust anchor data */
+ ta_size = ca.dn_len;
+ switch(pkey->key_type) {
+ case BR_KEYTYPE_RSA:
+ ta_size += pkey->key.rsa.nlen + pkey->key.rsa.elen;
+ break;
+ case BR_KEYTYPE_EC:
+ ta_size += pkey->key.ec.qlen;
+ break;
+ default:
+ ca.err = CURLE_FAILED_INIT;
+ goto fail;
+ }
+
+ /* fill in trust anchor DN and public key data */
+ ta->dn.data = malloc(ta_size);
+ if(!ta->dn.data) {
+ ca.err = CURLE_OUT_OF_MEMORY;
+ goto fail;
+ }
+ memcpy(ta->dn.data, ca.dn, ca.dn_len);
+ ta->dn.len = ca.dn_len;
+ switch(pkey->key_type) {
+ case BR_KEYTYPE_RSA:
+ ta->pkey.key.rsa.n = ta->dn.data + ta->dn.len;
+ memcpy(ta->pkey.key.rsa.n, pkey->key.rsa.n, pkey->key.rsa.nlen);
+ ta->pkey.key.rsa.e = ta->pkey.key.rsa.n + ta->pkey.key.rsa.nlen;
+ memcpy(ta->pkey.key.rsa.e, pkey->key.rsa.e, pkey->key.rsa.elen);
+ break;
+ case BR_KEYTYPE_EC:
+ ta->pkey.key.ec.q = ta->dn.data + ta->dn.len;
+ memcpy(ta->pkey.key.ec.q, pkey->key.ec.q, pkey->key.ec.qlen);
+ break;
+ }
+ break;
+ default:
+ ca.err = CURLE_SSL_CACERT_BADFILE;
+ goto fail;
+ }
+ }
+ } while(source->type != CAFILE_SOURCE_BLOB);
+ if(fp && ferror(fp))
+ ca.err = CURLE_READ_ERROR;
+ else if(ca.in_cert)
+ ca.err = CURLE_SSL_CACERT_BADFILE;
+
+fail:
+ if(fp)
+ fclose(fp);
+ if(ca.err == CURLE_OK) {
+ *anchors = ca.anchors;
+ *anchors_len = ca.anchors_len;
+ }
+ else {
+ for(i = 0; i < ca.anchors_len; ++i)
+ free(ca.anchors[i].dn.data);
+ free(ca.anchors);
+ }
+
+ return ca.err;
+}
+
+static void x509_start_chain(const br_x509_class **ctx,
+ const char *server_name)
+{
+ struct x509_context *x509 = (struct x509_context *)ctx;
+
+ if(!x509->verifypeer) {
+ x509->cert_num = 0;
+ return;
+ }
+
+ if(!x509->verifyhost)
+ server_name = NULL;
+ x509->minimal.vtable->start_chain(&x509->minimal.vtable, server_name);
+}
+
+static void x509_start_cert(const br_x509_class **ctx, uint32_t length)
+{
+ struct x509_context *x509 = (struct x509_context *)ctx;
+
+ if(!x509->verifypeer) {
+ /* Only decode the first cert in the chain to obtain the public key */
+ if(x509->cert_num == 0)
+ br_x509_decoder_init(&x509->decoder, NULL, NULL);
+ return;
+ }
+
+ x509->minimal.vtable->start_cert(&x509->minimal.vtable, length);
+}
+
+static void x509_append(const br_x509_class **ctx, const unsigned char *buf,
+ size_t len)
+{
+ struct x509_context *x509 = (struct x509_context *)ctx;
+
+ if(!x509->verifypeer) {
+ if(x509->cert_num == 0)
+ br_x509_decoder_push(&x509->decoder, buf, len);
+ return;
+ }
+
+ x509->minimal.vtable->append(&x509->minimal.vtable, buf, len);
+}
+
+static void x509_end_cert(const br_x509_class **ctx)
+{
+ struct x509_context *x509 = (struct x509_context *)ctx;
+
+ if(!x509->verifypeer) {
+ x509->cert_num++;
+ return;
+ }
+
+ x509->minimal.vtable->end_cert(&x509->minimal.vtable);
+}
+
+static unsigned x509_end_chain(const br_x509_class **ctx)
+{
+ struct x509_context *x509 = (struct x509_context *)ctx;
+
+ if(!x509->verifypeer) {
+ return br_x509_decoder_last_error(&x509->decoder);
+ }
+
+ return x509->minimal.vtable->end_chain(&x509->minimal.vtable);
+}
+
+static const br_x509_pkey *x509_get_pkey(const br_x509_class *const *ctx,
+ unsigned *usages)
+{
+ struct x509_context *x509 = (struct x509_context *)ctx;
+
+ if(!x509->verifypeer) {
+ /* Nothing in the chain is verified, just return the public key of the
+ first certificate and allow its usage for both TLS_RSA_* and
+ TLS_ECDHE_* */
+ if(usages)
+ *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN;
+ return br_x509_decoder_get_pkey(&x509->decoder);
+ }
+
+ return x509->minimal.vtable->get_pkey(&x509->minimal.vtable, usages);
+}
+
+static const br_x509_class x509_vtable = {
+ sizeof(struct x509_context),
+ x509_start_chain,
+ x509_start_cert,
+ x509_append,
+ x509_end_cert,
+ x509_end_chain,
+ x509_get_pkey
+};
+
+struct st_cipher {
+ const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */
+ const char *alias_name; /* Alias name is the same as OpenSSL cipher name */
+ uint16_t num; /* BearSSL cipher suite */
+};
+
+/* Macro to initialize st_cipher data structure */
+#define CIPHER_DEF(num, alias) { #num, alias, BR_##num }
+
+static const struct st_cipher ciphertable[] = {
+ /* RFC 2246 TLS 1.0 */
+ CIPHER_DEF(TLS_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */
+ "DES-CBC3-SHA"),
+
+ /* RFC 3268 TLS 1.0 AES */
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */
+ "AES128-SHA"),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */
+ "AES256-SHA"),
+
+ /* RFC 5246 TLS 1.2 */
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */
+ "AES128-SHA256"),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */
+ "AES256-SHA256"),
+
+ /* RFC 5288 TLS 1.2 AES GCM */
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */
+ "AES128-GCM-SHA256"),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */
+ "AES256-GCM-SHA384"),
+
+ /* RFC 4492 TLS 1.0 ECC */
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */
+ "ECDH-ECDSA-DES-CBC3-SHA"),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */
+ "ECDH-ECDSA-AES128-SHA"),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */
+ "ECDH-ECDSA-AES256-SHA"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */
+ "ECDHE-ECDSA-DES-CBC3-SHA"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */
+ "ECDHE-ECDSA-AES128-SHA"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */
+ "ECDHE-ECDSA-AES256-SHA"),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */
+ "ECDH-RSA-DES-CBC3-SHA"),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */
+ "ECDH-RSA-AES128-SHA"),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */
+ "ECDH-RSA-AES256-SHA"),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */
+ "ECDHE-RSA-DES-CBC3-SHA"),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */
+ "ECDHE-RSA-AES128-SHA"),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */
+ "ECDHE-RSA-AES256-SHA"),
+
+ /* RFC 5289 TLS 1.2 ECC HMAC SHA256/384 */
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */
+ "ECDHE-ECDSA-AES128-SHA256"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */
+ "ECDHE-ECDSA-AES256-SHA384"),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */
+ "ECDH-ECDSA-AES128-SHA256"),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */
+ "ECDH-ECDSA-AES256-SHA384"),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */
+ "ECDHE-RSA-AES128-SHA256"),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */
+ "ECDHE-RSA-AES256-SHA384"),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */
+ "ECDH-RSA-AES128-SHA256"),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */
+ "ECDH-RSA-AES256-SHA384"),
+
+ /* RFC 5289 TLS 1.2 GCM */
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */
+ "ECDHE-ECDSA-AES128-GCM-SHA256"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */
+ "ECDHE-ECDSA-AES256-GCM-SHA384"),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */
+ "ECDH-ECDSA-AES128-GCM-SHA256"),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */
+ "ECDH-ECDSA-AES256-GCM-SHA384"),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */
+ "ECDHE-RSA-AES128-GCM-SHA256"),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */
+ "ECDHE-RSA-AES256-GCM-SHA384"),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */
+ "ECDH-RSA-AES128-GCM-SHA256"),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */
+ "ECDH-RSA-AES256-GCM-SHA384"),
+#ifdef BR_TLS_RSA_WITH_AES_128_CCM
+
+ /* RFC 6655 TLS 1.2 CCM
+ Supported since BearSSL 0.6 */
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM, /* 0xC09C */
+ "AES128-CCM"),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM, /* 0xC09D */
+ "AES256-CCM"),
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_CCM_8, /* 0xC0A0 */
+ "AES128-CCM8"),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_CCM_8, /* 0xC0A1 */
+ "AES256-CCM8"),
+
+ /* RFC 7251 TLS 1.2 ECC CCM
+ Supported since BearSSL 0.6 */
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM, /* 0xC0AC */
+ "ECDHE-ECDSA-AES128-CCM"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM, /* 0xC0AD */
+ "ECDHE-ECDSA-AES256-CCM"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, /* 0xC0AE */
+ "ECDHE-ECDSA-AES128-CCM8"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8, /* 0xC0AF */
+ "ECDHE-ECDSA-AES256-CCM8"),
+#endif
+
+ /* RFC 7905 TLS 1.2 ChaCha20-Poly1305
+ Supported since BearSSL 0.2 */
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */
+ "ECDHE-RSA-CHACHA20-POLY1305"),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */
+ "ECDHE-ECDSA-CHACHA20-POLY1305"),
+};
+
+#define NUM_OF_CIPHERS (sizeof(ciphertable) / sizeof(ciphertable[0]))
+#define CIPHER_NAME_BUF_LEN 64
+
+static bool is_separator(char c)
+{
+ /* Return whether character is a cipher list separator. */
+ switch(c) {
+ case ' ':
+ case '\t':
+ case ':':
+ case ',':
+ case ';':
+ return true;
+ }
+ return false;
+}
+
+static CURLcode bearssl_set_selected_ciphers(struct Curl_easy *data,
+ br_ssl_engine_context *ssl_eng,
+ const char *ciphers)
+{
+ uint16_t selected_ciphers[NUM_OF_CIPHERS];
+ size_t selected_count = 0;
+ char cipher_name[CIPHER_NAME_BUF_LEN];
+ const char *cipher_start = ciphers;
+ const char *cipher_end;
+ size_t i, j;
+
+ if(!cipher_start)
+ return CURLE_SSL_CIPHER;
+
+ while(true) {
+ /* Extract the next cipher name from the ciphers string */
+ while(is_separator(*cipher_start))
+ ++cipher_start;
+ if(*cipher_start == '\0')
+ break;
+ cipher_end = cipher_start;
+ while(*cipher_end != '\0' && !is_separator(*cipher_end))
+ ++cipher_end;
+ j = cipher_end - cipher_start < CIPHER_NAME_BUF_LEN - 1 ?
+ cipher_end - cipher_start : CIPHER_NAME_BUF_LEN - 1;
+ strncpy(cipher_name, cipher_start, j);
+ cipher_name[j] = '\0';
+ cipher_start = cipher_end;
+
+ /* Lookup the cipher name in the table of available ciphers. If the cipher
+ name starts with "TLS_" we do the lookup by IANA name. Otherwise, we try
+ to match cipher name by an (OpenSSL) alias. */
+ if(strncasecompare(cipher_name, "TLS_", 4)) {
+ for(i = 0; i < NUM_OF_CIPHERS &&
+ !strcasecompare(cipher_name, ciphertable[i].name); ++i);
+ }
+ else {
+ for(i = 0; i < NUM_OF_CIPHERS &&
+ !strcasecompare(cipher_name, ciphertable[i].alias_name); ++i);
+ }
+ if(i == NUM_OF_CIPHERS) {
+ infof(data, "BearSSL: unknown cipher in list: %s", cipher_name);
+ continue;
+ }
+
+ /* No duplicates allowed */
+ for(j = 0; j < selected_count &&
+ selected_ciphers[j] != ciphertable[i].num; j++);
+ if(j < selected_count) {
+ infof(data, "BearSSL: duplicate cipher in list: %s", cipher_name);
+ continue;
+ }
+
+ DEBUGASSERT(selected_count < NUM_OF_CIPHERS);
+ selected_ciphers[selected_count] = ciphertable[i].num;
+ ++selected_count;
+ }
+
+ if(selected_count == 0) {
+ failf(data, "BearSSL: no supported cipher in list");
+ return CURLE_SSL_CIPHER;
+ }
+
+ br_ssl_engine_set_suites(ssl_eng, selected_ciphers, selected_count);
+ return CURLE_OK;
+}
+
+static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
+ const char * const ssl_cafile =
+ /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+ (ca_info_blob ? NULL : conn_config->CAfile);
+ const char *hostname = connssl->hostname;
+ const bool verifypeer = conn_config->verifypeer;
+ const bool verifyhost = conn_config->verifyhost;
+ CURLcode ret;
+ unsigned version_min, version_max;
+#ifdef ENABLE_IPV6
+ struct in6_addr addr;
+#else
+ struct in_addr addr;
+#endif
+
+ DEBUGASSERT(backend);
+
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_SSLv2:
+ failf(data, "BearSSL does not support SSLv2");
+ return CURLE_SSL_CONNECT_ERROR;
+ case CURL_SSLVERSION_SSLv3:
+ failf(data, "BearSSL does not support SSLv3");
+ return CURLE_SSL_CONNECT_ERROR;
+ case CURL_SSLVERSION_TLSv1_0:
+ version_min = BR_TLS10;
+ version_max = BR_TLS10;
+ break;
+ case CURL_SSLVERSION_TLSv1_1:
+ version_min = BR_TLS11;
+ version_max = BR_TLS11;
+ break;
+ case CURL_SSLVERSION_TLSv1_2:
+ version_min = BR_TLS12;
+ version_max = BR_TLS12;
+ break;
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ version_min = BR_TLS10;
+ version_max = BR_TLS12;
+ break;
+ default:
+ failf(data, "BearSSL: unknown CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if(ca_info_blob) {
+ struct cafile_source source;
+ source.type = CAFILE_SOURCE_BLOB;
+ source.data = ca_info_blob->data;
+ source.len = ca_info_blob->len;
+
+ ret = load_cafile(&source, &backend->anchors, &backend->anchors_len);
+ if(ret != CURLE_OK) {
+ if(verifypeer) {
+ failf(data, "error importing CA certificate blob");
+ return ret;
+ }
+ /* Only warn if no certificate verification is required. */
+ infof(data, "error importing CA certificate blob, continuing anyway");
+ }
+ }
+
+ if(ssl_cafile) {
+ struct cafile_source source;
+ source.type = CAFILE_SOURCE_PATH;
+ source.data = ssl_cafile;
+ source.len = 0;
+
+ ret = load_cafile(&source, &backend->anchors, &backend->anchors_len);
+ if(ret != CURLE_OK) {
+ if(verifypeer) {
+ failf(data, "error setting certificate verify locations."
+ " CAfile: %s", ssl_cafile);
+ return ret;
+ }
+ infof(data, "error setting certificate verify locations,"
+ " continuing anyway:");
+ }
+ }
+
+ /* initialize SSL context */
+ br_ssl_client_init_full(&backend->ctx, &backend->x509.minimal,
+ backend->anchors, backend->anchors_len);
+ br_ssl_engine_set_versions(&backend->ctx.eng, version_min, version_max);
+ br_ssl_engine_set_buffer(&backend->ctx.eng, backend->buf,
+ sizeof(backend->buf), 1);
+
+ if(conn_config->cipher_list) {
+ /* Override the ciphers as specified. For the default cipher list see the
+ BearSSL source code of br_ssl_client_init_full() */
+ ret = bearssl_set_selected_ciphers(data, &backend->ctx.eng,
+ conn_config->cipher_list);
+ if(ret)
+ return ret;
+ }
+
+ /* initialize X.509 context */
+ backend->x509.vtable = &x509_vtable;
+ backend->x509.verifypeer = verifypeer;
+ backend->x509.verifyhost = verifyhost;
+ br_ssl_engine_set_x509(&backend->ctx.eng, &backend->x509.vtable);
+
+ if(ssl_config->primary.sessionid) {
+ void *session;
+
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, &session, NULL)) {
+ br_ssl_engine_set_session_parameters(&backend->ctx.eng, session);
+ infof(data, "BearSSL: re-using session ID");
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+ if(cf->conn->bits.tls_enable_alpn) {
+ int cur = 0;
+
+ /* NOTE: when adding more protocols here, increase the size of the
+ * protocols array in `struct ssl_backend_data`.
+ */
+
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2
+#ifndef CURL_DISABLE_PROXY
+ && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
+#endif
+ ) {
+ backend->protocols[cur++] = ALPN_H2;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+ }
+#endif
+
+ backend->protocols[cur++] = ALPN_HTTP_1_1;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+
+ br_ssl_engine_set_protocol_names(&backend->ctx.eng,
+ backend->protocols, cur);
+ }
+
+ if((1 == Curl_inet_pton(AF_INET, hostname, &addr))
+#ifdef ENABLE_IPV6
+ || (1 == Curl_inet_pton(AF_INET6, hostname, &addr))
+#endif
+ ) {
+ if(verifyhost) {
+ failf(data, "BearSSL: "
+ "host verification of IP address is not supported");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ hostname = NULL;
+ }
+ else {
+ char *snihost = Curl_ssl_snihost(data, hostname, NULL);
+ if(!snihost) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ hostname = snihost;
+ }
+
+ /* give application a chance to interfere with SSL set up. */
+ if(data->set.ssl.fsslctx) {
+ Curl_set_in_callback(data, true);
+ ret = (*data->set.ssl.fsslctx)(data, &backend->ctx,
+ data->set.ssl.fsslctxp);
+ Curl_set_in_callback(data, false);
+ if(ret) {
+ failf(data, "BearSSL: error signaled by ssl ctx callback");
+ return ret;
+ }
+ }
+
+ if(!br_ssl_client_reset(&backend->ctx, hostname, 1))
+ return CURLE_FAILED_INIT;
+ backend->active = TRUE;
+
+ connssl->connecting_state = ssl_connect_2;
+
+ return CURLE_OK;
+}
+
+static CURLcode bearssl_run_until(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ unsigned target)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ unsigned state;
+ unsigned char *buf;
+ size_t len;
+ ssize_t ret;
+ CURLcode result;
+ int err;
+
+ DEBUGASSERT(backend);
+
+ for(;;) {
+ state = br_ssl_engine_current_state(&backend->ctx.eng);
+ if(state & BR_SSL_CLOSED) {
+ err = br_ssl_engine_last_error(&backend->ctx.eng);
+ switch(err) {
+ case BR_ERR_OK:
+ /* TLS close notify */
+ if(connssl->state != ssl_connection_complete) {
+ failf(data, "SSL: connection closed during handshake");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ return CURLE_OK;
+ case BR_ERR_X509_EXPIRED:
+ failf(data, "SSL: X.509 verification: "
+ "certificate is expired or not yet valid");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case BR_ERR_X509_BAD_SERVER_NAME:
+ failf(data, "SSL: X.509 verification: "
+ "expected server name was not found in the chain");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case BR_ERR_X509_NOT_TRUSTED:
+ failf(data, "SSL: X.509 verification: "
+ "chain could not be linked to a trust anchor");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ /* X.509 errors are documented to have the range 32..63 */
+ if(err >= 32 && err < 64)
+ return CURLE_PEER_FAILED_VERIFICATION;
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ if(state & target)
+ return CURLE_OK;
+ if(state & BR_SSL_SENDREC) {
+ buf = br_ssl_engine_sendrec_buf(&backend->ctx.eng, &len);
+ ret = Curl_conn_cf_send(cf->next, data, (char *)buf, len, &result);
+ if(ret <= 0) {
+ return result;
+ }
+ br_ssl_engine_sendrec_ack(&backend->ctx.eng, ret);
+ }
+ else if(state & BR_SSL_RECVREC) {
+ buf = br_ssl_engine_recvrec_buf(&backend->ctx.eng, &len);
+ ret = Curl_conn_cf_recv(cf->next, data, (char *)buf, len, &result);
+ if(ret == 0) {
+ failf(data, "SSL: EOF without close notify");
+ return CURLE_READ_ERROR;
+ }
+ if(ret <= 0) {
+ return result;
+ }
+ br_ssl_engine_recvrec_ack(&backend->ctx.eng, ret);
+ }
+ }
+}
+
+static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ CURLcode ret;
+
+ DEBUGASSERT(backend);
+
+ ret = bearssl_run_until(cf, data, BR_SSL_SENDAPP | BR_SSL_RECVAPP);
+ if(ret == CURLE_AGAIN)
+ return CURLE_OK;
+ if(ret == CURLE_OK) {
+ if(br_ssl_engine_current_state(&backend->ctx.eng) == BR_SSL_CLOSED) {
+ failf(data, "SSL: connection closed during handshake");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ connssl->connecting_state = ssl_connect_3;
+ }
+ return ret;
+}
+
+static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ CURLcode ret;
+
+ DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
+ DEBUGASSERT(backend);
+
+ if(cf->conn->bits.tls_enable_alpn) {
+ const char *protocol;
+
+ protocol = br_ssl_engine_get_selected_protocol(&backend->ctx.eng);
+ if(protocol) {
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, protocol);
+
+#ifdef USE_HTTP2
+ if(!strcmp(protocol, ALPN_H2))
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+ else
+#endif
+ if(!strcmp(protocol, ALPN_HTTP_1_1))
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+ else
+ infof(data, "ALPN, unrecognized protocol %s", protocol);
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+ }
+ else
+ infof(data, VTLS_INFOF_NO_ALPN);
+ }
+
+ if(ssl_config->primary.sessionid) {
+ bool incache;
+ bool added = FALSE;
+ void *oldsession;
+ br_ssl_session_parameters *session;
+
+ session = malloc(sizeof(*session));
+ if(!session)
+ return CURLE_OUT_OF_MEMORY;
+ br_ssl_engine_get_session_parameters(&backend->ctx.eng, session);
+ Curl_ssl_sessionid_lock(data);
+ incache = !(Curl_ssl_getsessionid(cf, data, &oldsession, NULL));
+ if(incache)
+ Curl_ssl_delsessionid(data, oldsession);
+ ret = Curl_ssl_addsessionid(cf, data, session, 0, &added);
+ Curl_ssl_sessionid_unlock(data);
+ if(!added)
+ free(session);
+ if(ret) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+
+ connssl->connecting_state = ssl_connect_done;
+
+ return CURLE_OK;
+}
+
+static ssize_t bearssl_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ unsigned char *app;
+ size_t applen;
+
+ DEBUGASSERT(backend);
+
+ for(;;) {
+ *err = bearssl_run_until(cf, data, BR_SSL_SENDAPP);
+ if (*err != CURLE_OK)
+ return -1;
+ app = br_ssl_engine_sendapp_buf(&backend->ctx.eng, &applen);
+ if(!app) {
+ failf(data, "SSL: connection closed during write");
+ *err = CURLE_SEND_ERROR;
+ return -1;
+ }
+ if(backend->pending_write) {
+ applen = backend->pending_write;
+ backend->pending_write = 0;
+ return applen;
+ }
+ if(applen > len)
+ applen = len;
+ memcpy(app, buf, applen);
+ br_ssl_engine_sendapp_ack(&backend->ctx.eng, applen);
+ br_ssl_engine_flush(&backend->ctx.eng, 0);
+ backend->pending_write = applen;
+ }
+}
+
+static ssize_t bearssl_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ unsigned char *app;
+ size_t applen;
+
+ DEBUGASSERT(backend);
+
+ *err = bearssl_run_until(cf, data, BR_SSL_RECVAPP);
+ if(*err != CURLE_OK)
+ return -1;
+ app = br_ssl_engine_recvapp_buf(&backend->ctx.eng, &applen);
+ if(!app)
+ return 0;
+ if(applen > len)
+ applen = len;
+ memcpy(buf, app, applen);
+ br_ssl_engine_recvapp_ack(&backend->ctx.eng, applen);
+
+ return applen;
+}
+
+static CURLcode bearssl_connect_common(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool nonblocking,
+ bool *done)
+{
+ CURLcode ret;
+ struct ssl_connect_data *connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ timediff_t timeout_ms;
+ int what;
+
+ /* check if the connection has already been established */
+ if(ssl_connection_complete == connssl->state) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ if(ssl_connect_1 == connssl->connecting_state) {
+ ret = bearssl_connect_step1(cf, data);
+ if(ret)
+ return ret;
+ }
+
+ while(ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state) {
+ /* check allowed time left */
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ /* if ssl is expecting something, check if it's available. */
+ if(ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state) {
+
+ curl_socket_t writefd = ssl_connect_2_writing ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ nonblocking?0:timeout_ms);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else if(0 == what) {
+ if(nonblocking) {
+ *done = FALSE;
+ return CURLE_OK;
+ }
+ else {
+ /* timeout */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+ /* socket is readable or writable */
+ }
+
+ /* Run transaction, and return to the caller if it failed or if this
+ * connection is done nonblocking and this loop would execute again. This
+ * permits the owner of a multi handle to abort a connection attempt
+ * before step2 has completed while ensuring that a client using select()
+ * or epoll() will always have a valid fdset to wait on.
+ */
+ ret = bearssl_connect_step2(cf, data);
+ if(ret || (nonblocking &&
+ (ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state)))
+ return ret;
+ }
+
+ if(ssl_connect_3 == connssl->connecting_state) {
+ ret = bearssl_connect_step3(cf, data);
+ if(ret)
+ return ret;
+ }
+
+ if(ssl_connect_done == connssl->connecting_state) {
+ connssl->state = ssl_connection_complete;
+ *done = TRUE;
+ }
+ else
+ *done = FALSE;
+
+ /* Reset our connect state machine */
+ connssl->connecting_state = ssl_connect_1;
+
+ return CURLE_OK;
+}
+
+static size_t bearssl_version(char *buffer, size_t size)
+{
+ return msnprintf(buffer, size, "BearSSL");
+}
+
+static bool bearssl_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx = cf->ctx;
+
+ (void)data;
+ DEBUGASSERT(ctx && ctx->backend);
+ return br_ssl_engine_current_state(&ctx->backend->ctx.eng) & BR_SSL_RECVAPP;
+}
+
+static CURLcode bearssl_random(struct Curl_easy *data UNUSED_PARAM,
+ unsigned char *entropy, size_t length)
+{
+ static br_hmac_drbg_context ctx;
+ static bool seeded = FALSE;
+
+ if(!seeded) {
+ br_prng_seeder seeder;
+
+ br_hmac_drbg_init(&ctx, &br_sha256_vtable, NULL, 0);
+ seeder = br_prng_seeder_system(NULL);
+ if(!seeder || !seeder(&ctx.vtable))
+ return CURLE_FAILED_INIT;
+ seeded = TRUE;
+ }
+ br_hmac_drbg_generate(&ctx, entropy, length);
+
+ return CURLE_OK;
+}
+
+static CURLcode bearssl_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode ret;
+ bool done = FALSE;
+
+ ret = bearssl_connect_common(cf, data, FALSE, &done);
+ if(ret)
+ return ret;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+static CURLcode bearssl_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return bearssl_connect_common(cf, data, TRUE, done);
+}
+
+static void *bearssl_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ DEBUGASSERT(backend);
+ return &backend->ctx;
+}
+
+static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ size_t i;
+
+ DEBUGASSERT(backend);
+
+ if(backend->active) {
+ backend->active = FALSE;
+ br_ssl_engine_close(&backend->ctx.eng);
+ (void)bearssl_run_until(cf, data, BR_SSL_CLOSED);
+ }
+ if(backend->anchors) {
+ for(i = 0; i < backend->anchors_len; ++i)
+ free(backend->anchors[i].dn.data);
+ Curl_safefree(backend->anchors);
+ }
+}
+
+static void bearssl_session_free(void *ptr)
+{
+ free(ptr);
+}
+
+static CURLcode bearssl_sha256sum(const unsigned char *input,
+ size_t inputlen,
+ unsigned char *sha256sum,
+ size_t sha256len UNUSED_PARAM)
+{
+ br_sha256_context ctx;
+
+ br_sha256_init(&ctx);
+ br_sha256_update(&ctx, input, inputlen);
+ br_sha256_out(&ctx, sha256sum);
+ return CURLE_OK;
+}
+
+const struct Curl_ssl Curl_ssl_bearssl = {
+ { CURLSSLBACKEND_BEARSSL, "bearssl" }, /* info */
+ SSLSUPP_CAINFO_BLOB | SSLSUPP_SSL_CTX | SSLSUPP_HTTPS_PROXY,
+ sizeof(struct ssl_backend_data),
+
+ Curl_none_init, /* init */
+ Curl_none_cleanup, /* cleanup */
+ bearssl_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ Curl_none_shutdown, /* shutdown */
+ bearssl_data_pending, /* data_pending */
+ bearssl_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ bearssl_connect, /* connect */
+ bearssl_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ bearssl_get_internals, /* get_internals */
+ bearssl_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ bearssl_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ bearssl_sha256sum, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ bearssl_recv, /* recv decrypted data */
+ bearssl_send, /* send data to encrypt */
+};
+
+#endif /* USE_BEARSSL */
diff --git a/libs/libcurl/src/vtls/bearssl.h b/libs/libcurl/src/vtls/bearssl.h index 5125359961..771c1a4b62 100644 --- a/libs/libcurl/src/vtls/bearssl.h +++ b/libs/libcurl/src/vtls/bearssl.h @@ -1,34 +1,34 @@ -#ifndef HEADER_CURL_BEARSSL_H -#define HEADER_CURL_BEARSSL_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef USE_BEARSSL - -extern const struct Curl_ssl Curl_ssl_bearssl; - -#endif /* USE_BEARSSL */ -#endif /* HEADER_CURL_BEARSSL_H */ +#ifndef HEADER_CURL_BEARSSL_H
+#define HEADER_CURL_BEARSSL_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_BEARSSL
+
+extern const struct Curl_ssl Curl_ssl_bearssl;
+
+#endif /* USE_BEARSSL */
+#endif /* HEADER_CURL_BEARSSL_H */
diff --git a/libs/libcurl/src/vtls/gskit.c b/libs/libcurl/src/vtls/gskit.c index 4ee4edea6c..ba452f56e8 100644 --- a/libs/libcurl/src/vtls/gskit.c +++ b/libs/libcurl/src/vtls/gskit.c @@ -1,1329 +1,1326 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef USE_GSKIT - -#include <gskssl.h> -#include <qsoasync.h> -#undef HAVE_SOCKETPAIR /* because the native one isn't good enough */ -#include "socketpair.h" -#include "strerror.h" - -/* Some symbols are undefined/unsupported on OS400 versions < V7R1. */ -#ifndef GSK_SSL_EXTN_SERVERNAME_REQUEST -#define GSK_SSL_EXTN_SERVERNAME_REQUEST 230 -#endif - -#ifndef GSK_TLSV10_CIPHER_SPECS -#define GSK_TLSV10_CIPHER_SPECS 236 -#endif - -#ifndef GSK_TLSV11_CIPHER_SPECS -#define GSK_TLSV11_CIPHER_SPECS 237 -#endif - -#ifndef GSK_TLSV12_CIPHER_SPECS -#define GSK_TLSV12_CIPHER_SPECS 238 -#endif - -#ifndef GSK_PROTOCOL_TLSV11 -#define GSK_PROTOCOL_TLSV11 437 -#endif - -#ifndef GSK_PROTOCOL_TLSV12 -#define GSK_PROTOCOL_TLSV12 438 -#endif - -#ifndef GSK_FALSE -#define GSK_FALSE 0 -#endif - -#ifndef GSK_TRUE -#define GSK_TRUE 1 -#endif - - -#include <limits.h> - -#include <curl/curl.h> -#include "urldata.h" -#include "sendf.h" -#include "gskit.h" -#include "vtls.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" -#include "timediff.h" -#include "x509asn1.h" -#include "curl_printf.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - - -/* Directions. */ -#define SOS_READ 0x01 -#define SOS_WRITE 0x02 - -/* SSL version flags. */ -#define CURL_GSKPROTO_SSLV2 0 -#define CURL_GSKPROTO_SSLV2_MASK (1 << CURL_GSKPROTO_SSLV2) -#define CURL_GSKPROTO_SSLV3 1 -#define CURL_GSKPROTO_SSLV3_MASK (1 << CURL_GSKPROTO_SSLV3) -#define CURL_GSKPROTO_TLSV10 2 -#define CURL_GSKPROTO_TLSV10_MASK (1 << CURL_GSKPROTO_TLSV10) -#define CURL_GSKPROTO_TLSV11 3 -#define CURL_GSKPROTO_TLSV11_MASK (1 << CURL_GSKPROTO_TLSV11) -#define CURL_GSKPROTO_TLSV12 4 -#define CURL_GSKPROTO_TLSV12_MASK (1 << CURL_GSKPROTO_TLSV12) -#define CURL_GSKPROTO_LAST 5 - -struct ssl_backend_data { - gsk_handle handle; - int iocport; -#ifndef CURL_DISABLE_PROXY - int localfd; - int remotefd; -#endif -}; - -#define BACKEND connssl->backend - -/* Supported ciphers. */ -struct gskit_cipher { - const char *name; /* Cipher name. */ - const char *gsktoken; /* Corresponding token for GSKit String. */ - unsigned int versions; /* SSL version flags. */ -}; - -static const struct gskit_cipher ciphertable[] = { - { "null-md5", "01", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "null-sha", "02", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "exp-rc4-md5", "03", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK }, - { "rc4-md5", "04", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "rc4-sha", "05", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "exp-rc2-cbc-md5", "06", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK }, - { "exp-des-cbc-sha", "09", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK }, - { "des-cbc3-sha", "0A", - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-sha", "2F", - CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK | - CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-sha", "35", - CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK | - CURL_GSKPROTO_TLSV12_MASK }, - { "null-sha256", "3B", CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-sha256", "3C", CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-sha256", "3D", CURL_GSKPROTO_TLSV12_MASK }, - { "aes128-gcm-sha256", - "9C", CURL_GSKPROTO_TLSV12_MASK }, - { "aes256-gcm-sha384", - "9D", CURL_GSKPROTO_TLSV12_MASK }, - { "rc4-md5", "1", CURL_GSKPROTO_SSLV2_MASK }, - { "exp-rc4-md5", "2", CURL_GSKPROTO_SSLV2_MASK }, - { "rc2-md5", "3", CURL_GSKPROTO_SSLV2_MASK }, - { "exp-rc2-md5", "4", CURL_GSKPROTO_SSLV2_MASK }, - { "des-cbc-md5", "6", CURL_GSKPROTO_SSLV2_MASK }, - { "des-cbc3-md5", "7", CURL_GSKPROTO_SSLV2_MASK }, - { (const char *) NULL, (const char *) NULL, 0 } -}; - - -static bool is_separator(char c) -{ - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; -} - - -static CURLcode gskit_status(struct Curl_easy *data, int rc, - const char *procname, CURLcode defcode) -{ - char buffer[STRERROR_LEN]; - /* Process GSKit status and map it to a CURLcode. */ - switch(rc) { - case GSK_OK: - case GSK_OS400_ASYNCHRONOUS_SOC_INIT: - return CURLE_OK; - case GSK_KEYRING_OPEN_ERROR: - case GSK_OS400_ERROR_NO_ACCESS: - return CURLE_SSL_CACERT_BADFILE; - case GSK_INSUFFICIENT_STORAGE: - return CURLE_OUT_OF_MEMORY; - case GSK_ERROR_BAD_V2_CIPHER: - case GSK_ERROR_BAD_V3_CIPHER: - case GSK_ERROR_NO_CIPHERS: - return CURLE_SSL_CIPHER; - case GSK_OS400_ERROR_NOT_TRUSTED_ROOT: - case GSK_ERROR_CERT_VALIDATION: - return CURLE_PEER_FAILED_VERIFICATION; - case GSK_OS400_ERROR_TIMED_OUT: - return CURLE_OPERATION_TIMEDOUT; - case GSK_WOULD_BLOCK: - return CURLE_AGAIN; - case GSK_OS400_ERROR_NOT_REGISTERED: - break; - case GSK_ERROR_IO: - switch(errno) { - case ENOMEM: - return CURLE_OUT_OF_MEMORY; - default: - failf(data, "%s I/O error: %s", procname, - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - } - break; - default: - failf(data, "%s: %s", procname, gsk_strerror(rc)); - break; - } - return defcode; -} - - -static CURLcode set_enum(struct Curl_easy *data, gsk_handle h, - GSK_ENUM_ID id, GSK_ENUM_VALUE value, bool unsupported_ok) -{ - char buffer[STRERROR_LEN]; - int rc = gsk_attribute_set_enum(h, id, value); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_enum() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - case GSK_ATTRIBUTE_INVALID_ID: - if(unsupported_ok) - return CURLE_UNSUPPORTED_PROTOCOL; - default: - failf(data, "gsk_attribute_set_enum(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_buffer(struct Curl_easy *data, gsk_handle h, - GSK_BUF_ID id, const char *buf, bool unsupported_ok) -{ - char buffer[STRERROR_LEN]; - int rc = gsk_attribute_set_buffer(h, id, buf, 0); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_buffer() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - case GSK_ATTRIBUTE_INVALID_ID: - if(unsupported_ok) - return CURLE_UNSUPPORTED_PROTOCOL; - default: - failf(data, "gsk_attribute_set_buffer(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_numeric(struct Curl_easy *data, - gsk_handle h, GSK_NUM_ID id, int value) -{ - char buffer[STRERROR_LEN]; - int rc = gsk_attribute_set_numeric_value(h, id, value); - - switch(rc) { - case GSK_OK: - return CURLE_OK; - case GSK_ERROR_IO: - failf(data, "gsk_attribute_set_numeric_value() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - break; - default: - failf(data, "gsk_attribute_set_numeric_value(): %s", gsk_strerror(rc)); - break; - } - return CURLE_SSL_CONNECT_ERROR; -} - - -static CURLcode set_ciphers(struct Curl_easy *data, - gsk_handle h, unsigned int *protoflags) -{ - struct connectdata *conn = data->conn; - const char *cipherlist = SSL_CONN_CONFIG(cipher_list); - const char *clp; - const struct gskit_cipher *ctp; - int i; - int l; - bool unsupported; - CURLcode result; - struct { - char *buf; - char *ptr; - } ciphers[CURL_GSKPROTO_LAST]; - - /* Compile cipher list into GSKit-compatible cipher lists. */ - - if(!cipherlist) - return CURLE_OK; - while(is_separator(*cipherlist)) /* Skip initial separators. */ - cipherlist++; - if(!*cipherlist) - return CURLE_OK; - - /* We allocate GSKit buffers of the same size as the input string: since - GSKit tokens are always shorter than their cipher names, allocated buffers - will always be large enough to accommodate the result. */ - l = strlen(cipherlist) + 1; - memset((char *) ciphers, 0, sizeof(ciphers)); - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - ciphers[i].buf = malloc(l); - if(!ciphers[i].buf) { - while(i--) - free(ciphers[i].buf); - return CURLE_OUT_OF_MEMORY; - } - ciphers[i].ptr = ciphers[i].buf; - *ciphers[i].ptr = '\0'; - } - - /* Process each cipher in input string. */ - unsupported = FALSE; - result = CURLE_OK; - for(;;) { - for(clp = cipherlist; *cipherlist && !is_separator(*cipherlist);) - cipherlist++; - l = cipherlist - clp; - if(!l) - break; - /* Search the cipher in our table. */ - for(ctp = ciphertable; ctp->name; ctp++) - if(strncasecompare(ctp->name, clp, l) && !ctp->name[l]) - break; - if(!ctp->name) { - failf(data, "Unknown cipher %.*s", l, clp); - result = CURLE_SSL_CIPHER; - } - else { - unsupported |= !(ctp->versions & (CURL_GSKPROTO_SSLV2_MASK | - CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK)); - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - if(ctp->versions & (1 << i)) { - strcpy(ciphers[i].ptr, ctp->gsktoken); - ciphers[i].ptr += strlen(ctp->gsktoken); - } - } - } - - /* Advance to next cipher name or end of string. */ - while(is_separator(*cipherlist)) - cipherlist++; - } - - /* Disable protocols with empty cipher lists. */ - for(i = 0; i < CURL_GSKPROTO_LAST; i++) { - if(!(*protoflags & (1 << i)) || !ciphers[i].buf[0]) { - *protoflags &= ~(1 << i); - ciphers[i].buf[0] = '\0'; - } - } - - /* Try to set-up TLSv1.1 and TLSv2.1 ciphers. */ - if(*protoflags & CURL_GSKPROTO_TLSV11_MASK) { - result = set_buffer(data, h, GSK_TLSV11_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV11].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(unsupported) { - failf(data, "TLSv1.1-only ciphers are not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result && (*protoflags & CURL_GSKPROTO_TLSV12_MASK)) { - result = set_buffer(data, h, GSK_TLSV12_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV12].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(unsupported) { - failf(data, "TLSv1.2-only ciphers are not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - - /* Try to set-up TLSv1.0 ciphers. If not successful, concatenate them to - the SSLv3 ciphers. OS/400 prior to version 7.1 will understand it. */ - if(!result && (*protoflags & CURL_GSKPROTO_TLSV10_MASK)) { - result = set_buffer(data, h, GSK_TLSV10_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_TLSV10].buf, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - strcpy(ciphers[CURL_GSKPROTO_SSLV3].ptr, - ciphers[CURL_GSKPROTO_TLSV10].ptr); - } - } - - /* Set-up other ciphers. */ - if(!result && (*protoflags & CURL_GSKPROTO_SSLV3_MASK)) - result = set_buffer(data, h, GSK_V3_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_SSLV3].buf, FALSE); - if(!result && (*protoflags & CURL_GSKPROTO_SSLV2_MASK)) - result = set_buffer(data, h, GSK_V2_CIPHER_SPECS, - ciphers[CURL_GSKPROTO_SSLV2].buf, FALSE); - - /* Clean-up. */ - for(i = 0; i < CURL_GSKPROTO_LAST; i++) - free(ciphers[i].buf); - - return result; -} - - -static int gskit_init(void) -{ - /* No initialization needed. */ - return 1; -} - - -static void gskit_cleanup(void) -{ - /* Nothing to do. */ -} - - -static CURLcode init_environment(struct Curl_easy *data, - gsk_handle *envir, const char *appid, - const char *file, const char *label, - const char *password) -{ - int rc; - CURLcode result; - gsk_handle h; - - /* Creates the GSKit environment. */ - - rc = gsk_environment_open(&h); - switch(rc) { - case GSK_OK: - break; - case GSK_INSUFFICIENT_STORAGE: - return CURLE_OUT_OF_MEMORY; - default: - failf(data, "gsk_environment_open(): %s", gsk_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - - result = set_enum(data, h, GSK_SESSION_TYPE, GSK_CLIENT_SESSION, FALSE); - if(!result && appid) - result = set_buffer(data, h, GSK_OS400_APPLICATION_ID, appid, FALSE); - if(!result && file) - result = set_buffer(data, h, GSK_KEYRING_FILE, file, FALSE); - if(!result && label) - result = set_buffer(data, h, GSK_KEYRING_LABEL, label, FALSE); - if(!result && password) - result = set_buffer(data, h, GSK_KEYRING_PW, password, FALSE); - - if(!result) { - /* Locate CAs, Client certificate and key according to our settings. - Note: this call may be blocking for some tenths of seconds. */ - result = gskit_status(data, gsk_environment_init(h), - "gsk_environment_init()", CURLE_SSL_CERTPROBLEM); - if(!result) { - *envir = h; - return result; - } - } - /* Error: rollback. */ - gsk_environment_close(&h); - return result; -} - - -static void cancel_async_handshake(struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - Qso_OverlappedIO_t cstat; - - DEBUGASSERT(BACKEND); - - if(QsoCancelOperation(conn->sock[sockindex], 0) > 0) - QsoWaitForIOCompletion(BACKEND->iocport, &cstat, (struct timeval *) NULL); -} - - -static void close_async_handshake(struct ssl_connect_data *connssl) -{ - DEBUGASSERT(BACKEND); - QsoDestroyIOCompletionPort(BACKEND->iocport); - BACKEND->iocport = -1; -} - -static int pipe_ssloverssl(struct connectdata *conn, int sockindex, - int directions) -{ -#ifndef CURL_DISABLE_PROXY - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_connect_data *connproxyssl = &conn->proxy_ssl[sockindex]; - struct pollfd fds[2]; - int n; - int m; - int i; - int ret = 0; - char buf[CURL_MAX_WRITE_SIZE]; - - DEBUGASSERT(BACKEND); - DEBUGASSERT(connproxyssl->backend); - - if(!connssl->use || !connproxyssl->use) - return 0; /* No SSL over SSL: OK. */ - - n = 1; - fds[0].fd = BACKEND->remotefd; - fds[1].fd = conn->sock[sockindex]; - - if(directions & SOS_READ) { - fds[0].events |= POLLOUT; - } - if(directions & SOS_WRITE) { - n = 2; - fds[0].events |= POLLIN; - fds[1].events |= POLLOUT; - } - i = Curl_poll(fds, n, 0); - if(i < 0) - return -1; /* Select error. */ - - if(fds[0].revents & POLLOUT) { - /* Try getting data from HTTPS proxy and pipe it upstream. */ - n = 0; - i = gsk_secure_soc_read(connproxyssl->backend->handle, - buf, sizeof(buf), &n); - switch(i) { - case GSK_OK: - if(n) { - i = write(BACKEND->remotefd, buf, n); - if(i < 0) - return -1; - ret = 1; - } - break; - case GSK_OS400_ERROR_TIMED_OUT: - case GSK_WOULD_BLOCK: - break; - default: - return -1; - } - } - - if((fds[0].revents & POLLIN) && (fds[1].revents & POLLOUT)) { - /* Pipe data to HTTPS proxy. */ - n = read(BACKEND->remotefd, buf, sizeof(buf)); - if(n < 0) - return -1; - if(n) { - i = gsk_secure_soc_write(connproxyssl->backend->handle, buf, n, &m); - if(i != GSK_OK || n != m) - return -1; - ret = 1; - } - } - - return ret; /* OK */ -#else - return 0; -#endif -} - - -static void close_one(struct ssl_connect_data *connssl, struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - DEBUGASSERT(BACKEND); - if(BACKEND->handle) { - gskit_status(data, gsk_secure_soc_close(&BACKEND->handle), - "gsk_secure_soc_close()", 0); - /* Last chance to drain output. */ - while(pipe_ssloverssl(conn, sockindex, SOS_WRITE) > 0) - ; - BACKEND->handle = (gsk_handle) NULL; -#ifndef CURL_DISABLE_PROXY - if(BACKEND->localfd >= 0) { - close(BACKEND->localfd); - BACKEND->localfd = -1; - } - if(BACKEND->remotefd >= 0) { - close(BACKEND->remotefd); - BACKEND->remotefd = -1; - } -#endif - } - if(BACKEND->iocport >= 0) - close_async_handshake(connssl); -} - - -static ssize_t gskit_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - CURLcode cc = CURLE_SEND_ERROR; - int written; - - DEBUGASSERT(BACKEND); - - if(pipe_ssloverssl(conn, sockindex, SOS_WRITE) >= 0) { - cc = gskit_status(data, - gsk_secure_soc_write(BACKEND->handle, - (char *) mem, (int) len, &written), - "gsk_secure_soc_write()", CURLE_SEND_ERROR); - if(cc == CURLE_OK) - if(pipe_ssloverssl(conn, sockindex, SOS_WRITE) < 0) - cc = CURLE_SEND_ERROR; - } - if(cc != CURLE_OK) { - *curlcode = cc; - written = -1; - } - return (ssize_t) written; /* number of bytes */ -} - - -static ssize_t gskit_recv(struct Curl_easy *data, int num, char *buf, - size_t buffersize, CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[num]; - int nread; - CURLcode cc = CURLE_RECV_ERROR; - - DEBUGASSERT(BACKEND); - - if(pipe_ssloverssl(conn, num, SOS_READ) >= 0) { - int buffsize = buffersize > (size_t) INT_MAX? INT_MAX: (int) buffersize; - cc = gskit_status(data, gsk_secure_soc_read(BACKEND->handle, - buf, buffsize, &nread), - "gsk_secure_soc_read()", CURLE_RECV_ERROR); - } - switch(cc) { - case CURLE_OK: - break; - case CURLE_OPERATION_TIMEDOUT: - cc = CURLE_AGAIN; - default: - *curlcode = cc; - nread = -1; - break; - } - return (ssize_t) nread; -} - -static CURLcode -set_ssl_version_min_max(unsigned int *protoflags, struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - long ssl_version = SSL_CONN_CONFIG(version); - long ssl_version_max = SSL_CONN_CONFIG(version_max); - long i = ssl_version; - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = CURL_SSLVERSION_TLSv1_2; - break; - } - for(; i <= (ssl_version_max >> 16); ++i) { - switch(i) { - case CURL_SSLVERSION_TLSv1_0: - *protoflags |= CURL_GSKPROTO_TLSV10_MASK; - break; - case CURL_SSLVERSION_TLSv1_1: - *protoflags |= CURL_GSKPROTO_TLSV11_MASK; - break; - case CURL_SSLVERSION_TLSv1_2: - *protoflags |= CURL_GSKPROTO_TLSV11_MASK; - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "GSKit: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; - } - } - - return CURLE_OK; -} - -static CURLcode gskit_connect_step1(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - gsk_handle envir; - CURLcode result; - const char * const keyringfile = SSL_CONN_CONFIG(CAfile); - const char * const keyringpwd = SSL_SET_OPTION(key_passwd); - const char * const keyringlabel = SSL_SET_OPTION(primary.clientcert); - const long int ssl_version = SSL_CONN_CONFIG(version); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - const char * const hostname = SSL_HOST_NAME(); - const char *sni; - unsigned int protoflags = 0; - Qso_OverlappedIO_t commarea; -#ifndef CURL_DISABLE_PROXY - int sockpair[2]; - static const int sobufsize = CURL_MAX_WRITE_SIZE; -#endif - - /* Create SSL environment, start (preferably asynchronous) handshake. */ - DEBUGASSERT(BACKEND); - - BACKEND->handle = (gsk_handle) NULL; - BACKEND->iocport = -1; -#ifndef CURL_DISABLE_PROXY - BACKEND->localfd = -1; - BACKEND->remotefd = -1; -#endif - - /* GSKit supports two ways of specifying an SSL context: either by - * application identifier (that should have been defined at the system - * level) or by keyring file, password and certificate label. - * Local certificate name (CURLOPT_SSLCERT) is used to hold either the - * application identifier of the certificate label. - * Key password (CURLOPT_KEYPASSWD) holds the keyring password. - * It is not possible to have different keyrings for the CAs and the - * local certificate. We thus use the CA file (CURLOPT_CAINFO) to identify - * the keyring file. - * If no key password is given and the keyring is the system keyring, - * application identifier mode is tried first, as recommended in IBM doc. - */ - - envir = (gsk_handle) NULL; - - if(keyringlabel && *keyringlabel && !keyringpwd && - !strcmp(keyringfile, CURL_CA_BUNDLE)) { - /* Try application identifier mode. */ - init_environment(data, &envir, keyringlabel, (const char *) NULL, - (const char *) NULL, (const char *) NULL); - } - - if(!envir) { - /* Use keyring mode. */ - result = init_environment(data, &envir, (const char *) NULL, - keyringfile, keyringlabel, keyringpwd); - if(result) - return result; - } - - /* Create secure session. */ - result = gskit_status(data, gsk_secure_soc_open(envir, &BACKEND->handle), - "gsk_secure_soc_open()", CURLE_SSL_CONNECT_ERROR); - gsk_environment_close(&envir); - if(result) - return result; - -#ifndef CURL_DISABLE_PROXY - /* Establish a pipelining socket pair for SSL over SSL. */ - if(conn->proxy_ssl[sockindex].use) { - if(Curl_socketpair(0, 0, 0, sockpair)) - return CURLE_SSL_CONNECT_ERROR; - BACKEND->localfd = sockpair[0]; - BACKEND->remotefd = sockpair[1]; - setsockopt(BACKEND->localfd, SOL_SOCKET, SO_RCVBUF, - (void *) &sobufsize, sizeof(sobufsize)); - setsockopt(BACKEND->remotefd, SOL_SOCKET, SO_RCVBUF, - (void *) &sobufsize, sizeof(sobufsize)); - setsockopt(BACKEND->localfd, SOL_SOCKET, SO_SNDBUF, - (void *) &sobufsize, sizeof(sobufsize)); - setsockopt(BACKEND->remotefd, SOL_SOCKET, SO_SNDBUF, - (void *) &sobufsize, sizeof(sobufsize)); - curlx_nonblock(BACKEND->localfd, TRUE); - curlx_nonblock(BACKEND->remotefd, TRUE); - } -#endif - - /* Determine which SSL/TLS version should be enabled. */ - sni = hostname; - switch(ssl_version) { - case CURL_SSLVERSION_SSLv2: - protoflags = CURL_GSKPROTO_SSLV2_MASK; - sni = NULL; - break; - case CURL_SSLVERSION_SSLv3: - protoflags = CURL_GSKPROTO_SSLV3_MASK; - sni = NULL; - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - protoflags = CURL_GSKPROTO_TLSV10_MASK | - CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK; - break; - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - result = set_ssl_version_min_max(&protoflags, data); - if(result != CURLE_OK) - return result; - break; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Process SNI. Ignore if not supported (on OS400 < V7R1). */ - if(sni) { - char *snihost = Curl_ssl_snihost(data, sni, NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - result = set_buffer(data, BACKEND->handle, - GSK_SSL_EXTN_SERVERNAME_REQUEST, snihost, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) - result = CURLE_OK; - } - - /* Set session parameters. */ - if(!result) { - /* Compute the handshake timeout. Since GSKit granularity is 1 second, - we round up the required value. */ - timediff_t timeout = Curl_timeleft(data, NULL, TRUE); - if(timeout < 0) - result = CURLE_OPERATION_TIMEDOUT; - else - result = set_numeric(data, BACKEND->handle, GSK_HANDSHAKE_TIMEOUT, - (timeout + 999) / 1000); - } - if(!result) - result = set_numeric(data, BACKEND->handle, GSK_OS400_READ_TIMEOUT, 1); - if(!result) -#ifndef CURL_DISABLE_PROXY - result = set_numeric(data, BACKEND->handle, GSK_FD, BACKEND->localfd >= 0? - BACKEND->localfd: conn->sock[sockindex]); -#else - result = set_numeric(data, BACKEND->handle, GSK_FD, - conn->sock[sockindex]); -#endif - if(!result) - result = set_ciphers(data, BACKEND->handle, &protoflags); - if(!protoflags) { - failf(data, "No SSL protocol/cipher combination enabled"); - result = CURLE_SSL_CIPHER; - } - if(!result) - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_SSLV2, - (protoflags & CURL_GSKPROTO_SSLV2_MASK)? - GSK_PROTOCOL_SSLV2_ON: GSK_PROTOCOL_SSLV2_OFF, FALSE); - if(!result) - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_SSLV3, - (protoflags & CURL_GSKPROTO_SSLV3_MASK)? - GSK_PROTOCOL_SSLV3_ON: GSK_PROTOCOL_SSLV3_OFF, FALSE); - if(!result) - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV1, - (protoflags & CURL_GSKPROTO_TLSV10_MASK)? - GSK_PROTOCOL_TLSV1_ON: GSK_PROTOCOL_TLSV1_OFF, FALSE); - if(!result) { - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV11, - (protoflags & CURL_GSKPROTO_TLSV11_MASK)? - GSK_TRUE: GSK_FALSE, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(protoflags == CURL_GSKPROTO_TLSV11_MASK) { - failf(data, "TLS 1.1 not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result) { - result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV12, - (protoflags & CURL_GSKPROTO_TLSV12_MASK)? - GSK_TRUE: GSK_FALSE, TRUE); - if(result == CURLE_UNSUPPORTED_PROTOCOL) { - result = CURLE_OK; - if(protoflags == CURL_GSKPROTO_TLSV12_MASK) { - failf(data, "TLS 1.2 not yet supported"); - result = CURLE_SSL_CIPHER; - } - } - } - if(!result) - result = set_enum(data, BACKEND->handle, GSK_SERVER_AUTH_TYPE, - verifypeer? GSK_SERVER_AUTH_FULL: - GSK_SERVER_AUTH_PASSTHRU, FALSE); - - if(!result) { - /* Start handshake. Try asynchronous first. */ - memset(&commarea, 0, sizeof(commarea)); - BACKEND->iocport = QsoCreateIOCompletionPort(); - if(BACKEND->iocport != -1) { - result = gskit_status(data, - gsk_secure_soc_startInit(BACKEND->handle, - BACKEND->iocport, - &commarea), - "gsk_secure_soc_startInit()", - CURLE_SSL_CONNECT_ERROR); - if(!result) { - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; - } - else - close_async_handshake(connssl); - } - else if(errno != ENOBUFS) - result = gskit_status(data, GSK_ERROR_IO, - "QsoCreateIOCompletionPort()", 0); -#ifndef CURL_DISABLE_PROXY - else if(conn->proxy_ssl[sockindex].use) { - /* Cannot pipeline while handshaking synchronously. */ - result = CURLE_SSL_CONNECT_ERROR; - } -#endif - else { - /* No more completion port available. Use synchronous IO. */ - result = gskit_status(data, gsk_secure_soc_init(BACKEND->handle), - "gsk_secure_soc_init()", CURLE_SSL_CONNECT_ERROR); - if(!result) { - connssl->connecting_state = ssl_connect_3; - return CURLE_OK; - } - } - } - - /* Error: rollback. */ - close_one(connssl, data, conn, sockindex); - return result; -} - - -static CURLcode gskit_connect_step2(struct Curl_easy *data, - struct connectdata *conn, int sockindex, - bool nonblocking) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - Qso_OverlappedIO_t cstat; - struct timeval stmv; - CURLcode result; - - /* Poll or wait for end of SSL asynchronous handshake. */ - DEBUGASSERT(BACKEND); - - for(;;) { - timediff_t timeout_ms = nonblocking? 0: Curl_timeleft(data, NULL, TRUE); - stmv.tv_sec = 0; - stmv.tv_usec = 0; - if(timeout_ms < 0) - timeout_ms = 0; - switch(QsoWaitForIOCompletion(BACKEND->iocport, &cstat, - curlx_mstotv(&stmv, timeout_ms))) { - case 1: /* Operation complete. */ - break; - case -1: /* An error occurred: handshake still in progress. */ - if(errno == EINTR) { - if(nonblocking) - return CURLE_OK; - continue; /* Retry. */ - } - if(errno != ETIME) { - char buffer[STRERROR_LEN]; - failf(data, "QsoWaitForIOCompletion() I/O error: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - cancel_async_handshake(conn, sockindex); - close_async_handshake(connssl); - return CURLE_SSL_CONNECT_ERROR; - } - /* FALL INTO... */ - case 0: /* Handshake in progress, timeout occurred. */ - if(nonblocking) - return CURLE_OK; - cancel_async_handshake(conn, sockindex); - close_async_handshake(connssl); - return CURLE_OPERATION_TIMEDOUT; - } - break; - } - result = gskit_status(data, cstat.returnValue, "SSL handshake", - CURLE_SSL_CONNECT_ERROR); - if(!result) - connssl->connecting_state = ssl_connect_3; - close_async_handshake(connssl); - return result; -} - - -static CURLcode gskit_connect_step3(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - const gsk_cert_data_elem *cdev; - int cdec; - const gsk_cert_data_elem *p; - const char *cert = (const char *) NULL; - const char *certend; - const char *ptr; - CURLcode result; - - /* SSL handshake done: gather certificate info and verify host. */ - DEBUGASSERT(BACKEND); - - if(gskit_status(data, gsk_attribute_get_cert_info(BACKEND->handle, - GSK_PARTNER_CERT_INFO, - &cdev, &cdec), - "gsk_attribute_get_cert_info()", CURLE_SSL_CONNECT_ERROR) == - CURLE_OK) { - int i; - - infof(data, "Server certificate:"); - p = cdev; - for(i = 0; i++ < cdec; p++) - switch(p->cert_data_id) { - case CERT_BODY_DER: - cert = p->cert_data_p; - certend = cert + cdev->cert_data_l; - break; - case CERT_DN_PRINTABLE: - infof(data, "\t subject: %.*s", p->cert_data_l, p->cert_data_p); - break; - case CERT_ISSUER_DN_PRINTABLE: - infof(data, "\t issuer: %.*s", p->cert_data_l, p->cert_data_p); - break; - case CERT_VALID_FROM: - infof(data, "\t start date: %.*s", p->cert_data_l, p->cert_data_p); - break; - case CERT_VALID_TO: - infof(data, "\t expire date: %.*s", p->cert_data_l, p->cert_data_p); - break; - } - } - - /* Verify host. */ - result = Curl_verifyhost(data, conn, cert, certend); - if(result) - return result; - - /* The only place GSKit can get the whole CA chain is a validation - callback where no user data pointer is available. Therefore it's not - possible to copy this chain into our structures for CAINFO. - However the server certificate may be available, thus we can return - info about it. */ - if(data->set.ssl.certinfo) { - result = Curl_ssl_init_certinfo(data, 1); - if(result) - return result; - - if(cert) { - result = Curl_extract_certinfo(data, 0, cert, certend); - if(result) - return result; - } - } - - /* Check pinned public key. */ - ptr = SSL_PINNED_PUB_KEY(); - if(!result && ptr) { - struct Curl_X509certificate x509; - struct Curl_asn1Element *p; - - memset(&x509, 0, sizeof(x509)); - if(Curl_parseX509(&x509, cert, certend)) - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - p = &x509.subjectPublicKeyInfo; - result = Curl_pin_peer_pubkey(data, ptr, p->header, p->end - p->header); - if(result) { - failf(data, "SSL: public key does not match pinned public key"); - return result; - } - } - - connssl->connecting_state = ssl_connect_done; - return CURLE_OK; -} - - -static CURLcode gskit_connect_common(struct Curl_easy *data, - struct connectdata *conn, int sockindex, - bool nonblocking, bool *done) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - timediff_t timeout_ms; - CURLcode result = CURLE_OK; - - *done = connssl->state == ssl_connection_complete; - if(*done) - return CURLE_OK; - - /* Step 1: create session, start handshake. */ - if(connssl->connecting_state == ssl_connect_1) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; - } - else - result = gskit_connect_step1(data, conn, sockindex); - } - - /* Handle handshake pipelining. */ - if(!result) - if(pipe_ssloverssl(conn, sockindex, SOS_READ | SOS_WRITE) < 0) - result = CURLE_SSL_CONNECT_ERROR; - - /* Step 2: check if handshake is over. */ - if(!result && connssl->connecting_state == ssl_connect_2) { - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - result = CURLE_OPERATION_TIMEDOUT; - } - else - result = gskit_connect_step2(data, conn, sockindex, nonblocking); - } - - /* Handle handshake pipelining. */ - if(!result) - if(pipe_ssloverssl(conn, sockindex, SOS_READ | SOS_WRITE) < 0) - result = CURLE_SSL_CONNECT_ERROR; - - /* Step 3: gather certificate info, verify host. */ - if(!result && connssl->connecting_state == ssl_connect_3) - result = gskit_connect_step3(data, conn, sockindex); - - if(result) - close_one(connssl, data, conn, sockindex); - else if(connssl->connecting_state == ssl_connect_done) { - connssl->state = ssl_connection_complete; - connssl->connecting_state = ssl_connect_1; - conn->recv[sockindex] = gskit_recv; - conn->send[sockindex] = gskit_send; - *done = TRUE; - } - - return result; -} - - -static CURLcode gskit_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - CURLcode result; - - result = gskit_connect_common(data, conn, sockindex, TRUE, done); - if(*done || result) - conn->ssl[sockindex].connecting_state = ssl_connect_1; - return result; -} - - -static CURLcode gskit_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - CURLcode result; - bool done; - - conn->ssl[sockindex].connecting_state = ssl_connect_1; - result = gskit_connect_common(data, conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - - -static void gskit_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - close_one(&conn->ssl[sockindex], data, conn, sockindex); -#ifndef CURL_DISABLE_PROXY - close_one(&conn->proxy_ssl[sockindex], data, conn, sockindex); -#endif -} - - -static int gskit_shutdown(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - int what; - int rc; - char buf[120]; - int loop = 10; /* don't get stuck */ - - DEBUGASSERT(BACKEND); - - if(!BACKEND->handle) - return 0; - -#ifndef CURL_DISABLE_FTP - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; -#endif - - close_one(connssl, data, conn, sockindex); - rc = 0; - what = SOCKET_READABLE(conn->sock[sockindex], - SSL_SHUTDOWN_TIMEOUT); - - while(loop--) { - ssize_t nread; - - if(what < 0) { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - rc = -1; - break; - } - - if(!what) { /* timeout */ - failf(data, "SSL shutdown timeout"); - break; - } - - /* Something to read, let's do it and hope that it is the close - notify alert from the server. No way to gsk_secure_soc_read() now, so - use read(). */ - - nread = read(conn->sock[sockindex], buf, sizeof(buf)); - - if(nread < 0) { - char buffer[STRERROR_LEN]; - failf(data, "read: %s", Curl_strerror(errno, buffer, sizeof(buffer))); - rc = -1; - } - - if(nread <= 0) - break; - - what = SOCKET_READABLE(conn->sock[sockindex], 0); - } - - return rc; -} - - -static size_t gskit_version(char *buffer, size_t size) -{ - return msnprintf(buffer, size, "GSKit"); -} - - -static int gskit_check_cxn(struct connectdata *cxn) -{ - struct ssl_connect_data *connssl = &cxn->ssl[FIRSTSOCKET]; - int err; - int errlen; - - /* The only thing that can be tested here is at the socket level. */ - DEBUGASSERT(BACKEND); - - if(!BACKEND->handle) - return 0; /* connection has been closed */ - - err = 0; - errlen = sizeof(err); - - if(getsockopt(cxn->sock[FIRSTSOCKET], SOL_SOCKET, SO_ERROR, - (unsigned char *) &err, &errlen) || - errlen != sizeof(err) || err) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -static void *gskit_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - (void)info; - DEBUGASSERT(BACKEND); - return BACKEND->handle; -} - -const struct Curl_ssl Curl_ssl_gskit = { - { CURLSSLBACKEND_GSKIT, "gskit" }, /* info */ - - SSLSUPP_CERTINFO | - SSLSUPP_PINNEDPUBKEY, - - sizeof(struct ssl_backend_data), - - gskit_init, /* init */ - gskit_cleanup, /* cleanup */ - gskit_version, /* version */ - gskit_check_cxn, /* check_cxn */ - gskit_shutdown, /* shutdown */ - Curl_none_data_pending, /* data_pending */ - Curl_none_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - gskit_connect, /* connect */ - gskit_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - gskit_get_internals, /* get_internals */ - gskit_close, /* close_one */ - Curl_none_close_all, /* close_all */ - /* No session handling for GSKit */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - NULL, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif /* USE_GSKIT */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_GSKIT
+
+#include <gskssl.h>
+#include <qsoasync.h>
+#undef HAVE_SOCKETPAIR /* because the native one isn't good enough */
+#include "socketpair.h"
+#include "strerror.h"
+
+/* Some symbols are undefined/unsupported on OS400 versions < V7R1. */
+#ifndef GSK_SSL_EXTN_SERVERNAME_REQUEST
+#define GSK_SSL_EXTN_SERVERNAME_REQUEST 230
+#endif
+
+#ifndef GSK_TLSV10_CIPHER_SPECS
+#define GSK_TLSV10_CIPHER_SPECS 236
+#endif
+
+#ifndef GSK_TLSV11_CIPHER_SPECS
+#define GSK_TLSV11_CIPHER_SPECS 237
+#endif
+
+#ifndef GSK_TLSV12_CIPHER_SPECS
+#define GSK_TLSV12_CIPHER_SPECS 238
+#endif
+
+#ifndef GSK_PROTOCOL_TLSV11
+#define GSK_PROTOCOL_TLSV11 437
+#endif
+
+#ifndef GSK_PROTOCOL_TLSV12
+#define GSK_PROTOCOL_TLSV12 438
+#endif
+
+#ifndef GSK_FALSE
+#define GSK_FALSE 0
+#endif
+
+#ifndef GSK_TRUE
+#define GSK_TRUE 1
+#endif
+
+
+#include <limits.h>
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "sendf.h"
+#include "gskit.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "connect.h" /* for the connect timeout */
+#include "select.h"
+#include "strcase.h"
+#include "timediff.h"
+#include "x509asn1.h"
+#include "curl_printf.h"
+
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+
+/* Directions. */
+#define SOS_READ 0x01
+#define SOS_WRITE 0x02
+
+/* SSL version flags. */
+#define CURL_GSKPROTO_SSLV2 0
+#define CURL_GSKPROTO_SSLV2_MASK (1 << CURL_GSKPROTO_SSLV2)
+#define CURL_GSKPROTO_SSLV3 1
+#define CURL_GSKPROTO_SSLV3_MASK (1 << CURL_GSKPROTO_SSLV3)
+#define CURL_GSKPROTO_TLSV10 2
+#define CURL_GSKPROTO_TLSV10_MASK (1 << CURL_GSKPROTO_TLSV10)
+#define CURL_GSKPROTO_TLSV11 3
+#define CURL_GSKPROTO_TLSV11_MASK (1 << CURL_GSKPROTO_TLSV11)
+#define CURL_GSKPROTO_TLSV12 4
+#define CURL_GSKPROTO_TLSV12_MASK (1 << CURL_GSKPROTO_TLSV12)
+#define CURL_GSKPROTO_LAST 5
+
+struct ssl_backend_data {
+ gsk_handle handle;
+ int iocport;
+ int localfd;
+ int remotefd;
+};
+
+#define BACKEND connssl->backend
+
+/* Supported ciphers. */
+struct gskit_cipher {
+ const char *name; /* Cipher name. */
+ const char *gsktoken; /* Corresponding token for GSKit String. */
+ unsigned int versions; /* SSL version flags. */
+};
+
+static const struct gskit_cipher ciphertable[] = {
+ { "null-md5", "01",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK |
+ CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK },
+ { "null-sha", "02",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK |
+ CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK },
+ { "exp-rc4-md5", "03",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK },
+ { "rc4-md5", "04",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK |
+ CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK },
+ { "rc4-sha", "05",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK |
+ CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK },
+ { "exp-rc2-cbc-md5", "06",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK },
+ { "exp-des-cbc-sha", "09",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK |
+ CURL_GSKPROTO_TLSV11_MASK },
+ { "des-cbc3-sha", "0A",
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK |
+ CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK },
+ { "aes128-sha", "2F",
+ CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK |
+ CURL_GSKPROTO_TLSV12_MASK },
+ { "aes256-sha", "35",
+ CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK |
+ CURL_GSKPROTO_TLSV12_MASK },
+ { "null-sha256", "3B", CURL_GSKPROTO_TLSV12_MASK },
+ { "aes128-sha256", "3C", CURL_GSKPROTO_TLSV12_MASK },
+ { "aes256-sha256", "3D", CURL_GSKPROTO_TLSV12_MASK },
+ { "aes128-gcm-sha256",
+ "9C", CURL_GSKPROTO_TLSV12_MASK },
+ { "aes256-gcm-sha384",
+ "9D", CURL_GSKPROTO_TLSV12_MASK },
+ { "rc4-md5", "1", CURL_GSKPROTO_SSLV2_MASK },
+ { "exp-rc4-md5", "2", CURL_GSKPROTO_SSLV2_MASK },
+ { "rc2-md5", "3", CURL_GSKPROTO_SSLV2_MASK },
+ { "exp-rc2-md5", "4", CURL_GSKPROTO_SSLV2_MASK },
+ { "des-cbc-md5", "6", CURL_GSKPROTO_SSLV2_MASK },
+ { "des-cbc3-md5", "7", CURL_GSKPROTO_SSLV2_MASK },
+ { (const char *) NULL, (const char *) NULL, 0 }
+};
+
+
+static bool is_separator(char c)
+{
+ /* Return whether character is a cipher list separator. */
+ switch(c) {
+ case ' ':
+ case '\t':
+ case ':':
+ case ',':
+ case ';':
+ return true;
+ }
+ return false;
+}
+
+
+static CURLcode gskit_status(struct Curl_easy *data, int rc,
+ const char *procname, CURLcode defcode)
+{
+ char buffer[STRERROR_LEN];
+ /* Process GSKit status and map it to a CURLcode. */
+ switch(rc) {
+ case GSK_OK:
+ case GSK_OS400_ASYNCHRONOUS_SOC_INIT:
+ return CURLE_OK;
+ case GSK_KEYRING_OPEN_ERROR:
+ case GSK_OS400_ERROR_NO_ACCESS:
+ return CURLE_SSL_CACERT_BADFILE;
+ case GSK_INSUFFICIENT_STORAGE:
+ return CURLE_OUT_OF_MEMORY;
+ case GSK_ERROR_BAD_V2_CIPHER:
+ case GSK_ERROR_BAD_V3_CIPHER:
+ case GSK_ERROR_NO_CIPHERS:
+ return CURLE_SSL_CIPHER;
+ case GSK_OS400_ERROR_NOT_TRUSTED_ROOT:
+ case GSK_ERROR_CERT_VALIDATION:
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case GSK_OS400_ERROR_TIMED_OUT:
+ return CURLE_OPERATION_TIMEDOUT;
+ case GSK_WOULD_BLOCK:
+ return CURLE_AGAIN;
+ case GSK_OS400_ERROR_NOT_REGISTERED:
+ break;
+ case GSK_ERROR_IO:
+ switch(errno) {
+ case ENOMEM:
+ return CURLE_OUT_OF_MEMORY;
+ default:
+ failf(data, "%s I/O error: %s", procname,
+ Curl_strerror(errno, buffer, sizeof(buffer)));
+ break;
+ }
+ break;
+ default:
+ failf(data, "%s: %s", procname, gsk_strerror(rc));
+ break;
+ }
+ return defcode;
+}
+
+
+static CURLcode set_enum(struct Curl_easy *data, gsk_handle h,
+ GSK_ENUM_ID id, GSK_ENUM_VALUE value, bool unsupported_ok)
+{
+ char buffer[STRERROR_LEN];
+ int rc = gsk_attribute_set_enum(h, id, value);
+
+ switch(rc) {
+ case GSK_OK:
+ return CURLE_OK;
+ case GSK_ERROR_IO:
+ failf(data, "gsk_attribute_set_enum() I/O error: %s",
+ Curl_strerror(errno, buffer, sizeof(buffer)));
+ break;
+ case GSK_ATTRIBUTE_INVALID_ID:
+ if(unsupported_ok)
+ return CURLE_UNSUPPORTED_PROTOCOL;
+ default:
+ failf(data, "gsk_attribute_set_enum(): %s", gsk_strerror(rc));
+ break;
+ }
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+
+static CURLcode set_buffer(struct Curl_easy *data, gsk_handle h,
+ GSK_BUF_ID id, const char *buf, bool unsupported_ok)
+{
+ char buffer[STRERROR_LEN];
+ int rc = gsk_attribute_set_buffer(h, id, buf, 0);
+
+ switch(rc) {
+ case GSK_OK:
+ return CURLE_OK;
+ case GSK_ERROR_IO:
+ failf(data, "gsk_attribute_set_buffer() I/O error: %s",
+ Curl_strerror(errno, buffer, sizeof(buffer)));
+ break;
+ case GSK_ATTRIBUTE_INVALID_ID:
+ if(unsupported_ok)
+ return CURLE_UNSUPPORTED_PROTOCOL;
+ default:
+ failf(data, "gsk_attribute_set_buffer(): %s", gsk_strerror(rc));
+ break;
+ }
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+
+static CURLcode set_numeric(struct Curl_easy *data,
+ gsk_handle h, GSK_NUM_ID id, int value)
+{
+ char buffer[STRERROR_LEN];
+ int rc = gsk_attribute_set_numeric_value(h, id, value);
+
+ switch(rc) {
+ case GSK_OK:
+ return CURLE_OK;
+ case GSK_ERROR_IO:
+ failf(data, "gsk_attribute_set_numeric_value() I/O error: %s",
+ Curl_strerror(errno, buffer, sizeof(buffer)));
+ break;
+ default:
+ failf(data, "gsk_attribute_set_numeric_value(): %s", gsk_strerror(rc));
+ break;
+ }
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+
+static CURLcode set_ciphers(struct Curl_cfilter *cf, struct Curl_easy *data,
+ gsk_handle h, unsigned int *protoflags)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct connectdata *conn = data->conn;
+ const char *cipherlist = conn_config->cipher_list;
+ const char *clp;
+ const struct gskit_cipher *ctp;
+ int i;
+ int l;
+ bool unsupported;
+ CURLcode result;
+ struct {
+ char *buf;
+ char *ptr;
+ } ciphers[CURL_GSKPROTO_LAST];
+
+ /* Compile cipher list into GSKit-compatible cipher lists. */
+
+ if(!cipherlist)
+ return CURLE_OK;
+ while(is_separator(*cipherlist)) /* Skip initial separators. */
+ cipherlist++;
+ if(!*cipherlist)
+ return CURLE_OK;
+
+ /* We allocate GSKit buffers of the same size as the input string: since
+ GSKit tokens are always shorter than their cipher names, allocated buffers
+ will always be large enough to accommodate the result. */
+ l = strlen(cipherlist) + 1;
+ memset(ciphers, 0, sizeof(ciphers));
+ for(i = 0; i < CURL_GSKPROTO_LAST; i++) {
+ ciphers[i].buf = malloc(l);
+ if(!ciphers[i].buf) {
+ while(i--)
+ free(ciphers[i].buf);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ ciphers[i].ptr = ciphers[i].buf;
+ *ciphers[i].ptr = '\0';
+ }
+
+ /* Process each cipher in input string. */
+ unsupported = FALSE;
+ result = CURLE_OK;
+ for(;;) {
+ for(clp = cipherlist; *cipherlist && !is_separator(*cipherlist);)
+ cipherlist++;
+ l = cipherlist - clp;
+ if(!l)
+ break;
+ /* Search the cipher in our table. */
+ for(ctp = ciphertable; ctp->name; ctp++)
+ if(strncasecompare(ctp->name, clp, l) && !ctp->name[l])
+ break;
+ if(!ctp->name) {
+ failf(data, "Unknown cipher %.*s", l, clp);
+ result = CURLE_SSL_CIPHER;
+ }
+ else {
+ unsupported |= !(ctp->versions & (CURL_GSKPROTO_SSLV2_MASK |
+ CURL_GSKPROTO_SSLV3_MASK | CURL_GSKPROTO_TLSV10_MASK));
+ for(i = 0; i < CURL_GSKPROTO_LAST; i++) {
+ if(ctp->versions & (1 << i)) {
+ strcpy(ciphers[i].ptr, ctp->gsktoken);
+ ciphers[i].ptr += strlen(ctp->gsktoken);
+ }
+ }
+ }
+
+ /* Advance to next cipher name or end of string. */
+ while(is_separator(*cipherlist))
+ cipherlist++;
+ }
+
+ /* Disable protocols with empty cipher lists. */
+ for(i = 0; i < CURL_GSKPROTO_LAST; i++) {
+ if(!(*protoflags & (1 << i)) || !ciphers[i].buf[0]) {
+ *protoflags &= ~(1 << i);
+ ciphers[i].buf[0] = '\0';
+ }
+ }
+
+ /* Try to set-up TLSv1.1 and TLSv2.1 ciphers. */
+ if(*protoflags & CURL_GSKPROTO_TLSV11_MASK) {
+ result = set_buffer(data, h, GSK_TLSV11_CIPHER_SPECS,
+ ciphers[CURL_GSKPROTO_TLSV11].buf, TRUE);
+ if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+ result = CURLE_OK;
+ if(unsupported) {
+ failf(data, "TLSv1.1-only ciphers are not yet supported");
+ result = CURLE_SSL_CIPHER;
+ }
+ }
+ }
+ if(!result && (*protoflags & CURL_GSKPROTO_TLSV12_MASK)) {
+ result = set_buffer(data, h, GSK_TLSV12_CIPHER_SPECS,
+ ciphers[CURL_GSKPROTO_TLSV12].buf, TRUE);
+ if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+ result = CURLE_OK;
+ if(unsupported) {
+ failf(data, "TLSv1.2-only ciphers are not yet supported");
+ result = CURLE_SSL_CIPHER;
+ }
+ }
+ }
+
+ /* Try to set-up TLSv1.0 ciphers. If not successful, concatenate them to
+ the SSLv3 ciphers. OS/400 prior to version 7.1 will understand it. */
+ if(!result && (*protoflags & CURL_GSKPROTO_TLSV10_MASK)) {
+ result = set_buffer(data, h, GSK_TLSV10_CIPHER_SPECS,
+ ciphers[CURL_GSKPROTO_TLSV10].buf, TRUE);
+ if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+ result = CURLE_OK;
+ strcpy(ciphers[CURL_GSKPROTO_SSLV3].ptr,
+ ciphers[CURL_GSKPROTO_TLSV10].ptr);
+ }
+ }
+
+ /* Set-up other ciphers. */
+ if(!result && (*protoflags & CURL_GSKPROTO_SSLV3_MASK))
+ result = set_buffer(data, h, GSK_V3_CIPHER_SPECS,
+ ciphers[CURL_GSKPROTO_SSLV3].buf, FALSE);
+ if(!result && (*protoflags & CURL_GSKPROTO_SSLV2_MASK))
+ result = set_buffer(data, h, GSK_V2_CIPHER_SPECS,
+ ciphers[CURL_GSKPROTO_SSLV2].buf, FALSE);
+
+ /* Clean-up. */
+ for(i = 0; i < CURL_GSKPROTO_LAST; i++)
+ free(ciphers[i].buf);
+
+ return result;
+}
+
+
+static int gskit_init(void)
+{
+ /* No initialization needed. */
+ return 1;
+}
+
+
+static void gskit_cleanup(void)
+{
+ /* Nothing to do. */
+}
+
+
+static CURLcode init_environment(struct Curl_easy *data,
+ gsk_handle *envir, const char *appid,
+ const char *file, const char *label,
+ const char *password)
+{
+ int rc;
+ CURLcode result;
+ gsk_handle h;
+
+ /* Creates the GSKit environment. */
+
+ rc = gsk_environment_open(&h);
+ switch(rc) {
+ case GSK_OK:
+ break;
+ case GSK_INSUFFICIENT_STORAGE:
+ return CURLE_OUT_OF_MEMORY;
+ default:
+ failf(data, "gsk_environment_open(): %s", gsk_strerror(rc));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ result = set_enum(data, h, GSK_SESSION_TYPE, GSK_CLIENT_SESSION, FALSE);
+ if(!result && appid)
+ result = set_buffer(data, h, GSK_OS400_APPLICATION_ID, appid, FALSE);
+ if(!result && file)
+ result = set_buffer(data, h, GSK_KEYRING_FILE, file, FALSE);
+ if(!result && label)
+ result = set_buffer(data, h, GSK_KEYRING_LABEL, label, FALSE);
+ if(!result && password)
+ result = set_buffer(data, h, GSK_KEYRING_PW, password, FALSE);
+
+ if(!result) {
+ /* Locate CAs, Client certificate and key according to our settings.
+ Note: this call may be blocking for some tenths of seconds. */
+ result = gskit_status(data, gsk_environment_init(h),
+ "gsk_environment_init()", CURLE_SSL_CERTPROBLEM);
+ if(!result) {
+ *envir = h;
+ return result;
+ }
+ }
+ /* Error: rollback. */
+ gsk_environment_close(&h);
+ return result;
+}
+
+
+static void cancel_async_handshake(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ Qso_OverlappedIO_t cstat;
+
+ (void)data;
+ DEBUGASSERT(BACKEND);
+
+ if(QsoCancelOperation(cf->conn->sock[cf->sockindex], 0) > 0)
+ QsoWaitForIOCompletion(BACKEND->iocport, &cstat, (struct timeval *) NULL);
+}
+
+
+static void close_async_handshake(struct ssl_connect_data *connssl)
+{
+ DEBUGASSERT(BACKEND);
+ QsoDestroyIOCompletionPort(BACKEND->iocport);
+ BACKEND->iocport = -1;
+}
+
+static int pipe_ssloverssl(struct Curl_cfilter *cf, int directions)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next);
+ struct ssl_connect_data *connssl_next = cf_ssl_next?
+ cf_ssl_next->ctx : NULL;
+ struct pollfd fds[2];
+ int n;
+ int m;
+ int i;
+ int ret = 0;
+ char buf[CURL_MAX_WRITE_SIZE];
+
+ DEBUGASSERT(BACKEND);
+
+ if(!connssl_next)
+ return 0; /* No SSL over SSL: OK. */
+
+ DEBUGASSERT(connssl_next->backend);
+ n = 1;
+ fds[0].fd = BACKEND->remotefd;
+ fds[1].fd = cf->conn->sock[cf->sockindex];
+
+ if(directions & SOS_READ) {
+ fds[0].events |= POLLOUT;
+ }
+ if(directions & SOS_WRITE) {
+ n = 2;
+ fds[0].events |= POLLIN;
+ fds[1].events |= POLLOUT;
+ }
+ i = Curl_poll(fds, n, 0);
+ if(i < 0)
+ return -1; /* Select error. */
+
+ if(fds[0].revents & POLLOUT) {
+ /* Try getting data from HTTPS proxy and pipe it upstream. */
+ n = 0;
+ i = gsk_secure_soc_read(connssl_next->backend->handle,
+ buf, sizeof(buf), &n);
+ switch(i) {
+ case GSK_OK:
+ if(n) {
+ i = write(BACKEND->remotefd, buf, n);
+ if(i < 0)
+ return -1;
+ ret = 1;
+ }
+ break;
+ case GSK_OS400_ERROR_TIMED_OUT:
+ case GSK_WOULD_BLOCK:
+ break;
+ default:
+ return -1;
+ }
+ }
+
+ if((fds[0].revents & POLLIN) && (fds[1].revents & POLLOUT)) {
+ /* Pipe data to HTTPS proxy. */
+ n = read(BACKEND->remotefd, buf, sizeof(buf));
+ if(n < 0)
+ return -1;
+ if(n) {
+ i = gsk_secure_soc_write(connssl_next->backend->handle, buf, n, &m);
+ if(i != GSK_OK || n != m)
+ return -1;
+ ret = 1;
+ }
+ }
+
+ return ret; /* OK */
+}
+
+
+static void close_one(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+
+ DEBUGASSERT(BACKEND);
+ if(BACKEND->handle) {
+ gskit_status(data, gsk_secure_soc_close(&BACKEND->handle),
+ "gsk_secure_soc_close()", 0);
+ /* Last chance to drain output. */
+ while(pipe_ssloverssl(cf, SOS_WRITE) > 0)
+ ;
+ BACKEND->handle = (gsk_handle) NULL;
+ if(BACKEND->localfd >= 0) {
+ close(BACKEND->localfd);
+ BACKEND->localfd = -1;
+ }
+ if(BACKEND->remotefd >= 0) {
+ close(BACKEND->remotefd);
+ BACKEND->remotefd = -1;
+ }
+ }
+ if(BACKEND->iocport >= 0)
+ close_async_handshake(connssl);
+}
+
+
+static ssize_t gskit_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *mem, size_t len, CURLcode *curlcode)
+{
+ struct connectdata *conn = cf->conn;
+ struct ssl_connect_data *connssl = cf->ctx;
+ CURLcode cc = CURLE_SEND_ERROR;
+ int written;
+
+ DEBUGASSERT(BACKEND);
+
+ if(pipe_ssloverssl(cf, SOS_WRITE) >= 0) {
+ cc = gskit_status(data,
+ gsk_secure_soc_write(BACKEND->handle,
+ (char *) mem, (int) len, &written),
+ "gsk_secure_soc_write()", CURLE_SEND_ERROR);
+ if(cc == CURLE_OK)
+ if(pipe_ssloverssl(cf, SOS_WRITE) < 0)
+ cc = CURLE_SEND_ERROR;
+ }
+ if(cc != CURLE_OK) {
+ *curlcode = cc;
+ written = -1;
+ }
+ return (ssize_t) written; /* number of bytes */
+}
+
+
+static ssize_t gskit_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t buffersize, CURLcode *curlcode)
+{
+ struct connectdata *conn = cf->conn;
+ struct ssl_connect_data *connssl = cf->ctx;
+ int nread;
+ CURLcode cc = CURLE_RECV_ERROR;
+
+ (void)data;
+ DEBUGASSERT(BACKEND);
+
+ if(pipe_ssloverssl(cf, SOS_READ) >= 0) {
+ int buffsize = buffersize > (size_t) INT_MAX? INT_MAX: (int) buffersize;
+ cc = gskit_status(data, gsk_secure_soc_read(BACKEND->handle,
+ buf, buffsize, &nread),
+ "gsk_secure_soc_read()", CURLE_RECV_ERROR);
+ }
+ switch(cc) {
+ case CURLE_OK:
+ break;
+ case CURLE_OPERATION_TIMEDOUT:
+ cc = CURLE_AGAIN;
+ default:
+ *curlcode = cc;
+ nread = -1;
+ break;
+ }
+ return (ssize_t) nread;
+}
+
+static CURLcode
+set_ssl_version_min_max(unsigned int *protoflags,
+ struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct connectdata *conn = data->conn;
+ long ssl_version = conn_config->version;
+ long ssl_version_max = conn_config->version_max;
+ long i = ssl_version;
+ switch(ssl_version_max) {
+ case CURL_SSLVERSION_MAX_NONE:
+ case CURL_SSLVERSION_MAX_DEFAULT:
+ ssl_version_max = CURL_SSLVERSION_TLSv1_2;
+ break;
+ }
+ for(; i <= (ssl_version_max >> 16); ++i) {
+ switch(i) {
+ case CURL_SSLVERSION_TLSv1_0:
+ *protoflags |= CURL_GSKPROTO_TLSV10_MASK;
+ break;
+ case CURL_SSLVERSION_TLSv1_1:
+ *protoflags |= CURL_GSKPROTO_TLSV11_MASK;
+ break;
+ case CURL_SSLVERSION_TLSv1_2:
+ *protoflags |= CURL_GSKPROTO_TLSV11_MASK;
+ break;
+ case CURL_SSLVERSION_TLSv1_3:
+ failf(data, "GSKit: TLS 1.3 is not yet supported");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ return CURLE_OK;
+}
+
+static CURLcode gskit_connect_step1(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next);
+ struct ssl_connect_data *connssl_next = cf_ssl_next?
+ cf_ssl_next->ctx : NULL;
+ gsk_handle envir;
+ CURLcode result;
+ const char * const keyringfile = conn_config->CAfile;
+ const char * const keyringpwd = conn_config->key_passwd;
+ const char * const keyringlabel = ssl_config->primary.clientcert;
+ const long int ssl_version = conn_config->version;
+ const bool verifypeer = conn_config->verifypeer;
+ const char *hostname = connssl->hostname;
+ const char *sni;
+ unsigned int protoflags = 0;
+ Qso_OverlappedIO_t commarea;
+ int sockpair[2];
+ static const int sobufsize = CURL_MAX_WRITE_SIZE;
+
+ /* Create SSL environment, start (preferably asynchronous) handshake. */
+ DEBUGASSERT(BACKEND);
+
+ BACKEND->handle = (gsk_handle) NULL;
+ BACKEND->iocport = -1;
+ BACKEND->localfd = -1;
+ BACKEND->remotefd = -1;
+
+ /* GSKit supports two ways of specifying an SSL context: either by
+ * application identifier (that should have been defined at the system
+ * level) or by keyring file, password and certificate label.
+ * Local certificate name (CURLOPT_SSLCERT) is used to hold either the
+ * application identifier of the certificate label.
+ * Key password (CURLOPT_KEYPASSWD) holds the keyring password.
+ * It is not possible to have different keyrings for the CAs and the
+ * local certificate. We thus use the CA file (CURLOPT_CAINFO) to identify
+ * the keyring file.
+ * If no key password is given and the keyring is the system keyring,
+ * application identifier mode is tried first, as recommended in IBM doc.
+ */
+
+ envir = (gsk_handle) NULL;
+
+ if(keyringlabel && *keyringlabel && !keyringpwd &&
+ !strcmp(keyringfile, CURL_CA_BUNDLE)) {
+ /* Try application identifier mode. */
+ init_environment(data, &envir, keyringlabel, (const char *) NULL,
+ (const char *) NULL, (const char *) NULL);
+ }
+
+ if(!envir) {
+ /* Use keyring mode. */
+ result = init_environment(data, &envir, (const char *) NULL,
+ keyringfile, keyringlabel, keyringpwd);
+ if(result)
+ return result;
+ }
+
+ /* Create secure session. */
+ result = gskit_status(data, gsk_secure_soc_open(envir, &BACKEND->handle),
+ "gsk_secure_soc_open()", CURLE_SSL_CONNECT_ERROR);
+ gsk_environment_close(&envir);
+ if(result)
+ return result;
+
+ /* Establish a pipelining socket pair for SSL over SSL. */
+ if(connssl_next) {
+ if(Curl_socketpair(0, 0, 0, sockpair))
+ return CURLE_SSL_CONNECT_ERROR;
+ BACKEND->localfd = sockpair[0];
+ BACKEND->remotefd = sockpair[1];
+ setsockopt(BACKEND->localfd, SOL_SOCKET, SO_RCVBUF,
+ (void *) &sobufsize, sizeof(sobufsize));
+ setsockopt(BACKEND->remotefd, SOL_SOCKET, SO_RCVBUF,
+ (void *) &sobufsize, sizeof(sobufsize));
+ setsockopt(BACKEND->localfd, SOL_SOCKET, SO_SNDBUF,
+ (void *) &sobufsize, sizeof(sobufsize));
+ setsockopt(BACKEND->remotefd, SOL_SOCKET, SO_SNDBUF,
+ (void *) &sobufsize, sizeof(sobufsize));
+ curlx_nonblock(BACKEND->localfd, TRUE);
+ curlx_nonblock(BACKEND->remotefd, TRUE);
+ }
+
+ /* Determine which SSL/TLS version should be enabled. */
+ sni = hostname;
+ switch(ssl_version) {
+ case CURL_SSLVERSION_SSLv2:
+ protoflags = CURL_GSKPROTO_SSLV2_MASK;
+ sni = NULL;
+ break;
+ case CURL_SSLVERSION_SSLv3:
+ protoflags = CURL_GSKPROTO_SSLV3_MASK;
+ sni = NULL;
+ break;
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ protoflags = CURL_GSKPROTO_TLSV10_MASK |
+ CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK;
+ break;
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1_1:
+ case CURL_SSLVERSION_TLSv1_2:
+ case CURL_SSLVERSION_TLSv1_3:
+ result = set_ssl_version_min_max(&protoflags, cf, data);
+ if(result != CURLE_OK)
+ return result;
+ break;
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ /* Process SNI. Ignore if not supported (on OS400 < V7R1). */
+ if(sni) {
+ char *snihost = Curl_ssl_snihost(data, sni, NULL);
+ if(!snihost) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ result = set_buffer(data, BACKEND->handle,
+ GSK_SSL_EXTN_SERVERNAME_REQUEST, snihost, TRUE);
+ if(result == CURLE_UNSUPPORTED_PROTOCOL)
+ result = CURLE_OK;
+ }
+
+ /* Set session parameters. */
+ if(!result) {
+ /* Compute the handshake timeout. Since GSKit granularity is 1 second,
+ we round up the required value. */
+ timediff_t timeout = Curl_timeleft(data, NULL, TRUE);
+ if(timeout < 0)
+ result = CURLE_OPERATION_TIMEDOUT;
+ else
+ result = set_numeric(data, BACKEND->handle, GSK_HANDSHAKE_TIMEOUT,
+ (timeout + 999) / 1000);
+ }
+ if(!result)
+ result = set_numeric(data, BACKEND->handle, GSK_OS400_READ_TIMEOUT, 1);
+ if(!result)
+ result = set_numeric(data, BACKEND->handle, GSK_FD, BACKEND->localfd >= 0?
+ BACKEND->localfd: cf->conn->sock[cf->sockindex]);
+ if(!result)
+ result = set_ciphers(cf, data, BACKEND->handle, &protoflags);
+ if(!protoflags) {
+ failf(data, "No SSL protocol/cipher combination enabled");
+ result = CURLE_SSL_CIPHER;
+ }
+ if(!result)
+ result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_SSLV2,
+ (protoflags & CURL_GSKPROTO_SSLV2_MASK)?
+ GSK_PROTOCOL_SSLV2_ON: GSK_PROTOCOL_SSLV2_OFF, FALSE);
+ if(!result)
+ result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_SSLV3,
+ (protoflags & CURL_GSKPROTO_SSLV3_MASK)?
+ GSK_PROTOCOL_SSLV3_ON: GSK_PROTOCOL_SSLV3_OFF, FALSE);
+ if(!result)
+ result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV1,
+ (protoflags & CURL_GSKPROTO_TLSV10_MASK)?
+ GSK_PROTOCOL_TLSV1_ON: GSK_PROTOCOL_TLSV1_OFF, FALSE);
+ if(!result) {
+ result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV11,
+ (protoflags & CURL_GSKPROTO_TLSV11_MASK)?
+ GSK_TRUE: GSK_FALSE, TRUE);
+ if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+ result = CURLE_OK;
+ if(protoflags == CURL_GSKPROTO_TLSV11_MASK) {
+ failf(data, "TLS 1.1 not yet supported");
+ result = CURLE_SSL_CIPHER;
+ }
+ }
+ }
+ if(!result) {
+ result = set_enum(data, BACKEND->handle, GSK_PROTOCOL_TLSV12,
+ (protoflags & CURL_GSKPROTO_TLSV12_MASK)?
+ GSK_TRUE: GSK_FALSE, TRUE);
+ if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+ result = CURLE_OK;
+ if(protoflags == CURL_GSKPROTO_TLSV12_MASK) {
+ failf(data, "TLS 1.2 not yet supported");
+ result = CURLE_SSL_CIPHER;
+ }
+ }
+ }
+ if(!result)
+ result = set_enum(data, BACKEND->handle, GSK_SERVER_AUTH_TYPE,
+ verifypeer? GSK_SERVER_AUTH_FULL:
+ GSK_SERVER_AUTH_PASSTHRU, FALSE);
+
+ if(!result) {
+ /* Start handshake. Try asynchronous first. */
+ memset(&commarea, 0, sizeof(commarea));
+ BACKEND->iocport = QsoCreateIOCompletionPort();
+ if(BACKEND->iocport != -1) {
+ result = gskit_status(data,
+ gsk_secure_soc_startInit(BACKEND->handle,
+ BACKEND->iocport,
+ &commarea),
+ "gsk_secure_soc_startInit()",
+ CURLE_SSL_CONNECT_ERROR);
+ if(!result) {
+ connssl->connecting_state = ssl_connect_2;
+ return CURLE_OK;
+ }
+ else
+ close_async_handshake(connssl);
+ }
+ else if(errno != ENOBUFS)
+ result = gskit_status(data, GSK_ERROR_IO,
+ "QsoCreateIOCompletionPort()", 0);
+ else if(connssl_next) {
+ /* Cannot pipeline while handshaking synchronously. */
+ result = CURLE_SSL_CONNECT_ERROR;
+ }
+ else {
+ /* No more completion port available. Use synchronous IO. */
+ result = gskit_status(data, gsk_secure_soc_init(BACKEND->handle),
+ "gsk_secure_soc_init()", CURLE_SSL_CONNECT_ERROR);
+ if(!result) {
+ connssl->connecting_state = ssl_connect_3;
+ return CURLE_OK;
+ }
+ }
+ }
+
+ /* Error: rollback. */
+ close_one(connssl, data, conn, sockindex);
+ return result;
+}
+
+
+static CURLcode gskit_connect_step2(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool nonblocking)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ Qso_OverlappedIO_t cstat;
+ struct timeval stmv;
+ CURLcode result;
+
+ /* Poll or wait for end of SSL asynchronous handshake. */
+ DEBUGASSERT(BACKEND);
+
+ for(;;) {
+ timediff_t timeout_ms = nonblocking? 0: Curl_timeleft(data, NULL, TRUE);
+ stmv.tv_sec = 0;
+ stmv.tv_usec = 0;
+ if(timeout_ms < 0)
+ timeout_ms = 0;
+ switch(QsoWaitForIOCompletion(BACKEND->iocport, &cstat,
+ curlx_mstotv(&stmv, timeout_ms))) {
+ case 1: /* Operation complete. */
+ break;
+ case -1: /* An error occurred: handshake still in progress. */
+ if(errno == EINTR) {
+ if(nonblocking)
+ return CURLE_OK;
+ continue; /* Retry. */
+ }
+ if(errno != ETIME) {
+ char buffer[STRERROR_LEN];
+ failf(data, "QsoWaitForIOCompletion() I/O error: %s",
+ Curl_strerror(errno, buffer, sizeof(buffer)));
+ cancel_async_handshake(cf, data);
+ close_async_handshake(connssl);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ /* FALL INTO... */
+ case 0: /* Handshake in progress, timeout occurred. */
+ if(nonblocking)
+ return CURLE_OK;
+ cancel_async_handshake(cf, data);
+ close_async_handshake(connssl);
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ break;
+ }
+ result = gskit_status(data, cstat.returnValue, "SSL handshake",
+ CURLE_SSL_CONNECT_ERROR);
+ if(!result)
+ connssl->connecting_state = ssl_connect_3;
+ close_async_handshake(connssl);
+ return result;
+}
+
+
+static CURLcode gskit_connect_step3(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ const gsk_cert_data_elem *cdev;
+ int cdec;
+ const gsk_cert_data_elem *p;
+ const char *cert = (const char *) NULL;
+ const char *certend = (const char *) NULL;
+ const char *ptr;
+ CURLcode result;
+
+ /* SSL handshake done: gather certificate info and verify host. */
+ DEBUGASSERT(BACKEND);
+
+ if(gskit_status(data, gsk_attribute_get_cert_info(BACKEND->handle,
+ GSK_PARTNER_CERT_INFO,
+ &cdev, &cdec),
+ "gsk_attribute_get_cert_info()", CURLE_SSL_CONNECT_ERROR) ==
+ CURLE_OK) {
+ int i;
+
+ infof(data, "Server certificate:");
+ p = cdev;
+ for(i = 0; i++ < cdec; p++)
+ switch(p->cert_data_id) {
+ case CERT_BODY_DER:
+ cert = p->cert_data_p;
+ certend = cert + cdev->cert_data_l;
+ break;
+ case CERT_DN_PRINTABLE:
+ infof(data, "\t subject: %.*s", p->cert_data_l, p->cert_data_p);
+ break;
+ case CERT_ISSUER_DN_PRINTABLE:
+ infof(data, "\t issuer: %.*s", p->cert_data_l, p->cert_data_p);
+ break;
+ case CERT_VALID_FROM:
+ infof(data, "\t start date: %.*s", p->cert_data_l, p->cert_data_p);
+ break;
+ case CERT_VALID_TO:
+ infof(data, "\t expire date: %.*s", p->cert_data_l, p->cert_data_p);
+ break;
+ }
+ }
+
+ /* Verify host. */
+ result = Curl_verifyhost(cf, data, cert, certend);
+ if(result)
+ return result;
+
+ /* The only place GSKit can get the whole CA chain is a validation
+ callback where no user data pointer is available. Therefore it's not
+ possible to copy this chain into our structures for CAINFO.
+ However the server certificate may be available, thus we can return
+ info about it. */
+ if(data->set.ssl.certinfo) {
+ result = Curl_ssl_init_certinfo(data, 1);
+ if(result)
+ return result;
+
+ if(cert) {
+ result = Curl_extract_certinfo(data, 0, cert, certend);
+ if(result)
+ return result;
+ }
+ }
+
+ /* Check pinned public key. */
+ ptr = Curl_ssl_cf_is_proxy(cf)?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]:
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+ if(!result && ptr) {
+ struct Curl_X509certificate x509;
+ struct Curl_asn1Element *p;
+
+ memset(&x509, 0, sizeof(x509));
+ if(Curl_parseX509(&x509, cert, certend))
+ return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ p = &x509.subjectPublicKeyInfo;
+ result = Curl_pin_peer_pubkey(data, ptr, p->header, p->end - p->header);
+ if(result) {
+ failf(data, "SSL: public key does not match pinned public key");
+ return result;
+ }
+ }
+
+ connssl->connecting_state = ssl_connect_done;
+ return CURLE_OK;
+}
+
+
+static CURLcode gskit_connect_common(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool nonblocking, bool *done)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ timediff_t timeout_ms;
+ CURLcode result = CURLE_OK;
+
+ *done = connssl->state == ssl_connection_complete;
+ if(*done)
+ return CURLE_OK;
+
+ /* Step 1: create session, start handshake. */
+ if(connssl->connecting_state == ssl_connect_1) {
+ /* check allowed time left */
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ result = CURLE_OPERATION_TIMEDOUT;
+ }
+ else
+ result = gskit_connect_step1(cf, data);
+ }
+
+ /* Handle handshake pipelining. */
+ if(!result)
+ if(pipe_ssloverssl(cf, SOS_READ | SOS_WRITE) < 0)
+ result = CURLE_SSL_CONNECT_ERROR;
+
+ /* Step 2: check if handshake is over. */
+ if(!result && connssl->connecting_state == ssl_connect_2) {
+ /* check allowed time left */
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ result = CURLE_OPERATION_TIMEDOUT;
+ }
+ else
+ result = gskit_connect_step2(cf, data, nonblocking);
+ }
+
+ /* Handle handshake pipelining. */
+ if(!result)
+ if(pipe_ssloverssl(cf, SOS_READ | SOS_WRITE) < 0)
+ result = CURLE_SSL_CONNECT_ERROR;
+
+ /* Step 3: gather certificate info, verify host. */
+ if(!result && connssl->connecting_state == ssl_connect_3)
+ result = gskit_connect_step3(cf, data);
+
+ if(result)
+ close_one(connssl, data, conn, sockindex);
+ else if(connssl->connecting_state == ssl_connect_done) {
+ connssl->state = ssl_connection_complete;
+ connssl->connecting_state = ssl_connect_1;
+ *done = TRUE;
+ }
+
+ return result;
+}
+
+
+static CURLcode gskit_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ CURLcode result;
+
+ result = gskit_connect_common(cf, data, TRUE, done);
+ if(*done || result)
+ connssl->connecting_state = ssl_connect_1;
+ return result;
+}
+
+
+static CURLcode gskit_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ CURLcode result;
+ bool done;
+
+ connssl->connecting_state = ssl_connect_1;
+ result = gskit_connect_common(cf, data, FALSE, &done);
+ if(result)
+ return result;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+
+static void gskit_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ close_one(cf, data);
+}
+
+
+static int gskit_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ int what;
+ int rc;
+ char buf[120];
+ int loop = 10; /* don't get stuck */
+
+ DEBUGASSERT(BACKEND);
+
+ if(!BACKEND->handle)
+ return 0;
+
+#ifndef CURL_DISABLE_FTP
+ if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE)
+ return 0;
+#endif
+
+ close_one(cf, data);
+ rc = 0;
+ what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+ SSL_SHUTDOWN_TIMEOUT);
+
+ while(loop--) {
+ ssize_t nread;
+
+ if(what < 0) {
+ /* anything that gets here is fatally bad */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ rc = -1;
+ break;
+ }
+
+ if(!what) { /* timeout */
+ failf(data, "SSL shutdown timeout");
+ break;
+ }
+
+ /* Something to read, let's do it and hope that it is the close
+ notify alert from the server. No way to gsk_secure_soc_read() now, so
+ use read(). */
+
+ nread = read(cf->conn->sock[cf->sockindex], buf, sizeof(buf));
+
+ if(nread < 0) {
+ char buffer[STRERROR_LEN];
+ failf(data, "read: %s", Curl_strerror(errno, buffer, sizeof(buffer)));
+ rc = -1;
+ }
+
+ if(nread <= 0)
+ break;
+
+ what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
+ }
+
+ return rc;
+}
+
+
+static size_t gskit_version(char *buffer, size_t size)
+{
+ return msnprintf(buffer, size, "GSKit");
+}
+
+
+static int gskit_check_cxn(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ int err;
+ int errlen;
+
+ (void)data;
+ /* The only thing that can be tested here is at the socket level. */
+ DEBUGASSERT(BACKEND);
+
+ if(!BACKEND->handle)
+ return 0; /* connection has been closed */
+
+ err = 0;
+ errlen = sizeof(err);
+
+ if(getsockopt(cxn->sock[FIRSTSOCKET], SOL_SOCKET, SO_ERROR,
+ (unsigned char *) &err, &errlen) ||
+ errlen != sizeof(err) || err)
+ return 0; /* connection has been closed */
+
+ return -1; /* connection status unknown */
+}
+
+static void *gskit_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ (void)info;
+ DEBUGASSERT(BACKEND);
+ return BACKEND->handle;
+}
+
+const struct Curl_ssl Curl_ssl_gskit = {
+ { CURLSSLBACKEND_GSKIT, "gskit" }, /* info */
+
+ SSLSUPP_CERTINFO |
+ SSLSUPP_PINNEDPUBKEY,
+
+ sizeof(struct ssl_backend_data),
+
+ gskit_init, /* init */
+ gskit_cleanup, /* cleanup */
+ gskit_version, /* version */
+ gskit_check_cxn, /* check_cxn */
+ gskit_shutdown, /* shutdown */
+ Curl_none_data_pending, /* data_pending */
+ Curl_none_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ gskit_connect, /* connect */
+ gskit_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ gskit_get_internals, /* get_internals */
+ gskit_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ /* No session handling for GSKit */
+ Curl_none_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ NULL, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ gskit_recv, /* recv decrypted data */
+ gskit_send, /* send data to encrypt */
+};
+
+#endif /* USE_GSKIT */
diff --git a/libs/libcurl/src/vtls/gskit.h b/libs/libcurl/src/vtls/gskit.h index cf923f6b85..909bbb0c9c 100644 --- a/libs/libcurl/src/vtls/gskit.h +++ b/libs/libcurl/src/vtls/gskit.h @@ -1,40 +1,40 @@ -#ifndef HEADER_CURL_GSKIT_H -#define HEADER_CURL_GSKIT_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -/* - * This header should only be needed to get included by vtls.c and gskit.c - */ - -#include "urldata.h" - -#ifdef USE_GSKIT - -extern const struct Curl_ssl Curl_ssl_gskit; - -#endif /* USE_GSKIT */ - -#endif /* HEADER_CURL_GSKIT_H */ +#ifndef HEADER_CURL_GSKIT_H
+#define HEADER_CURL_GSKIT_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+/*
+ * This header should only be needed to get included by vtls.c and gskit.c
+ */
+
+#include "urldata.h"
+
+#ifdef USE_GSKIT
+
+extern const struct Curl_ssl Curl_ssl_gskit;
+
+#endif /* USE_GSKIT */
+
+#endif /* HEADER_CURL_GSKIT_H */
diff --git a/libs/libcurl/src/vtls/gtls.c b/libs/libcurl/src/vtls/gtls.c index cf3dbc5238..7b6feaa538 100644 --- a/libs/libcurl/src/vtls/gtls.c +++ b/libs/libcurl/src/vtls/gtls.c @@ -1,1705 +1,1699 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code - * but vtls.c should ever call or use these functions. - * - * Note: don't use the GnuTLS' *_t variable type names in this source code, - * since they were not present in 1.0.X. - */ - -#include "curl_setup.h" - -#ifdef USE_GNUTLS - -#include <gnutls/abstract.h> -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> -#include <gnutls/crypto.h> -#include <nettle/sha2.h> - -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "gtls.h" -#include "vtls.h" -#include "vauth/vauth.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" -#include "warnless.h" -#include "x509asn1.h" -#include "multiif.h" -#include "curl_printf.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -#ifdef HAVE_GNUTLS_SRP -/* the function exists */ -#ifdef USE_TLS_SRP -/* the functionality is not disabled */ -#define USE_GNUTLS_SRP -#endif -#endif - -/* Enable GnuTLS debugging by defining GTLSDEBUG */ -/*#define GTLSDEBUG */ - -#ifdef GTLSDEBUG -static void tls_log_func(int level, const char *str) -{ - fprintf(stderr, "|<%d>| %s", level, str); -} -#endif -static bool gtls_inited = FALSE; - -#if !defined(GNUTLS_VERSION_NUMBER) || (GNUTLS_VERSION_NUMBER < 0x03010a) -#error "too old GnuTLS version" -#endif - -# include <gnutls/ocsp.h> - -struct ssl_backend_data { - gnutls_session_t session; - gnutls_certificate_credentials_t cred; -#ifdef USE_GNUTLS_SRP - gnutls_srp_client_credentials_t srp_client_cred; -#endif -}; - -static ssize_t gtls_push(void *s, const void *buf, size_t len) -{ - curl_socket_t sock = *(curl_socket_t *)s; - ssize_t ret = swrite(sock, buf, len); - return ret; -} - -static ssize_t gtls_pull(void *s, void *buf, size_t len) -{ - curl_socket_t sock = *(curl_socket_t *)s; - ssize_t ret = sread(sock, buf, len); - return ret; -} - -static ssize_t gtls_push_ssl(void *s, const void *buf, size_t len) -{ - return gnutls_record_send((gnutls_session_t) s, buf, len); -} - -static ssize_t gtls_pull_ssl(void *s, void *buf, size_t len) -{ - return gnutls_record_recv((gnutls_session_t) s, buf, len); -} - -/* gtls_init() - * - * Global GnuTLS init, called from Curl_ssl_init(). This calls functions that - * are not thread-safe and thus this function itself is not thread-safe and - * must only be called from within curl_global_init() to keep the thread - * situation under control! - */ -static int gtls_init(void) -{ - int ret = 1; - if(!gtls_inited) { - ret = gnutls_global_init()?0:1; -#ifdef GTLSDEBUG - gnutls_global_set_log_function(tls_log_func); - gnutls_global_set_log_level(2); -#endif - gtls_inited = TRUE; - } - return ret; -} - -static void gtls_cleanup(void) -{ - if(gtls_inited) { - gnutls_global_deinit(); - gtls_inited = FALSE; - } -} - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void showtime(struct Curl_easy *data, - const char *text, - time_t stamp) -{ - struct tm buffer; - const struct tm *tm = &buffer; - char str[96]; - CURLcode result = Curl_gmtime(stamp, &buffer); - if(result) - return; - - msnprintf(str, - sizeof(str), - " %s: %s, %02d %s %4d %02d:%02d:%02d GMT", - text, - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - infof(data, "%s", str); -} -#endif - -static gnutls_datum_t load_file(const char *file) -{ - FILE *f; - gnutls_datum_t loaded_file = { NULL, 0 }; - long filelen; - void *ptr; - - f = fopen(file, "rb"); - if(!f) - return loaded_file; - if(fseek(f, 0, SEEK_END) != 0 - || (filelen = ftell(f)) < 0 - || fseek(f, 0, SEEK_SET) != 0 - || !(ptr = malloc((size_t)filelen))) - goto out; - if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) { - free(ptr); - goto out; - } - - loaded_file.data = ptr; - loaded_file.size = (unsigned int)filelen; -out: - fclose(f); - return loaded_file; -} - -static void unload_file(gnutls_datum_t data) -{ - free(data.data); -} - - -/* this function does a SSL/TLS (re-)handshake */ -static CURLcode handshake(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool duringconnect, - bool nonblocking) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - gnutls_session_t session; - curl_socket_t sockfd = conn->sock[sockindex]; - - DEBUGASSERT(backend); - session = backend->session; - - for(;;) { - timediff_t timeout_ms; - int rc; - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, duringconnect); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - int what; - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0: - timeout_ms?timeout_ms:1000); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) - return CURLE_OK; - else if(timeout_ms) { - /* timeout */ - failf(data, "SSL connection timeout at %ld", (long)timeout_ms); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - rc = gnutls_handshake(session); - - if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { - connssl->connecting_state = - gnutls_record_get_direction(session)? - ssl_connect_2_writing:ssl_connect_2_reading; - continue; - } - else if((rc < 0) && !gnutls_error_is_fatal(rc)) { - const char *strerr = NULL; - - if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); - strerr = gnutls_alert_get_name(alert); - } - - if(!strerr) - strerr = gnutls_strerror(rc); - - infof(data, "gnutls_handshake() warning: %s", strerr); - continue; - } - else if(rc < 0) { - const char *strerr = NULL; - - if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { - int alert = gnutls_alert_get(session); - strerr = gnutls_alert_get_name(alert); - } - - if(!strerr) - strerr = gnutls_strerror(rc); - - failf(data, "gnutls_handshake() failed: %s", strerr); - return CURLE_SSL_CONNECT_ERROR; - } - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - return CURLE_OK; - } -} - -static gnutls_x509_crt_fmt_t do_file_type(const char *type) -{ - if(!type || !type[0]) - return GNUTLS_X509_FMT_PEM; - if(strcasecompare(type, "PEM")) - return GNUTLS_X509_FMT_PEM; - if(strcasecompare(type, "DER")) - return GNUTLS_X509_FMT_DER; - return GNUTLS_X509_FMT_PEM; /* default to PEM */ -} - -#define GNUTLS_CIPHERS "NORMAL:-ARCFOUR-128:-CTYPE-ALL:+CTYPE-X509" -/* If GnuTLS was compiled without support for SRP it will error out if SRP is - requested in the priority string, so treat it specially - */ -#define GNUTLS_SRP "+SRP" - -static CURLcode -set_ssl_version_min_max(struct Curl_easy *data, - const char **prioritylist, - const char *tls13support) -{ - struct connectdata *conn = data->conn; - long ssl_version = SSL_CONN_CONFIG(version); - long ssl_version_max = SSL_CONN_CONFIG(version_max); - - if((ssl_version == CURL_SSLVERSION_DEFAULT) || - (ssl_version == CURL_SSLVERSION_TLSv1)) - ssl_version = CURL_SSLVERSION_TLSv1_0; - if(ssl_version_max == CURL_SSLVERSION_MAX_NONE) - ssl_version_max = CURL_SSLVERSION_MAX_DEFAULT; - if(!tls13support) { - /* If the running GnuTLS doesn't support TLS 1.3, we must not specify a - prioritylist involving that since it will make GnuTLS return an en - error back at us */ - if((ssl_version_max == CURL_SSLVERSION_MAX_TLSv1_3) || - (ssl_version_max == CURL_SSLVERSION_MAX_DEFAULT)) { - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; - } - } - else if(ssl_version_max == CURL_SSLVERSION_MAX_DEFAULT) { - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; - } - - switch(ssl_version | ssl_version_max) { - case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_0: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.0"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_1: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.1:+VERS-TLS1.0"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_2: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_1: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.1"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_2: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.2:+VERS-TLS1.1"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.2"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3 | CURL_SSLVERSION_MAX_TLSv1_3: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.3"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_3: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_3: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.3:+VERS-TLS1.2:+VERS-TLS1.1"; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_3: - *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:" - "+VERS-TLS1.3:+VERS-TLS1.2"; - return CURLE_OK; - } - - failf(data, "GnuTLS: cannot set ssl protocol"); - return CURLE_SSL_CONNECT_ERROR; -} - -static CURLcode -gtls_connect_step1(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - unsigned int init_flags; - gnutls_session_t session; - int rc; - bool sni = TRUE; /* default is SNI enabled */ - void *transport_ptr = NULL; - gnutls_push_func gnutls_transport_push = NULL; - gnutls_pull_func gnutls_transport_pull = NULL; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - const char *prioritylist; - const char *err = NULL; - const char * const hostname = SSL_HOST_NAME(); - long * const certverifyresult = &SSL_SET_OPTION_LVALUE(certverifyresult); - const char *tls13support; - CURLcode result; - - DEBUGASSERT(backend); - - if(connssl->state == ssl_connection_complete) - /* to make us tolerant against being called more than once for the - same connection */ - return CURLE_OK; - - if(!gtls_inited) - gtls_init(); - - /* Initialize certverifyresult to OK */ - *certverifyresult = 0; - - if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv2) { - failf(data, "GnuTLS does not support SSLv2"); - return CURLE_SSL_CONNECT_ERROR; - } - else if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv3) - sni = FALSE; /* SSLv3 has no SNI */ - - /* allocate a cred struct */ - rc = gnutls_certificate_allocate_credentials(&backend->cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef USE_GNUTLS_SRP - if((SSL_SET_OPTION(primary.authtype) == CURL_TLSAUTH_SRP) && - Curl_auth_allowed_to_host(data)) { - infof(data, "Using TLS-SRP username: %s", - SSL_SET_OPTION(primary.username)); - - rc = gnutls_srp_allocate_client_credentials(&backend->srp_client_cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_allocate_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_OUT_OF_MEMORY; - } - - rc = gnutls_srp_set_client_credentials(backend->srp_client_cred, - SSL_SET_OPTION(primary.username), - SSL_SET_OPTION(primary.password)); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_srp_set_client_cred() failed: %s", - gnutls_strerror(rc)); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - } -#endif - - if(SSL_CONN_CONFIG(CAfile)) { - /* set the trusted CA cert bundle file */ - gnutls_certificate_set_verify_flags(backend->cred, - GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT); - - rc = gnutls_certificate_set_x509_trust_file(backend->cred, - SSL_CONN_CONFIG(CAfile), - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)", - SSL_CONN_CONFIG(CAfile), gnutls_strerror(rc)); - if(SSL_CONN_CONFIG(verifypeer)) { - *certverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found %d certificates in %s", rc, - SSL_CONN_CONFIG(CAfile)); - } - - if(SSL_CONN_CONFIG(CApath)) { - /* set the trusted CA cert directory */ - rc = gnutls_certificate_set_x509_trust_dir(backend->cred, - SSL_CONN_CONFIG(CApath), - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - infof(data, "error reading ca cert file %s (%s)", - SSL_CONN_CONFIG(CApath), gnutls_strerror(rc)); - if(SSL_CONN_CONFIG(verifypeer)) { - *certverifyresult = rc; - return CURLE_SSL_CACERT_BADFILE; - } - } - else - infof(data, "found %d certificates in %s", - rc, SSL_CONN_CONFIG(CApath)); - } - -#ifdef CURL_CA_FALLBACK - /* use system ca certificate store as fallback */ - if(SSL_CONN_CONFIG(verifypeer) && - !(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(CApath))) { - /* this ignores errors on purpose */ - gnutls_certificate_set_x509_system_trust(backend->cred); - } -#endif - - if(SSL_SET_OPTION(primary.CRLfile)) { - /* set the CRL list file */ - rc = gnutls_certificate_set_x509_crl_file(backend->cred, - SSL_SET_OPTION(primary.CRLfile), - GNUTLS_X509_FMT_PEM); - if(rc < 0) { - failf(data, "error reading crl file %s (%s)", - SSL_SET_OPTION(primary.CRLfile), gnutls_strerror(rc)); - return CURLE_SSL_CRL_BADFILE; - } - else - infof(data, "found %d CRL in %s", - rc, SSL_SET_OPTION(primary.CRLfile)); - } - - /* Initialize TLS session as a client */ - init_flags = GNUTLS_CLIENT; - -#if defined(GNUTLS_FORCE_CLIENT_CERT) - init_flags |= GNUTLS_FORCE_CLIENT_CERT; -#endif - -#if defined(GNUTLS_NO_TICKETS) - /* Disable TLS session tickets */ - init_flags |= GNUTLS_NO_TICKETS; -#endif - - rc = gnutls_init(&backend->session, init_flags); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_init() failed: %d", rc); - return CURLE_SSL_CONNECT_ERROR; - } - - /* convenient assign */ - session = backend->session; - - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, hostname, &snilen); - if(!snihost || gnutls_server_name_set(session, GNUTLS_NAME_DNS, snihost, - snilen) < 0) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* Use default priorities */ - rc = gnutls_set_default_priority(session); - if(rc != GNUTLS_E_SUCCESS) - return CURLE_SSL_CONNECT_ERROR; - - /* "In GnuTLS 3.6.5, TLS 1.3 is enabled by default" */ - tls13support = gnutls_check_version("3.6.5"); - - /* Ensure +SRP comes at the *end* of all relevant strings so that it can be - * removed if a run-time error indicates that SRP is not supported by this - * GnuTLS version */ - - if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv2 || - SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv3) { - failf(data, "GnuTLS does not support SSLv2 or SSLv3"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(SSL_CONN_CONFIG(version) == CURL_SSLVERSION_TLSv1_3) { - if(!tls13support) { - failf(data, "This GnuTLS installation does not support TLS 1.3"); - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* At this point we know we have a supported TLS version, so set it */ - result = set_ssl_version_min_max(data, &prioritylist, tls13support); - if(result) - return result; - -#ifdef USE_GNUTLS_SRP - /* Only add SRP to the cipher list if SRP is requested. Otherwise - * GnuTLS will disable TLS 1.3 support. */ - if(SSL_SET_OPTION(primary.authtype) == CURL_TLSAUTH_SRP) { - size_t len = strlen(prioritylist); - - char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1); - if(!prioritysrp) - return CURLE_OUT_OF_MEMORY; - strcpy(prioritysrp, prioritylist); - strcpy(prioritysrp + len, ":" GNUTLS_SRP); - rc = gnutls_priority_set_direct(session, prioritysrp, &err); - free(prioritysrp); - - if((rc == GNUTLS_E_INVALID_REQUEST) && err) { - infof(data, "This GnuTLS does not support SRP"); - } - } - else { -#endif - infof(data, "GnuTLS ciphers: %s", prioritylist); - rc = gnutls_priority_set_direct(session, prioritylist, &err); -#ifdef USE_GNUTLS_SRP - } -#endif - - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "Error %d setting GnuTLS cipher list starting with %s", - rc, err); - return CURLE_SSL_CONNECT_ERROR; - } - - if(conn->bits.tls_enable_alpn) { - int cur = 0; - gnutls_datum_t protocols[2]; - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur].data = (unsigned char *)ALPN_H2; - protocols[cur].size = ALPN_H2_LENGTH; - cur++; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1; - protocols[cur].size = ALPN_HTTP_1_1_LENGTH; - cur++; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - if(gnutls_alpn_set_protocols(session, protocols, cur, 0)) { - failf(data, "failed setting ALPN"); - return CURLE_SSL_CONNECT_ERROR; - } - } - - if(SSL_SET_OPTION(primary.clientcert)) { - if(SSL_SET_OPTION(key_passwd)) { - const unsigned int supported_key_encryption_algorithms = - GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR | - GNUTLS_PKCS_USE_PKCS12_RC2_40 | GNUTLS_PKCS_USE_PBES2_3DES | - GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 | - GNUTLS_PKCS_USE_PBES2_AES_256; - rc = gnutls_certificate_set_x509_key_file2( - backend->cred, - SSL_SET_OPTION(primary.clientcert), - SSL_SET_OPTION(key) ? - SSL_SET_OPTION(key) : SSL_SET_OPTION(primary.clientcert), - do_file_type(SSL_SET_OPTION(cert_type)), - SSL_SET_OPTION(key_passwd), - supported_key_encryption_algorithms); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, - "error reading X.509 potentially-encrypted key file: %s", - gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { - if(gnutls_certificate_set_x509_key_file( - backend->cred, - SSL_SET_OPTION(primary.clientcert), - SSL_SET_OPTION(key) ? - SSL_SET_OPTION(key) : SSL_SET_OPTION(primary.clientcert), - do_file_type(SSL_SET_OPTION(cert_type)) ) != - GNUTLS_E_SUCCESS) { - failf(data, "error reading X.509 key or certificate file"); - return CURLE_SSL_CONNECT_ERROR; - } - } - } - -#ifdef USE_GNUTLS_SRP - /* put the credentials to the current session */ - if(SSL_SET_OPTION(primary.authtype) == CURL_TLSAUTH_SRP) { - rc = gnutls_credentials_set(session, GNUTLS_CRD_SRP, - backend->srp_client_cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - } - else -#endif - { - rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, - backend->cred); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc)); - return CURLE_SSL_CONNECT_ERROR; - } - } - -#ifndef CURL_DISABLE_PROXY - if(conn->proxy_ssl[sockindex].use) { - struct ssl_backend_data *proxy_backend; - proxy_backend = conn->proxy_ssl[sockindex].backend; - DEBUGASSERT(proxy_backend); - transport_ptr = proxy_backend->session; - gnutls_transport_push = gtls_push_ssl; - gnutls_transport_pull = gtls_pull_ssl; - } - else -#endif - { - /* file descriptor for the socket */ - transport_ptr = &conn->sock[sockindex]; - gnutls_transport_push = gtls_push; - gnutls_transport_pull = gtls_pull; - } - - /* set the connection handle */ - gnutls_transport_set_ptr(session, transport_ptr); - - /* register callback functions to send and receive data. */ - gnutls_transport_set_push_function(session, gnutls_transport_push); - gnutls_transport_set_pull_function(session, gnutls_transport_pull); - - if(SSL_CONN_CONFIG(verifystatus)) { - rc = gnutls_ocsp_status_request_enable_client(session, NULL, 0, NULL); - if(rc != GNUTLS_E_SUCCESS) { - failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc); - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* This might be a reconnect, so we check for a session ID in the cache - to speed up things */ - if(SSL_SET_OPTION(primary.sessionid)) { - void *ssl_sessionid; - size_t ssl_idsize; - - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - &ssl_sessionid, &ssl_idsize, sockindex)) { - /* we got a session id, use it! */ - gnutls_session_set_data(session, ssl_sessionid, ssl_idsize); - - /* Informational message */ - infof(data, "SSL re-using session ID"); - } - Curl_ssl_sessionid_unlock(data); - } - - return CURLE_OK; -} - -static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, - gnutls_x509_crt_t cert, - const char *pinnedpubkey) -{ - /* Scratch */ - size_t len1 = 0, len2 = 0; - unsigned char *buff1 = NULL; - - gnutls_pubkey_t key = NULL; - - /* Result is returned to caller */ - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - - /* if a path wasn't specified, don't pin */ - if(!pinnedpubkey) - return CURLE_OK; - - if(!cert) - return result; - - do { - int ret; - - /* Begin Gyrations to get the public key */ - gnutls_pubkey_init(&key); - - ret = gnutls_pubkey_import_x509(key, cert, 0); - if(ret < 0) - break; /* failed */ - - ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, NULL, &len1); - if(ret != GNUTLS_E_SHORT_MEMORY_BUFFER || len1 == 0) - break; /* failed */ - - buff1 = malloc(len1); - if(!buff1) - break; /* failed */ - - len2 = len1; - - ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, buff1, &len2); - if(ret < 0 || len1 != len2) - break; /* failed */ - - /* End Gyrations */ - - /* The one good exit point */ - result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1); - } while(0); - - if(key) - gnutls_pubkey_deinit(key); - - Curl_safefree(buff1); - - return result; -} - -static Curl_recv gtls_recv; -static Curl_send gtls_send; - -CURLcode -Curl_gtls_verifyserver(struct Curl_easy *data, - struct connectdata *conn, - gnutls_session_t session, - int sockindex) -{ - unsigned int cert_list_size; - const gnutls_datum_t *chainp; - unsigned int verify_status = 0; - gnutls_x509_crt_t x509_cert, x509_issuer; - gnutls_datum_t issuerp; - gnutls_datum_t certfields; - char certname[65] = ""; /* limited to 64 chars by ASN.1 */ - size_t size; - time_t certclock; - const char *ptr; - int rc; - gnutls_datum_t proto; - CURLcode result = CURLE_OK; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - unsigned int algo; - unsigned int bits; - gnutls_protocol_t version = gnutls_protocol_get_version(session); -#endif - const char * const hostname = SSL_HOST_NAME(); - long * const certverifyresult = &SSL_SET_OPTION_LVALUE(certverifyresult); - - /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */ - ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(session), - gnutls_cipher_get(session), - gnutls_mac_get(session)); - - infof(data, "SSL connection using %s / %s", - gnutls_protocol_get_name(version), ptr); - - /* This function will return the peer's raw certificate (chain) as sent by - the peer. These certificates are in raw format (DER encoded for - X.509). In case of a X.509 then a certificate list may be present. The - first certificate in the list is the peer's certificate, following the - issuer's certificate, then the issuer's issuer etc. */ - - chainp = gnutls_certificate_get_peers(session, &cert_list_size); - if(!chainp) { - if(SSL_CONN_CONFIG(verifypeer) || - SSL_CONN_CONFIG(verifyhost) || - SSL_CONN_CONFIG(issuercert)) { -#ifdef USE_GNUTLS_SRP - if(SSL_SET_OPTION(primary.authtype) == CURL_TLSAUTH_SRP - && SSL_SET_OPTION(primary.username) - && !SSL_CONN_CONFIG(verifypeer) - && gnutls_cipher_get(session)) { - /* no peer cert, but auth is ok if we have SRP user and cipher and no - peer verify */ - } - else { -#endif - failf(data, "failed to get server cert"); - *certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND; - return CURLE_PEER_FAILED_VERIFICATION; -#ifdef USE_GNUTLS_SRP - } -#endif - } - infof(data, " common name: WARNING couldn't obtain"); - } - - if(data->set.ssl.certinfo && chainp) { - unsigned int i; - - result = Curl_ssl_init_certinfo(data, cert_list_size); - if(result) - return result; - - for(i = 0; i < cert_list_size; i++) { - const char *beg = (const char *) chainp[i].data; - const char *end = beg + chainp[i].size; - - result = Curl_extract_certinfo(data, i, beg, end); - if(result) - return result; - } - } - - if(SSL_CONN_CONFIG(verifypeer)) { - /* This function will try to verify the peer's certificate and return its - status (trusted, invalid etc.). The value of status should be one or - more of the gnutls_certificate_status_t enumerated elements bitwise - or'd. To avoid denial of service attacks some default upper limits - regarding the certificate key size and chain size are set. To override - them use gnutls_certificate_set_verify_limits(). */ - - rc = gnutls_certificate_verify_peers2(session, &verify_status); - if(rc < 0) { - failf(data, "server cert verify failed: %d", rc); - *certverifyresult = rc; - return CURLE_SSL_CONNECT_ERROR; - } - - *certverifyresult = verify_status; - - /* verify_status is a bitmask of gnutls_certificate_status bits */ - if(verify_status & GNUTLS_CERT_INVALID) { - if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "server certificate verification failed. CAfile: %s " - "CRLfile: %s", SSL_CONN_CONFIG(CAfile) ? SSL_CONN_CONFIG(CAfile): - "none", - SSL_SET_OPTION(primary.CRLfile) ? - SSL_SET_OPTION(primary.CRLfile) : "none"); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, " server certificate verification FAILED"); - } - else - infof(data, " server certificate verification OK"); - } - else - infof(data, " server certificate verification SKIPPED"); - - if(SSL_CONN_CONFIG(verifystatus)) { - if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) { - gnutls_datum_t status_request; - gnutls_ocsp_resp_t ocsp_resp; - - gnutls_ocsp_cert_status_t status; - gnutls_x509_crl_reason_t reason; - - rc = gnutls_ocsp_status_request_get(session, &status_request); - - infof(data, " server certificate status verification FAILED"); - - if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { - failf(data, "No OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } - - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } - - gnutls_ocsp_resp_init(&ocsp_resp); - - rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request); - if(rc < 0) { - failf(data, "Invalid OCSP response received"); - return CURLE_SSL_INVALIDCERTSTATUS; - } - - (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL, - &status, NULL, NULL, NULL, &reason); - - switch(status) { - case GNUTLS_OCSP_CERT_GOOD: - break; - - case GNUTLS_OCSP_CERT_REVOKED: { - const char *crl_reason; - - switch(reason) { - default: - case GNUTLS_X509_CRLREASON_UNSPECIFIED: - crl_reason = "unspecified reason"; - break; - - case GNUTLS_X509_CRLREASON_KEYCOMPROMISE: - crl_reason = "private key compromised"; - break; - - case GNUTLS_X509_CRLREASON_CACOMPROMISE: - crl_reason = "CA compromised"; - break; - - case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED: - crl_reason = "affiliation has changed"; - break; - - case GNUTLS_X509_CRLREASON_SUPERSEDED: - crl_reason = "certificate superseded"; - break; - - case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION: - crl_reason = "operation has ceased"; - break; - - case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD: - crl_reason = "certificate is on hold"; - break; - - case GNUTLS_X509_CRLREASON_REMOVEFROMCRL: - crl_reason = "will be removed from delta CRL"; - break; - - case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN: - crl_reason = "privilege withdrawn"; - break; - - case GNUTLS_X509_CRLREASON_AACOMPROMISE: - crl_reason = "AA compromised"; - break; - } - - failf(data, "Server certificate was revoked: %s", crl_reason); - break; - } - - default: - case GNUTLS_OCSP_CERT_UNKNOWN: - failf(data, "Server certificate status is unknown"); - break; - } - - gnutls_ocsp_resp_deinit(ocsp_resp); - - return CURLE_SSL_INVALIDCERTSTATUS; - } - else - infof(data, " server certificate status verification OK"); - } - else - infof(data, " server certificate status verification SKIPPED"); - - /* initialize an X.509 certificate structure. */ - gnutls_x509_crt_init(&x509_cert); - - if(chainp) - /* convert the given DER or PEM encoded Certificate to the native - gnutls_x509_crt_t format */ - gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); - - if(SSL_CONN_CONFIG(issuercert)) { - gnutls_x509_crt_init(&x509_issuer); - issuerp = load_file(SSL_CONN_CONFIG(issuercert)); - gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM); - rc = gnutls_x509_crt_check_issuer(x509_cert, x509_issuer); - gnutls_x509_crt_deinit(x509_issuer); - unload_file(issuerp); - if(rc <= 0) { - failf(data, "server certificate issuer check failed (IssuerCert: %s)", - SSL_CONN_CONFIG(issuercert)?SSL_CONN_CONFIG(issuercert):"none"); - gnutls_x509_crt_deinit(x509_cert); - return CURLE_SSL_ISSUER_ERROR; - } - infof(data, " server certificate issuer check OK (Issuer Cert: %s)", - SSL_CONN_CONFIG(issuercert)?SSL_CONN_CONFIG(issuercert):"none"); - } - - size = sizeof(certname); - rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME, - 0, /* the first and only one */ - FALSE, - certname, - &size); - if(rc) { - infof(data, "error fetching CN from cert:%s", - gnutls_strerror(rc)); - } - - /* This function will check if the given certificate's subject matches the - given hostname. This is a basic implementation of the matching described - in RFC2818 (HTTPS), which takes into account wildcards, and the subject - alternative name PKIX extension. Returns non zero on success, and zero on - failure. */ - rc = gnutls_x509_crt_check_hostname(x509_cert, hostname); -#if GNUTLS_VERSION_NUMBER < 0x030306 - /* Before 3.3.6, gnutls_x509_crt_check_hostname() didn't check IP - addresses. */ - if(!rc) { -#ifdef ENABLE_IPV6 - #define use_addr in6_addr -#else - #define use_addr in_addr -#endif - unsigned char addrbuf[sizeof(struct use_addr)]; - size_t addrlen = 0; - - if(Curl_inet_pton(AF_INET, hostname, addrbuf) > 0) - addrlen = 4; -#ifdef ENABLE_IPV6 - else if(Curl_inet_pton(AF_INET6, hostname, addrbuf) > 0) - addrlen = 16; -#endif - - if(addrlen) { - unsigned char certaddr[sizeof(struct use_addr)]; - int i; - - for(i = 0; ; i++) { - size_t certaddrlen = sizeof(certaddr); - int ret = gnutls_x509_crt_get_subject_alt_name(x509_cert, i, certaddr, - &certaddrlen, NULL); - /* If this happens, it wasn't an IP address. */ - if(ret == GNUTLS_E_SHORT_MEMORY_BUFFER) - continue; - if(ret < 0) - break; - if(ret != GNUTLS_SAN_IPADDRESS) - continue; - if(certaddrlen == addrlen && !memcmp(addrbuf, certaddr, addrlen)) { - rc = 1; - break; - } - } - } - } -#endif - if(!rc) { - if(SSL_CONN_CONFIG(verifyhost)) { - failf(data, "SSL: certificate subject name (%s) does not match " - "target host name '%s'", certname, SSL_HOST_DISPNAME()); - gnutls_x509_crt_deinit(x509_cert); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, " common name: %s (does not match '%s')", - certname, SSL_HOST_DISPNAME()); - } - else - infof(data, " common name: %s (matched)", certname); - - /* Check for time-based validity */ - certclock = gnutls_x509_crt_get_expiration_time(x509_cert); - - if(certclock == (time_t)-1) { - if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "server cert expiration date verify failed"); - *certverifyresult = GNUTLS_CERT_EXPIRED; - gnutls_x509_crt_deinit(x509_cert); - return CURLE_SSL_CONNECT_ERROR; - } - else - infof(data, " server certificate expiration date verify FAILED"); - } - else { - if(certclock < time(NULL)) { - if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "server certificate expiration date has passed."); - *certverifyresult = GNUTLS_CERT_EXPIRED; - gnutls_x509_crt_deinit(x509_cert); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, " server certificate expiration date FAILED"); - } - else - infof(data, " server certificate expiration date OK"); - } - - certclock = gnutls_x509_crt_get_activation_time(x509_cert); - - if(certclock == (time_t)-1) { - if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "server cert activation date verify failed"); - *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; - gnutls_x509_crt_deinit(x509_cert); - return CURLE_SSL_CONNECT_ERROR; - } - else - infof(data, " server certificate activation date verify FAILED"); - } - else { - if(certclock > time(NULL)) { - if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "server certificate not activated yet."); - *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED; - gnutls_x509_crt_deinit(x509_cert); - return CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, " server certificate activation date FAILED"); - } - else - infof(data, " server certificate activation date OK"); - } - - ptr = SSL_PINNED_PUB_KEY(); - if(ptr) { - result = pkp_pin_peer_pubkey(data, x509_cert, ptr); - if(result != CURLE_OK) { - failf(data, "SSL: public key does not match pinned public key"); - gnutls_x509_crt_deinit(x509_cert); - return result; - } - } - - /* Show: - - - subject - - start date - - expire date - - common name - - issuer - - */ - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - /* public key algorithm's parameters */ - algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); - infof(data, " certificate public key: %s", - gnutls_pk_algorithm_get_name(algo)); - - /* version of the X.509 certificate. */ - infof(data, " certificate version: #%d", - gnutls_x509_crt_get_version(x509_cert)); - - - rc = gnutls_x509_crt_get_dn2(x509_cert, &certfields); - if(rc) - infof(data, "Failed to get certificate name"); - else { - infof(data, " subject: %s", certfields.data); - - certclock = gnutls_x509_crt_get_activation_time(x509_cert); - showtime(data, "start date", certclock); - - certclock = gnutls_x509_crt_get_expiration_time(x509_cert); - showtime(data, "expire date", certclock); - - gnutls_free(certfields.data); - } - - rc = gnutls_x509_crt_get_issuer_dn2(x509_cert, &certfields); - if(rc) - infof(data, "Failed to get certificate issuer"); - else { - infof(data, " issuer: %s", certfields.data); - - gnutls_free(certfields.data); - } -#endif - - gnutls_x509_crt_deinit(x509_cert); - - if(conn->bits.tls_enable_alpn) { - rc = gnutls_alpn_get_selected_protocol(session, &proto); - if(rc == 0) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto.size, - proto.data); - -#ifdef USE_HTTP2 - if(proto.size == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, proto.data, - ALPN_H2_LENGTH)) { - conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(proto.size == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) { - conn->alpn = CURL_HTTP_VERSION_1_1; - } - } - else - infof(data, VTLS_INFOF_NO_ALPN); - - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } - - conn->ssl[sockindex].state = ssl_connection_complete; - - if(SSL_SET_OPTION(primary.sessionid)) { - /* we always unconditionally get the session id here, as even if we - already got it from the cache and asked to use it in the connection, it - might've been rejected and then a new one is in use now and we need to - detect that. */ - void *connect_sessionid; - size_t connect_idsize = 0; - - /* get the session ID data size */ - gnutls_session_get_data(session, NULL, &connect_idsize); - connect_sessionid = malloc(connect_idsize); /* get a buffer for it */ - - if(connect_sessionid) { - bool incache; - bool added = FALSE; - void *ssl_sessionid; - - /* extract session ID to the allocated buffer */ - gnutls_session_get_data(session, connect_sessionid, &connect_idsize); - - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - &ssl_sessionid, NULL, sockindex)); - if(incache) { - /* there was one before in the cache, so instead of risking that the - previous one was rejected, we just kill that and store the new */ - Curl_ssl_delsessionid(data, ssl_sessionid); - } - - /* store this session id */ - result = Curl_ssl_addsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - connect_sessionid, connect_idsize, - sockindex, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) - free(connect_sessionid); - if(result) { - result = CURLE_OUT_OF_MEMORY; - } - } - else - result = CURLE_OUT_OF_MEMORY; - } - - return result; -} - - -/* - * This function is called after the TCP connect has completed. Setup the TLS - * layer and do all necessary magic. - */ -/* We use connssl->connecting_state to keep track of the connection status; - there are three states: 'ssl_connect_1' (not started yet or complete), - 'ssl_connect_2_reading' (waiting for data from server), and - 'ssl_connect_2_writing' (waiting to be able to write). - */ -static CURLcode -gtls_connect_common(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - int rc; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - /* Initiate the connection, if not already done */ - if(ssl_connect_1 == connssl->connecting_state) { - rc = gtls_connect_step1(data, conn, sockindex); - if(rc) - return rc; - } - - rc = handshake(data, conn, sockindex, TRUE, nonblocking); - if(rc) - /* handshake() sets its own error message with failf() */ - return rc; - - /* Finish connecting once the handshake is done */ - if(ssl_connect_1 == connssl->connecting_state) { - struct ssl_backend_data *backend = connssl->backend; - gnutls_session_t session; - DEBUGASSERT(backend); - session = backend->session; - rc = Curl_gtls_verifyserver(data, conn, session, sockindex); - if(rc) - return rc; - conn->recv[sockindex] = gtls_recv; - conn->send[sockindex] = gtls_send; - } - - *done = ssl_connect_1 == connssl->connecting_state; - - return CURLE_OK; -} - -static CURLcode gtls_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - return gtls_connect_common(data, conn, sockindex, TRUE, done); -} - -static CURLcode gtls_connect(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = gtls_connect_common(data, conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static bool gtls_data_pending(const struct connectdata *conn, - int connindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[connindex]; - bool res = FALSE; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(backend->session && - 0 != gnutls_record_check_pending(backend->session)) - res = TRUE; - -#ifndef CURL_DISABLE_PROXY - connssl = &conn->proxy_ssl[connindex]; - backend = connssl->backend; - DEBUGASSERT(backend); - if(backend->session && - 0 != gnutls_record_check_pending(backend->session)) - res = TRUE; -#endif - - return res; -} - -static ssize_t gtls_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - ssize_t rc; - - DEBUGASSERT(backend); - rc = gnutls_record_send(backend->session, mem, len); - - if(rc < 0) { - *curlcode = (rc == GNUTLS_E_AGAIN) - ? CURLE_AGAIN - : CURLE_SEND_ERROR; - - rc = -1; - } - - return rc; -} - -static void close_one(struct ssl_connect_data *connssl) -{ - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - - if(backend->session) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)gnutls_record_recv(backend->session, buf, sizeof(buf)); - gnutls_bye(backend->session, GNUTLS_SHUT_WR); - gnutls_deinit(backend->session); - backend->session = NULL; - } - if(backend->cred) { - gnutls_certificate_free_credentials(backend->cred); - backend->cred = NULL; - } -#ifdef USE_GNUTLS_SRP - if(backend->srp_client_cred) { - gnutls_srp_free_client_credentials(backend->srp_client_cred); - backend->srp_client_cred = NULL; - } -#endif -} - -static void gtls_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - (void) data; - close_one(&conn->ssl[sockindex]); -#ifndef CURL_DISABLE_PROXY - close_one(&conn->proxy_ssl[sockindex]); -#endif -} - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int gtls_shutdown(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - int retval = 0; - - DEBUGASSERT(backend); - -#ifndef CURL_DISABLE_FTP - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - gnutls_bye(backend->session, GNUTLS_SHUT_WR); -#endif - - if(backend->session) { - ssize_t result; - bool done = FALSE; - char buf[120]; - - while(!done) { - int what = SOCKET_READABLE(conn->sock[sockindex], - SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - result = gnutls_record_recv(backend->session, - buf, sizeof(buf)); - switch(result) { - case 0: - /* This is the expected response. There was no data but only - the close notify alert */ - done = TRUE; - break; - case GNUTLS_E_AGAIN: - case GNUTLS_E_INTERRUPTED: - infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED"); - break; - default: - retval = -1; - done = TRUE; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = TRUE; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = TRUE; - } - } - gnutls_deinit(backend->session); - } - gnutls_certificate_free_credentials(backend->cred); - -#ifdef USE_GNUTLS_SRP - if(SSL_SET_OPTION(primary.authtype) == CURL_TLSAUTH_SRP - && SSL_SET_OPTION(primary.username) != NULL) - gnutls_srp_free_client_credentials(backend->srp_client_cred); -#endif - - backend->cred = NULL; - backend->session = NULL; - - return retval; -} - -static ssize_t gtls_recv(struct Curl_easy *data, /* connection data */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[num]; - struct ssl_backend_data *backend = connssl->backend; - ssize_t ret; - - DEBUGASSERT(backend); - - ret = gnutls_record_recv(backend->session, buf, buffersize); - if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { - *curlcode = CURLE_AGAIN; - return -1; - } - - if(ret == GNUTLS_E_REHANDSHAKE) { - /* BLOCKING call, this is bad but a work-around for now. Fixing this "the - proper way" takes a whole lot of work. */ - CURLcode result = handshake(data, conn, num, FALSE, FALSE); - if(result) - /* handshake() writes error message on its own */ - *curlcode = result; - else - *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */ - return -1; - } - - if(ret < 0) { - failf(data, "GnuTLS recv error (%d): %s", - - (int)ret, gnutls_strerror((int)ret)); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - - return ret; -} - -static void gtls_session_free(void *ptr) -{ - free(ptr); -} - -static size_t gtls_version(char *buffer, size_t size) -{ - return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL)); -} - -/* data might be NULL! */ -static CURLcode gtls_random(struct Curl_easy *data, - unsigned char *entropy, size_t length) -{ - int rc; - (void)data; - rc = gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length); - return rc?CURLE_FAILED_INIT:CURLE_OK; -} - -static CURLcode gtls_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len) -{ - struct sha256_ctx SHA256pw; - sha256_init(&SHA256pw); - sha256_update(&SHA256pw, (unsigned int)tmplen, tmp); - sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum); - return CURLE_OK; -} - -static bool gtls_cert_status_request(void) -{ - return TRUE; -} - -static void *gtls_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - (void)info; - DEBUGASSERT(backend); - return backend->session; -} - -const struct Curl_ssl Curl_ssl_gnutls = { - { CURLSSLBACKEND_GNUTLS, "gnutls" }, /* info */ - - SSLSUPP_CA_PATH | - SSLSUPP_CERTINFO | - SSLSUPP_PINNEDPUBKEY | - SSLSUPP_HTTPS_PROXY, - - sizeof(struct ssl_backend_data), - - gtls_init, /* init */ - gtls_cleanup, /* cleanup */ - gtls_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - gtls_shutdown, /* shutdown */ - gtls_data_pending, /* data_pending */ - gtls_random, /* random */ - gtls_cert_status_request, /* cert_status_request */ - gtls_connect, /* connect */ - gtls_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - gtls_get_internals, /* get_internals */ - gtls_close, /* close_one */ - Curl_none_close_all, /* close_all */ - gtls_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - gtls_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif /* USE_GNUTLS */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for all GnuTLS-specific code for the TLS/SSL layer. No code
+ * but vtls.c should ever call or use these functions.
+ *
+ * Note: don't use the GnuTLS' *_t variable type names in this source code,
+ * since they were not present in 1.0.X.
+ */
+
+#include "curl_setup.h"
+
+#ifdef USE_GNUTLS
+
+#include <gnutls/abstract.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <gnutls/crypto.h>
+#include <nettle/sha2.h>
+
+#include "urldata.h"
+#include "sendf.h"
+#include "inet_pton.h"
+#include "gtls.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "vauth/vauth.h"
+#include "parsedate.h"
+#include "connect.h" /* for the connect timeout */
+#include "select.h"
+#include "strcase.h"
+#include "warnless.h"
+#include "x509asn1.h"
+#include "multiif.h"
+#include "curl_printf.h"
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+/* Enable GnuTLS debugging by defining GTLSDEBUG */
+/*#define GTLSDEBUG */
+
+#ifdef GTLSDEBUG
+static void tls_log_func(int level, const char *str)
+{
+ fprintf(stderr, "|<%d>| %s", level, str);
+}
+#endif
+static bool gtls_inited = FALSE;
+
+#if !defined(GNUTLS_VERSION_NUMBER) || (GNUTLS_VERSION_NUMBER < 0x03010a)
+#error "too old GnuTLS version"
+#endif
+
+# include <gnutls/ocsp.h>
+
+struct ssl_backend_data {
+ struct gtls_instance gtls;
+};
+
+static ssize_t gtls_push(void *s, const void *buf, size_t blen)
+{
+ struct Curl_cfilter *cf = s;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nwritten;
+ CURLcode result;
+
+ DEBUGASSERT(data);
+ nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
+ if(nwritten < 0) {
+ gnutls_transport_set_errno(connssl->backend->gtls.session,
+ (CURLE_AGAIN == result)? EAGAIN : EINVAL);
+ nwritten = -1;
+ }
+ return nwritten;
+}
+
+static ssize_t gtls_pull(void *s, void *buf, size_t blen)
+{
+ struct Curl_cfilter *cf = s;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nread;
+ CURLcode result;
+
+ DEBUGASSERT(data);
+ nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
+ if(nread < 0) {
+ gnutls_transport_set_errno(connssl->backend->gtls.session,
+ (CURLE_AGAIN == result)? EAGAIN : EINVAL);
+ nread = -1;
+ }
+ return nread;
+}
+
+/* gtls_init()
+ *
+ * Global GnuTLS init, called from Curl_ssl_init(). This calls functions that
+ * are not thread-safe and thus this function itself is not thread-safe and
+ * must only be called from within curl_global_init() to keep the thread
+ * situation under control!
+ */
+static int gtls_init(void)
+{
+ int ret = 1;
+ if(!gtls_inited) {
+ ret = gnutls_global_init()?0:1;
+#ifdef GTLSDEBUG
+ gnutls_global_set_log_function(tls_log_func);
+ gnutls_global_set_log_level(2);
+#endif
+ gtls_inited = TRUE;
+ }
+ return ret;
+}
+
+static void gtls_cleanup(void)
+{
+ if(gtls_inited) {
+ gnutls_global_deinit();
+ gtls_inited = FALSE;
+ }
+}
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+static void showtime(struct Curl_easy *data,
+ const char *text,
+ time_t stamp)
+{
+ struct tm buffer;
+ const struct tm *tm = &buffer;
+ char str[96];
+ CURLcode result = Curl_gmtime(stamp, &buffer);
+ if(result)
+ return;
+
+ msnprintf(str,
+ sizeof(str),
+ " %s: %s, %02d %s %4d %02d:%02d:%02d GMT",
+ text,
+ Curl_wkday[tm->tm_wday?tm->tm_wday-1:6],
+ tm->tm_mday,
+ Curl_month[tm->tm_mon],
+ tm->tm_year + 1900,
+ tm->tm_hour,
+ tm->tm_min,
+ tm->tm_sec);
+ infof(data, "%s", str);
+}
+#endif
+
+static gnutls_datum_t load_file(const char *file)
+{
+ FILE *f;
+ gnutls_datum_t loaded_file = { NULL, 0 };
+ long filelen;
+ void *ptr;
+
+ f = fopen(file, "rb");
+ if(!f)
+ return loaded_file;
+ if(fseek(f, 0, SEEK_END) != 0
+ || (filelen = ftell(f)) < 0
+ || fseek(f, 0, SEEK_SET) != 0
+ || !(ptr = malloc((size_t)filelen)))
+ goto out;
+ if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) {
+ free(ptr);
+ goto out;
+ }
+
+ loaded_file.data = ptr;
+ loaded_file.size = (unsigned int)filelen;
+out:
+ fclose(f);
+ return loaded_file;
+}
+
+static void unload_file(gnutls_datum_t data)
+{
+ free(data.data);
+}
+
+
+/* this function does a SSL/TLS (re-)handshake */
+static CURLcode handshake(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool duringconnect,
+ bool nonblocking)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ gnutls_session_t session;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+
+ DEBUGASSERT(backend);
+ session = backend->gtls.session;
+
+ for(;;) {
+ timediff_t timeout_ms;
+ int rc;
+
+ /* check allowed time left */
+ timeout_ms = Curl_timeleft(data, NULL, duringconnect);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ /* if ssl is expecting something, check if it's available. */
+ if(connssl->connecting_state == ssl_connect_2_reading
+ || connssl->connecting_state == ssl_connect_2_writing) {
+ int what;
+ curl_socket_t writefd = ssl_connect_2_writing ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ nonblocking?0:
+ timeout_ms?timeout_ms:1000);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else if(0 == what) {
+ if(nonblocking)
+ return CURLE_OK;
+ else if(timeout_ms) {
+ /* timeout */
+ failf(data, "SSL connection timeout at %ld", (long)timeout_ms);
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+ /* socket is readable or writable */
+ }
+
+ rc = gnutls_handshake(session);
+
+ if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
+ connssl->connecting_state =
+ gnutls_record_get_direction(session)?
+ ssl_connect_2_writing:ssl_connect_2_reading;
+ continue;
+ }
+ else if((rc < 0) && !gnutls_error_is_fatal(rc)) {
+ const char *strerr = NULL;
+
+ if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) {
+ int alert = gnutls_alert_get(session);
+ strerr = gnutls_alert_get_name(alert);
+ }
+
+ if(!strerr)
+ strerr = gnutls_strerror(rc);
+
+ infof(data, "gnutls_handshake() warning: %s", strerr);
+ continue;
+ }
+ else if(rc < 0) {
+ const char *strerr = NULL;
+
+ if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) {
+ int alert = gnutls_alert_get(session);
+ strerr = gnutls_alert_get_name(alert);
+ }
+
+ if(!strerr)
+ strerr = gnutls_strerror(rc);
+
+ failf(data, "gnutls_handshake() failed: %s", strerr);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ /* Reset our connect state machine */
+ connssl->connecting_state = ssl_connect_1;
+ return CURLE_OK;
+ }
+}
+
+static gnutls_x509_crt_fmt_t do_file_type(const char *type)
+{
+ if(!type || !type[0])
+ return GNUTLS_X509_FMT_PEM;
+ if(strcasecompare(type, "PEM"))
+ return GNUTLS_X509_FMT_PEM;
+ if(strcasecompare(type, "DER"))
+ return GNUTLS_X509_FMT_DER;
+ return GNUTLS_X509_FMT_PEM; /* default to PEM */
+}
+
+#define GNUTLS_CIPHERS "NORMAL:-ARCFOUR-128:-CTYPE-ALL:+CTYPE-X509"
+/* If GnuTLS was compiled without support for SRP it will error out if SRP is
+ requested in the priority string, so treat it specially
+ */
+#define GNUTLS_SRP "+SRP"
+
+static CURLcode
+set_ssl_version_min_max(struct Curl_easy *data,
+ struct ssl_primary_config *conn_config,
+ const char **prioritylist,
+ const char *tls13support)
+{
+ long ssl_version = conn_config->version;
+ long ssl_version_max = conn_config->version_max;
+
+ if((ssl_version == CURL_SSLVERSION_DEFAULT) ||
+ (ssl_version == CURL_SSLVERSION_TLSv1))
+ ssl_version = CURL_SSLVERSION_TLSv1_0;
+ if(ssl_version_max == CURL_SSLVERSION_MAX_NONE)
+ ssl_version_max = CURL_SSLVERSION_MAX_DEFAULT;
+ if(!tls13support) {
+ /* If the running GnuTLS doesn't support TLS 1.3, we must not specify a
+ prioritylist involving that since it will make GnuTLS return an en
+ error back at us */
+ if((ssl_version_max == CURL_SSLVERSION_MAX_TLSv1_3) ||
+ (ssl_version_max == CURL_SSLVERSION_MAX_DEFAULT)) {
+ ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2;
+ }
+ }
+ else if(ssl_version_max == CURL_SSLVERSION_MAX_DEFAULT) {
+ ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3;
+ }
+
+ switch(ssl_version | ssl_version_max) {
+ case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_0:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.0";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_1:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.1:+VERS-TLS1.0";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_2:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_1:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.1";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_2:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.2:+VERS-TLS1.1";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.2";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_3 | CURL_SSLVERSION_MAX_TLSv1_3:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.3";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_3:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_3:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.3:+VERS-TLS1.2:+VERS-TLS1.1";
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_3:
+ *prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
+ "+VERS-TLS1.3:+VERS-TLS1.2";
+ return CURLE_OK;
+ }
+
+ failf(data, "GnuTLS: cannot set ssl protocol");
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+CURLcode gtls_client_init(struct Curl_easy *data,
+ struct ssl_primary_config *config,
+ struct ssl_config_data *ssl_config,
+ const char *hostname,
+ struct gtls_instance *gtls,
+ long *pverifyresult)
+{
+ unsigned int init_flags;
+ int rc;
+ bool sni = TRUE; /* default is SNI enabled */
+#ifdef ENABLE_IPV6
+ struct in6_addr addr;
+#else
+ struct in_addr addr;
+#endif
+ const char *prioritylist;
+ const char *err = NULL;
+ const char *tls13support;
+ CURLcode result;
+
+ if(!gtls_inited)
+ gtls_init();
+
+ *pverifyresult = 0;
+
+ if(config->version == CURL_SSLVERSION_SSLv2) {
+ failf(data, "GnuTLS does not support SSLv2");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else if(config->version == CURL_SSLVERSION_SSLv3)
+ sni = FALSE; /* SSLv3 has no SNI */
+
+ /* allocate a cred struct */
+ rc = gnutls_certificate_allocate_credentials(>ls->cred);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+#ifdef USE_GNUTLS_SRP
+ if((config->authtype == CURL_TLSAUTH_SRP) &&
+ Curl_auth_allowed_to_host(data)) {
+ infof(data, "Using TLS-SRP username: %s", config->username);
+
+ rc = gnutls_srp_allocate_client_credentials(
+ >ls->srp_client_cred);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "gnutls_srp_allocate_client_cred() failed: %s",
+ gnutls_strerror(rc));
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred,
+ config->username,
+ config->password);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "gnutls_srp_set_client_cred() failed: %s",
+ gnutls_strerror(rc));
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+ }
+#endif
+
+ if(config->CAfile) {
+ /* set the trusted CA cert bundle file */
+ gnutls_certificate_set_verify_flags(gtls->cred,
+ GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
+
+ rc = gnutls_certificate_set_x509_trust_file(gtls->cred,
+ config->CAfile,
+ GNUTLS_X509_FMT_PEM);
+ if(rc < 0) {
+ infof(data, "error reading ca cert file %s (%s)",
+ config->CAfile, gnutls_strerror(rc));
+ if(config->verifypeer) {
+ *pverifyresult = rc;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+ else
+ infof(data, "found %d certificates in %s", rc, config->CAfile);
+ }
+
+ if(config->CApath) {
+ /* set the trusted CA cert directory */
+ rc = gnutls_certificate_set_x509_trust_dir(gtls->cred,
+ config->CApath,
+ GNUTLS_X509_FMT_PEM);
+ if(rc < 0) {
+ infof(data, "error reading ca cert file %s (%s)",
+ config->CApath, gnutls_strerror(rc));
+ if(config->verifypeer) {
+ *pverifyresult = rc;
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+ else
+ infof(data, "found %d certificates in %s", rc, config->CApath);
+ }
+
+#ifdef CURL_CA_FALLBACK
+ /* use system ca certificate store as fallback */
+ if(config->verifypeer && !(config->CAfile || config->CApath)) {
+ /* this ignores errors on purpose */
+ gnutls_certificate_set_x509_system_trust(gtls->cred);
+ }
+#endif
+
+ if(config->CRLfile) {
+ /* set the CRL list file */
+ rc = gnutls_certificate_set_x509_crl_file(gtls->cred,
+ config->CRLfile,
+ GNUTLS_X509_FMT_PEM);
+ if(rc < 0) {
+ failf(data, "error reading crl file %s (%s)",
+ config->CRLfile, gnutls_strerror(rc));
+ return CURLE_SSL_CRL_BADFILE;
+ }
+ else
+ infof(data, "found %d CRL in %s", rc, config->CRLfile);
+ }
+
+ /* Initialize TLS session as a client */
+ init_flags = GNUTLS_CLIENT;
+
+#if defined(GNUTLS_FORCE_CLIENT_CERT)
+ init_flags |= GNUTLS_FORCE_CLIENT_CERT;
+#endif
+
+#if defined(GNUTLS_NO_TICKETS)
+ /* Disable TLS session tickets */
+ init_flags |= GNUTLS_NO_TICKETS;
+#endif
+
+ rc = gnutls_init(>ls->session, init_flags);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "gnutls_init() failed: %d", rc);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) &&
+#ifdef ENABLE_IPV6
+ (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
+#endif
+ sni) {
+ size_t snilen;
+ char *snihost = Curl_ssl_snihost(data, hostname, &snilen);
+ if(!snihost || gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS,
+ snihost, snilen) < 0) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ /* Use default priorities */
+ rc = gnutls_set_default_priority(gtls->session);
+ if(rc != GNUTLS_E_SUCCESS)
+ return CURLE_SSL_CONNECT_ERROR;
+
+ /* "In GnuTLS 3.6.5, TLS 1.3 is enabled by default" */
+ tls13support = gnutls_check_version("3.6.5");
+
+ /* Ensure +SRP comes at the *end* of all relevant strings so that it can be
+ * removed if a run-time error indicates that SRP is not supported by this
+ * GnuTLS version */
+
+ if(config->version == CURL_SSLVERSION_SSLv2 ||
+ config->version == CURL_SSLVERSION_SSLv3) {
+ failf(data, "GnuTLS does not support SSLv2 or SSLv3");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if(config->version == CURL_SSLVERSION_TLSv1_3) {
+ if(!tls13support) {
+ failf(data, "This GnuTLS installation does not support TLS 1.3");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ /* At this point we know we have a supported TLS version, so set it */
+ result = set_ssl_version_min_max(data, config, &prioritylist, tls13support);
+ if(result)
+ return result;
+
+#ifdef USE_GNUTLS_SRP
+ /* Only add SRP to the cipher list if SRP is requested. Otherwise
+ * GnuTLS will disable TLS 1.3 support. */
+ if(config->authtype == CURL_TLSAUTH_SRP) {
+ size_t len = strlen(prioritylist);
+
+ char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1);
+ if(!prioritysrp)
+ return CURLE_OUT_OF_MEMORY;
+ strcpy(prioritysrp, prioritylist);
+ strcpy(prioritysrp + len, ":" GNUTLS_SRP);
+ rc = gnutls_priority_set_direct(gtls->session, prioritysrp, &err);
+ free(prioritysrp);
+
+ if((rc == GNUTLS_E_INVALID_REQUEST) && err) {
+ infof(data, "This GnuTLS does not support SRP");
+ }
+ }
+ else {
+#endif
+ infof(data, "GnuTLS ciphers: %s", prioritylist);
+ rc = gnutls_priority_set_direct(gtls->session, prioritylist, &err);
+#ifdef USE_GNUTLS_SRP
+ }
+#endif
+
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "Error %d setting GnuTLS cipher list starting with %s",
+ rc, err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if(config->clientcert) {
+ if(ssl_config->key_passwd) {
+ const unsigned int supported_key_encryption_algorithms =
+ GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR |
+ GNUTLS_PKCS_USE_PKCS12_RC2_40 | GNUTLS_PKCS_USE_PBES2_3DES |
+ GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 |
+ GNUTLS_PKCS_USE_PBES2_AES_256;
+ rc = gnutls_certificate_set_x509_key_file2(
+ gtls->cred,
+ config->clientcert,
+ ssl_config->key ? ssl_config->key : config->clientcert,
+ do_file_type(ssl_config->cert_type),
+ ssl_config->key_passwd,
+ supported_key_encryption_algorithms);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data,
+ "error reading X.509 potentially-encrypted key file: %s",
+ gnutls_strerror(rc));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ else {
+ if(gnutls_certificate_set_x509_key_file(
+ gtls->cred,
+ config->clientcert,
+ ssl_config->key ? ssl_config->key : config->clientcert,
+ do_file_type(ssl_config->cert_type) ) !=
+ GNUTLS_E_SUCCESS) {
+ failf(data, "error reading X.509 key or certificate file");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ }
+
+#ifdef USE_GNUTLS_SRP
+ /* put the credentials to the current session */
+ if(config->authtype == CURL_TLSAUTH_SRP) {
+ rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP,
+ gtls->srp_client_cred);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ else
+#endif
+ {
+ rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE,
+ gtls->cred);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ if(config->verifystatus) {
+ rc = gnutls_ocsp_status_request_enable_client(gtls->session,
+ NULL, 0, NULL);
+ if(rc != GNUTLS_E_SUCCESS) {
+ failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ return CURLE_OK;
+}
+
+static CURLcode
+gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ long * const pverifyresult = &ssl_config->certverifyresult;
+ CURLcode result;
+
+ DEBUGASSERT(backend);
+
+ if(connssl->state == ssl_connection_complete)
+ /* to make us tolerant against being called more than once for the
+ same connection */
+ return CURLE_OK;
+
+ result = gtls_client_init(data, conn_config, ssl_config,
+ connssl->hostname,
+ &backend->gtls, pverifyresult);
+ if(result)
+ return result;
+
+ if(cf->conn->bits.tls_enable_alpn) {
+ int cur = 0;
+ gnutls_datum_t protocols[2];
+
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2
+#ifndef CURL_DISABLE_PROXY
+ && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
+#endif
+ ) {
+ protocols[cur].data = (unsigned char *)ALPN_H2;
+ protocols[cur].size = ALPN_H2_LENGTH;
+ cur++;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+ }
+#endif
+
+ protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1;
+ protocols[cur].size = ALPN_HTTP_1_1_LENGTH;
+ cur++;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+
+ if(gnutls_alpn_set_protocols(backend->gtls.session, protocols, cur, 0)) {
+ failf(data, "failed setting ALPN");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ /* This might be a reconnect, so we check for a session ID in the cache
+ to speed up things */
+ if(conn_config->sessionid) {
+ void *ssl_sessionid;
+ size_t ssl_idsize;
+
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, &ssl_idsize)) {
+ /* we got a session id, use it! */
+ gnutls_session_set_data(backend->gtls.session,
+ ssl_sessionid, ssl_idsize);
+
+ /* Informational message */
+ infof(data, "SSL re-using session ID");
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+ /* register callback functions and handle to send and receive data. */
+ gnutls_transport_set_ptr(backend->gtls.session, cf);
+ gnutls_transport_set_push_function(backend->gtls.session, gtls_push);
+ gnutls_transport_set_pull_function(backend->gtls.session, gtls_pull);
+
+ return CURLE_OK;
+}
+
+static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data,
+ gnutls_x509_crt_t cert,
+ const char *pinnedpubkey)
+{
+ /* Scratch */
+ size_t len1 = 0, len2 = 0;
+ unsigned char *buff1 = NULL;
+
+ gnutls_pubkey_t key = NULL;
+
+ /* Result is returned to caller */
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+ /* if a path wasn't specified, don't pin */
+ if(!pinnedpubkey)
+ return CURLE_OK;
+
+ if(!cert)
+ return result;
+
+ do {
+ int ret;
+
+ /* Begin Gyrations to get the public key */
+ gnutls_pubkey_init(&key);
+
+ ret = gnutls_pubkey_import_x509(key, cert, 0);
+ if(ret < 0)
+ break; /* failed */
+
+ ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, NULL, &len1);
+ if(ret != GNUTLS_E_SHORT_MEMORY_BUFFER || len1 == 0)
+ break; /* failed */
+
+ buff1 = malloc(len1);
+ if(!buff1)
+ break; /* failed */
+
+ len2 = len1;
+
+ ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, buff1, &len2);
+ if(ret < 0 || len1 != len2)
+ break; /* failed */
+
+ /* End Gyrations */
+
+ /* The one good exit point */
+ result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1);
+ } while(0);
+
+ if(key)
+ gnutls_pubkey_deinit(key);
+
+ Curl_safefree(buff1);
+
+ return result;
+}
+
+CURLcode
+Curl_gtls_verifyserver(struct Curl_easy *data,
+ gnutls_session_t session,
+ struct ssl_primary_config *config,
+ struct ssl_config_data *ssl_config,
+ const char *hostname,
+ const char *dispname,
+ const char *pinned_key)
+{
+ unsigned int cert_list_size;
+ const gnutls_datum_t *chainp;
+ unsigned int verify_status = 0;
+ gnutls_x509_crt_t x509_cert, x509_issuer;
+ gnutls_datum_t issuerp;
+ gnutls_datum_t certfields;
+ char certname[65] = ""; /* limited to 64 chars by ASN.1 */
+ size_t size;
+ time_t certclock;
+ const char *ptr;
+ int rc;
+ CURLcode result = CURLE_OK;
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ unsigned int algo;
+ unsigned int bits;
+ gnutls_protocol_t version = gnutls_protocol_get_version(session);
+#endif
+ long * const certverifyresult = &ssl_config->certverifyresult;
+
+ /* the name of the cipher suite used, e.g. ECDHE_RSA_AES_256_GCM_SHA384. */
+ ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(session),
+ gnutls_cipher_get(session),
+ gnutls_mac_get(session));
+
+ infof(data, "SSL connection using %s / %s",
+ gnutls_protocol_get_name(version), ptr);
+
+ /* This function will return the peer's raw certificate (chain) as sent by
+ the peer. These certificates are in raw format (DER encoded for
+ X.509). In case of a X.509 then a certificate list may be present. The
+ first certificate in the list is the peer's certificate, following the
+ issuer's certificate, then the issuer's issuer etc. */
+
+ chainp = gnutls_certificate_get_peers(session, &cert_list_size);
+ if(!chainp) {
+ if(config->verifypeer ||
+ config->verifyhost ||
+ config->issuercert) {
+#ifdef USE_GNUTLS_SRP
+ if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP
+ && ssl_config->primary.username
+ && !config->verifypeer
+ && gnutls_cipher_get(session)) {
+ /* no peer cert, but auth is ok if we have SRP user and cipher and no
+ peer verify */
+ }
+ else {
+#endif
+ failf(data, "failed to get server cert");
+ *certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND;
+ return CURLE_PEER_FAILED_VERIFICATION;
+#ifdef USE_GNUTLS_SRP
+ }
+#endif
+ }
+ infof(data, " common name: WARNING couldn't obtain");
+ }
+
+ if(data->set.ssl.certinfo && chainp) {
+ unsigned int i;
+
+ result = Curl_ssl_init_certinfo(data, cert_list_size);
+ if(result)
+ return result;
+
+ for(i = 0; i < cert_list_size; i++) {
+ const char *beg = (const char *) chainp[i].data;
+ const char *end = beg + chainp[i].size;
+
+ result = Curl_extract_certinfo(data, i, beg, end);
+ if(result)
+ return result;
+ }
+ }
+
+ if(config->verifypeer) {
+ /* This function will try to verify the peer's certificate and return its
+ status (trusted, invalid etc.). The value of status should be one or
+ more of the gnutls_certificate_status_t enumerated elements bitwise
+ or'd. To avoid denial of service attacks some default upper limits
+ regarding the certificate key size and chain size are set. To override
+ them use gnutls_certificate_set_verify_limits(). */
+
+ rc = gnutls_certificate_verify_peers2(session, &verify_status);
+ if(rc < 0) {
+ failf(data, "server cert verify failed: %d", rc);
+ *certverifyresult = rc;
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ *certverifyresult = verify_status;
+
+ /* verify_status is a bitmask of gnutls_certificate_status bits */
+ if(verify_status & GNUTLS_CERT_INVALID) {
+ if(config->verifypeer) {
+ failf(data, "server certificate verification failed. CAfile: %s "
+ "CRLfile: %s", config->CAfile ? config->CAfile:
+ "none",
+ ssl_config->primary.CRLfile ?
+ ssl_config->primary.CRLfile : "none");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else
+ infof(data, " server certificate verification FAILED");
+ }
+ else
+ infof(data, " server certificate verification OK");
+ }
+ else
+ infof(data, " server certificate verification SKIPPED");
+
+ if(config->verifystatus) {
+ if(gnutls_ocsp_status_request_is_checked(session, 0) == 0) {
+ gnutls_datum_t status_request;
+ gnutls_ocsp_resp_t ocsp_resp;
+
+ gnutls_ocsp_cert_status_t status;
+ gnutls_x509_crl_reason_t reason;
+
+ rc = gnutls_ocsp_status_request_get(session, &status_request);
+
+ infof(data, " server certificate status verification FAILED");
+
+ if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
+ failf(data, "No OCSP response received");
+ return CURLE_SSL_INVALIDCERTSTATUS;
+ }
+
+ if(rc < 0) {
+ failf(data, "Invalid OCSP response received");
+ return CURLE_SSL_INVALIDCERTSTATUS;
+ }
+
+ gnutls_ocsp_resp_init(&ocsp_resp);
+
+ rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request);
+ if(rc < 0) {
+ failf(data, "Invalid OCSP response received");
+ return CURLE_SSL_INVALIDCERTSTATUS;
+ }
+
+ (void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL,
+ &status, NULL, NULL, NULL, &reason);
+
+ switch(status) {
+ case GNUTLS_OCSP_CERT_GOOD:
+ break;
+
+ case GNUTLS_OCSP_CERT_REVOKED: {
+ const char *crl_reason;
+
+ switch(reason) {
+ default:
+ case GNUTLS_X509_CRLREASON_UNSPECIFIED:
+ crl_reason = "unspecified reason";
+ break;
+
+ case GNUTLS_X509_CRLREASON_KEYCOMPROMISE:
+ crl_reason = "private key compromised";
+ break;
+
+ case GNUTLS_X509_CRLREASON_CACOMPROMISE:
+ crl_reason = "CA compromised";
+ break;
+
+ case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED:
+ crl_reason = "affiliation has changed";
+ break;
+
+ case GNUTLS_X509_CRLREASON_SUPERSEDED:
+ crl_reason = "certificate superseded";
+ break;
+
+ case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION:
+ crl_reason = "operation has ceased";
+ break;
+
+ case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD:
+ crl_reason = "certificate is on hold";
+ break;
+
+ case GNUTLS_X509_CRLREASON_REMOVEFROMCRL:
+ crl_reason = "will be removed from delta CRL";
+ break;
+
+ case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN:
+ crl_reason = "privilege withdrawn";
+ break;
+
+ case GNUTLS_X509_CRLREASON_AACOMPROMISE:
+ crl_reason = "AA compromised";
+ break;
+ }
+
+ failf(data, "Server certificate was revoked: %s", crl_reason);
+ break;
+ }
+
+ default:
+ case GNUTLS_OCSP_CERT_UNKNOWN:
+ failf(data, "Server certificate status is unknown");
+ break;
+ }
+
+ gnutls_ocsp_resp_deinit(ocsp_resp);
+
+ return CURLE_SSL_INVALIDCERTSTATUS;
+ }
+ else
+ infof(data, " server certificate status verification OK");
+ }
+ else
+ infof(data, " server certificate status verification SKIPPED");
+
+ /* initialize an X.509 certificate structure. */
+ gnutls_x509_crt_init(&x509_cert);
+
+ if(chainp)
+ /* convert the given DER or PEM encoded Certificate to the native
+ gnutls_x509_crt_t format */
+ gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER);
+
+ if(config->issuercert) {
+ gnutls_x509_crt_init(&x509_issuer);
+ issuerp = load_file(config->issuercert);
+ gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM);
+ rc = gnutls_x509_crt_check_issuer(x509_cert, x509_issuer);
+ gnutls_x509_crt_deinit(x509_issuer);
+ unload_file(issuerp);
+ if(rc <= 0) {
+ failf(data, "server certificate issuer check failed (IssuerCert: %s)",
+ config->issuercert?config->issuercert:"none");
+ gnutls_x509_crt_deinit(x509_cert);
+ return CURLE_SSL_ISSUER_ERROR;
+ }
+ infof(data, " server certificate issuer check OK (Issuer Cert: %s)",
+ config->issuercert?config->issuercert:"none");
+ }
+
+ size = sizeof(certname);
+ rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME,
+ 0, /* the first and only one */
+ FALSE,
+ certname,
+ &size);
+ if(rc) {
+ infof(data, "error fetching CN from cert:%s",
+ gnutls_strerror(rc));
+ }
+
+ /* This function will check if the given certificate's subject matches the
+ given hostname. This is a basic implementation of the matching described
+ in RFC2818 (HTTPS), which takes into account wildcards, and the subject
+ alternative name PKIX extension. Returns non zero on success, and zero on
+ failure. */
+ rc = gnutls_x509_crt_check_hostname(x509_cert, hostname);
+#if GNUTLS_VERSION_NUMBER < 0x030306
+ /* Before 3.3.6, gnutls_x509_crt_check_hostname() didn't check IP
+ addresses. */
+ if(!rc) {
+#ifdef ENABLE_IPV6
+ #define use_addr in6_addr
+#else
+ #define use_addr in_addr
+#endif
+ unsigned char addrbuf[sizeof(struct use_addr)];
+ size_t addrlen = 0;
+
+ if(Curl_inet_pton(AF_INET, hostname, addrbuf) > 0)
+ addrlen = 4;
+#ifdef ENABLE_IPV6
+ else if(Curl_inet_pton(AF_INET6, hostname, addrbuf) > 0)
+ addrlen = 16;
+#endif
+
+ if(addrlen) {
+ unsigned char certaddr[sizeof(struct use_addr)];
+ int i;
+
+ for(i = 0; ; i++) {
+ size_t certaddrlen = sizeof(certaddr);
+ int ret = gnutls_x509_crt_get_subject_alt_name(x509_cert, i, certaddr,
+ &certaddrlen, NULL);
+ /* If this happens, it wasn't an IP address. */
+ if(ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
+ continue;
+ if(ret < 0)
+ break;
+ if(ret != GNUTLS_SAN_IPADDRESS)
+ continue;
+ if(certaddrlen == addrlen && !memcmp(addrbuf, certaddr, addrlen)) {
+ rc = 1;
+ break;
+ }
+ }
+ }
+ }
+#endif
+ if(!rc) {
+ if(config->verifyhost) {
+ failf(data, "SSL: certificate subject name (%s) does not match "
+ "target host name '%s'", certname, dispname);
+ gnutls_x509_crt_deinit(x509_cert);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else
+ infof(data, " common name: %s (does not match '%s')",
+ certname, dispname);
+ }
+ else
+ infof(data, " common name: %s (matched)", certname);
+
+ /* Check for time-based validity */
+ certclock = gnutls_x509_crt_get_expiration_time(x509_cert);
+
+ if(certclock == (time_t)-1) {
+ if(config->verifypeer) {
+ failf(data, "server cert expiration date verify failed");
+ *certverifyresult = GNUTLS_CERT_EXPIRED;
+ gnutls_x509_crt_deinit(x509_cert);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else
+ infof(data, " server certificate expiration date verify FAILED");
+ }
+ else {
+ if(certclock < time(NULL)) {
+ if(config->verifypeer) {
+ failf(data, "server certificate expiration date has passed.");
+ *certverifyresult = GNUTLS_CERT_EXPIRED;
+ gnutls_x509_crt_deinit(x509_cert);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else
+ infof(data, " server certificate expiration date FAILED");
+ }
+ else
+ infof(data, " server certificate expiration date OK");
+ }
+
+ certclock = gnutls_x509_crt_get_activation_time(x509_cert);
+
+ if(certclock == (time_t)-1) {
+ if(config->verifypeer) {
+ failf(data, "server cert activation date verify failed");
+ *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED;
+ gnutls_x509_crt_deinit(x509_cert);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else
+ infof(data, " server certificate activation date verify FAILED");
+ }
+ else {
+ if(certclock > time(NULL)) {
+ if(config->verifypeer) {
+ failf(data, "server certificate not activated yet.");
+ *certverifyresult = GNUTLS_CERT_NOT_ACTIVATED;
+ gnutls_x509_crt_deinit(x509_cert);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else
+ infof(data, " server certificate activation date FAILED");
+ }
+ else
+ infof(data, " server certificate activation date OK");
+ }
+
+ if(pinned_key) {
+ result = pkp_pin_peer_pubkey(data, x509_cert, pinned_key);
+ if(result != CURLE_OK) {
+ failf(data, "SSL: public key does not match pinned public key");
+ gnutls_x509_crt_deinit(x509_cert);
+ return result;
+ }
+ }
+
+ /* Show:
+
+ - subject
+ - start date
+ - expire date
+ - common name
+ - issuer
+
+ */
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ /* public key algorithm's parameters */
+ algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits);
+ infof(data, " certificate public key: %s",
+ gnutls_pk_algorithm_get_name(algo));
+
+ /* version of the X.509 certificate. */
+ infof(data, " certificate version: #%d",
+ gnutls_x509_crt_get_version(x509_cert));
+
+
+ rc = gnutls_x509_crt_get_dn2(x509_cert, &certfields);
+ if(rc)
+ infof(data, "Failed to get certificate name");
+ else {
+ infof(data, " subject: %s", certfields.data);
+
+ certclock = gnutls_x509_crt_get_activation_time(x509_cert);
+ showtime(data, "start date", certclock);
+
+ certclock = gnutls_x509_crt_get_expiration_time(x509_cert);
+ showtime(data, "expire date", certclock);
+
+ gnutls_free(certfields.data);
+ }
+
+ rc = gnutls_x509_crt_get_issuer_dn2(x509_cert, &certfields);
+ if(rc)
+ infof(data, "Failed to get certificate issuer");
+ else {
+ infof(data, " issuer: %s", certfields.data);
+
+ gnutls_free(certfields.data);
+ }
+#endif
+
+ gnutls_x509_crt_deinit(x509_cert);
+
+ return result;
+}
+
+static CURLcode gtls_verifyserver(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ gnutls_session_t session)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ const char *pinned_key = Curl_ssl_cf_is_proxy(cf)?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]:
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+ CURLcode result;
+
+ result = Curl_gtls_verifyserver(data, session, conn_config, ssl_config,
+ connssl->hostname, connssl->dispname,
+ pinned_key);
+ if(result)
+ goto out;
+
+ if(cf->conn->bits.tls_enable_alpn) {
+ gnutls_datum_t proto;
+ int rc;
+
+ rc = gnutls_alpn_get_selected_protocol(session, &proto);
+ if(rc == 0) {
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto.size,
+ proto.data);
+
+#ifdef USE_HTTP2
+ if(proto.size == ALPN_H2_LENGTH &&
+ !memcmp(ALPN_H2, proto.data,
+ ALPN_H2_LENGTH)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+ }
+ else
+#endif
+ if(proto.size == ALPN_HTTP_1_1_LENGTH &&
+ !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+ }
+ }
+ else
+ infof(data, VTLS_INFOF_NO_ALPN);
+
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+ }
+
+ if(ssl_config->primary.sessionid) {
+ /* we always unconditionally get the session id here, as even if we
+ already got it from the cache and asked to use it in the connection, it
+ might've been rejected and then a new one is in use now and we need to
+ detect that. */
+ void *connect_sessionid;
+ size_t connect_idsize = 0;
+
+ /* get the session ID data size */
+ gnutls_session_get_data(session, NULL, &connect_idsize);
+ connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
+
+ if(connect_sessionid) {
+ bool incache;
+ bool added = FALSE;
+ void *ssl_sessionid;
+
+ /* extract session ID to the allocated buffer */
+ gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
+
+ Curl_ssl_sessionid_lock(data);
+ incache = !(Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL));
+ if(incache) {
+ /* there was one before in the cache, so instead of risking that the
+ previous one was rejected, we just kill that and store the new */
+ Curl_ssl_delsessionid(data, ssl_sessionid);
+ }
+
+ /* store this session id */
+ result = Curl_ssl_addsessionid(cf, data, connect_sessionid,
+ connect_idsize, &added);
+ Curl_ssl_sessionid_unlock(data);
+ if(!added)
+ free(connect_sessionid);
+ if(result) {
+ result = CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else
+ result = CURLE_OUT_OF_MEMORY;
+ }
+
+out:
+ return result;
+}
+
+/*
+ * This function is called after the TCP connect has completed. Setup the TLS
+ * layer and do all necessary magic.
+ */
+/* We use connssl->connecting_state to keep track of the connection status;
+ there are three states: 'ssl_connect_1' (not started yet or complete),
+ 'ssl_connect_2_reading' (waiting for data from server), and
+ 'ssl_connect_2_writing' (waiting to be able to write).
+ */
+static CURLcode
+gtls_connect_common(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool nonblocking,
+ bool *done)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ int rc;
+ CURLcode result = CURLE_OK;
+
+ /* Initiate the connection, if not already done */
+ if(ssl_connect_1 == connssl->connecting_state) {
+ rc = gtls_connect_step1(cf, data);
+ if(rc) {
+ result = rc;
+ goto out;
+ }
+ }
+
+ rc = handshake(cf, data, TRUE, nonblocking);
+ if(rc) {
+ /* handshake() sets its own error message with failf() */
+ result = rc;
+ goto out;
+ }
+
+ /* Finish connecting once the handshake is done */
+ if(ssl_connect_1 == connssl->connecting_state) {
+ struct ssl_backend_data *backend = connssl->backend;
+ gnutls_session_t session;
+ DEBUGASSERT(backend);
+ session = backend->gtls.session;
+ rc = gtls_verifyserver(cf, data, session);
+ if(rc) {
+ result = rc;
+ goto out;
+ }
+ connssl->state = ssl_connection_complete;
+ }
+
+out:
+ *done = ssl_connect_1 == connssl->connecting_state;
+
+ return result;
+}
+
+static CURLcode gtls_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return gtls_connect_common(cf, data, TRUE, done);
+}
+
+static CURLcode gtls_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode result;
+ bool done = FALSE;
+
+ result = gtls_connect_common(cf, data, FALSE, &done);
+ if(result)
+ return result;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+static bool gtls_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx = cf->ctx;
+
+ (void)data;
+ DEBUGASSERT(ctx && ctx->backend);
+ if(ctx->backend->gtls.session &&
+ 0 != gnutls_record_check_pending(ctx->backend->gtls.session))
+ return TRUE;
+ return FALSE;
+}
+
+static ssize_t gtls_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ ssize_t rc;
+
+ (void)data;
+ DEBUGASSERT(backend);
+ rc = gnutls_record_send(backend->gtls.session, mem, len);
+
+ if(rc < 0) {
+ *curlcode = (rc == GNUTLS_E_AGAIN)
+ ? CURLE_AGAIN
+ : CURLE_SEND_ERROR;
+
+ rc = -1;
+ }
+
+ return rc;
+}
+
+static void gtls_close(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ (void) data;
+ DEBUGASSERT(backend);
+
+ if(backend->gtls.session) {
+ char buf[32];
+ /* Maybe the server has already sent a close notify alert.
+ Read it to avoid an RST on the TCP connection. */
+ (void)gnutls_record_recv(backend->gtls.session, buf, sizeof(buf));
+ gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR);
+ gnutls_deinit(backend->gtls.session);
+ backend->gtls.session = NULL;
+ }
+ if(backend->gtls.cred) {
+ gnutls_certificate_free_credentials(backend->gtls.cred);
+ backend->gtls.cred = NULL;
+ }
+#ifdef USE_GNUTLS_SRP
+ if(backend->gtls.srp_client_cred) {
+ gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred);
+ backend->gtls.srp_client_cred = NULL;
+ }
+#endif
+}
+
+/*
+ * This function is called to shut down the SSL layer but keep the
+ * socket open (CCC - Clear Command Channel)
+ */
+static int gtls_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ struct ssl_backend_data *backend = connssl->backend;
+ int retval = 0;
+
+ DEBUGASSERT(backend);
+
+#ifndef CURL_DISABLE_FTP
+ /* This has only been tested on the proftpd server, and the mod_tls code
+ sends a close notify alert without waiting for a close notify alert in
+ response. Thus we wait for a close notify alert from the server, but
+ we do not send one. Let's hope other servers do the same... */
+
+ if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)
+ gnutls_bye(backend->gtls.session, GNUTLS_SHUT_WR);
+#endif
+
+ if(backend->gtls.session) {
+ ssize_t result;
+ bool done = FALSE;
+ char buf[120];
+
+ while(!done) {
+ int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+ SSL_SHUTDOWN_TIMEOUT);
+ if(what > 0) {
+ /* Something to read, let's do it and hope that it is the close
+ notify alert from the server */
+ result = gnutls_record_recv(backend->gtls.session,
+ buf, sizeof(buf));
+ switch(result) {
+ case 0:
+ /* This is the expected response. There was no data but only
+ the close notify alert */
+ done = TRUE;
+ break;
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_INTERRUPTED:
+ infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED");
+ break;
+ default:
+ retval = -1;
+ done = TRUE;
+ break;
+ }
+ }
+ else if(0 == what) {
+ /* timeout */
+ failf(data, "SSL shutdown timeout");
+ done = TRUE;
+ }
+ else {
+ /* anything that gets here is fatally bad */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ retval = -1;
+ done = TRUE;
+ }
+ }
+ gnutls_deinit(backend->gtls.session);
+ }
+ gnutls_certificate_free_credentials(backend->gtls.cred);
+
+#ifdef USE_GNUTLS_SRP
+ if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP
+ && ssl_config->primary.username != NULL)
+ gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred);
+#endif
+
+ backend->gtls.cred = NULL;
+ backend->gtls.session = NULL;
+
+ return retval;
+}
+
+static ssize_t gtls_recv(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *buf,
+ size_t buffersize,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ ssize_t ret;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ ret = gnutls_record_recv(backend->gtls.session, buf, buffersize);
+ if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) {
+ *curlcode = CURLE_AGAIN;
+ ret = -1;
+ goto out;
+ }
+
+ if(ret == GNUTLS_E_REHANDSHAKE) {
+ /* BLOCKING call, this is bad but a work-around for now. Fixing this "the
+ proper way" takes a whole lot of work. */
+ CURLcode result = handshake(cf, data, FALSE, FALSE);
+ if(result)
+ /* handshake() writes error message on its own */
+ *curlcode = result;
+ else
+ *curlcode = CURLE_AGAIN; /* then return as if this was a wouldblock */
+ ret = -1;
+ goto out;
+ }
+
+ if(ret < 0) {
+ failf(data, "GnuTLS recv error (%d): %s",
+
+ (int)ret, gnutls_strerror((int)ret));
+ *curlcode = CURLE_RECV_ERROR;
+ ret = -1;
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static void gtls_session_free(void *ptr)
+{
+ free(ptr);
+}
+
+static size_t gtls_version(char *buffer, size_t size)
+{
+ return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL));
+}
+
+/* data might be NULL! */
+static CURLcode gtls_random(struct Curl_easy *data,
+ unsigned char *entropy, size_t length)
+{
+ int rc;
+ (void)data;
+ rc = gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length);
+ return rc?CURLE_FAILED_INIT:CURLE_OK;
+}
+
+static CURLcode gtls_sha256sum(const unsigned char *tmp, /* input */
+ size_t tmplen,
+ unsigned char *sha256sum, /* output */
+ size_t sha256len)
+{
+ struct sha256_ctx SHA256pw;
+ sha256_init(&SHA256pw);
+ sha256_update(&SHA256pw, (unsigned int)tmplen, tmp);
+ sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum);
+ return CURLE_OK;
+}
+
+static bool gtls_cert_status_request(void)
+{
+ return TRUE;
+}
+
+static void *gtls_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ (void)info;
+ DEBUGASSERT(backend);
+ return backend->gtls.session;
+}
+
+const struct Curl_ssl Curl_ssl_gnutls = {
+ { CURLSSLBACKEND_GNUTLS, "gnutls" }, /* info */
+
+ SSLSUPP_CA_PATH |
+ SSLSUPP_CERTINFO |
+ SSLSUPP_PINNEDPUBKEY |
+ SSLSUPP_HTTPS_PROXY,
+
+ sizeof(struct ssl_backend_data),
+
+ gtls_init, /* init */
+ gtls_cleanup, /* cleanup */
+ gtls_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ gtls_shutdown, /* shutdown */
+ gtls_data_pending, /* data_pending */
+ gtls_random, /* random */
+ gtls_cert_status_request, /* cert_status_request */
+ gtls_connect, /* connect */
+ gtls_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ gtls_get_internals, /* get_internals */
+ gtls_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ gtls_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ gtls_sha256sum, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ gtls_recv, /* recv decrypted data */
+ gtls_send, /* send data to encrypt */
+};
+
+#endif /* USE_GNUTLS */
diff --git a/libs/libcurl/src/vtls/gtls.h b/libs/libcurl/src/vtls/gtls.h index abade73f80..06e8414fdc 100644 --- a/libs/libcurl/src/vtls/gtls.h +++ b/libs/libcurl/src/vtls/gtls.h @@ -1,40 +1,75 @@ -#ifndef HEADER_CURL_GTLS_H -#define HEADER_CURL_GTLS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef USE_GNUTLS - -#include "urldata.h" -#include <gnutls/gnutls.h> -CURLcode -Curl_gtls_verifyserver(struct Curl_easy *data, struct connectdata *conn, - gnutls_session_t session, - int sockindex); -extern const struct Curl_ssl Curl_ssl_gnutls; - -#endif /* USE_GNUTLS */ -#endif /* HEADER_CURL_GTLS_H */ +#ifndef HEADER_CURL_GTLS_H
+#define HEADER_CURL_GTLS_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include <curl/curl.h>
+
+#ifdef USE_GNUTLS
+
+#include <gnutls/gnutls.h>
+
+#ifdef HAVE_GNUTLS_SRP
+/* the function exists */
+#ifdef USE_TLS_SRP
+/* the functionality is not disabled */
+#define USE_GNUTLS_SRP
+#endif
+#endif
+
+struct Curl_easy;
+struct Curl_cfilter;
+struct ssl_primary_config;
+struct ssl_config_data;
+
+struct gtls_instance {
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t cred;
+#ifdef USE_GNUTLS_SRP
+ gnutls_srp_client_credentials_t srp_client_cred;
+#endif
+};
+
+CURLcode
+gtls_client_init(struct Curl_easy *data,
+ struct ssl_primary_config *config,
+ struct ssl_config_data *ssl_config,
+ const char *hostname,
+ struct gtls_instance *gtls,
+ long *pverifyresult);
+
+CURLcode
+Curl_gtls_verifyserver(struct Curl_easy *data,
+ gnutls_session_t session,
+ struct ssl_primary_config *config,
+ struct ssl_config_data *ssl_config,
+ const char *hostname,
+ const char *dispname,
+ const char *pinned_key);
+
+extern const struct Curl_ssl Curl_ssl_gnutls;
+
+#endif /* USE_GNUTLS */
+#endif /* HEADER_CURL_GTLS_H */
diff --git a/libs/libcurl/src/vtls/hostcheck.c b/libs/libcurl/src/vtls/hostcheck.c index 2a648f20a9..6173ea2b5b 100644 --- a/libs/libcurl/src/vtls/hostcheck.c +++ b/libs/libcurl/src/vtls/hostcheck.c @@ -1,142 +1,142 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_OPENSSL) \ - || defined(USE_GSKIT) \ - || defined(USE_SCHANNEL) -/* these backends use functions from this file */ - -#ifdef HAVE_NETINET_IN_H -#include <netinet/in.h> -#endif -#ifdef HAVE_NETINET_IN6_H -#include <netinet/in6.h> -#endif -#include "curl_memrchr.h" - -#include "hostcheck.h" -#include "strcase.h" -#include "hostip.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* check the two input strings with given length, but do not - assume they end in nul-bytes */ -static bool pmatch(const char *hostname, size_t hostlen, - const char *pattern, size_t patternlen) -{ - if(hostlen != patternlen) - return FALSE; - return strncasecompare(hostname, pattern, hostlen); -} - -/* - * Match a hostname against a wildcard pattern. - * E.g. - * "foo.host.com" matches "*.host.com". - * - * We use the matching rule described in RFC6125, section 6.4.3. - * https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3 - * - * In addition: ignore trailing dots in the host names and wildcards, so that - * the names are used normalized. This is what the browsers do. - * - * Do not allow wildcard matching on IP numbers. There are apparently - * certificates being used with an IP address in the CN field, thus making no - * apparent distinction between a name and an IP. We need to detect the use of - * an IP address and not wildcard match on such names. - * - * Return TRUE on a match. FALSE if not. - */ - -static bool hostmatch(const char *hostname, - size_t hostlen, - const char *pattern, - size_t patternlen) -{ - const char *pattern_label_end, *wildcard, *hostname_label_end; - size_t prefixlen, suffixlen; - - /* normalize pattern and hostname by stripping off trailing dots */ - DEBUGASSERT(patternlen); - if(hostname[hostlen-1]=='.') - hostlen--; - if(pattern[patternlen-1]=='.') - patternlen--; - - wildcard = memchr(pattern, '*', patternlen); - if(!wildcard) - return pmatch(hostname, hostlen, pattern, patternlen); - - /* detect IP address as hostname and fail the match if so */ - if(Curl_host_is_ipnum(hostname)) - return FALSE; - - /* We require at least 2 dots in the pattern to avoid too wide wildcard - match. */ - pattern_label_end = memchr(pattern, '.', patternlen); - if(!pattern_label_end || - (memrchr(pattern, '.', patternlen) == pattern_label_end) || - strncasecompare(pattern, "xn--", 4)) - return pmatch(hostname, hostlen, pattern, patternlen); - - hostname_label_end = memchr(hostname, '.', hostlen); - if(!hostname_label_end) - return FALSE; - else { - size_t skiphost = hostname_label_end - hostname; - size_t skiplen = pattern_label_end - pattern; - if(!pmatch(hostname_label_end, hostlen - skiphost, - pattern_label_end, patternlen - skiplen)) - return FALSE; - } - /* The wildcard must match at least one character, so the left-most - label of the hostname is at least as large as the left-most label - of the pattern. */ - if(hostname_label_end - hostname < pattern_label_end - pattern) - return FALSE; - - prefixlen = wildcard - pattern; - suffixlen = pattern_label_end - (wildcard + 1); - return strncasecompare(pattern, hostname, prefixlen) && - strncasecompare(wildcard + 1, hostname_label_end - suffixlen, - suffixlen) ? TRUE : FALSE; -} - -/* - * Curl_cert_hostcheck() returns TRUE if a match and FALSE if not. - */ -bool Curl_cert_hostcheck(const char *match, size_t matchlen, - const char *hostname, size_t hostlen) -{ - if(match && *match && hostname && *hostname) - return hostmatch(hostname, hostlen, match, matchlen); - return FALSE; -} - -#endif /* OPENSSL, GSKIT or schannel+wince */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if defined(USE_OPENSSL) \
+ || defined(USE_GSKIT) \
+ || defined(USE_SCHANNEL)
+/* these backends use functions from this file */
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+#ifdef HAVE_NETINET_IN6_H
+#include <netinet/in6.h>
+#endif
+#include "curl_memrchr.h"
+
+#include "hostcheck.h"
+#include "strcase.h"
+#include "hostip.h"
+
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+/* check the two input strings with given length, but do not
+ assume they end in nul-bytes */
+static bool pmatch(const char *hostname, size_t hostlen,
+ const char *pattern, size_t patternlen)
+{
+ if(hostlen != patternlen)
+ return FALSE;
+ return strncasecompare(hostname, pattern, hostlen);
+}
+
+/*
+ * Match a hostname against a wildcard pattern.
+ * E.g.
+ * "foo.host.com" matches "*.host.com".
+ *
+ * We use the matching rule described in RFC6125, section 6.4.3.
+ * https://datatracker.ietf.org/doc/html/rfc6125#section-6.4.3
+ *
+ * In addition: ignore trailing dots in the host names and wildcards, so that
+ * the names are used normalized. This is what the browsers do.
+ *
+ * Do not allow wildcard matching on IP numbers. There are apparently
+ * certificates being used with an IP address in the CN field, thus making no
+ * apparent distinction between a name and an IP. We need to detect the use of
+ * an IP address and not wildcard match on such names.
+ *
+ * Return TRUE on a match. FALSE if not.
+ */
+
+static bool hostmatch(const char *hostname,
+ size_t hostlen,
+ const char *pattern,
+ size_t patternlen)
+{
+ const char *pattern_label_end, *wildcard, *hostname_label_end;
+ size_t prefixlen, suffixlen;
+
+ /* normalize pattern and hostname by stripping off trailing dots */
+ DEBUGASSERT(patternlen);
+ if(hostname[hostlen-1]=='.')
+ hostlen--;
+ if(pattern[patternlen-1]=='.')
+ patternlen--;
+
+ wildcard = memchr(pattern, '*', patternlen);
+ if(!wildcard)
+ return pmatch(hostname, hostlen, pattern, patternlen);
+
+ /* detect IP address as hostname and fail the match if so */
+ if(Curl_host_is_ipnum(hostname))
+ return FALSE;
+
+ /* We require at least 2 dots in the pattern to avoid too wide wildcard
+ match. */
+ pattern_label_end = memchr(pattern, '.', patternlen);
+ if(!pattern_label_end ||
+ (memrchr(pattern, '.', patternlen) == pattern_label_end) ||
+ strncasecompare(pattern, "xn--", 4))
+ return pmatch(hostname, hostlen, pattern, patternlen);
+
+ hostname_label_end = memchr(hostname, '.', hostlen);
+ if(!hostname_label_end)
+ return FALSE;
+ else {
+ size_t skiphost = hostname_label_end - hostname;
+ size_t skiplen = pattern_label_end - pattern;
+ if(!pmatch(hostname_label_end, hostlen - skiphost,
+ pattern_label_end, patternlen - skiplen))
+ return FALSE;
+ }
+ /* The wildcard must match at least one character, so the left-most
+ label of the hostname is at least as large as the left-most label
+ of the pattern. */
+ if(hostname_label_end - hostname < pattern_label_end - pattern)
+ return FALSE;
+
+ prefixlen = wildcard - pattern;
+ suffixlen = pattern_label_end - (wildcard + 1);
+ return strncasecompare(pattern, hostname, prefixlen) &&
+ strncasecompare(wildcard + 1, hostname_label_end - suffixlen,
+ suffixlen) ? TRUE : FALSE;
+}
+
+/*
+ * Curl_cert_hostcheck() returns TRUE if a match and FALSE if not.
+ */
+bool Curl_cert_hostcheck(const char *match, size_t matchlen,
+ const char *hostname, size_t hostlen)
+{
+ if(match && *match && hostname && *hostname)
+ return hostmatch(hostname, hostlen, match, matchlen);
+ return FALSE;
+}
+
+#endif /* OPENSSL, GSKIT or schannel+wince */
diff --git a/libs/libcurl/src/vtls/hostcheck.h b/libs/libcurl/src/vtls/hostcheck.h index d3c4eab56d..27149100fd 100644 --- a/libs/libcurl/src/vtls/hostcheck.h +++ b/libs/libcurl/src/vtls/hostcheck.h @@ -1,33 +1,33 @@ -#ifndef HEADER_CURL_HOSTCHECK_H -#define HEADER_CURL_HOSTCHECK_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include <curl/curl.h> - -/* returns TRUE if there's a match */ -bool Curl_cert_hostcheck(const char *match_pattern, size_t matchlen, - const char *hostname, size_t hostlen); - -#endif /* HEADER_CURL_HOSTCHECK_H */ +#ifndef HEADER_CURL_HOSTCHECK_H
+#define HEADER_CURL_HOSTCHECK_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include <curl/curl.h>
+
+/* returns TRUE if there's a match */
+bool Curl_cert_hostcheck(const char *match_pattern, size_t matchlen,
+ const char *hostname, size_t hostlen);
+
+#endif /* HEADER_CURL_HOSTCHECK_H */
diff --git a/libs/libcurl/src/vtls/keylog.c b/libs/libcurl/src/vtls/keylog.c index 1952a690ca..d42aaa8156 100644 --- a/libs/libcurl/src/vtls/keylog.c +++ b/libs/libcurl/src/vtls/keylog.c @@ -1,159 +1,159 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#include "keylog.h" -#include <curl/curl.h> - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1) - -#define CLIENT_RANDOM_SIZE 32 - -/* - * The master secret in TLS 1.2 and before is always 48 bytes. In TLS 1.3, the - * secret size depends on the cipher suite's hash function which is 32 bytes - * for SHA-256 and 48 bytes for SHA-384. - */ -#define SECRET_MAXLEN 48 - - -/* The fp for the open SSLKEYLOGFILE, or NULL if not open */ -static FILE *keylog_file_fp; - -void -Curl_tls_keylog_open(void) -{ - char *keylog_file_name; - - if(!keylog_file_fp) { - keylog_file_name = curl_getenv("SSLKEYLOGFILE"); - if(keylog_file_name) { - keylog_file_fp = fopen(keylog_file_name, FOPEN_APPENDTEXT); - if(keylog_file_fp) { -#ifdef WIN32 - if(setvbuf(keylog_file_fp, NULL, _IONBF, 0)) -#else - if(setvbuf(keylog_file_fp, NULL, _IOLBF, 4096)) -#endif - { - fclose(keylog_file_fp); - keylog_file_fp = NULL; - } - } - Curl_safefree(keylog_file_name); - } - } -} - -void -Curl_tls_keylog_close(void) -{ - if(keylog_file_fp) { - fclose(keylog_file_fp); - keylog_file_fp = NULL; - } -} - -bool -Curl_tls_keylog_enabled(void) -{ - return keylog_file_fp != NULL; -} - -bool -Curl_tls_keylog_write_line(const char *line) -{ - /* The current maximum valid keylog line length LF and NUL is 195. */ - size_t linelen; - char buf[256]; - - if(!keylog_file_fp || !line) { - return false; - } - - linelen = strlen(line); - if(linelen == 0 || linelen > sizeof(buf) - 2) { - /* Empty line or too big to fit in a LF and NUL. */ - return false; - } - - memcpy(buf, line, linelen); - if(line[linelen - 1] != '\n') { - buf[linelen++] = '\n'; - } - buf[linelen] = '\0'; - - /* Using fputs here instead of fprintf since libcurl's fprintf replacement - may not be thread-safe. */ - fputs(buf, keylog_file_fp); - return true; -} - -bool -Curl_tls_keylog_write(const char *label, - const unsigned char client_random[CLIENT_RANDOM_SIZE], - const unsigned char *secret, size_t secretlen) -{ - const char *hex = "0123456789ABCDEF"; - size_t pos, i; - char line[KEYLOG_LABEL_MAXLEN + 1 + 2 * CLIENT_RANDOM_SIZE + 1 + - 2 * SECRET_MAXLEN + 1 + 1]; - - if(!keylog_file_fp) { - return false; - } - - pos = strlen(label); - if(pos > KEYLOG_LABEL_MAXLEN || !secretlen || secretlen > SECRET_MAXLEN) { - /* Should never happen - sanity check anyway. */ - return false; - } - - memcpy(line, label, pos); - line[pos++] = ' '; - - /* Client Random */ - for(i = 0; i < CLIENT_RANDOM_SIZE; i++) { - line[pos++] = hex[client_random[i] >> 4]; - line[pos++] = hex[client_random[i] & 0xF]; - } - line[pos++] = ' '; - - /* Secret */ - for(i = 0; i < secretlen; i++) { - line[pos++] = hex[secret[i] >> 4]; - line[pos++] = hex[secret[i] & 0xF]; - } - line[pos++] = '\n'; - line[pos] = '\0'; - - /* Using fputs here instead of fprintf since libcurl's fprintf replacement - may not be thread-safe. */ - fputs(line, keylog_file_fp); - return true; -} +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#include "keylog.h"
+#include <curl/curl.h>
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define KEYLOG_LABEL_MAXLEN (sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET") - 1)
+
+#define CLIENT_RANDOM_SIZE 32
+
+/*
+ * The master secret in TLS 1.2 and before is always 48 bytes. In TLS 1.3, the
+ * secret size depends on the cipher suite's hash function which is 32 bytes
+ * for SHA-256 and 48 bytes for SHA-384.
+ */
+#define SECRET_MAXLEN 48
+
+
+/* The fp for the open SSLKEYLOGFILE, or NULL if not open */
+static FILE *keylog_file_fp;
+
+void
+Curl_tls_keylog_open(void)
+{
+ char *keylog_file_name;
+
+ if(!keylog_file_fp) {
+ keylog_file_name = curl_getenv("SSLKEYLOGFILE");
+ if(keylog_file_name) {
+ keylog_file_fp = fopen(keylog_file_name, FOPEN_APPENDTEXT);
+ if(keylog_file_fp) {
+#ifdef WIN32
+ if(setvbuf(keylog_file_fp, NULL, _IONBF, 0))
+#else
+ if(setvbuf(keylog_file_fp, NULL, _IOLBF, 4096))
+#endif
+ {
+ fclose(keylog_file_fp);
+ keylog_file_fp = NULL;
+ }
+ }
+ Curl_safefree(keylog_file_name);
+ }
+ }
+}
+
+void
+Curl_tls_keylog_close(void)
+{
+ if(keylog_file_fp) {
+ fclose(keylog_file_fp);
+ keylog_file_fp = NULL;
+ }
+}
+
+bool
+Curl_tls_keylog_enabled(void)
+{
+ return keylog_file_fp != NULL;
+}
+
+bool
+Curl_tls_keylog_write_line(const char *line)
+{
+ /* The current maximum valid keylog line length LF and NUL is 195. */
+ size_t linelen;
+ char buf[256];
+
+ if(!keylog_file_fp || !line) {
+ return false;
+ }
+
+ linelen = strlen(line);
+ if(linelen == 0 || linelen > sizeof(buf) - 2) {
+ /* Empty line or too big to fit in a LF and NUL. */
+ return false;
+ }
+
+ memcpy(buf, line, linelen);
+ if(line[linelen - 1] != '\n') {
+ buf[linelen++] = '\n';
+ }
+ buf[linelen] = '\0';
+
+ /* Using fputs here instead of fprintf since libcurl's fprintf replacement
+ may not be thread-safe. */
+ fputs(buf, keylog_file_fp);
+ return true;
+}
+
+bool
+Curl_tls_keylog_write(const char *label,
+ const unsigned char client_random[CLIENT_RANDOM_SIZE],
+ const unsigned char *secret, size_t secretlen)
+{
+ const char *hex = "0123456789ABCDEF";
+ size_t pos, i;
+ char line[KEYLOG_LABEL_MAXLEN + 1 + 2 * CLIENT_RANDOM_SIZE + 1 +
+ 2 * SECRET_MAXLEN + 1 + 1];
+
+ if(!keylog_file_fp) {
+ return false;
+ }
+
+ pos = strlen(label);
+ if(pos > KEYLOG_LABEL_MAXLEN || !secretlen || secretlen > SECRET_MAXLEN) {
+ /* Should never happen - sanity check anyway. */
+ return false;
+ }
+
+ memcpy(line, label, pos);
+ line[pos++] = ' ';
+
+ /* Client Random */
+ for(i = 0; i < CLIENT_RANDOM_SIZE; i++) {
+ line[pos++] = hex[client_random[i] >> 4];
+ line[pos++] = hex[client_random[i] & 0xF];
+ }
+ line[pos++] = ' ';
+
+ /* Secret */
+ for(i = 0; i < secretlen; i++) {
+ line[pos++] = hex[secret[i] >> 4];
+ line[pos++] = hex[secret[i] & 0xF];
+ }
+ line[pos++] = '\n';
+ line[pos] = '\0';
+
+ /* Using fputs here instead of fprintf since libcurl's fprintf replacement
+ may not be thread-safe. */
+ fputs(line, keylog_file_fp);
+ return true;
+}
diff --git a/libs/libcurl/src/vtls/keylog.h b/libs/libcurl/src/vtls/keylog.h index 5d3c675b3e..92530f5eb6 100644 --- a/libs/libcurl/src/vtls/keylog.h +++ b/libs/libcurl/src/vtls/keylog.h @@ -1,58 +1,58 @@ -#ifndef HEADER_CURL_KEYLOG_H -#define HEADER_CURL_KEYLOG_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -/* - * Opens the TLS key log file if requested by the user. The SSLKEYLOGFILE - * environment variable specifies the output file. - */ -void Curl_tls_keylog_open(void); - -/* - * Closes the TLS key log file if not already. - */ -void Curl_tls_keylog_close(void); - -/* - * Returns true if the user successfully enabled the TLS key log file. - */ -bool Curl_tls_keylog_enabled(void); - -/* - * Appends a key log file entry. - * Returns true iff the key log file is open and a valid entry was provided. - */ -bool Curl_tls_keylog_write(const char *label, - const unsigned char client_random[32], - const unsigned char *secret, size_t secretlen); - -/* - * Appends a line to the key log file, ensure it is terminated by a LF. - * Returns true iff the key log file is open and a valid line was provided. - */ -bool Curl_tls_keylog_write_line(const char *line); - -#endif /* HEADER_CURL_KEYLOG_H */ +#ifndef HEADER_CURL_KEYLOG_H
+#define HEADER_CURL_KEYLOG_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+/*
+ * Opens the TLS key log file if requested by the user. The SSLKEYLOGFILE
+ * environment variable specifies the output file.
+ */
+void Curl_tls_keylog_open(void);
+
+/*
+ * Closes the TLS key log file if not already.
+ */
+void Curl_tls_keylog_close(void);
+
+/*
+ * Returns true if the user successfully enabled the TLS key log file.
+ */
+bool Curl_tls_keylog_enabled(void);
+
+/*
+ * Appends a key log file entry.
+ * Returns true iff the key log file is open and a valid entry was provided.
+ */
+bool Curl_tls_keylog_write(const char *label,
+ const unsigned char client_random[32],
+ const unsigned char *secret, size_t secretlen);
+
+/*
+ * Appends a line to the key log file, ensure it is terminated by a LF.
+ * Returns true iff the key log file is open and a valid line was provided.
+ */
+bool Curl_tls_keylog_write_line(const char *line);
+
+#endif /* HEADER_CURL_KEYLOG_H */
diff --git a/libs/libcurl/src/vtls/mbedtls.c b/libs/libcurl/src/vtls/mbedtls.c index fbde8976eb..cc88f6adbd 100644 --- a/libs/libcurl/src/vtls/mbedtls.c +++ b/libs/libcurl/src/vtls/mbedtls.c @@ -1,1273 +1,1301 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010 - 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for all mbedTLS-specific code for the TLS/SSL layer. No code - * but vtls.c should ever call or use these functions. - * - */ - -#include "curl_setup.h" - -#ifdef USE_MBEDTLS - -/* Define this to enable lots of debugging for mbedTLS */ -/* #define MBEDTLS_DEBUG */ - -#include <mbedtls/version.h> -#if MBEDTLS_VERSION_NUMBER >= 0x02040000 -#include <mbedtls/net_sockets.h> -#else -#include <mbedtls/net.h> -#endif -#include <mbedtls/ssl.h> -#include <mbedtls/x509.h> - -#include <mbedtls/error.h> -#include <mbedtls/entropy.h> -#include <mbedtls/ctr_drbg.h> -#include <mbedtls/sha256.h> - -#if MBEDTLS_VERSION_MAJOR >= 2 -# ifdef MBEDTLS_DEBUG -# include <mbedtls/debug.h> -# endif -#endif - -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "mbedtls.h" -#include "vtls.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "multiif.h" -#include "mbedtls_threadlock.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* ALPN for http2 */ -#ifdef USE_HTTP2 -# undef HAS_ALPN -# ifdef MBEDTLS_SSL_ALPN -# define HAS_ALPN -# endif -#endif - -struct ssl_backend_data { - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_entropy_context entropy; - mbedtls_ssl_context ssl; - mbedtls_x509_crt cacert; - mbedtls_x509_crt clicert; -#ifdef MBEDTLS_X509_CRL_PARSE_C - mbedtls_x509_crl crl; -#endif - mbedtls_pk_context pk; - mbedtls_ssl_config config; -#ifdef HAS_ALPN - const char *protocols[3]; -#endif -}; - -/* apply threading? */ -#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32) -#define THREADING_SUPPORT -#endif - -#ifndef MBEDTLS_ERROR_C -#define mbedtls_strerror(a,b,c) b[0] = 0 -#endif - -#if defined(THREADING_SUPPORT) -static mbedtls_entropy_context ts_entropy; - -static int entropy_init_initialized = 0; - -/* start of entropy_init_mutex() */ -static void entropy_init_mutex(mbedtls_entropy_context *ctx) -{ - /* lock 0 = entropy_init_mutex() */ - Curl_mbedtlsthreadlock_lock_function(0); - if(entropy_init_initialized == 0) { - mbedtls_entropy_init(ctx); - entropy_init_initialized = 1; - } - Curl_mbedtlsthreadlock_unlock_function(0); -} -/* end of entropy_init_mutex() */ - -/* start of entropy_func_mutex() */ -static int entropy_func_mutex(void *data, unsigned char *output, size_t len) -{ - int ret; - /* lock 1 = entropy_func_mutex() */ - Curl_mbedtlsthreadlock_lock_function(1); - ret = mbedtls_entropy_func(data, output, len); - Curl_mbedtlsthreadlock_unlock_function(1); - - return ret; -} -/* end of entropy_func_mutex() */ - -#endif /* THREADING_SUPPORT */ - -#ifdef MBEDTLS_DEBUG -static void mbed_debug(void *context, int level, const char *f_name, - int line_nb, const char *line) -{ - struct Curl_easy *data = NULL; - - if(!context) - return; - - data = (struct Curl_easy *)context; - - infof(data, "%s", line); - (void) level; -} -#else -#endif - -/* - * profile - */ -static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr = -{ - /* Hashes from SHA-1 and above */ - MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA1) | - MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_RIPEMD160) | - MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA224) | - MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA256) | - MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA384) | - MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA512), - 0xFFFFFFF, /* Any PK alg */ - 0xFFFFFFF, /* Any curve */ - 1024, /* RSA min key len */ -}; - -/* See https://tls.mbed.org/discussions/generic/ - howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der -*/ -#define RSA_PUB_DER_MAX_BYTES (38 + 2 * MBEDTLS_MPI_MAX_SIZE) -#define ECP_PUB_DER_MAX_BYTES (30 + 2 * MBEDTLS_ECP_MAX_BYTES) - -#define PUB_DER_MAX_BYTES (RSA_PUB_DER_MAX_BYTES > ECP_PUB_DER_MAX_BYTES ? \ - RSA_PUB_DER_MAX_BYTES : ECP_PUB_DER_MAX_BYTES) - -static Curl_recv mbed_recv; -static Curl_send mbed_send; - -static CURLcode mbedtls_version_from_curl(int *mbedver, long version) -{ -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - switch(version) { - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - break; - } -#else - switch(version) { - case CURL_SSLVERSION_TLSv1_0: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_1; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_1: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_2; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_2: - *mbedver = MBEDTLS_SSL_MINOR_VERSION_3; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - break; - } -#endif - - return CURLE_SSL_CONNECT_ERROR; -} - -static CURLcode -set_ssl_version_min_max(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_3; - int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_3; -#else - int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_1; - int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_1; -#endif - long ssl_version = SSL_CONN_CONFIG(version); - long ssl_version_max = SSL_CONN_CONFIG(version_max); - CURLcode result = CURLE_OK; - - DEBUGASSERT(backend); - - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - ssl_version = CURL_SSLVERSION_TLSv1_0; - break; - } - - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; - break; - } - - result = mbedtls_version_from_curl(&mbedtls_ver_min, ssl_version); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; - } - result = mbedtls_version_from_curl(&mbedtls_ver_max, ssl_version_max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; - } - - mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - mbedtls_ver_min); - mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - mbedtls_ver_max); - - return result; -} - -static CURLcode -mbed_connect_step1(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob); - const char * const ssl_cafile = - /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ - (ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile)); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - const char * const ssl_capath = SSL_CONN_CONFIG(CApath); - char * const ssl_cert = SSL_SET_OPTION(primary.clientcert); - const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob); - const char * const ssl_crlfile = SSL_SET_OPTION(primary.CRLfile); - const char * const hostname = SSL_HOST_NAME(); -#ifndef CURL_DISABLE_VERBOSE_STRINGS - const long int port = SSL_HOST_PORT(); -#endif - int ret = -1; - char errorbuf[128]; - - DEBUGASSERT(backend); - - if((SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv2) || - (SSL_CONN_CONFIG(version) == CURL_SSLVERSION_SSLv3)) { - failf(data, "Not supported SSL version"); - return CURLE_NOT_BUILT_IN; - } - -#ifdef THREADING_SUPPORT - entropy_init_mutex(&ts_entropy); - mbedtls_ctr_drbg_init(&backend->ctr_drbg); - - ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, entropy_func_mutex, - &ts_entropy, NULL, 0); - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", - -ret, errorbuf); - return CURLE_FAILED_INIT; - } -#else - mbedtls_entropy_init(&backend->entropy); - mbedtls_ctr_drbg_init(&backend->ctr_drbg); - - ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, mbedtls_entropy_func, - &backend->entropy, NULL, 0); - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", - -ret, errorbuf); - return CURLE_FAILED_INIT; - } -#endif /* THREADING_SUPPORT */ - - /* Load the trusted CA */ - mbedtls_x509_crt_init(&backend->cacert); - - if(ca_info_blob && verifypeer) { - /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null - terminated even when provided the exact length, forcing us to waste - extra memory here. */ - unsigned char *newblob = malloc(ca_info_blob->len + 1); - if(!newblob) - return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ca_info_blob->data, ca_info_blob->len); - newblob[ca_info_blob->len] = 0; /* null terminate */ - ret = mbedtls_x509_crt_parse(&backend->cacert, newblob, - ca_info_blob->len + 1); - free(newblob); - if(ret<0) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error importing ca cert blob - mbedTLS: (-0x%04X) %s", - -ret, errorbuf); - return CURLE_SSL_CERTPROBLEM; - } - } - - if(ssl_cafile && verifypeer) { -#ifdef MBEDTLS_FS_IO - ret = mbedtls_x509_crt_parse_file(&backend->cacert, ssl_cafile); - - if(ret<0) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading ca cert file %s - mbedTLS: (-0x%04X) %s", - ssl_cafile, -ret, errorbuf); - return CURLE_SSL_CACERT_BADFILE; - } -#else - failf(data, "mbedtls: functions that use the filesystem not built in"); - return CURLE_NOT_BUILT_IN; -#endif - } - - if(ssl_capath) { -#ifdef MBEDTLS_FS_IO - ret = mbedtls_x509_crt_parse_path(&backend->cacert, ssl_capath); - - if(ret<0) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading ca cert path %s - mbedTLS: (-0x%04X) %s", - ssl_capath, -ret, errorbuf); - - if(verifypeer) - return CURLE_SSL_CACERT_BADFILE; - } -#else - failf(data, "mbedtls: functions that use the filesystem not built in"); - return CURLE_NOT_BUILT_IN; -#endif - } - - /* Load the client certificate */ - mbedtls_x509_crt_init(&backend->clicert); - - if(ssl_cert) { -#ifdef MBEDTLS_FS_IO - ret = mbedtls_x509_crt_parse_file(&backend->clicert, ssl_cert); - - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading client cert file %s - mbedTLS: (-0x%04X) %s", - ssl_cert, -ret, errorbuf); - - return CURLE_SSL_CERTPROBLEM; - } -#else - failf(data, "mbedtls: functions that use the filesystem not built in"); - return CURLE_NOT_BUILT_IN; -#endif - } - - if(ssl_cert_blob) { - /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null - terminated even when provided the exact length, forcing us to waste - extra memory here. */ - unsigned char *newblob = malloc(ssl_cert_blob->len + 1); - if(!newblob) - return CURLE_OUT_OF_MEMORY; - memcpy(newblob, ssl_cert_blob->data, ssl_cert_blob->len); - newblob[ssl_cert_blob->len] = 0; /* null terminate */ - ret = mbedtls_x509_crt_parse(&backend->clicert, newblob, - ssl_cert_blob->len + 1); - free(newblob); - - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s", - SSL_SET_OPTION(key), -ret, errorbuf); - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Load the client private key */ - mbedtls_pk_init(&backend->pk); - - if(SSL_SET_OPTION(key) || SSL_SET_OPTION(key_blob)) { - if(SSL_SET_OPTION(key)) { -#ifdef MBEDTLS_FS_IO -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - ret = mbedtls_pk_parse_keyfile(&backend->pk, SSL_SET_OPTION(key), - SSL_SET_OPTION(key_passwd), - mbedtls_ctr_drbg_random, - &backend->ctr_drbg); -#else - ret = mbedtls_pk_parse_keyfile(&backend->pk, SSL_SET_OPTION(key), - SSL_SET_OPTION(key_passwd)); -#endif - - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s", - SSL_SET_OPTION(key), -ret, errorbuf); - return CURLE_SSL_CERTPROBLEM; - } -#else - failf(data, "mbedtls: functions that use the filesystem not built in"); - return CURLE_NOT_BUILT_IN; -#endif - } - else { - const struct curl_blob *ssl_key_blob = SSL_SET_OPTION(key_blob); - const unsigned char *key_data = - (const unsigned char *)ssl_key_blob->data; - const char *passwd = SSL_SET_OPTION(key_passwd); -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len, - (const unsigned char *)passwd, - passwd ? strlen(passwd) : 0, - mbedtls_ctr_drbg_random, - &backend->ctr_drbg); -#else - ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len, - (const unsigned char *)passwd, - passwd ? strlen(passwd) : 0); -#endif - - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error parsing private key - mbedTLS: (-0x%04X) %s", - -ret, errorbuf); - return CURLE_SSL_CERTPROBLEM; - } - } - - if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) || - mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY))) - ret = MBEDTLS_ERR_PK_TYPE_MISMATCH; - } - - /* Load the CRL */ -#ifdef MBEDTLS_X509_CRL_PARSE_C - mbedtls_x509_crl_init(&backend->crl); - - if(ssl_crlfile) { -#ifdef MBEDTLS_FS_IO - ret = mbedtls_x509_crl_parse_file(&backend->crl, ssl_crlfile); - - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "Error reading CRL file %s - mbedTLS: (-0x%04X) %s", - ssl_crlfile, -ret, errorbuf); - - return CURLE_SSL_CRL_BADFILE; - } -#else - failf(data, "mbedtls: functions that use the filesystem not built in"); - return CURLE_NOT_BUILT_IN; -#endif - } -#else - if(ssl_crlfile) { - failf(data, "mbedtls: crl support not built in"); - return CURLE_NOT_BUILT_IN; - } -#endif - - infof(data, "mbedTLS: Connecting to %s:%ld", hostname, port); - - mbedtls_ssl_config_init(&backend->config); - ret = mbedtls_ssl_config_defaults(&backend->config, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT); - if(ret) { - failf(data, "mbedTLS: ssl_config failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - mbedtls_ssl_init(&backend->ssl); - if(mbedtls_ssl_setup(&backend->ssl, &backend->config)) { - failf(data, "mbedTLS: ssl_init failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* new profile with RSA min key len = 1024 ... */ - mbedtls_ssl_conf_cert_profile(&backend->config, - &mbedtls_x509_crt_profile_fr); - - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if MBEDTLS_VERSION_NUMBER < 0x03000000 - mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3, - MBEDTLS_SSL_MINOR_VERSION_1); - infof(data, "mbedTLS: Set min SSL version to TLS 1.0"); - break; -#endif - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(data, conn, sockindex); - if(result != CURLE_OK) - return result; - break; - } - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL); - - mbedtls_ssl_conf_rng(&backend->config, mbedtls_ctr_drbg_random, - &backend->ctr_drbg); - mbedtls_ssl_set_bio(&backend->ssl, &conn->sock[sockindex], - mbedtls_net_send, - mbedtls_net_recv, - NULL /* rev_timeout() */); - - mbedtls_ssl_conf_ciphersuites(&backend->config, - mbedtls_ssl_list_ciphersuites()); - -#if defined(MBEDTLS_SSL_RENEGOTIATION) - mbedtls_ssl_conf_renegotiation(&backend->config, - MBEDTLS_SSL_RENEGOTIATION_ENABLED); -#endif - -#if defined(MBEDTLS_SSL_SESSION_TICKETS) - mbedtls_ssl_conf_session_tickets(&backend->config, - MBEDTLS_SSL_SESSION_TICKETS_DISABLED); -#endif - - /* Check if there's a cached ID we can/should use here! */ - if(SSL_SET_OPTION(primary.sessionid)) { - void *old_session = NULL; - - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - &old_session, NULL, sockindex)) { - ret = mbedtls_ssl_set_session(&backend->ssl, old_session); - if(ret) { - Curl_ssl_sessionid_unlock(data); - failf(data, "mbedtls_ssl_set_session returned -0x%x", -ret); - return CURLE_SSL_CONNECT_ERROR; - } - infof(data, "mbedTLS re-using session"); - } - Curl_ssl_sessionid_unlock(data); - } - - mbedtls_ssl_conf_ca_chain(&backend->config, - &backend->cacert, -#ifdef MBEDTLS_X509_CRL_PARSE_C - &backend->crl); -#else - NULL); -#endif - - if(SSL_SET_OPTION(key) || SSL_SET_OPTION(key_blob)) { - mbedtls_ssl_conf_own_cert(&backend->config, - &backend->clicert, &backend->pk); - } - { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || mbedtls_ssl_set_hostname(&backend->ssl, snihost)) { - /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and - the name to set in the SNI extension. So even if curl connects to a - host specified as an IP address, this function must be used. */ - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - } - -#ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - const char **p = &backend->protocols[0]; -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) - *p++ = ALPN_H2; -#endif - *p++ = ALPN_HTTP_1_1; - *p = NULL; - /* this function doesn't clone the protocols array, which is why we need - to keep it around */ - if(mbedtls_ssl_conf_alpn_protocols(&backend->config, - &backend->protocols[0])) { - failf(data, "Failed setting ALPN protocols"); - return CURLE_SSL_CONNECT_ERROR; - } - for(p = &backend->protocols[0]; *p; ++p) - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, *p); - } -#endif - -#ifdef MBEDTLS_DEBUG - /* In order to make that work in mbedtls MBEDTLS_DEBUG_C must be defined. */ - mbedtls_ssl_conf_dbg(&backend->config, mbed_debug, data); - /* - 0 No debug - * - 1 Error - * - 2 State change - * - 3 Informational - * - 4 Verbose - */ - mbedtls_debug_set_threshold(4); -#endif - - /* give application a chance to interfere with mbedTLS set up. */ - if(data->set.ssl.fsslctx) { - ret = (*data->set.ssl.fsslctx)(data, &backend->config, - data->set.ssl.fsslctxp); - if(ret) { - failf(data, "error signaled by ssl ctx callback"); - return ret; - } - } - - connssl->connecting_state = ssl_connect_2; - - return CURLE_OK; -} - -static CURLcode -mbed_connect_step2(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - int ret; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - const mbedtls_x509_crt *peercert; - const char * const pinnedpubkey = SSL_PINNED_PUB_KEY(); - - DEBUGASSERT(backend); - - conn->recv[sockindex] = mbed_recv; - conn->send[sockindex] = mbed_send; - - ret = mbedtls_ssl_handshake(&backend->ssl); - - if(ret == MBEDTLS_ERR_SSL_WANT_READ) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - } - else if(ret) { - char errorbuf[128]; - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "ssl_handshake returned - mbedTLS: (-0x%04X) %s", - -ret, errorbuf); - return CURLE_SSL_CONNECT_ERROR; - } - - infof(data, "mbedTLS: Handshake complete, cipher is %s", - mbedtls_ssl_get_ciphersuite(&backend->ssl)); - - ret = mbedtls_ssl_get_verify_result(&backend->ssl); - - if(!SSL_CONN_CONFIG(verifyhost)) - /* Ignore hostname errors if verifyhost is disabled */ - ret &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH; - - if(ret && SSL_CONN_CONFIG(verifypeer)) { - if(ret & MBEDTLS_X509_BADCERT_EXPIRED) - failf(data, "Cert verify failed: BADCERT_EXPIRED"); - - else if(ret & MBEDTLS_X509_BADCERT_REVOKED) - failf(data, "Cert verify failed: BADCERT_REVOKED"); - - else if(ret & MBEDTLS_X509_BADCERT_CN_MISMATCH) - failf(data, "Cert verify failed: BADCERT_CN_MISMATCH"); - - else if(ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED) - failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED"); - - else if(ret & MBEDTLS_X509_BADCERT_FUTURE) - failf(data, "Cert verify failed: BADCERT_FUTURE"); - - return CURLE_PEER_FAILED_VERIFICATION; - } - - peercert = mbedtls_ssl_get_peer_cert(&backend->ssl); - - if(peercert && data->set.verbose) { - const size_t bufsize = 16384; - char *buffer = malloc(bufsize); - - if(!buffer) - return CURLE_OUT_OF_MEMORY; - - if(mbedtls_x509_crt_info(buffer, bufsize, "* ", peercert) > 0) - infof(data, "Dumping cert info: %s", buffer); - else - infof(data, "Unable to dump certificate information"); - - free(buffer); - } - - if(pinnedpubkey) { - int size; - CURLcode result; - mbedtls_x509_crt *p = NULL; - unsigned char *pubkey = NULL; - -#if MBEDTLS_VERSION_NUMBER == 0x03000000 - if(!peercert || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p) || - !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len)) { -#else - if(!peercert || !peercert->raw.p || !peercert->raw.len) { -#endif - failf(data, "Failed due to missing peer certificate"); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - p = calloc(1, sizeof(*p)); - - if(!p) - return CURLE_OUT_OF_MEMORY; - - pubkey = malloc(PUB_DER_MAX_BYTES); - - if(!pubkey) { - result = CURLE_OUT_OF_MEMORY; - goto pinnedpubkey_error; - } - - mbedtls_x509_crt_init(p); - - /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der - needs a non-const key, for now. - https://github.com/ARMmbed/mbedtls/issues/396 */ -#if MBEDTLS_VERSION_NUMBER == 0x03000000 - if(mbedtls_x509_crt_parse_der(p, - peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p), - peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len))) { -#else - if(mbedtls_x509_crt_parse_der(p, peercert->raw.p, peercert->raw.len)) { -#endif - failf(data, "Failed copying peer certificate"); - result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - goto pinnedpubkey_error; - } - -#if MBEDTLS_VERSION_NUMBER == 0x03000000 - size = mbedtls_pk_write_pubkey_der(&p->MBEDTLS_PRIVATE(pk), pubkey, - PUB_DER_MAX_BYTES); -#else - size = mbedtls_pk_write_pubkey_der(&p->pk, pubkey, PUB_DER_MAX_BYTES); -#endif - - if(size <= 0) { - failf(data, "Failed copying public key from peer certificate"); - result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - goto pinnedpubkey_error; - } - - /* mbedtls_pk_write_pubkey_der writes data at the end of the buffer. */ - result = Curl_pin_peer_pubkey(data, - pinnedpubkey, - &pubkey[PUB_DER_MAX_BYTES - size], size); - pinnedpubkey_error: - mbedtls_x509_crt_free(p); - free(p); - free(pubkey); - if(result) { - return result; - } - } - -#ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - const char *next_protocol = mbedtls_ssl_get_alpn_protocol(&backend->ssl); - - if(next_protocol) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, next_protocol); -#ifdef USE_HTTP2 - if(!strncmp(next_protocol, ALPN_H2, ALPN_H2_LENGTH) && - !next_protocol[ALPN_H2_LENGTH]) { - conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH) && - !next_protocol[ALPN_HTTP_1_1_LENGTH]) { - conn->alpn = CURL_HTTP_VERSION_1_1; - } - } - else { - infof(data, VTLS_INFOF_NO_ALPN); - } - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } -#endif - - connssl->connecting_state = ssl_connect_3; - infof(data, "SSL connected"); - - return CURLE_OK; -} - -static CURLcode -mbed_connect_step3(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - CURLcode retcode = CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - DEBUGASSERT(backend); - - if(SSL_SET_OPTION(primary.sessionid)) { - int ret; - mbedtls_ssl_session *our_ssl_sessionid; - void *old_ssl_sessionid = NULL; - bool isproxy = SSL_IS_PROXY() ? TRUE : FALSE; - bool added = FALSE; - - our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session)); - if(!our_ssl_sessionid) - return CURLE_OUT_OF_MEMORY; - - mbedtls_ssl_session_init(our_ssl_sessionid); - - ret = mbedtls_ssl_get_session(&backend->ssl, our_ssl_sessionid); - if(ret) { - if(ret != MBEDTLS_ERR_SSL_ALLOC_FAILED) - mbedtls_ssl_session_free(our_ssl_sessionid); - free(our_ssl_sessionid); - failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret); - return CURLE_SSL_CONNECT_ERROR; - } - - /* If there's already a matching session in the cache, delete it */ - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, isproxy, &old_ssl_sessionid, NULL, - sockindex)) - Curl_ssl_delsessionid(data, old_ssl_sessionid); - - retcode = Curl_ssl_addsessionid(data, conn, isproxy, our_ssl_sessionid, - 0, sockindex, &added); - Curl_ssl_sessionid_unlock(data); - if(!added) { - mbedtls_ssl_session_free(our_ssl_sessionid); - free(our_ssl_sessionid); - } - if(retcode) { - failf(data, "failed to store ssl session"); - return retcode; - } - } - - connssl->connecting_state = ssl_connect_done; - - return CURLE_OK; -} - -static ssize_t mbed_send(struct Curl_easy *data, int sockindex, - const void *mem, size_t len, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - int ret = -1; - - DEBUGASSERT(backend); - - ret = mbedtls_ssl_write(&backend->ssl, (unsigned char *)mem, len); - - if(ret < 0) { - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_WRITE) ? - CURLE_AGAIN : CURLE_SEND_ERROR; - ret = -1; - } - - return ret; -} - -static void mbedtls_close_all(struct Curl_easy *data) -{ - (void)data; -} - -static void mbedtls_close(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - char buf[32]; - (void) data; - - DEBUGASSERT(backend); - - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, sizeof(buf)); - - mbedtls_pk_free(&backend->pk); - mbedtls_x509_crt_free(&backend->clicert); - mbedtls_x509_crt_free(&backend->cacert); -#ifdef MBEDTLS_X509_CRL_PARSE_C - mbedtls_x509_crl_free(&backend->crl); -#endif - mbedtls_ssl_config_free(&backend->config); - mbedtls_ssl_free(&backend->ssl); - mbedtls_ctr_drbg_free(&backend->ctr_drbg); -#ifndef THREADING_SUPPORT - mbedtls_entropy_free(&backend->entropy); -#endif /* THREADING_SUPPORT */ -} - -static ssize_t mbed_recv(struct Curl_easy *data, int num, - char *buf, size_t buffersize, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[num]; - struct ssl_backend_data *backend = connssl->backend; - int ret = -1; - ssize_t len = -1; - - DEBUGASSERT(backend); - - ret = mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, - buffersize); - - if(ret <= 0) { - if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) - return 0; - - *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_READ) ? - CURLE_AGAIN : CURLE_RECV_ERROR; - return -1; - } - - len = ret; - - return len; -} - -static void mbedtls_session_free(void *ptr) -{ - mbedtls_ssl_session_free(ptr); - free(ptr); -} - -static size_t mbedtls_version(char *buffer, size_t size) -{ -#ifdef MBEDTLS_VERSION_C - /* if mbedtls_version_get_number() is available it is better */ - unsigned int version = mbedtls_version_get_number(); - return msnprintf(buffer, size, "mbedTLS/%u.%u.%u", version>>24, - (version>>16)&0xff, (version>>8)&0xff); -#else - return msnprintf(buffer, size, "mbedTLS/%s", MBEDTLS_VERSION_STRING); -#endif -} - -static CURLcode mbedtls_random(struct Curl_easy *data, - unsigned char *entropy, size_t length) -{ -#if defined(MBEDTLS_CTR_DRBG_C) - int ret = -1; - char errorbuf[128]; - mbedtls_entropy_context ctr_entropy; - mbedtls_ctr_drbg_context ctr_drbg; - mbedtls_entropy_init(&ctr_entropy); - mbedtls_ctr_drbg_init(&ctr_drbg); - - ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, - &ctr_entropy, NULL, 0); - - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s", - -ret, errorbuf); - } - else { - ret = mbedtls_ctr_drbg_random(&ctr_drbg, entropy, length); - - if(ret) { - mbedtls_strerror(ret, errorbuf, sizeof(errorbuf)); - failf(data, "mbedtls_ctr_drbg_random returned (-0x%04X) %s", - -ret, errorbuf); - } - } - - mbedtls_ctr_drbg_free(&ctr_drbg); - mbedtls_entropy_free(&ctr_entropy); - - return ret == 0 ? CURLE_OK : CURLE_FAILED_INIT; -#elif defined(MBEDTLS_HAVEGE_C) - mbedtls_havege_state hs; - mbedtls_havege_init(&hs); - mbedtls_havege_random(&hs, entropy, length); - mbedtls_havege_free(&hs); - return CURLE_OK; -#else - return CURLE_NOT_BUILT_IN; -#endif -} - -static CURLcode -mbed_connect_common(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode retcode; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - timediff_t timeout_ms; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - retcode = mbed_connect_step1(data, conn, sockindex); - if(retcode) - return retcode; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking ? 0 : timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ - retcode = mbed_connect_step2(data, conn, sockindex); - if(retcode || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return retcode; - - } /* repeat step2 until all transactions are done. */ - - if(ssl_connect_3 == connssl->connecting_state) { - retcode = mbed_connect_step3(data, conn, sockindex); - if(retcode) - return retcode; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = mbed_recv; - conn->send[sockindex] = mbed_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode mbedtls_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - return mbed_connect_common(data, conn, sockindex, TRUE, done); -} - - -static CURLcode mbedtls_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - CURLcode retcode; - bool done = FALSE; - - retcode = mbed_connect_common(data, conn, sockindex, FALSE, &done); - if(retcode) - return retcode; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -/* - * return 0 error initializing SSL - * return 1 SSL initialized successfully - */ -static int mbedtls_init(void) -{ - return Curl_mbedtlsthreadlock_thread_setup(); -} - -static void mbedtls_cleanup(void) -{ - (void)Curl_mbedtlsthreadlock_thread_cleanup(); -} - -static bool mbedtls_data_pending(const struct connectdata *conn, - int sockindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - return mbedtls_ssl_get_bytes_avail(&backend->ssl) != 0; -} - -static CURLcode mbedtls_sha256sum(const unsigned char *input, - size_t inputlen, - unsigned char *sha256sum, - size_t sha256len UNUSED_PARAM) -{ - /* TODO: explain this for different mbedtls 2.x vs 3 version */ - (void)sha256len; -#if MBEDTLS_VERSION_NUMBER < 0x02070000 - mbedtls_sha256(input, inputlen, sha256sum, 0); -#else - /* returns 0 on success, otherwise failure */ -#if MBEDTLS_VERSION_NUMBER >= 0x03000000 - if(mbedtls_sha256(input, inputlen, sha256sum, 0) != 0) -#else - if(mbedtls_sha256_ret(input, inputlen, sha256sum, 0) != 0) -#endif - return CURLE_BAD_FUNCTION_ARGUMENT; -#endif - return CURLE_OK; -} - -static void *mbedtls_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - (void)info; - DEBUGASSERT(backend); - return &backend->ssl; -} - -const struct Curl_ssl Curl_ssl_mbedtls = { - { CURLSSLBACKEND_MBEDTLS, "mbedtls" }, /* info */ - - SSLSUPP_CA_PATH | - SSLSUPP_CAINFO_BLOB | - SSLSUPP_PINNEDPUBKEY | - SSLSUPP_SSL_CTX, - - sizeof(struct ssl_backend_data), - - mbedtls_init, /* init */ - mbedtls_cleanup, /* cleanup */ - mbedtls_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ - mbedtls_data_pending, /* data_pending */ - mbedtls_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - mbedtls_connect, /* connect */ - mbedtls_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - mbedtls_get_internals, /* get_internals */ - mbedtls_close, /* close_one */ - mbedtls_close_all, /* close_all */ - mbedtls_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - mbedtls_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif /* USE_MBEDTLS */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2010 - 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for all mbedTLS-specific code for the TLS/SSL layer. No code
+ * but vtls.c should ever call or use these functions.
+ *
+ */
+
+#include "curl_setup.h"
+
+#ifdef USE_MBEDTLS
+
+/* Define this to enable lots of debugging for mbedTLS */
+/* #define MBEDTLS_DEBUG */
+
+#include <mbedtls/version.h>
+#if MBEDTLS_VERSION_NUMBER >= 0x02040000
+#include <mbedtls/net_sockets.h>
+#else
+#include <mbedtls/net.h>
+#endif
+#include <mbedtls/ssl.h>
+#include <mbedtls/x509.h>
+
+#include <mbedtls/error.h>
+#include <mbedtls/entropy.h>
+#include <mbedtls/ctr_drbg.h>
+#include <mbedtls/sha256.h>
+
+#if MBEDTLS_VERSION_MAJOR >= 2
+# ifdef MBEDTLS_DEBUG
+# include <mbedtls/debug.h>
+# endif
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "inet_pton.h"
+#include "mbedtls.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "parsedate.h"
+#include "connect.h" /* for the connect timeout */
+#include "select.h"
+#include "multiif.h"
+#include "mbedtls_threadlock.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* ALPN for http2 */
+#ifdef USE_HTTP2
+# undef HAS_ALPN
+# ifdef MBEDTLS_SSL_ALPN
+# define HAS_ALPN
+# endif
+#endif
+
+struct ssl_backend_data {
+ mbedtls_ctr_drbg_context ctr_drbg;
+ mbedtls_entropy_context entropy;
+ mbedtls_ssl_context ssl;
+ mbedtls_x509_crt cacert;
+ mbedtls_x509_crt clicert;
+#ifdef MBEDTLS_X509_CRL_PARSE_C
+ mbedtls_x509_crl crl;
+#endif
+ mbedtls_pk_context pk;
+ mbedtls_ssl_config config;
+#ifdef HAS_ALPN
+ const char *protocols[3];
+#endif
+};
+
+/* apply threading? */
+#if defined(USE_THREADS_POSIX) || defined(USE_THREADS_WIN32)
+#define THREADING_SUPPORT
+#endif
+
+#ifndef MBEDTLS_ERROR_C
+#define mbedtls_strerror(a,b,c) b[0] = 0
+#endif
+
+#if defined(THREADING_SUPPORT)
+static mbedtls_entropy_context ts_entropy;
+
+static int entropy_init_initialized = 0;
+
+/* start of entropy_init_mutex() */
+static void entropy_init_mutex(mbedtls_entropy_context *ctx)
+{
+ /* lock 0 = entropy_init_mutex() */
+ Curl_mbedtlsthreadlock_lock_function(0);
+ if(entropy_init_initialized == 0) {
+ mbedtls_entropy_init(ctx);
+ entropy_init_initialized = 1;
+ }
+ Curl_mbedtlsthreadlock_unlock_function(0);
+}
+/* end of entropy_init_mutex() */
+
+/* start of entropy_func_mutex() */
+static int entropy_func_mutex(void *data, unsigned char *output, size_t len)
+{
+ int ret;
+ /* lock 1 = entropy_func_mutex() */
+ Curl_mbedtlsthreadlock_lock_function(1);
+ ret = mbedtls_entropy_func(data, output, len);
+ Curl_mbedtlsthreadlock_unlock_function(1);
+
+ return ret;
+}
+/* end of entropy_func_mutex() */
+
+#endif /* THREADING_SUPPORT */
+
+#ifdef MBEDTLS_DEBUG
+static void mbed_debug(void *context, int level, const char *f_name,
+ int line_nb, const char *line)
+{
+ struct Curl_easy *data = NULL;
+
+ if(!context)
+ return;
+
+ data = (struct Curl_easy *)context;
+
+ infof(data, "%s", line);
+ (void) level;
+}
+#else
+#endif
+
+static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen)
+{
+ struct Curl_cfilter *cf = bio;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nwritten;
+ CURLcode result;
+
+ DEBUGASSERT(data);
+ nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result);
+ /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
+ blen, (int)nwritten, result)); */
+ if(nwritten < 0 && CURLE_AGAIN == result) {
+ nwritten = MBEDTLS_ERR_SSL_WANT_WRITE;
+ }
+ return (int)nwritten;
+}
+
+static int bio_cf_read(void *bio, unsigned char *buf, size_t blen)
+{
+ struct Curl_cfilter *cf = bio;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nread;
+ CURLcode result;
+
+ DEBUGASSERT(data);
+ /* OpenSSL catches this case, so should we. */
+ if(!buf)
+ return 0;
+
+ nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &result);
+ /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
+ blen, (int)nread, result)); */
+ if(nread < 0 && CURLE_AGAIN == result) {
+ nread = MBEDTLS_ERR_SSL_WANT_READ;
+ }
+ return (int)nread;
+}
+
+/*
+ * profile
+ */
+static const mbedtls_x509_crt_profile mbedtls_x509_crt_profile_fr =
+{
+ /* Hashes from SHA-1 and above */
+ MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA1) |
+ MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_RIPEMD160) |
+ MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA224) |
+ MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA256) |
+ MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA384) |
+ MBEDTLS_X509_ID_FLAG(MBEDTLS_MD_SHA512),
+ 0xFFFFFFF, /* Any PK alg */
+ 0xFFFFFFF, /* Any curve */
+ 1024, /* RSA min key len */
+};
+
+/* See https://tls.mbed.org/discussions/generic/
+ howto-determine-exact-buffer-len-for-mbedtls_pk_write_pubkey_der
+*/
+#define RSA_PUB_DER_MAX_BYTES (38 + 2 * MBEDTLS_MPI_MAX_SIZE)
+#define ECP_PUB_DER_MAX_BYTES (30 + 2 * MBEDTLS_ECP_MAX_BYTES)
+
+#define PUB_DER_MAX_BYTES (RSA_PUB_DER_MAX_BYTES > ECP_PUB_DER_MAX_BYTES ? \
+ RSA_PUB_DER_MAX_BYTES : ECP_PUB_DER_MAX_BYTES)
+
+static CURLcode mbedtls_version_from_curl(int *mbedver, long version)
+{
+#if MBEDTLS_VERSION_NUMBER >= 0x03000000
+ switch(version) {
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1_1:
+ case CURL_SSLVERSION_TLSv1_2:
+ *mbedver = MBEDTLS_SSL_MINOR_VERSION_3;
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_3:
+ break;
+ }
+#else
+ switch(version) {
+ case CURL_SSLVERSION_TLSv1_0:
+ *mbedver = MBEDTLS_SSL_MINOR_VERSION_1;
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_1:
+ *mbedver = MBEDTLS_SSL_MINOR_VERSION_2;
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_2:
+ *mbedver = MBEDTLS_SSL_MINOR_VERSION_3;
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_3:
+ break;
+ }
+#endif
+
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+static CURLcode
+set_ssl_version_min_max(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+#if MBEDTLS_VERSION_NUMBER >= 0x03000000
+ int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_3;
+ int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_3;
+#else
+ int mbedtls_ver_min = MBEDTLS_SSL_MINOR_VERSION_1;
+ int mbedtls_ver_max = MBEDTLS_SSL_MINOR_VERSION_1;
+#endif
+ long ssl_version = conn_config->version;
+ long ssl_version_max = conn_config->version_max;
+ CURLcode result = CURLE_OK;
+
+ DEBUGASSERT(backend);
+
+ switch(ssl_version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ ssl_version = CURL_SSLVERSION_TLSv1_0;
+ break;
+ }
+
+ switch(ssl_version_max) {
+ case CURL_SSLVERSION_MAX_NONE:
+ case CURL_SSLVERSION_MAX_DEFAULT:
+ ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2;
+ break;
+ }
+
+ result = mbedtls_version_from_curl(&mbedtls_ver_min, ssl_version);
+ if(result) {
+ failf(data, "unsupported min version passed via CURLOPT_SSLVERSION");
+ return result;
+ }
+ result = mbedtls_version_from_curl(&mbedtls_ver_max, ssl_version_max >> 16);
+ if(result) {
+ failf(data, "unsupported max version passed via CURLOPT_SSLVERSION");
+ return result;
+ }
+
+ mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3,
+ mbedtls_ver_min);
+ mbedtls_ssl_conf_max_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3,
+ mbedtls_ver_max);
+
+ return result;
+}
+
+static CURLcode
+mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ const char * const ssl_cafile =
+ /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+ (ca_info_blob ? NULL : conn_config->CAfile);
+ const bool verifypeer = conn_config->verifypeer;
+ const char * const ssl_capath = conn_config->CApath;
+ char * const ssl_cert = ssl_config->primary.clientcert;
+ const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
+ const char * const ssl_crlfile = ssl_config->primary.CRLfile;
+ const char *hostname = connssl->hostname;
+ int ret = -1;
+ char errorbuf[128];
+
+ DEBUGASSERT(backend);
+
+ if((conn_config->version == CURL_SSLVERSION_SSLv2) ||
+ (conn_config->version == CURL_SSLVERSION_SSLv3)) {
+ failf(data, "Not supported SSL version");
+ return CURLE_NOT_BUILT_IN;
+ }
+
+#ifdef THREADING_SUPPORT
+ entropy_init_mutex(&ts_entropy);
+ mbedtls_ctr_drbg_init(&backend->ctr_drbg);
+
+ ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, entropy_func_mutex,
+ &ts_entropy, NULL, 0);
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s",
+ -ret, errorbuf);
+ return CURLE_FAILED_INIT;
+ }
+#else
+ mbedtls_entropy_init(&backend->entropy);
+ mbedtls_ctr_drbg_init(&backend->ctr_drbg);
+
+ ret = mbedtls_ctr_drbg_seed(&backend->ctr_drbg, mbedtls_entropy_func,
+ &backend->entropy, NULL, 0);
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s",
+ -ret, errorbuf);
+ return CURLE_FAILED_INIT;
+ }
+#endif /* THREADING_SUPPORT */
+
+ /* Load the trusted CA */
+ mbedtls_x509_crt_init(&backend->cacert);
+
+ if(ca_info_blob && verifypeer) {
+ /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null
+ terminated even when provided the exact length, forcing us to waste
+ extra memory here. */
+ unsigned char *newblob = malloc(ca_info_blob->len + 1);
+ if(!newblob)
+ return CURLE_OUT_OF_MEMORY;
+ memcpy(newblob, ca_info_blob->data, ca_info_blob->len);
+ newblob[ca_info_blob->len] = 0; /* null terminate */
+ ret = mbedtls_x509_crt_parse(&backend->cacert, newblob,
+ ca_info_blob->len + 1);
+ free(newblob);
+ if(ret<0) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error importing ca cert blob - mbedTLS: (-0x%04X) %s",
+ -ret, errorbuf);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
+
+ if(ssl_cafile && verifypeer) {
+#ifdef MBEDTLS_FS_IO
+ ret = mbedtls_x509_crt_parse_file(&backend->cacert, ssl_cafile);
+
+ if(ret<0) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error reading ca cert file %s - mbedTLS: (-0x%04X) %s",
+ ssl_cafile, -ret, errorbuf);
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+#else
+ failf(data, "mbedtls: functions that use the filesystem not built in");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+
+ if(ssl_capath) {
+#ifdef MBEDTLS_FS_IO
+ ret = mbedtls_x509_crt_parse_path(&backend->cacert, ssl_capath);
+
+ if(ret<0) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error reading ca cert path %s - mbedTLS: (-0x%04X) %s",
+ ssl_capath, -ret, errorbuf);
+
+ if(verifypeer)
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+#else
+ failf(data, "mbedtls: functions that use the filesystem not built in");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+
+ /* Load the client certificate */
+ mbedtls_x509_crt_init(&backend->clicert);
+
+ if(ssl_cert) {
+#ifdef MBEDTLS_FS_IO
+ ret = mbedtls_x509_crt_parse_file(&backend->clicert, ssl_cert);
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error reading client cert file %s - mbedTLS: (-0x%04X) %s",
+ ssl_cert, -ret, errorbuf);
+
+ return CURLE_SSL_CERTPROBLEM;
+ }
+#else
+ failf(data, "mbedtls: functions that use the filesystem not built in");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+
+ if(ssl_cert_blob) {
+ /* Unfortunately, mbedtls_x509_crt_parse() requires the data to be null
+ terminated even when provided the exact length, forcing us to waste
+ extra memory here. */
+ unsigned char *newblob = malloc(ssl_cert_blob->len + 1);
+ if(!newblob)
+ return CURLE_OUT_OF_MEMORY;
+ memcpy(newblob, ssl_cert_blob->data, ssl_cert_blob->len);
+ newblob[ssl_cert_blob->len] = 0; /* null terminate */
+ ret = mbedtls_x509_crt_parse(&backend->clicert, newblob,
+ ssl_cert_blob->len + 1);
+ free(newblob);
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s",
+ ssl_config->key, -ret, errorbuf);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
+
+ /* Load the client private key */
+ mbedtls_pk_init(&backend->pk);
+
+ if(ssl_config->key || ssl_config->key_blob) {
+ if(ssl_config->key) {
+#ifdef MBEDTLS_FS_IO
+#if MBEDTLS_VERSION_NUMBER >= 0x03000000
+ ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key,
+ ssl_config->key_passwd,
+ mbedtls_ctr_drbg_random,
+ &backend->ctr_drbg);
+#else
+ ret = mbedtls_pk_parse_keyfile(&backend->pk, ssl_config->key,
+ ssl_config->key_passwd);
+#endif
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error reading private key %s - mbedTLS: (-0x%04X) %s",
+ ssl_config->key, -ret, errorbuf);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+#else
+ failf(data, "mbedtls: functions that use the filesystem not built in");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+ else {
+ const struct curl_blob *ssl_key_blob = ssl_config->key_blob;
+ const unsigned char *key_data =
+ (const unsigned char *)ssl_key_blob->data;
+ const char *passwd = ssl_config->key_passwd;
+#if MBEDTLS_VERSION_NUMBER >= 0x03000000
+ ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len,
+ (const unsigned char *)passwd,
+ passwd ? strlen(passwd) : 0,
+ mbedtls_ctr_drbg_random,
+ &backend->ctr_drbg);
+#else
+ ret = mbedtls_pk_parse_key(&backend->pk, key_data, ssl_key_blob->len,
+ (const unsigned char *)passwd,
+ passwd ? strlen(passwd) : 0);
+#endif
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error parsing private key - mbedTLS: (-0x%04X) %s",
+ -ret, errorbuf);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
+
+ if(ret == 0 && !(mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_RSA) ||
+ mbedtls_pk_can_do(&backend->pk, MBEDTLS_PK_ECKEY)))
+ ret = MBEDTLS_ERR_PK_TYPE_MISMATCH;
+ }
+
+ /* Load the CRL */
+#ifdef MBEDTLS_X509_CRL_PARSE_C
+ mbedtls_x509_crl_init(&backend->crl);
+
+ if(ssl_crlfile) {
+#ifdef MBEDTLS_FS_IO
+ ret = mbedtls_x509_crl_parse_file(&backend->crl, ssl_crlfile);
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "Error reading CRL file %s - mbedTLS: (-0x%04X) %s",
+ ssl_crlfile, -ret, errorbuf);
+
+ return CURLE_SSL_CRL_BADFILE;
+ }
+#else
+ failf(data, "mbedtls: functions that use the filesystem not built in");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+#else
+ if(ssl_crlfile) {
+ failf(data, "mbedtls: crl support not built in");
+ return CURLE_NOT_BUILT_IN;
+ }
+#endif
+
+ infof(data, "mbedTLS: Connecting to %s:%d", hostname, connssl->port);
+
+ mbedtls_ssl_config_init(&backend->config);
+ ret = mbedtls_ssl_config_defaults(&backend->config,
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT);
+ if(ret) {
+ failf(data, "mbedTLS: ssl_config failed");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ mbedtls_ssl_init(&backend->ssl);
+ if(mbedtls_ssl_setup(&backend->ssl, &backend->config)) {
+ failf(data, "mbedTLS: ssl_init failed");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ /* new profile with RSA min key len = 1024 ... */
+ mbedtls_ssl_conf_cert_profile(&backend->config,
+ &mbedtls_x509_crt_profile_fr);
+
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+#if MBEDTLS_VERSION_NUMBER < 0x03000000
+ mbedtls_ssl_conf_min_version(&backend->config, MBEDTLS_SSL_MAJOR_VERSION_3,
+ MBEDTLS_SSL_MINOR_VERSION_1);
+ infof(data, "mbedTLS: Set min SSL version to TLS 1.0");
+ break;
+#endif
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1_1:
+ case CURL_SSLVERSION_TLSv1_2:
+ case CURL_SSLVERSION_TLSv1_3:
+ {
+ CURLcode result = set_ssl_version_min_max(cf, data);
+ if(result != CURLE_OK)
+ return result;
+ break;
+ }
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ mbedtls_ssl_conf_authmode(&backend->config, MBEDTLS_SSL_VERIFY_OPTIONAL);
+
+ mbedtls_ssl_conf_rng(&backend->config, mbedtls_ctr_drbg_random,
+ &backend->ctr_drbg);
+ mbedtls_ssl_set_bio(&backend->ssl, cf, bio_cf_write, bio_cf_read,
+ NULL /* rev_timeout() */);
+
+ mbedtls_ssl_conf_ciphersuites(&backend->config,
+ mbedtls_ssl_list_ciphersuites());
+
+#if defined(MBEDTLS_SSL_RENEGOTIATION)
+ mbedtls_ssl_conf_renegotiation(&backend->config,
+ MBEDTLS_SSL_RENEGOTIATION_ENABLED);
+#endif
+
+#if defined(MBEDTLS_SSL_SESSION_TICKETS)
+ mbedtls_ssl_conf_session_tickets(&backend->config,
+ MBEDTLS_SSL_SESSION_TICKETS_DISABLED);
+#endif
+
+ /* Check if there's a cached ID we can/should use here! */
+ if(ssl_config->primary.sessionid) {
+ void *old_session = NULL;
+
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, &old_session, NULL)) {
+ ret = mbedtls_ssl_set_session(&backend->ssl, old_session);
+ if(ret) {
+ Curl_ssl_sessionid_unlock(data);
+ failf(data, "mbedtls_ssl_set_session returned -0x%x", -ret);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ infof(data, "mbedTLS re-using session");
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+ mbedtls_ssl_conf_ca_chain(&backend->config,
+ &backend->cacert,
+#ifdef MBEDTLS_X509_CRL_PARSE_C
+ &backend->crl);
+#else
+ NULL);
+#endif
+
+ if(ssl_config->key || ssl_config->key_blob) {
+ mbedtls_ssl_conf_own_cert(&backend->config,
+ &backend->clicert, &backend->pk);
+ }
+ {
+ char *snihost = Curl_ssl_snihost(data, hostname, NULL);
+ if(!snihost || mbedtls_ssl_set_hostname(&backend->ssl, snihost)) {
+ /* mbedtls_ssl_set_hostname() sets the name to use in CN/SAN checks and
+ the name to set in the SNI extension. So even if curl connects to a
+ host specified as an IP address, this function must be used. */
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+#ifdef HAS_ALPN
+ if(cf->conn->bits.tls_enable_alpn) {
+ const char **p = &backend->protocols[0];
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2)
+ *p++ = ALPN_H2;
+#endif
+ *p++ = ALPN_HTTP_1_1;
+ *p = NULL;
+ /* this function doesn't clone the protocols array, which is why we need
+ to keep it around */
+ if(mbedtls_ssl_conf_alpn_protocols(&backend->config,
+ &backend->protocols[0])) {
+ failf(data, "Failed setting ALPN protocols");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ for(p = &backend->protocols[0]; *p; ++p)
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, *p);
+ }
+#endif
+
+#ifdef MBEDTLS_DEBUG
+ /* In order to make that work in mbedtls MBEDTLS_DEBUG_C must be defined. */
+ mbedtls_ssl_conf_dbg(&backend->config, mbed_debug, data);
+ /* - 0 No debug
+ * - 1 Error
+ * - 2 State change
+ * - 3 Informational
+ * - 4 Verbose
+ */
+ mbedtls_debug_set_threshold(4);
+#endif
+
+ /* give application a chance to interfere with mbedTLS set up. */
+ if(data->set.ssl.fsslctx) {
+ ret = (*data->set.ssl.fsslctx)(data, &backend->config,
+ data->set.ssl.fsslctxp);
+ if(ret) {
+ failf(data, "error signaled by ssl ctx callback");
+ return ret;
+ }
+ }
+
+ connssl->connecting_state = ssl_connect_2;
+
+ return CURLE_OK;
+}
+
+static CURLcode
+mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ int ret;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ const mbedtls_x509_crt *peercert;
+ const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]:
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+
+ DEBUGASSERT(backend);
+
+ ret = mbedtls_ssl_handshake(&backend->ssl);
+
+ if(ret == MBEDTLS_ERR_SSL_WANT_READ) {
+ connssl->connecting_state = ssl_connect_2_reading;
+ return CURLE_OK;
+ }
+ else if(ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ connssl->connecting_state = ssl_connect_2_writing;
+ return CURLE_OK;
+ }
+ else if(ret) {
+ char errorbuf[128];
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "ssl_handshake returned - mbedTLS: (-0x%04X) %s",
+ -ret, errorbuf);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ infof(data, "mbedTLS: Handshake complete, cipher is %s",
+ mbedtls_ssl_get_ciphersuite(&backend->ssl));
+
+ ret = mbedtls_ssl_get_verify_result(&backend->ssl);
+
+ if(!conn_config->verifyhost)
+ /* Ignore hostname errors if verifyhost is disabled */
+ ret &= ~MBEDTLS_X509_BADCERT_CN_MISMATCH;
+
+ if(ret && conn_config->verifypeer) {
+ if(ret & MBEDTLS_X509_BADCERT_EXPIRED)
+ failf(data, "Cert verify failed: BADCERT_EXPIRED");
+
+ else if(ret & MBEDTLS_X509_BADCERT_REVOKED)
+ failf(data, "Cert verify failed: BADCERT_REVOKED");
+
+ else if(ret & MBEDTLS_X509_BADCERT_CN_MISMATCH)
+ failf(data, "Cert verify failed: BADCERT_CN_MISMATCH");
+
+ else if(ret & MBEDTLS_X509_BADCERT_NOT_TRUSTED)
+ failf(data, "Cert verify failed: BADCERT_NOT_TRUSTED");
+
+ else if(ret & MBEDTLS_X509_BADCERT_FUTURE)
+ failf(data, "Cert verify failed: BADCERT_FUTURE");
+
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ peercert = mbedtls_ssl_get_peer_cert(&backend->ssl);
+
+ if(peercert && data->set.verbose) {
+ const size_t bufsize = 16384;
+ char *buffer = malloc(bufsize);
+
+ if(!buffer)
+ return CURLE_OUT_OF_MEMORY;
+
+ if(mbedtls_x509_crt_info(buffer, bufsize, "* ", peercert) > 0)
+ infof(data, "Dumping cert info: %s", buffer);
+ else
+ infof(data, "Unable to dump certificate information");
+
+ free(buffer);
+ }
+
+ if(pinnedpubkey) {
+ int size;
+ CURLcode result;
+ mbedtls_x509_crt *p = NULL;
+ unsigned char *pubkey = NULL;
+
+#if MBEDTLS_VERSION_NUMBER == 0x03000000
+ if(!peercert || !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p) ||
+ !peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len)) {
+#else
+ if(!peercert || !peercert->raw.p || !peercert->raw.len) {
+#endif
+ failf(data, "Failed due to missing peer certificate");
+ return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ }
+
+ p = calloc(1, sizeof(*p));
+
+ if(!p)
+ return CURLE_OUT_OF_MEMORY;
+
+ pubkey = malloc(PUB_DER_MAX_BYTES);
+
+ if(!pubkey) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto pinnedpubkey_error;
+ }
+
+ mbedtls_x509_crt_init(p);
+
+ /* Make a copy of our const peercert because mbedtls_pk_write_pubkey_der
+ needs a non-const key, for now.
+ https://github.com/ARMmbed/mbedtls/issues/396 */
+#if MBEDTLS_VERSION_NUMBER == 0x03000000
+ if(mbedtls_x509_crt_parse_der(p,
+ peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(p),
+ peercert->MBEDTLS_PRIVATE(raw).MBEDTLS_PRIVATE(len))) {
+#else
+ if(mbedtls_x509_crt_parse_der(p, peercert->raw.p, peercert->raw.len)) {
+#endif
+ failf(data, "Failed copying peer certificate");
+ result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ goto pinnedpubkey_error;
+ }
+
+#if MBEDTLS_VERSION_NUMBER == 0x03000000
+ size = mbedtls_pk_write_pubkey_der(&p->MBEDTLS_PRIVATE(pk), pubkey,
+ PUB_DER_MAX_BYTES);
+#else
+ size = mbedtls_pk_write_pubkey_der(&p->pk, pubkey, PUB_DER_MAX_BYTES);
+#endif
+
+ if(size <= 0) {
+ failf(data, "Failed copying public key from peer certificate");
+ result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ goto pinnedpubkey_error;
+ }
+
+ /* mbedtls_pk_write_pubkey_der writes data at the end of the buffer. */
+ result = Curl_pin_peer_pubkey(data,
+ pinnedpubkey,
+ &pubkey[PUB_DER_MAX_BYTES - size], size);
+ pinnedpubkey_error:
+ mbedtls_x509_crt_free(p);
+ free(p);
+ free(pubkey);
+ if(result) {
+ return result;
+ }
+ }
+
+#ifdef HAS_ALPN
+ if(cf->conn->bits.tls_enable_alpn) {
+ const char *next_protocol = mbedtls_ssl_get_alpn_protocol(&backend->ssl);
+
+ if(next_protocol) {
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, next_protocol);
+#ifdef USE_HTTP2
+ if(!strncmp(next_protocol, ALPN_H2, ALPN_H2_LENGTH) &&
+ !next_protocol[ALPN_H2_LENGTH]) {
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+ }
+ else
+#endif
+ if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH) &&
+ !next_protocol[ALPN_HTTP_1_1_LENGTH]) {
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+ }
+ }
+ else {
+ infof(data, VTLS_INFOF_NO_ALPN);
+ }
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+ }
+#endif
+
+ connssl->connecting_state = ssl_connect_3;
+ infof(data, "SSL connected");
+
+ return CURLE_OK;
+}
+
+static CURLcode
+mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ CURLcode retcode = CURLE_OK;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+
+ DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
+ DEBUGASSERT(backend);
+
+ if(ssl_config->primary.sessionid) {
+ int ret;
+ mbedtls_ssl_session *our_ssl_sessionid;
+ void *old_ssl_sessionid = NULL;
+ bool added = FALSE;
+
+ our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session));
+ if(!our_ssl_sessionid)
+ return CURLE_OUT_OF_MEMORY;
+
+ mbedtls_ssl_session_init(our_ssl_sessionid);
+
+ ret = mbedtls_ssl_get_session(&backend->ssl, our_ssl_sessionid);
+ if(ret) {
+ if(ret != MBEDTLS_ERR_SSL_ALLOC_FAILED)
+ mbedtls_ssl_session_free(our_ssl_sessionid);
+ free(our_ssl_sessionid);
+ failf(data, "mbedtls_ssl_get_session returned -0x%x", -ret);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ /* If there's already a matching session in the cache, delete it */
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL))
+ Curl_ssl_delsessionid(data, old_ssl_sessionid);
+
+ retcode = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid,
+ 0, &added);
+ Curl_ssl_sessionid_unlock(data);
+ if(!added) {
+ mbedtls_ssl_session_free(our_ssl_sessionid);
+ free(our_ssl_sessionid);
+ }
+ if(retcode) {
+ failf(data, "failed to store ssl session");
+ return retcode;
+ }
+ }
+
+ connssl->connecting_state = ssl_connect_done;
+
+ return CURLE_OK;
+}
+
+static ssize_t mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *mem, size_t len,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ int ret = -1;
+
+ (void)data;
+ DEBUGASSERT(backend);
+ ret = mbedtls_ssl_write(&backend->ssl, (unsigned char *)mem, len);
+
+ if(ret < 0) {
+ *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_WRITE) ?
+ CURLE_AGAIN : CURLE_SEND_ERROR;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+static void mbedtls_close_all(struct Curl_easy *data)
+{
+ (void)data;
+}
+
+static void mbedtls_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ char buf[32];
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ /* Maybe the server has already sent a close notify alert.
+ Read it to avoid an RST on the TCP connection. */
+ (void)mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf, sizeof(buf));
+
+ mbedtls_pk_free(&backend->pk);
+ mbedtls_x509_crt_free(&backend->clicert);
+ mbedtls_x509_crt_free(&backend->cacert);
+#ifdef MBEDTLS_X509_CRL_PARSE_C
+ mbedtls_x509_crl_free(&backend->crl);
+#endif
+ mbedtls_ssl_config_free(&backend->config);
+ mbedtls_ssl_free(&backend->ssl);
+ mbedtls_ctr_drbg_free(&backend->ctr_drbg);
+#ifndef THREADING_SUPPORT
+ mbedtls_entropy_free(&backend->entropy);
+#endif /* THREADING_SUPPORT */
+}
+
+static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t buffersize,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ int ret = -1;
+ ssize_t len = -1;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ ret = mbedtls_ssl_read(&backend->ssl, (unsigned char *)buf,
+ buffersize);
+
+ if(ret <= 0) {
+ if(ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)
+ return 0;
+
+ *curlcode = (ret == MBEDTLS_ERR_SSL_WANT_READ) ?
+ CURLE_AGAIN : CURLE_RECV_ERROR;
+ return -1;
+ }
+
+ len = ret;
+
+ return len;
+}
+
+static void mbedtls_session_free(void *ptr)
+{
+ mbedtls_ssl_session_free(ptr);
+ free(ptr);
+}
+
+static size_t mbedtls_version(char *buffer, size_t size)
+{
+#ifdef MBEDTLS_VERSION_C
+ /* if mbedtls_version_get_number() is available it is better */
+ unsigned int version = mbedtls_version_get_number();
+ return msnprintf(buffer, size, "mbedTLS/%u.%u.%u", version>>24,
+ (version>>16)&0xff, (version>>8)&0xff);
+#else
+ return msnprintf(buffer, size, "mbedTLS/%s", MBEDTLS_VERSION_STRING);
+#endif
+}
+
+static CURLcode mbedtls_random(struct Curl_easy *data,
+ unsigned char *entropy, size_t length)
+{
+#if defined(MBEDTLS_CTR_DRBG_C)
+ int ret = -1;
+ char errorbuf[128];
+ mbedtls_entropy_context ctr_entropy;
+ mbedtls_ctr_drbg_context ctr_drbg;
+ mbedtls_entropy_init(&ctr_entropy);
+ mbedtls_ctr_drbg_init(&ctr_drbg);
+
+ ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
+ &ctr_entropy, NULL, 0);
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "mbedtls_ctr_drbg_seed returned (-0x%04X) %s",
+ -ret, errorbuf);
+ }
+ else {
+ ret = mbedtls_ctr_drbg_random(&ctr_drbg, entropy, length);
+
+ if(ret) {
+ mbedtls_strerror(ret, errorbuf, sizeof(errorbuf));
+ failf(data, "mbedtls_ctr_drbg_random returned (-0x%04X) %s",
+ -ret, errorbuf);
+ }
+ }
+
+ mbedtls_ctr_drbg_free(&ctr_drbg);
+ mbedtls_entropy_free(&ctr_entropy);
+
+ return ret == 0 ? CURLE_OK : CURLE_FAILED_INIT;
+#elif defined(MBEDTLS_HAVEGE_C)
+ mbedtls_havege_state hs;
+ mbedtls_havege_init(&hs);
+ mbedtls_havege_random(&hs, entropy, length);
+ mbedtls_havege_free(&hs);
+ return CURLE_OK;
+#else
+ return CURLE_NOT_BUILT_IN;
+#endif
+}
+
+static CURLcode
+mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data,
+ bool nonblocking,
+ bool *done)
+{
+ CURLcode retcode;
+ struct ssl_connect_data *connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ timediff_t timeout_ms;
+ int what;
+
+ /* check if the connection has already been established */
+ if(ssl_connection_complete == connssl->state) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ if(ssl_connect_1 == connssl->connecting_state) {
+ /* Find out how much more time we're allowed */
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ retcode = mbed_connect_step1(cf, data);
+ if(retcode)
+ return retcode;
+ }
+
+ while(ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state) {
+
+ /* check allowed time left */
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ /* if ssl is expecting something, check if it's available. */
+ if(connssl->connecting_state == ssl_connect_2_reading
+ || connssl->connecting_state == ssl_connect_2_writing) {
+
+ curl_socket_t writefd = ssl_connect_2_writing ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ nonblocking ? 0 : timeout_ms);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else if(0 == what) {
+ if(nonblocking) {
+ *done = FALSE;
+ return CURLE_OK;
+ }
+ else {
+ /* timeout */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+ /* socket is readable or writable */
+ }
+
+ /* Run transaction, and return to the caller if it failed or if
+ * this connection is part of a multi handle and this loop would
+ * execute again. This permits the owner of a multi handle to
+ * abort a connection attempt before step2 has completed while
+ * ensuring that a client using select() or epoll() will always
+ * have a valid fdset to wait on.
+ */
+ retcode = mbed_connect_step2(cf, data);
+ if(retcode || (nonblocking &&
+ (ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state)))
+ return retcode;
+
+ } /* repeat step2 until all transactions are done. */
+
+ if(ssl_connect_3 == connssl->connecting_state) {
+ retcode = mbed_connect_step3(cf, data);
+ if(retcode)
+ return retcode;
+ }
+
+ if(ssl_connect_done == connssl->connecting_state) {
+ connssl->state = ssl_connection_complete;
+ *done = TRUE;
+ }
+ else
+ *done = FALSE;
+
+ /* Reset our connect state machine */
+ connssl->connecting_state = ssl_connect_1;
+
+ return CURLE_OK;
+}
+
+static CURLcode mbedtls_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return mbed_connect_common(cf, data, TRUE, done);
+}
+
+
+static CURLcode mbedtls_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode retcode;
+ bool done = FALSE;
+
+ retcode = mbed_connect_common(cf, data, FALSE, &done);
+ if(retcode)
+ return retcode;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+/*
+ * return 0 error initializing SSL
+ * return 1 SSL initialized successfully
+ */
+static int mbedtls_init(void)
+{
+ return Curl_mbedtlsthreadlock_thread_setup();
+}
+
+static void mbedtls_cleanup(void)
+{
+ (void)Curl_mbedtlsthreadlock_thread_cleanup();
+}
+
+static bool mbedtls_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx = cf->ctx;
+
+ (void)data;
+ DEBUGASSERT(ctx && ctx->backend);
+ return mbedtls_ssl_get_bytes_avail(&ctx->backend->ssl) != 0;
+}
+
+static CURLcode mbedtls_sha256sum(const unsigned char *input,
+ size_t inputlen,
+ unsigned char *sha256sum,
+ size_t sha256len UNUSED_PARAM)
+{
+ /* TODO: explain this for different mbedtls 2.x vs 3 version */
+ (void)sha256len;
+#if MBEDTLS_VERSION_NUMBER < 0x02070000
+ mbedtls_sha256(input, inputlen, sha256sum, 0);
+#else
+ /* returns 0 on success, otherwise failure */
+#if MBEDTLS_VERSION_NUMBER >= 0x03000000
+ if(mbedtls_sha256(input, inputlen, sha256sum, 0) != 0)
+#else
+ if(mbedtls_sha256_ret(input, inputlen, sha256sum, 0) != 0)
+#endif
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+#endif
+ return CURLE_OK;
+}
+
+static void *mbedtls_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ (void)info;
+ DEBUGASSERT(backend);
+ return &backend->ssl;
+}
+
+const struct Curl_ssl Curl_ssl_mbedtls = {
+ { CURLSSLBACKEND_MBEDTLS, "mbedtls" }, /* info */
+
+ SSLSUPP_CA_PATH |
+ SSLSUPP_CAINFO_BLOB |
+ SSLSUPP_PINNEDPUBKEY |
+ SSLSUPP_SSL_CTX |
+ SSLSUPP_HTTPS_PROXY,
+
+ sizeof(struct ssl_backend_data),
+
+ mbedtls_init, /* init */
+ mbedtls_cleanup, /* cleanup */
+ mbedtls_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ Curl_none_shutdown, /* shutdown */
+ mbedtls_data_pending, /* data_pending */
+ mbedtls_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ mbedtls_connect, /* connect */
+ mbedtls_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ mbedtls_get_internals, /* get_internals */
+ mbedtls_close, /* close_one */
+ mbedtls_close_all, /* close_all */
+ mbedtls_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ mbedtls_sha256sum, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ mbed_recv, /* recv decrypted data */
+ mbed_send, /* send data to encrypt */
+};
+
+#endif /* USE_MBEDTLS */
diff --git a/libs/libcurl/src/vtls/mbedtls.h b/libs/libcurl/src/vtls/mbedtls.h index ec3b43bf9c..89131273a6 100644 --- a/libs/libcurl/src/vtls/mbedtls.h +++ b/libs/libcurl/src/vtls/mbedtls.h @@ -1,34 +1,34 @@ -#ifndef HEADER_CURL_MBEDTLS_H -#define HEADER_CURL_MBEDTLS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_MBEDTLS - -extern const struct Curl_ssl Curl_ssl_mbedtls; - -#endif /* USE_MBEDTLS */ -#endif /* HEADER_CURL_MBEDTLS_H */ +#ifndef HEADER_CURL_MBEDTLS_H
+#define HEADER_CURL_MBEDTLS_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_MBEDTLS
+
+extern const struct Curl_ssl Curl_ssl_mbedtls;
+
+#endif /* USE_MBEDTLS */
+#endif /* HEADER_CURL_MBEDTLS_H */
diff --git a/libs/libcurl/src/vtls/mbedtls_threadlock.c b/libs/libcurl/src/vtls/mbedtls_threadlock.c index 3971e69b25..c4a3ae2e84 100644 --- a/libs/libcurl/src/vtls/mbedtls_threadlock.c +++ b/libs/libcurl/src/vtls/mbedtls_threadlock.c @@ -1,135 +1,134 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010, 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#if defined(USE_MBEDTLS) && \ - ((defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ - (defined(USE_THREADS_WIN32) && defined(HAVE_PROCESS_H))) - -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) -# include <pthread.h> -# define MBEDTLS_MUTEX_T pthread_mutex_t -#elif defined(USE_THREADS_WIN32) && defined(HAVE_PROCESS_H) -# include <process.h> -# define MBEDTLS_MUTEX_T HANDLE -#endif - -#include "mbedtls_threadlock.h" -#include "curl_printf.h" -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* number of thread locks */ -#define NUMT 2 - -/* This array will store all of the mutexes available to Mbedtls. */ -static MBEDTLS_MUTEX_T *mutex_buf = NULL; - -int Curl_mbedtlsthreadlock_thread_setup(void) -{ - int i; - - mutex_buf = calloc(NUMT * sizeof(MBEDTLS_MUTEX_T), 1); - if(!mutex_buf) - return 0; /* error, no number of threads defined */ - - for(i = 0; i < NUMT; i++) { -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) - if(pthread_mutex_init(&mutex_buf[i], NULL)) - return 0; /* pthread_mutex_init failed */ -#elif defined(USE_THREADS_WIN32) && defined(HAVE_PROCESS_H) - mutex_buf[i] = CreateMutex(0, FALSE, 0); - if(mutex_buf[i] == 0) - return 0; /* CreateMutex failed */ -#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ - } - - return 1; /* OK */ -} - -int Curl_mbedtlsthreadlock_thread_cleanup(void) -{ - int i; - - if(!mutex_buf) - return 0; /* error, no threads locks defined */ - - for(i = 0; i < NUMT; i++) { -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) - if(pthread_mutex_destroy(&mutex_buf[i])) - return 0; /* pthread_mutex_destroy failed */ -#elif defined(USE_THREADS_WIN32) && defined(HAVE_PROCESS_H) - if(!CloseHandle(mutex_buf[i])) - return 0; /* CloseHandle failed */ -#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ - } - free(mutex_buf); - mutex_buf = NULL; - - return 1; /* OK */ -} - -int Curl_mbedtlsthreadlock_lock_function(int n) -{ - if(n < NUMT) { -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) - if(pthread_mutex_lock(&mutex_buf[n])) { - DEBUGF(fprintf(stderr, - "Error: mbedtlsthreadlock_lock_function failed\n")); - return 0; /* pthread_mutex_lock failed */ - } -#elif defined(USE_THREADS_WIN32) && defined(HAVE_PROCESS_H) - if(WaitForSingleObject(mutex_buf[n], INFINITE) == WAIT_FAILED) { - DEBUGF(fprintf(stderr, - "Error: mbedtlsthreadlock_lock_function failed\n")); - return 0; /* pthread_mutex_lock failed */ - } -#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ - } - return 1; /* OK */ -} - -int Curl_mbedtlsthreadlock_unlock_function(int n) -{ - if(n < NUMT) { -#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H) - if(pthread_mutex_unlock(&mutex_buf[n])) { - DEBUGF(fprintf(stderr, - "Error: mbedtlsthreadlock_unlock_function failed\n")); - return 0; /* pthread_mutex_unlock failed */ - } -#elif defined(USE_THREADS_WIN32) && defined(HAVE_PROCESS_H) - if(!ReleaseMutex(mutex_buf[n])) { - DEBUGF(fprintf(stderr, - "Error: mbedtlsthreadlock_unlock_function failed\n")); - return 0; /* pthread_mutex_lock failed */ - } -#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */ - } - return 1; /* OK */ -} - -#endif /* USE_MBEDTLS */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2010, 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#if defined(USE_MBEDTLS) && \
+ ((defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \
+ defined(USE_THREADS_WIN32))
+
+#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
+# include <pthread.h>
+# define MBEDTLS_MUTEX_T pthread_mutex_t
+#elif defined(USE_THREADS_WIN32)
+# define MBEDTLS_MUTEX_T HANDLE
+#endif
+
+#include "mbedtls_threadlock.h"
+#include "curl_printf.h"
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+/* number of thread locks */
+#define NUMT 2
+
+/* This array will store all of the mutexes available to Mbedtls. */
+static MBEDTLS_MUTEX_T *mutex_buf = NULL;
+
+int Curl_mbedtlsthreadlock_thread_setup(void)
+{
+ int i;
+
+ mutex_buf = calloc(NUMT * sizeof(MBEDTLS_MUTEX_T), 1);
+ if(!mutex_buf)
+ return 0; /* error, no number of threads defined */
+
+ for(i = 0; i < NUMT; i++) {
+#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
+ if(pthread_mutex_init(&mutex_buf[i], NULL))
+ return 0; /* pthread_mutex_init failed */
+#elif defined(USE_THREADS_WIN32)
+ mutex_buf[i] = CreateMutex(0, FALSE, 0);
+ if(mutex_buf[i] == 0)
+ return 0; /* CreateMutex failed */
+#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */
+ }
+
+ return 1; /* OK */
+}
+
+int Curl_mbedtlsthreadlock_thread_cleanup(void)
+{
+ int i;
+
+ if(!mutex_buf)
+ return 0; /* error, no threads locks defined */
+
+ for(i = 0; i < NUMT; i++) {
+#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
+ if(pthread_mutex_destroy(&mutex_buf[i]))
+ return 0; /* pthread_mutex_destroy failed */
+#elif defined(USE_THREADS_WIN32)
+ if(!CloseHandle(mutex_buf[i]))
+ return 0; /* CloseHandle failed */
+#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */
+ }
+ free(mutex_buf);
+ mutex_buf = NULL;
+
+ return 1; /* OK */
+}
+
+int Curl_mbedtlsthreadlock_lock_function(int n)
+{
+ if(n < NUMT) {
+#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
+ if(pthread_mutex_lock(&mutex_buf[n])) {
+ DEBUGF(fprintf(stderr,
+ "Error: mbedtlsthreadlock_lock_function failed\n"));
+ return 0; /* pthread_mutex_lock failed */
+ }
+#elif defined(USE_THREADS_WIN32)
+ if(WaitForSingleObject(mutex_buf[n], INFINITE) == WAIT_FAILED) {
+ DEBUGF(fprintf(stderr,
+ "Error: mbedtlsthreadlock_lock_function failed\n"));
+ return 0; /* pthread_mutex_lock failed */
+ }
+#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */
+ }
+ return 1; /* OK */
+}
+
+int Curl_mbedtlsthreadlock_unlock_function(int n)
+{
+ if(n < NUMT) {
+#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
+ if(pthread_mutex_unlock(&mutex_buf[n])) {
+ DEBUGF(fprintf(stderr,
+ "Error: mbedtlsthreadlock_unlock_function failed\n"));
+ return 0; /* pthread_mutex_unlock failed */
+ }
+#elif defined(USE_THREADS_WIN32)
+ if(!ReleaseMutex(mutex_buf[n])) {
+ DEBUGF(fprintf(stderr,
+ "Error: mbedtlsthreadlock_unlock_function failed\n"));
+ return 0; /* pthread_mutex_lock failed */
+ }
+#endif /* USE_THREADS_POSIX && HAVE_PTHREAD_H */
+ }
+ return 1; /* OK */
+}
+
+#endif /* USE_MBEDTLS */
diff --git a/libs/libcurl/src/vtls/mbedtls_threadlock.h b/libs/libcurl/src/vtls/mbedtls_threadlock.h index 3a50d03817..bc268f93eb 100644 --- a/libs/libcurl/src/vtls/mbedtls_threadlock.h +++ b/libs/libcurl/src/vtls/mbedtls_threadlock.h @@ -1,50 +1,50 @@ -#ifndef HEADER_CURL_MBEDTLS_THREADLOCK_H -#define HEADER_CURL_MBEDTLS_THREADLOCK_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_MBEDTLS - -#if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \ - (defined(USE_THREADS_WIN32) && defined(HAVE_PROCESS_H)) - -int Curl_mbedtlsthreadlock_thread_setup(void); -int Curl_mbedtlsthreadlock_thread_cleanup(void); -int Curl_mbedtlsthreadlock_lock_function(int n); -int Curl_mbedtlsthreadlock_unlock_function(int n); - -#else - -#define Curl_mbedtlsthreadlock_thread_setup() 1 -#define Curl_mbedtlsthreadlock_thread_cleanup() 1 -#define Curl_mbedtlsthreadlock_lock_function(x) 1 -#define Curl_mbedtlsthreadlock_unlock_function(x) 1 - -#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */ - -#endif /* USE_MBEDTLS */ - -#endif /* HEADER_CURL_MBEDTLS_THREADLOCK_H */ +#ifndef HEADER_CURL_MBEDTLS_THREADLOCK_H
+#define HEADER_CURL_MBEDTLS_THREADLOCK_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_MBEDTLS
+
+#if (defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)) || \
+ defined(USE_THREADS_WIN32)
+
+int Curl_mbedtlsthreadlock_thread_setup(void);
+int Curl_mbedtlsthreadlock_thread_cleanup(void);
+int Curl_mbedtlsthreadlock_lock_function(int n);
+int Curl_mbedtlsthreadlock_unlock_function(int n);
+
+#else
+
+#define Curl_mbedtlsthreadlock_thread_setup() 1
+#define Curl_mbedtlsthreadlock_thread_cleanup() 1
+#define Curl_mbedtlsthreadlock_lock_function(x) 1
+#define Curl_mbedtlsthreadlock_unlock_function(x) 1
+
+#endif /* USE_THREADS_POSIX || USE_THREADS_WIN32 */
+
+#endif /* USE_MBEDTLS */
+
+#endif /* HEADER_CURL_MBEDTLS_THREADLOCK_H */
diff --git a/libs/libcurl/src/vtls/nss.c b/libs/libcurl/src/vtls/nss.c index 12cf618f56..350c369d83 100644 --- a/libs/libcurl/src/vtls/nss.c +++ b/libs/libcurl/src/vtls/nss.c @@ -1,2537 +1,2569 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for all NSS-specific code for the TLS/SSL layer. No code - * but vtls.c should ever call or use these functions. - */ - -#include "curl_setup.h" - -#ifdef USE_NSS - -#include "urldata.h" -#include "sendf.h" -#include "formdata.h" /* for the boundary function */ -#include "url.h" /* for the ssl config check function */ -#include "connect.h" -#include "strcase.h" -#include "select.h" -#include "vtls.h" -#include "llist.h" -#include "multiif.h" -#include "curl_printf.h" -#include "nssg.h" -#include <nspr.h> -#include <nss.h> -#include <ssl.h> -#include <sslerr.h> -#include <secerr.h> -#include <secmod.h> -#include <sslproto.h> -#include <prtypes.h> -#include <pk11pub.h> -#include <prio.h> -#include <secitem.h> -#include <secport.h> -#include <certdb.h> -#include <base64.h> -#include <cert.h> -#include <prerror.h> -#include <keyhi.h> /* for SECKEY_DestroyPublicKey() */ -#include <private/pprio.h> /* for PR_ImportTCPSocket */ - -#define NSSVERNUM ((NSS_VMAJOR<<16)|(NSS_VMINOR<<8)|NSS_VPATCH) - -#if NSSVERNUM >= 0x030f00 /* 3.15.0 */ -#include <ocsp.h> -#endif - -#include "strcase.h" -#include "warnless.h" -#include "x509asn1.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#define SSL_DIR "/etc/pki/nssdb" - -/* enough to fit the string "PEM Token #[0|1]" */ -#define SLOTSIZE 13 - -struct ssl_backend_data { - PRFileDesc *handle; - char *client_nickname; - struct Curl_easy *data; - struct Curl_llist obj_list; - PK11GenericObject *obj_clicert; -}; - -static PRLock *nss_initlock = NULL; -static PRLock *nss_crllock = NULL; -static PRLock *nss_findslot_lock = NULL; -static PRLock *nss_trustload_lock = NULL; -static struct Curl_llist nss_crl_list; -static NSSInitContext *nss_context = NULL; -static volatile int initialized = 0; - -/* type used to wrap pointers as list nodes */ -struct ptr_list_wrap { - void *ptr; - struct Curl_llist_element node; -}; - -struct cipher_s { - const char *name; - int num; -}; - -#define PK11_SETATTRS(_attr, _idx, _type, _val, _len) do { \ - CK_ATTRIBUTE *ptr = (_attr) + ((_idx)++); \ - ptr->type = (_type); \ - ptr->pValue = (_val); \ - ptr->ulValueLen = (_len); \ -} while(0) - -#define CERT_NewTempCertificate __CERT_NewTempCertificate - -#define NUM_OF_CIPHERS sizeof(cipherlist)/sizeof(cipherlist[0]) -static const struct cipher_s cipherlist[] = { - /* SSL2 cipher suites */ - {"rc4", SSL_EN_RC4_128_WITH_MD5}, - {"rc4-md5", SSL_EN_RC4_128_WITH_MD5}, - {"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5}, - {"rc2", SSL_EN_RC2_128_CBC_WITH_MD5}, - {"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5}, - {"des", SSL_EN_DES_64_CBC_WITH_MD5}, - {"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5}, - /* SSL3/TLS cipher suites */ - {"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5}, - {"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA}, - {"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA}, - {"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA}, - {"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5}, - {"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5}, - {"rsa_null_md5", SSL_RSA_WITH_NULL_MD5}, - {"rsa_null_sha", SSL_RSA_WITH_NULL_SHA}, - {"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA}, - {"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA}, - {"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA}, - {"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA}, - {"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA}, - {"dhe_rsa_3des_sha", SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA}, - {"dhe_dss_3des_sha", SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA}, - {"dhe_rsa_des_sha", SSL_DHE_RSA_WITH_DES_CBC_SHA}, - {"dhe_dss_des_sha", SSL_DHE_DSS_WITH_DES_CBC_SHA}, - /* TLS 1.0: Exportable 56-bit Cipher Suites. */ - {"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA}, - {"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA}, - /* Ephemeral DH with RC4 bulk encryption */ - {"dhe_dss_rc4_128_sha", TLS_DHE_DSS_WITH_RC4_128_SHA}, - /* AES ciphers. */ - {"dhe_dss_aes_128_cbc_sha", TLS_DHE_DSS_WITH_AES_128_CBC_SHA}, - {"dhe_dss_aes_256_cbc_sha", TLS_DHE_DSS_WITH_AES_256_CBC_SHA}, - {"dhe_rsa_aes_128_cbc_sha", TLS_DHE_RSA_WITH_AES_128_CBC_SHA}, - {"dhe_rsa_aes_256_cbc_sha", TLS_DHE_RSA_WITH_AES_256_CBC_SHA}, - {"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA}, - {"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA}, - /* ECC ciphers. */ - {"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA}, - {"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA}, - {"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA}, - {"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA}, - {"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, - {"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA}, - {"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA}, - {"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA}, - {"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA}, - {"ecdhe_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA}, - {"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA}, - {"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA}, - {"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA}, - {"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, - {"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA}, - {"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA}, - {"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA}, - {"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA}, - {"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA}, -#ifdef TLS_RSA_WITH_NULL_SHA256 - /* new HMAC-SHA256 cipher suites specified in RFC */ - {"rsa_null_sha_256", TLS_RSA_WITH_NULL_SHA256}, - {"rsa_aes_128_cbc_sha_256", TLS_RSA_WITH_AES_128_CBC_SHA256}, - {"rsa_aes_256_cbc_sha_256", TLS_RSA_WITH_AES_256_CBC_SHA256}, - {"dhe_rsa_aes_128_cbc_sha_256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256}, - {"dhe_rsa_aes_256_cbc_sha_256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256}, - {"ecdhe_ecdsa_aes_128_cbc_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}, - {"ecdhe_rsa_aes_128_cbc_sha_256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256}, -#endif -#ifdef TLS_RSA_WITH_AES_128_GCM_SHA256 - /* AES GCM cipher suites in RFC 5288 and RFC 5289 */ - {"rsa_aes_128_gcm_sha_256", TLS_RSA_WITH_AES_128_GCM_SHA256}, - {"dhe_rsa_aes_128_gcm_sha_256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256}, - {"dhe_dss_aes_128_gcm_sha_256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256}, - {"ecdhe_ecdsa_aes_128_gcm_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}, - {"ecdh_ecdsa_aes_128_gcm_sha_256", TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256}, - {"ecdhe_rsa_aes_128_gcm_sha_256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, - {"ecdh_rsa_aes_128_gcm_sha_256", TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256}, -#endif -#ifdef TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - /* cipher suites using SHA384 */ - {"rsa_aes_256_gcm_sha_384", TLS_RSA_WITH_AES_256_GCM_SHA384}, - {"dhe_rsa_aes_256_gcm_sha_384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384}, - {"dhe_dss_aes_256_gcm_sha_384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384}, - {"ecdhe_ecdsa_aes_256_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384}, - {"ecdhe_rsa_aes_256_sha_384", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384}, - {"ecdhe_ecdsa_aes_256_gcm_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, - {"ecdhe_rsa_aes_256_gcm_sha_384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, -#endif -#ifdef TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - /* chacha20-poly1305 cipher suites */ - {"ecdhe_rsa_chacha20_poly1305_sha_256", - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, - {"ecdhe_ecdsa_chacha20_poly1305_sha_256", - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256}, - {"dhe_rsa_chacha20_poly1305_sha_256", - TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, -#endif -#ifdef TLS_AES_256_GCM_SHA384 - {"aes_128_gcm_sha_256", TLS_AES_128_GCM_SHA256}, - {"aes_256_gcm_sha_384", TLS_AES_256_GCM_SHA384}, - {"chacha20_poly1305_sha_256", TLS_CHACHA20_POLY1305_SHA256}, -#endif -#ifdef TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 - /* AES CBC cipher suites in RFC 5246. Introduced in NSS release 3.20 */ - {"dhe_dss_aes_128_sha_256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256}, - {"dhe_dss_aes_256_sha_256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256}, -#endif -#ifdef TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA - /* Camellia cipher suites in RFC 4132/5932. - Introduced in NSS release 3.12 */ - {"dhe_rsa_camellia_128_sha", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA}, - {"dhe_dss_camellia_128_sha", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA}, - {"dhe_rsa_camellia_256_sha", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA}, - {"dhe_dss_camellia_256_sha", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA}, - {"rsa_camellia_128_sha", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA}, - {"rsa_camellia_256_sha", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA}, -#endif -#ifdef TLS_RSA_WITH_SEED_CBC_SHA - /* SEED cipher suite in RFC 4162. Introduced in NSS release 3.12.3 */ - {"rsa_seed_sha", TLS_RSA_WITH_SEED_CBC_SHA}, -#endif -}; - -#if defined(WIN32) -static const char *pem_library = "nsspem.dll"; -static const char *trust_library = "nssckbi.dll"; -#elif defined(__APPLE__) -static const char *pem_library = "libnsspem.dylib"; -static const char *trust_library = "libnssckbi.dylib"; -#else -static const char *pem_library = "libnsspem.so"; -static const char *trust_library = "libnssckbi.so"; -#endif - -static SECMODModule *pem_module = NULL; -static SECMODModule *trust_module = NULL; - -/* NSPR I/O layer we use to detect blocking direction during SSL handshake */ -static PRDescIdentity nspr_io_identity = PR_INVALID_IO_LAYER; -static PRIOMethods nspr_io_methods; - -static const char *nss_error_to_name(PRErrorCode code) -{ - const char *name = PR_ErrorToName(code); - if(name) - return name; - - return "unknown error"; -} - -static void nss_print_error_message(struct Curl_easy *data, PRUint32 err) -{ - failf(data, "%s", PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT)); -} - -static char *nss_sslver_to_name(PRUint16 nssver) -{ - switch(nssver) { - case SSL_LIBRARY_VERSION_2: - return strdup("SSLv2"); - case SSL_LIBRARY_VERSION_3_0: - return strdup("SSLv3"); - case SSL_LIBRARY_VERSION_TLS_1_0: - return strdup("TLSv1.0"); -#ifdef SSL_LIBRARY_VERSION_TLS_1_1 - case SSL_LIBRARY_VERSION_TLS_1_1: - return strdup("TLSv1.1"); -#endif -#ifdef SSL_LIBRARY_VERSION_TLS_1_2 - case SSL_LIBRARY_VERSION_TLS_1_2: - return strdup("TLSv1.2"); -#endif -#ifdef SSL_LIBRARY_VERSION_TLS_1_3 - case SSL_LIBRARY_VERSION_TLS_1_3: - return strdup("TLSv1.3"); -#endif - default: - return curl_maprintf("0x%04x", nssver); - } -} - -/* the longest cipher name this supports */ -#define MAX_CIPHER_LENGTH 128 - -static SECStatus set_ciphers(struct Curl_easy *data, PRFileDesc *model, - const char *cipher_list) -{ - unsigned int i; - const char *cipher; - - /* use accessors to avoid dynamic linking issues after an update of NSS */ - const PRUint16 num_implemented_ciphers = SSL_GetNumImplementedCiphers(); - const PRUint16 *implemented_ciphers = SSL_GetImplementedCiphers(); - if(!implemented_ciphers) - return SECFailure; - - /* First disable all ciphers. This uses a different max value in case - * NSS adds more ciphers later we don't want them available by - * accident - */ - for(i = 0; i < num_implemented_ciphers; i++) { - SSL_CipherPrefSet(model, implemented_ciphers[i], PR_FALSE); - } - - cipher = cipher_list; - - while(cipher && cipher[0]) { - const char *end; - char name[MAX_CIPHER_LENGTH + 1]; - size_t len; - bool found = FALSE; - while((*cipher) && (ISBLANK(*cipher))) - ++cipher; - - end = strpbrk(cipher, ":, "); - if(end) - len = end - cipher; - else - len = strlen(cipher); - - if(len > MAX_CIPHER_LENGTH) { - failf(data, "Bad cipher list"); - return SECFailure; - } - else if(len) { - memcpy(name, cipher, len); - name[len] = 0; - - for(i = 0; i<NUM_OF_CIPHERS; i++) { - if(strcasecompare(name, cipherlist[i].name)) { - /* Enable the selected cipher */ - if(SSL_CipherPrefSet(model, cipherlist[i].num, PR_TRUE) != - SECSuccess) { - failf(data, "cipher-suite not supported by NSS: %s", name); - return SECFailure; - } - found = TRUE; - break; - } - } - } - - if(!found && len) { - failf(data, "Unknown cipher: %s", name); - return SECFailure; - } - if(end) - cipher = ++end; - else - break; - } - - return SECSuccess; -} - -/* - * Return true if at least one cipher-suite is enabled. Used to determine - * if we need to call NSS_SetDomesticPolicy() to enable the default ciphers. - */ -static bool any_cipher_enabled(void) -{ - unsigned int i; - - for(i = 0; i<NUM_OF_CIPHERS; i++) { - PRInt32 policy = 0; - SSL_CipherPolicyGet(cipherlist[i].num, &policy); - if(policy) - return TRUE; - } - - return FALSE; -} - -/* - * Determine whether the nickname passed in is a filename that needs to - * be loaded as a PEM or a regular NSS nickname. - * - * returns 1 for a file - * returns 0 for not a file (NSS nickname) - */ -static int is_file(const char *filename) -{ - struct_stat st; - - if(!filename) - return 0; - - if(stat(filename, &st) == 0) - if(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode) || S_ISCHR(st.st_mode)) - return 1; - - return 0; -} - -/* Check if the given string is filename or nickname of a certificate. If the - * given string is recognized as filename, return NULL. If the given string is - * recognized as nickname, return a duplicated string. The returned string - * should be later deallocated using free(). If the OOM failure occurs, we - * return NULL, too. - */ -static char *dup_nickname(struct Curl_easy *data, const char *str) -{ - const char *n; - - if(!is_file(str)) - /* no such file exists, use the string as nickname */ - return strdup(str); - - /* search the first slash; we require at least one slash in a file name */ - n = strchr(str, '/'); - if(!n) { - infof(data, "WARNING: certificate file name \"%s\" handled as nickname; " - "please use \"./%s\" to force file name", str, str); - return strdup(str); - } - - /* we'll use the PEM reader to read the certificate from file */ - return NULL; -} - -/* Lock/unlock wrapper for PK11_FindSlotByName() to work around race condition - * in nssSlot_IsTokenPresent() causing spurious SEC_ERROR_NO_TOKEN. For more - * details, go to <https://bugzilla.mozilla.org/1297397>. - */ -static PK11SlotInfo* nss_find_slot_by_name(const char *slot_name) -{ - PK11SlotInfo *slot; - PR_Lock(nss_findslot_lock); - slot = PK11_FindSlotByName(slot_name); - PR_Unlock(nss_findslot_lock); - return slot; -} - -/* wrap 'ptr' as list node and tail-insert into 'list' */ -static CURLcode insert_wrapped_ptr(struct Curl_llist *list, void *ptr) -{ - struct ptr_list_wrap *wrap = malloc(sizeof(*wrap)); - if(!wrap) - return CURLE_OUT_OF_MEMORY; - - wrap->ptr = ptr; - Curl_llist_insert_next(list, list->tail, wrap, &wrap->node); - return CURLE_OK; -} - -/* Call PK11_CreateGenericObject() with the given obj_class and filename. If - * the call succeeds, append the object handle to the list of objects so that - * the object can be destroyed in nss_close(). */ -static CURLcode nss_create_object(struct ssl_connect_data *connssl, - CK_OBJECT_CLASS obj_class, - const char *filename, bool cacert) -{ - PK11SlotInfo *slot; - PK11GenericObject *obj; - CK_BBOOL cktrue = CK_TRUE; - CK_BBOOL ckfalse = CK_FALSE; - CK_ATTRIBUTE attrs[/* max count of attributes */ 4]; - int attr_cnt = 0; - CURLcode result = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - const int slot_id = (cacert) ? 0 : 1; - char *slot_name = aprintf("PEM Token #%d", slot_id); - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(!slot_name) - return CURLE_OUT_OF_MEMORY; - - slot = nss_find_slot_by_name(slot_name); - free(slot_name); - if(!slot) - return result; - - PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class)); - PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL)); - PK11_SETATTRS(attrs, attr_cnt, CKA_LABEL, (unsigned char *)filename, - (CK_ULONG)strlen(filename) + 1); - - if(CKO_CERTIFICATE == obj_class) { - CK_BBOOL *pval = (cacert) ? (&cktrue) : (&ckfalse); - PK11_SETATTRS(attrs, attr_cnt, CKA_TRUST, pval, sizeof(*pval)); - } - - /* PK11_CreateManagedGenericObject() was introduced in NSS 3.34 because - * PK11_DestroyGenericObject() does not release resources allocated by - * PK11_CreateGenericObject() early enough. */ - obj = -#ifdef HAVE_PK11_CREATEMANAGEDGENERICOBJECT - PK11_CreateManagedGenericObject -#else - PK11_CreateGenericObject -#endif - (slot, attrs, attr_cnt, PR_FALSE); - - PK11_FreeSlot(slot); - if(!obj) - return result; - - if(insert_wrapped_ptr(&backend->obj_list, obj) != CURLE_OK) { - PK11_DestroyGenericObject(obj); - return CURLE_OUT_OF_MEMORY; - } - - if(!cacert && CKO_CERTIFICATE == obj_class) - /* store reference to a client certificate */ - backend->obj_clicert = obj; - - return CURLE_OK; -} - -/* Destroy the NSS object whose handle is given by ptr. This function is - * a callback of Curl_llist_alloc() used by Curl_llist_destroy() to destroy - * NSS objects in nss_close() */ -static void nss_destroy_object(void *user, void *ptr) -{ - struct ptr_list_wrap *wrap = (struct ptr_list_wrap *) ptr; - PK11GenericObject *obj = (PK11GenericObject *) wrap->ptr; - (void) user; - PK11_DestroyGenericObject(obj); - free(wrap); -} - -/* same as nss_destroy_object() but for CRL items */ -static void nss_destroy_crl_item(void *user, void *ptr) -{ - struct ptr_list_wrap *wrap = (struct ptr_list_wrap *) ptr; - SECItem *crl_der = (SECItem *) wrap->ptr; - (void) user; - SECITEM_FreeItem(crl_der, PR_TRUE); - free(wrap); -} - -static CURLcode nss_load_cert(struct ssl_connect_data *ssl, - const char *filename, PRBool cacert) -{ - CURLcode result = (cacert) - ? CURLE_SSL_CACERT_BADFILE - : CURLE_SSL_CERTPROBLEM; - - /* libnsspem.so leaks memory if the requested file does not exist. For more - * details, go to <https://bugzilla.redhat.com/734760>. */ - if(is_file(filename)) - result = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert); - - if(!result && !cacert) { - /* we have successfully loaded a client certificate */ - char *nickname = NULL; - char *n = strrchr(filename, '/'); - if(n) - n++; - - /* The following undocumented magic helps to avoid a SIGSEGV on call - * of PK11_ReadRawAttribute() from SelectClientCert() when using an - * immature version of libnsspem.so. For more details, go to - * <https://bugzilla.redhat.com/733685>. */ - nickname = aprintf("PEM Token #1:%s", n); - if(nickname) { - CERTCertificate *cert = PK11_FindCertFromNickname(nickname, NULL); - if(cert) - CERT_DestroyCertificate(cert); - - free(nickname); - } - } - - return result; -} - -/* add given CRL to cache if it is not already there */ -static CURLcode nss_cache_crl(SECItem *crl_der) -{ - CERTCertDBHandle *db = CERT_GetDefaultCertDB(); - CERTSignedCrl *crl = SEC_FindCrlByDERCert(db, crl_der, 0); - if(crl) { - /* CRL already cached */ - SEC_DestroyCrl(crl); - SECITEM_FreeItem(crl_der, PR_TRUE); - return CURLE_OK; - } - - /* acquire lock before call of CERT_CacheCRL() and accessing nss_crl_list */ - PR_Lock(nss_crllock); - - if(SECSuccess != CERT_CacheCRL(db, crl_der)) { - /* unable to cache CRL */ - SECITEM_FreeItem(crl_der, PR_TRUE); - PR_Unlock(nss_crllock); - return CURLE_SSL_CRL_BADFILE; - } - - /* store the CRL item so that we can free it in nss_cleanup() */ - if(insert_wrapped_ptr(&nss_crl_list, crl_der) != CURLE_OK) { - if(SECSuccess == CERT_UncacheCRL(db, crl_der)) - SECITEM_FreeItem(crl_der, PR_TRUE); - PR_Unlock(nss_crllock); - return CURLE_OUT_OF_MEMORY; - } - - /* we need to clear session cache, so that the CRL could take effect */ - SSL_ClearSessionCache(); - PR_Unlock(nss_crllock); - return CURLE_OK; -} - -static CURLcode nss_load_crl(const char *crlfilename) -{ - PRFileDesc *infile; - PRFileInfo info; - SECItem filedata = { 0, NULL, 0 }; - SECItem *crl_der = NULL; - char *body; - - infile = PR_Open(crlfilename, PR_RDONLY, 0); - if(!infile) - return CURLE_SSL_CRL_BADFILE; - - if(PR_SUCCESS != PR_GetOpenFileInfo(infile, &info)) - goto fail; - - if(!SECITEM_AllocItem(NULL, &filedata, info.size + /* zero ended */ 1)) - goto fail; - - if(info.size != PR_Read(infile, filedata.data, info.size)) - goto fail; - - crl_der = SECITEM_AllocItem(NULL, NULL, 0U); - if(!crl_der) - goto fail; - - /* place a trailing zero right after the visible data */ - body = (char *)filedata.data; - body[--filedata.len] = '\0'; - - body = strstr(body, "-----BEGIN"); - if(body) { - /* assume ASCII */ - char *trailer; - char *begin = PORT_Strchr(body, '\n'); - if(!begin) - begin = PORT_Strchr(body, '\r'); - if(!begin) - goto fail; - - trailer = strstr(++begin, "-----END"); - if(!trailer) - goto fail; - - /* retrieve DER from ASCII */ - *trailer = '\0'; - if(ATOB_ConvertAsciiToItem(crl_der, begin)) - goto fail; - - SECITEM_FreeItem(&filedata, PR_FALSE); - } - else - /* assume DER */ - *crl_der = filedata; - - PR_Close(infile); - return nss_cache_crl(crl_der); - -fail: - PR_Close(infile); - SECITEM_FreeItem(crl_der, PR_TRUE); - SECITEM_FreeItem(&filedata, PR_FALSE); - return CURLE_SSL_CRL_BADFILE; -} - -static CURLcode nss_load_key(struct Curl_easy *data, struct connectdata *conn, - int sockindex, char *key_file) -{ - PK11SlotInfo *slot, *tmp; - SECStatus status; - CURLcode result; - struct ssl_connect_data *ssl = conn->ssl; - - (void)sockindex; /* unused */ - - result = nss_create_object(ssl, CKO_PRIVATE_KEY, key_file, FALSE); - if(result) { - PR_SetError(SEC_ERROR_BAD_KEY, 0); - return result; - } - - slot = nss_find_slot_by_name("PEM Token #1"); - if(!slot) - return CURLE_SSL_CERTPROBLEM; - - /* This will force the token to be seen as re-inserted */ - tmp = SECMOD_WaitForAnyTokenEvent(pem_module, 0, 0); - if(tmp) - PK11_FreeSlot(tmp); - if(!PK11_IsPresent(slot)) { - PK11_FreeSlot(slot); - return CURLE_SSL_CERTPROBLEM; - } - - status = PK11_Authenticate(slot, PR_TRUE, SSL_SET_OPTION(key_passwd)); - PK11_FreeSlot(slot); - - return (SECSuccess == status) ? CURLE_OK : CURLE_SSL_CERTPROBLEM; -} - -static int display_error(struct Curl_easy *data, PRInt32 err, - const char *filename) -{ - switch(err) { - case SEC_ERROR_BAD_PASSWORD: - failf(data, "Unable to load client key: Incorrect password"); - return 1; - case SEC_ERROR_UNKNOWN_CERT: - failf(data, "Unable to load certificate %s", filename); - return 1; - default: - break; - } - return 0; /* The caller will print a generic error */ -} - -static CURLcode cert_stuff(struct Curl_easy *data, struct connectdata *conn, - int sockindex, char *cert_file, char *key_file) -{ - CURLcode result; - - if(cert_file) { - result = nss_load_cert(&conn->ssl[sockindex], cert_file, PR_FALSE); - if(result) { - const PRErrorCode err = PR_GetError(); - if(!display_error(data, err, cert_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client cert: %d (%s)", err, err_name); - } - - return result; - } - } - - if(key_file || (is_file(cert_file))) { - if(key_file) - result = nss_load_key(data, conn, sockindex, key_file); - else - /* In case the cert file also has the key */ - result = nss_load_key(data, conn, sockindex, cert_file); - if(result) { - const PRErrorCode err = PR_GetError(); - if(!display_error(data, err, key_file)) { - const char *err_name = nss_error_to_name(err); - failf(data, "unable to load client key: %d (%s)", err, err_name); - } - - return result; - } - } - - return CURLE_OK; -} - -static char *nss_get_password(PK11SlotInfo *slot, PRBool retry, void *arg) -{ - (void)slot; /* unused */ - - if(retry || !arg) - return NULL; - else - return (char *)PORT_Strdup((char *)arg); -} - -/* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ -static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig, - PRBool isServer) -{ - struct Curl_easy *data = (struct Curl_easy *)arg; - struct connectdata *conn = data->conn; - -#ifdef SSL_ENABLE_OCSP_STAPLING - if(SSL_CONN_CONFIG(verifystatus)) { - SECStatus cacheResult; - - const SECItemArray *csa = SSL_PeerStapledOCSPResponses(fd); - if(!csa) { - failf(data, "Invalid OCSP response"); - return SECFailure; - } - - if(csa->len == 0) { - failf(data, "No OCSP response received"); - return SECFailure; - } - - cacheResult = CERT_CacheOCSPResponseFromSideChannel( - CERT_GetDefaultCertDB(), SSL_PeerCertificate(fd), - PR_Now(), &csa->items[0], arg - ); - - if(cacheResult != SECSuccess) { - failf(data, "Invalid OCSP response"); - return cacheResult; - } - } -#endif - - if(!SSL_CONN_CONFIG(verifypeer)) { - infof(data, "skipping SSL peer certificate verification"); - return SECSuccess; - } - - return SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer); -} - -/** - * Inform the application that the handshake is complete. - */ -static void HandshakeCallback(PRFileDesc *sock, void *arg) -{ - struct Curl_easy *data = (struct Curl_easy *)arg; - struct connectdata *conn = data->conn; - unsigned int buflenmax = 50; - unsigned char buf[50]; - unsigned int buflen; - SSLNextProtoState state; - - if(!conn->bits.tls_enable_alpn) { - return; - } - - if(SSL_GetNextProto(sock, &state, buf, &buflen, buflenmax) == SECSuccess) { - - switch(state) { -#if NSSVERNUM >= 0x031a00 /* 3.26.0 */ - /* used by NSS internally to implement 0-RTT */ - case SSL_NEXT_PROTO_EARLY_VALUE: - /* fall through! */ -#endif - case SSL_NEXT_PROTO_NO_SUPPORT: - case SSL_NEXT_PROTO_NO_OVERLAP: - infof(data, VTLS_INFOF_NO_ALPN); - return; -#ifdef SSL_ENABLE_ALPN - case SSL_NEXT_PROTO_SELECTED: - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, buflen, buf); - break; -#endif - default: - /* ignore SSL_NEXT_PROTO_NEGOTIATED */ - break; - } - -#ifdef USE_HTTP2 - if(buflen == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, buf, ALPN_H2_LENGTH)) { - conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(buflen == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) { - conn->alpn = CURL_HTTP_VERSION_1_1; - } - - /* This callback might get called when PR_Recv() is used within - * close_one() during a connection shutdown. At that point there might not - * be any "bundle" associated with the connection anymore. - */ - if(conn->bundle) - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } -} - -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ -static SECStatus CanFalseStartCallback(PRFileDesc *sock, void *client_data, - PRBool *canFalseStart) -{ - struct Curl_easy *data = (struct Curl_easy *)client_data; - - SSLChannelInfo channelInfo; - SSLCipherSuiteInfo cipherInfo; - - SECStatus rv; - PRBool negotiatedExtension; - - *canFalseStart = PR_FALSE; - - if(SSL_GetChannelInfo(sock, &channelInfo, sizeof(channelInfo)) != SECSuccess) - return SECFailure; - - if(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, - sizeof(cipherInfo)) != SECSuccess) - return SECFailure; - - /* Prevent version downgrade attacks from TLS 1.2, and avoid False Start for - * TLS 1.3 and later. See https://bugzilla.mozilla.org/show_bug.cgi?id=861310 - */ - if(channelInfo.protocolVersion != SSL_LIBRARY_VERSION_TLS_1_2) - goto end; - - /* Only allow ECDHE key exchange algorithm. - * See https://bugzilla.mozilla.org/show_bug.cgi?id=952863 */ - if(cipherInfo.keaType != ssl_kea_ecdh) - goto end; - - /* Prevent downgrade attacks on the symmetric cipher. We do not allow CBC - * mode due to BEAST, POODLE, and other attacks on the MAC-then-Encrypt - * design. See https://bugzilla.mozilla.org/show_bug.cgi?id=1109766 */ - if(cipherInfo.symCipher != ssl_calg_aes_gcm) - goto end; - - /* Enforce ALPN to do False Start, as an indicator of server - compatibility. */ - rv = SSL_HandshakeNegotiatedExtension(sock, ssl_app_layer_protocol_xtn, - &negotiatedExtension); - if(rv != SECSuccess || !negotiatedExtension) { - rv = SSL_HandshakeNegotiatedExtension(sock, ssl_next_proto_nego_xtn, - &negotiatedExtension); - } - - if(rv != SECSuccess || !negotiatedExtension) - goto end; - - *canFalseStart = PR_TRUE; - - infof(data, "Trying TLS False Start"); - -end: - return SECSuccess; -} -#endif - -static void display_cert_info(struct Curl_easy *data, - CERTCertificate *cert) -{ - char *subject, *issuer, *common_name; - PRExplodedTime printableTime; - char timeString[256]; - PRTime notBefore, notAfter; - - subject = CERT_NameToAscii(&cert->subject); - issuer = CERT_NameToAscii(&cert->issuer); - common_name = CERT_GetCommonName(&cert->subject); - infof(data, "subject: %s", subject); - - CERT_GetCertTimes(cert, ¬Before, ¬After); - PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, " start date: %s", timeString); - PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime); - PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime); - infof(data, " expire date: %s", timeString); - infof(data, " common name: %s", common_name); - infof(data, " issuer: %s", issuer); - - PR_Free(subject); - PR_Free(issuer); - PR_Free(common_name); -} - -/* A number of certs that will never occur in a real server handshake */ -#define TOO_MANY_CERTS 300 - -static CURLcode display_conn_info(struct Curl_easy *data, PRFileDesc *sock) -{ - CURLcode result = CURLE_OK; - SSLChannelInfo channel; - SSLCipherSuiteInfo suite; - CERTCertificate *cert; - CERTCertificate *cert2; - CERTCertificate *cert3; - PRTime now; - - if(SSL_GetChannelInfo(sock, &channel, sizeof(channel)) == - SECSuccess && channel.length == sizeof(channel) && - channel.cipherSuite) { - if(SSL_GetCipherSuiteInfo(channel.cipherSuite, - &suite, sizeof(suite)) == SECSuccess) { - infof(data, "SSL connection using %s", suite.cipherSuiteName); - } - } - - cert = SSL_PeerCertificate(sock); - if(cert) { - infof(data, "Server certificate:"); - - if(!data->set.ssl.certinfo) { - display_cert_info(data, cert); - CERT_DestroyCertificate(cert); - } - else { - /* Count certificates in chain. */ - int i = 1; - now = PR_Now(); - if(!cert->isRoot) { - cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA); - while(cert2) { - i++; - if(i >= TOO_MANY_CERTS) { - CERT_DestroyCertificate(cert2); - failf(data, "certificate loop"); - return CURLE_SSL_CERTPROBLEM; - } - if(cert2->isRoot) { - CERT_DestroyCertificate(cert2); - break; - } - cert3 = CERT_FindCertIssuer(cert2, now, certUsageSSLCA); - CERT_DestroyCertificate(cert2); - cert2 = cert3; - } - } - - result = Curl_ssl_init_certinfo(data, i); - if(!result) { - for(i = 0; cert; cert = cert2) { - result = Curl_extract_certinfo(data, i++, (char *)cert->derCert.data, - (char *)cert->derCert.data + - cert->derCert.len); - if(result) - break; - - if(cert->isRoot) { - CERT_DestroyCertificate(cert); - break; - } - - cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA); - CERT_DestroyCertificate(cert); - } - } - } - } - - return result; -} - -static SECStatus BadCertHandler(void *arg, PRFileDesc *sock) -{ - struct Curl_easy *data = (struct Curl_easy *)arg; - struct connectdata *conn = data->conn; - PRErrorCode err = PR_GetError(); - CERTCertificate *cert; - - /* remember the cert verification result */ - SSL_SET_OPTION_LVALUE(certverifyresult) = err; - - if(err == SSL_ERROR_BAD_CERT_DOMAIN && !SSL_CONN_CONFIG(verifyhost)) - /* we are asked not to verify the host name */ - return SECSuccess; - - /* print only info about the cert, the error is printed off the callback */ - cert = SSL_PeerCertificate(sock); - if(cert) { - infof(data, "Server certificate:"); - display_cert_info(data, cert); - CERT_DestroyCertificate(cert); - } - - return SECFailure; -} - -/** - * - * Check that the Peer certificate's issuer certificate matches the one found - * by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the - * issuer check, so we provide comments that mimic the OpenSSL - * X509_check_issued function (in x509v3/v3_purp.c) - */ -static SECStatus check_issuer_cert(PRFileDesc *sock, - char *issuer_nickname) -{ - CERTCertificate *cert, *cert_issuer, *issuer; - SECStatus res = SECSuccess; - void *proto_win = NULL; - - cert = SSL_PeerCertificate(sock); - cert_issuer = CERT_FindCertIssuer(cert, PR_Now(), certUsageObjectSigner); - - proto_win = SSL_RevealPinArg(sock); - issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win); - - if((!cert_issuer) || (!issuer)) - res = SECFailure; - else if(SECITEM_CompareItem(&cert_issuer->derCert, - &issuer->derCert) != SECEqual) - res = SECFailure; - - CERT_DestroyCertificate(cert); - CERT_DestroyCertificate(issuer); - CERT_DestroyCertificate(cert_issuer); - return res; -} - -static CURLcode cmp_peer_pubkey(struct ssl_connect_data *connssl, - const char *pinnedpubkey) -{ - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - struct ssl_backend_data *backend = connssl->backend; - struct Curl_easy *data = NULL; - CERTCertificate *cert; - - DEBUGASSERT(backend); - data = backend->data; - - if(!pinnedpubkey) - /* no pinned public key specified */ - return CURLE_OK; - - /* get peer certificate */ - cert = SSL_PeerCertificate(backend->handle); - if(cert) { - /* extract public key from peer certificate */ - SECKEYPublicKey *pubkey = CERT_ExtractPublicKey(cert); - if(pubkey) { - /* encode the public key as DER */ - SECItem *cert_der = PK11_DEREncodePublicKey(pubkey); - if(cert_der) { - /* compare the public key with the pinned public key */ - result = Curl_pin_peer_pubkey(data, pinnedpubkey, cert_der->data, - cert_der->len); - SECITEM_FreeItem(cert_der, PR_TRUE); - } - SECKEY_DestroyPublicKey(pubkey); - } - CERT_DestroyCertificate(cert); - } - - /* report the resulting status */ - switch(result) { - case CURLE_OK: - infof(data, "pinned public key verified successfully"); - break; - case CURLE_SSL_PINNEDPUBKEYNOTMATCH: - failf(data, "failed to verify pinned public key"); - break; - default: - /* OOM, etc. */ - break; - } - - return result; -} - -/** - * - * Callback to pick the SSL client certificate. - */ -static SECStatus SelectClientCert(void *arg, PRFileDesc *sock, - struct CERTDistNamesStr *caNames, - struct CERTCertificateStr **pRetCert, - struct SECKEYPrivateKeyStr **pRetKey) -{ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg; - struct ssl_backend_data *backend = connssl->backend; - struct Curl_easy *data = NULL; - const char *nickname = NULL; - static const char pem_slotname[] = "PEM Token #1"; - - DEBUGASSERT(backend); - - data = backend->data; - nickname = backend->client_nickname; - - if(backend->obj_clicert) { - /* use the cert/key provided by PEM reader */ - SECItem cert_der = { 0, NULL, 0 }; - void *proto_win = SSL_RevealPinArg(sock); - struct CERTCertificateStr *cert; - struct SECKEYPrivateKeyStr *key; - - PK11SlotInfo *slot = nss_find_slot_by_name(pem_slotname); - if(!slot) { - failf(data, "NSS: PK11 slot not found: %s", pem_slotname); - return SECFailure; - } - - if(PK11_ReadRawAttribute(PK11_TypeGeneric, backend->obj_clicert, CKA_VALUE, - &cert_der) != SECSuccess) { - failf(data, "NSS: CKA_VALUE not found in PK11 generic object"); - PK11_FreeSlot(slot); - return SECFailure; - } - - cert = PK11_FindCertFromDERCertItem(slot, &cert_der, proto_win); - SECITEM_FreeItem(&cert_der, PR_FALSE); - if(!cert) { - failf(data, "NSS: client certificate from file not found"); - PK11_FreeSlot(slot); - return SECFailure; - } - - key = PK11_FindPrivateKeyFromCert(slot, cert, NULL); - PK11_FreeSlot(slot); - if(!key) { - failf(data, "NSS: private key from file not found"); - CERT_DestroyCertificate(cert); - return SECFailure; - } - - infof(data, "NSS: client certificate from file"); - display_cert_info(data, cert); - - *pRetCert = cert; - *pRetKey = key; - return SECSuccess; - } - - /* use the default NSS hook */ - if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames, - pRetCert, pRetKey) - || !*pRetCert) { - - if(!nickname) - failf(data, "NSS: client certificate not found (nickname not " - "specified)"); - else - failf(data, "NSS: client certificate not found: %s", nickname); - - return SECFailure; - } - - /* get certificate nickname if any */ - nickname = (*pRetCert)->nickname; - if(!nickname) - nickname = "[unknown]"; - - if(!strncmp(nickname, pem_slotname, sizeof(pem_slotname) - 1U)) { - failf(data, "NSS: refusing previously loaded certificate from file: %s", - nickname); - return SECFailure; - } - - if(!*pRetKey) { - failf(data, "NSS: private key not found for certificate: %s", nickname); - return SECFailure; - } - - infof(data, "NSS: using client certificate: %s", nickname); - display_cert_info(data, *pRetCert); - return SECSuccess; -} - -/* update blocking direction in case of PR_WOULD_BLOCK_ERROR */ -static void nss_update_connecting_state(ssl_connect_state state, void *secret) -{ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)secret; - if(PR_GetError() != PR_WOULD_BLOCK_ERROR) - /* an unrelated error is passing by */ - return; - - switch(connssl->connecting_state) { - case ssl_connect_2: - case ssl_connect_2_reading: - case ssl_connect_2_writing: - break; - default: - /* we are not called from an SSL handshake */ - return; - } - - /* update the state accordingly */ - connssl->connecting_state = state; -} - -/* recv() wrapper we use to detect blocking direction during SSL handshake */ -static PRInt32 nspr_io_recv(PRFileDesc *fd, void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - const PRRecvFN recv_fn = fd->lower->methods->recv; - const PRInt32 rv = recv_fn(fd->lower, buf, amount, flags, timeout); - if(rv < 0) - /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ - nss_update_connecting_state(ssl_connect_2_reading, fd->secret); - return rv; -} - -/* send() wrapper we use to detect blocking direction during SSL handshake */ -static PRInt32 nspr_io_send(PRFileDesc *fd, const void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - const PRSendFN send_fn = fd->lower->methods->send; - const PRInt32 rv = send_fn(fd->lower, buf, amount, flags, timeout); - if(rv < 0) - /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */ - nss_update_connecting_state(ssl_connect_2_writing, fd->secret); - return rv; -} - -/* close() wrapper to avoid assertion failure due to fd->secret != NULL */ -static PRStatus nspr_io_close(PRFileDesc *fd) -{ - const PRCloseFN close_fn = PR_GetDefaultIOMethods()->close; - fd->secret = NULL; - return close_fn(fd); -} - -/* load a PKCS #11 module */ -static CURLcode nss_load_module(SECMODModule **pmod, const char *library, - const char *name) -{ - char *config_string; - SECMODModule *module = *pmod; - if(module) - /* already loaded */ - return CURLE_OK; - - config_string = aprintf("library=%s name=%s", library, name); - if(!config_string) - return CURLE_OUT_OF_MEMORY; - - module = SECMOD_LoadUserModule(config_string, NULL, PR_FALSE); - free(config_string); - - if(module && module->loaded) { - /* loaded successfully */ - *pmod = module; - return CURLE_OK; - } - - if(module) - SECMOD_DestroyModule(module); - return CURLE_FAILED_INIT; -} - -/* unload a PKCS #11 module */ -static void nss_unload_module(SECMODModule **pmod) -{ - SECMODModule *module = *pmod; - if(!module) - /* not loaded */ - return; - - if(SECMOD_UnloadUserModule(module) != SECSuccess) - /* unload failed */ - return; - - SECMOD_DestroyModule(module); - *pmod = NULL; -} - -/* data might be NULL */ -static CURLcode nss_init_core(struct Curl_easy *data, const char *cert_dir) -{ - NSSInitParameters initparams; - PRErrorCode err; - const char *err_name; - - if(nss_context) - return CURLE_OK; - - memset((void *) &initparams, '\0', sizeof(initparams)); - initparams.length = sizeof(initparams); - - if(cert_dir) { - char *certpath = aprintf("sql:%s", cert_dir); - if(!certpath) - return CURLE_OUT_OF_MEMORY; - - infof(data, "Initializing NSS with certpath: %s", certpath); - nss_context = NSS_InitContext(certpath, "", "", "", &initparams, - NSS_INIT_READONLY | NSS_INIT_PK11RELOAD); - free(certpath); - - if(nss_context) - return CURLE_OK; - - err = PR_GetError(); - err_name = nss_error_to_name(err); - infof(data, "Unable to initialize NSS database: %d (%s)", err, err_name); - } - - infof(data, "Initializing NSS with certpath: none"); - nss_context = NSS_InitContext("", "", "", "", &initparams, NSS_INIT_READONLY - | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN - | NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD); - if(nss_context) - return CURLE_OK; - - err = PR_GetError(); - err_name = nss_error_to_name(err); - failf(data, "Unable to initialize NSS: %d (%s)", err, err_name); - return CURLE_SSL_CACERT_BADFILE; -} - -/* data might be NULL */ -static CURLcode nss_setup(struct Curl_easy *data) -{ - char *cert_dir; - struct_stat st; - CURLcode result; - - if(initialized) - return CURLE_OK; - - /* list of all CRL items we need to destroy in nss_cleanup() */ - Curl_llist_init(&nss_crl_list, nss_destroy_crl_item); - - /* First we check if $SSL_DIR points to a valid dir */ - cert_dir = getenv("SSL_DIR"); - if(cert_dir) { - if((stat(cert_dir, &st) != 0) || - (!S_ISDIR(st.st_mode))) { - cert_dir = NULL; - } - } - - /* Now we check if the default location is a valid dir */ - if(!cert_dir) { - if((stat(SSL_DIR, &st) == 0) && - (S_ISDIR(st.st_mode))) { - cert_dir = (char *)SSL_DIR; - } - } - - if(nspr_io_identity == PR_INVALID_IO_LAYER) { - /* allocate an identity for our own NSPR I/O layer */ - nspr_io_identity = PR_GetUniqueIdentity("libcurl"); - if(nspr_io_identity == PR_INVALID_IO_LAYER) - return CURLE_OUT_OF_MEMORY; - - /* the default methods just call down to the lower I/O layer */ - memcpy(&nspr_io_methods, PR_GetDefaultIOMethods(), - sizeof(nspr_io_methods)); - - /* override certain methods in the table by our wrappers */ - nspr_io_methods.recv = nspr_io_recv; - nspr_io_methods.send = nspr_io_send; - nspr_io_methods.close = nspr_io_close; - } - - result = nss_init_core(data, cert_dir); - if(result) - return result; - - if(!any_cipher_enabled()) - NSS_SetDomesticPolicy(); - - initialized = 1; - - return CURLE_OK; -} - -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -static int nss_init(void) -{ - /* curl_global_init() is not thread-safe so this test is ok */ - if(!nss_initlock) { - PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); - nss_initlock = PR_NewLock(); - nss_crllock = PR_NewLock(); - nss_findslot_lock = PR_NewLock(); - nss_trustload_lock = PR_NewLock(); - } - - /* We will actually initialize NSS later */ - - return 1; -} - -/* data might be NULL */ -CURLcode Curl_nss_force_init(struct Curl_easy *data) -{ - CURLcode result; - if(!nss_initlock) { - if(data) - failf(data, "unable to initialize NSS, curl_global_init() should have " - "been called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL"); - return CURLE_FAILED_INIT; - } - - PR_Lock(nss_initlock); - result = nss_setup(data); - PR_Unlock(nss_initlock); - - return result; -} - -/* Global cleanup */ -static void nss_cleanup(void) -{ - /* This function isn't required to be threadsafe and this is only done - * as a safety feature. - */ - PR_Lock(nss_initlock); - if(initialized) { - /* Free references to client certificates held in the SSL session cache. - * Omitting this hampers destruction of the security module owning - * the certificates. */ - SSL_ClearSessionCache(); - - nss_unload_module(&pem_module); - nss_unload_module(&trust_module); - NSS_ShutdownContext(nss_context); - nss_context = NULL; - } - - /* destroy all CRL items */ - Curl_llist_destroy(&nss_crl_list, NULL); - - PR_Unlock(nss_initlock); - - PR_DestroyLock(nss_initlock); - PR_DestroyLock(nss_crllock); - PR_DestroyLock(nss_findslot_lock); - PR_DestroyLock(nss_trustload_lock); - nss_initlock = NULL; - - initialized = 0; -} - -/* - * This function uses SSL_peek to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -static int nss_check_cxn(struct connectdata *conn) -{ - struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; - struct ssl_backend_data *backend = connssl->backend; - int rc; - char buf; - - DEBUGASSERT(backend); - - rc = - PR_Recv(backend->handle, (void *)&buf, 1, PR_MSG_PEEK, - PR_SecondsToInterval(1)); - if(rc > 0) - return 1; /* connection still in place */ - - if(rc == 0) - return 0; /* connection has been closed */ - - return -1; /* connection status unknown */ -} - -static void close_one(struct ssl_connect_data *connssl) -{ - /* before the cleanup, check whether we are using a client certificate */ - struct ssl_backend_data *backend = connssl->backend; - bool client_cert = true; - - DEBUGASSERT(backend); - - client_cert = (backend->client_nickname != NULL) - || (backend->obj_clicert != NULL); - - if(backend->handle) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)PR_Recv(backend->handle, buf, (int)sizeof(buf), 0, - PR_INTERVAL_NO_WAIT); - } - - free(backend->client_nickname); - backend->client_nickname = NULL; - - /* destroy all NSS objects in order to avoid failure of NSS shutdown */ - Curl_llist_destroy(&backend->obj_list, NULL); - backend->obj_clicert = NULL; - - if(backend->handle) { - if(client_cert) - /* A server might require different authentication based on the - * particular path being requested by the client. To support this - * scenario, we must ensure that a connection will never reuse the - * authentication data from a previous connection. */ - SSL_InvalidateSession(backend->handle); - - PR_Close(backend->handle); - backend->handle = NULL; - } -} - -/* - * This function is called when an SSL connection is closed. - */ -static void nss_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; -#ifndef CURL_DISABLE_PROXY - struct ssl_connect_data *connssl_proxy = &conn->proxy_ssl[sockindex]; -#endif - struct ssl_backend_data *backend = connssl->backend; - (void)data; - - DEBUGASSERT(backend); -#ifndef CURL_DISABLE_PROXY - DEBUGASSERT(connssl_proxy->backend != NULL); -#endif - - if(backend->handle -#ifndef CURL_DISABLE_PROXY - || connssl_proxy->backend->handle -#endif - ) { - /* NSS closes the socket we previously handed to it, so we must mark it - as closed to avoid double close */ - fake_sclose(conn->sock[sockindex]); - conn->sock[sockindex] = CURL_SOCKET_BAD; - } - -#ifndef CURL_DISABLE_PROXY - if(backend->handle) - /* nss_close(connssl) will transitively close also - connssl_proxy->backend->handle if both are used. Clear it to avoid - a double close leading to crash. */ - connssl_proxy->backend->handle = NULL; - - close_one(connssl_proxy); -#endif - close_one(connssl); -} - -/* return true if NSS can provide error code (and possibly msg) for the - error */ -static bool is_nss_error(CURLcode err) -{ - switch(err) { - case CURLE_PEER_FAILED_VERIFICATION: - case CURLE_SSL_CERTPROBLEM: - case CURLE_SSL_CONNECT_ERROR: - case CURLE_SSL_ISSUER_ERROR: - return true; - - default: - return false; - } -} - -/* return true if the given error code is related to a client certificate */ -static bool is_cc_error(PRInt32 err) -{ - switch(err) { - case SSL_ERROR_BAD_CERT_ALERT: - case SSL_ERROR_EXPIRED_CERT_ALERT: - case SSL_ERROR_REVOKED_CERT_ALERT: - return true; - - default: - return false; - } -} - -static Curl_recv nss_recv; -static Curl_send nss_send; - -static CURLcode nss_load_ca_certificates(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) -{ - const char *cafile = SSL_CONN_CONFIG(CAfile); - const char *capath = SSL_CONN_CONFIG(CApath); - bool use_trust_module; - CURLcode result = CURLE_OK; - - /* treat empty string as unset */ - if(cafile && !cafile[0]) - cafile = NULL; - if(capath && !capath[0]) - capath = NULL; - - infof(data, " CAfile: %s", cafile ? cafile : "none"); - infof(data, " CApath: %s", capath ? capath : "none"); - - /* load libnssckbi.so if no other trust roots were specified */ - use_trust_module = !cafile && !capath; - - PR_Lock(nss_trustload_lock); - if(use_trust_module && !trust_module) { - /* libnssckbi.so needed but not yet loaded --> load it! */ - result = nss_load_module(&trust_module, trust_library, "trust"); - infof(data, "%s %s", (result) ? "failed to load" : "loaded", - trust_library); - if(result == CURLE_FAILED_INIT) - /* If libnssckbi.so is not available (or fails to load), one can still - use CA certificates stored in NSS database. Ignore the failure. */ - result = CURLE_OK; - } - else if(!use_trust_module && trust_module) { - /* libnssckbi.so not needed but already loaded --> unload it! */ - infof(data, "unloading %s", trust_library); - nss_unload_module(&trust_module); - } - PR_Unlock(nss_trustload_lock); - - if(cafile) - result = nss_load_cert(&conn->ssl[sockindex], cafile, PR_TRUE); - - if(result) - return result; - - if(capath) { - struct_stat st; - if(stat(capath, &st) == -1) - return CURLE_SSL_CACERT_BADFILE; - - if(S_ISDIR(st.st_mode)) { - PRDirEntry *entry; - PRDir *dir = PR_OpenDir(capath); - if(!dir) - return CURLE_SSL_CACERT_BADFILE; - - while((entry = - PR_ReadDir(dir, (PRDirFlags)(PR_SKIP_BOTH | PR_SKIP_HIDDEN)))) { - char *fullpath = aprintf("%s/%s", capath, entry->name); - if(!fullpath) { - PR_CloseDir(dir); - return CURLE_OUT_OF_MEMORY; - } - - if(CURLE_OK != nss_load_cert(&conn->ssl[sockindex], fullpath, PR_TRUE)) - /* This is purposefully tolerant of errors so non-PEM files can - * be in the same directory */ - infof(data, "failed to load '%s' from CURLOPT_CAPATH", fullpath); - - free(fullpath); - } - - PR_CloseDir(dir); - } - else - infof(data, "WARNING: CURLOPT_CAPATH not a directory (%s)", capath); - } - - return CURLE_OK; -} - -static CURLcode nss_sslver_from_curl(PRUint16 *nssver, long version) -{ - switch(version) { - case CURL_SSLVERSION_SSLv2: - *nssver = SSL_LIBRARY_VERSION_2; - return CURLE_OK; - - case CURL_SSLVERSION_SSLv3: - return CURLE_NOT_BUILT_IN; - - case CURL_SSLVERSION_TLSv1_0: - *nssver = SSL_LIBRARY_VERSION_TLS_1_0; - return CURLE_OK; - - case CURL_SSLVERSION_TLSv1_1: -#ifdef SSL_LIBRARY_VERSION_TLS_1_1 - *nssver = SSL_LIBRARY_VERSION_TLS_1_1; - return CURLE_OK; -#else - return CURLE_SSL_CONNECT_ERROR; -#endif - - case CURL_SSLVERSION_TLSv1_2: -#ifdef SSL_LIBRARY_VERSION_TLS_1_2 - *nssver = SSL_LIBRARY_VERSION_TLS_1_2; - return CURLE_OK; -#else - return CURLE_SSL_CONNECT_ERROR; -#endif - - case CURL_SSLVERSION_TLSv1_3: -#ifdef SSL_LIBRARY_VERSION_TLS_1_3 - *nssver = SSL_LIBRARY_VERSION_TLS_1_3; - return CURLE_OK; -#else - return CURLE_SSL_CONNECT_ERROR; -#endif - - default: - return CURLE_SSL_CONNECT_ERROR; - } -} - -static CURLcode nss_init_sslver(SSLVersionRange *sslver, - struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result; - const long min = SSL_CONN_CONFIG(version); - const long max = SSL_CONN_CONFIG(version_max); - SSLVersionRange vrange; - - switch(min) { - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_DEFAULT: - /* Bump our minimum TLS version if NSS has stricter requirements. */ - if(SSL_VersionRangeGetDefault(ssl_variant_stream, &vrange) != SECSuccess) - return CURLE_SSL_CONNECT_ERROR; - if(sslver->min < vrange.min) - sslver->min = vrange.min; - break; - default: - result = nss_sslver_from_curl(&sslver->min, min); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; - } - } - - switch(max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - break; - default: - result = nss_sslver_from_curl(&sslver->max, max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; - } - } - - return CURLE_OK; -} - -static CURLcode nss_fail_connect(struct ssl_connect_data *connssl, - struct Curl_easy *data, - CURLcode curlerr) -{ - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(is_nss_error(curlerr)) { - /* read NSPR error code */ - PRErrorCode err = PR_GetError(); - if(is_cc_error(err)) - curlerr = CURLE_SSL_CERTPROBLEM; - - /* print the error number and error string */ - infof(data, "NSS error %d (%s)", err, nss_error_to_name(err)); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - } - - /* cleanup on connection failure */ - Curl_llist_destroy(&backend->obj_list, NULL); - - return curlerr; -} - -/* Switch the SSL socket into blocking or non-blocking mode. */ -static CURLcode nss_set_blocking(struct ssl_connect_data *connssl, - struct Curl_easy *data, - bool blocking) -{ - PRSocketOptionData sock_opt; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - sock_opt.option = PR_SockOpt_Nonblocking; - sock_opt.value.non_blocking = !blocking; - - if(PR_SetSocketOption(backend->handle, &sock_opt) != PR_SUCCESS) - return nss_fail_connect(connssl, data, CURLE_SSL_CONNECT_ERROR); - - return CURLE_OK; -} - -static CURLcode nss_setup_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - PRFileDesc *model = NULL; - PRFileDesc *nspr_io = NULL; - PRFileDesc *nspr_io_stub = NULL; - PRBool ssl_no_cache; - PRBool ssl_cbc_random_iv; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - CURLcode result; - bool second_layer = FALSE; - SSLVersionRange sslver_supported; - SSLVersionRange sslver = { - SSL_LIBRARY_VERSION_TLS_1_0, /* min */ -#ifdef SSL_LIBRARY_VERSION_TLS_1_3 - SSL_LIBRARY_VERSION_TLS_1_3 /* max */ -#elif defined SSL_LIBRARY_VERSION_TLS_1_2 - SSL_LIBRARY_VERSION_TLS_1_2 -#elif defined SSL_LIBRARY_VERSION_TLS_1_1 - SSL_LIBRARY_VERSION_TLS_1_1 -#else - SSL_LIBRARY_VERSION_TLS_1_0 -#endif - }; - char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - - DEBUGASSERT(backend); - - backend->data = data; - - /* list of all NSS objects we need to destroy in nss_do_close() */ - Curl_llist_init(&backend->obj_list, nss_destroy_object); - - PR_Lock(nss_initlock); - result = nss_setup(data); - if(result) { - PR_Unlock(nss_initlock); - goto error; - } - - PK11_SetPasswordFunc(nss_get_password); - - result = nss_load_module(&pem_module, pem_library, "PEM"); - PR_Unlock(nss_initlock); - if(result == CURLE_FAILED_INIT) - infof(data, "WARNING: failed to load NSS PEM library %s. Using " - "OpenSSL PEM certificates will not work.", pem_library); - else if(result) - goto error; - - result = CURLE_SSL_CONNECT_ERROR; - - model = PR_NewTCPSocket(); - if(!model) - goto error; - model = SSL_ImportFD(NULL, model); - - if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess) - goto error; - if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess) - goto error; - - /* do not use SSL cache if disabled or we are not going to verify peer */ - ssl_no_cache = (SSL_SET_OPTION(primary.sessionid) - && SSL_CONN_CONFIG(verifypeer)) ? PR_FALSE : PR_TRUE; - if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess) - goto error; - - /* enable/disable the requested SSL version(s) */ - if(nss_init_sslver(&sslver, data, conn) != CURLE_OK) - goto error; - if(SSL_VersionRangeGetSupported(ssl_variant_stream, - &sslver_supported) != SECSuccess) - goto error; - if(sslver_supported.max < sslver.max && sslver_supported.max >= sslver.min) { - char *sslver_req_str, *sslver_supp_str; - sslver_req_str = nss_sslver_to_name(sslver.max); - sslver_supp_str = nss_sslver_to_name(sslver_supported.max); - if(sslver_req_str && sslver_supp_str) - infof(data, "Falling back from %s to max supported SSL version (%s)", - sslver_req_str, sslver_supp_str); - free(sslver_req_str); - free(sslver_supp_str); - sslver.max = sslver_supported.max; - } - if(SSL_VersionRangeSet(model, &sslver) != SECSuccess) - goto error; - - ssl_cbc_random_iv = !SSL_SET_OPTION(enable_beast); -#ifdef SSL_CBC_RANDOM_IV - /* unless the user explicitly asks to allow the protocol vulnerability, we - use the work-around */ - if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess) - infof(data, "WARNING: failed to set SSL_CBC_RANDOM_IV = %d", - ssl_cbc_random_iv); -#else - if(ssl_cbc_random_iv) - infof(data, "WARNING: support for SSL_CBC_RANDOM_IV not compiled in"); -#endif - - if(SSL_CONN_CONFIG(cipher_list)) { - if(set_ciphers(data, model, SSL_CONN_CONFIG(cipher_list)) != SECSuccess) { - result = CURLE_SSL_CIPHER; - goto error; - } - } - - if(!SSL_CONN_CONFIG(verifypeer) && SSL_CONN_CONFIG(verifyhost)) - infof(data, "WARNING: ignoring value of ssl.verifyhost"); - - /* bypass the default SSL_AuthCertificate() hook in case we do not want to - * verify peer */ - if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, data) != SECSuccess) - goto error; - - /* not checked yet */ - SSL_SET_OPTION_LVALUE(certverifyresult) = 0; - - if(SSL_BadCertHook(model, BadCertHandler, data) != SECSuccess) - goto error; - - if(SSL_HandshakeCallback(model, HandshakeCallback, data) != SECSuccess) - goto error; - - { - const CURLcode rv = nss_load_ca_certificates(data, conn, sockindex); - if((rv == CURLE_SSL_CACERT_BADFILE) && !SSL_CONN_CONFIG(verifypeer)) - /* not a fatal error because we are not going to verify the peer */ - infof(data, "WARNING: CA certificates failed to load"); - else if(rv) { - result = rv; - goto error; - } - } - - if(SSL_SET_OPTION(primary.CRLfile)) { - const CURLcode rv = nss_load_crl(SSL_SET_OPTION(primary.CRLfile)); - if(rv) { - result = rv; - goto error; - } - infof(data, " CRLfile: %s", SSL_SET_OPTION(primary.CRLfile)); - } - - if(SSL_SET_OPTION(primary.clientcert)) { - char *nickname = dup_nickname(data, SSL_SET_OPTION(primary.clientcert)); - if(nickname) { - /* we are not going to use libnsspem.so to read the client cert */ - backend->obj_clicert = NULL; - } - else { - CURLcode rv = cert_stuff(data, conn, sockindex, - SSL_SET_OPTION(primary.clientcert), - SSL_SET_OPTION(key)); - if(rv) { - /* failf() is already done in cert_stuff() */ - result = rv; - goto error; - } - } - - /* store the nickname for SelectClientCert() called during handshake */ - backend->client_nickname = nickname; - } - else - backend->client_nickname = NULL; - - if(SSL_GetClientAuthDataHook(model, SelectClientCert, - (void *)connssl) != SECSuccess) { - result = CURLE_SSL_CERTPROBLEM; - goto error; - } - -#ifndef CURL_DISABLE_PROXY - if(conn->proxy_ssl[sockindex].use) { - struct ssl_backend_data *proxy_backend; - proxy_backend = conn->proxy_ssl[sockindex].backend; - DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state); - DEBUGASSERT(proxy_backend); - DEBUGASSERT(proxy_backend->handle); - nspr_io = proxy_backend->handle; - second_layer = TRUE; - } -#endif - else { - /* wrap OS file descriptor by NSPR's file descriptor abstraction */ - nspr_io = PR_ImportTCPSocket(sockfd); - if(!nspr_io) - goto error; - } - - /* create our own NSPR I/O layer */ - nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods); - if(!nspr_io_stub) { - if(!second_layer) - PR_Close(nspr_io); - goto error; - } - - /* make the per-connection data accessible from NSPR I/O callbacks */ - nspr_io_stub->secret = (void *)connssl; - - /* push our new layer to the NSPR I/O stack */ - if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) { - if(!second_layer) - PR_Close(nspr_io); - PR_Close(nspr_io_stub); - goto error; - } - - /* import our model socket onto the current I/O stack */ - backend->handle = SSL_ImportFD(model, nspr_io); - if(!backend->handle) { - if(!second_layer) - PR_Close(nspr_io); - goto error; - } - - PR_Close(model); /* We don't need this any more */ - model = NULL; - - /* This is the password associated with the cert that we're using */ - if(SSL_SET_OPTION(key_passwd)) { - SSL_SetPKCS11PinArg(backend->handle, SSL_SET_OPTION(key_passwd)); - } - -#ifdef SSL_ENABLE_OCSP_STAPLING - if(SSL_CONN_CONFIG(verifystatus)) { - if(SSL_OptionSet(backend->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE) - != SECSuccess) - goto error; - } -#endif - -#ifdef SSL_ENABLE_ALPN - if(SSL_OptionSet(backend->handle, SSL_ENABLE_ALPN, conn->bits.tls_enable_alpn - ? PR_TRUE : PR_FALSE) != SECSuccess) - goto error; -#endif - -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ - if(data->set.ssl.falsestart) { - if(SSL_OptionSet(backend->handle, SSL_ENABLE_FALSE_START, PR_TRUE) - != SECSuccess) - goto error; - - if(SSL_SetCanFalseStartCallback(backend->handle, CanFalseStartCallback, - data) != SECSuccess) - goto error; - } -#endif - -#if defined(SSL_ENABLE_ALPN) - if(conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur++] = ALPN_H2_LENGTH; - memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - } -#endif - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - - if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess) - goto error; - } -#endif - - - /* Force handshake on next I/O */ - if(SSL_ResetHandshake(backend->handle, /* asServer */ PR_FALSE) - != SECSuccess) - goto error; - - /* propagate hostname to the TLS layer */ - if(SSL_SetURL(backend->handle, snihost) != SECSuccess) - goto error; - - /* prevent NSS from re-using the session for a different hostname */ - if(SSL_SetSockPeerID(backend->handle, snihost) != SECSuccess) - goto error; - - return CURLE_OK; - -error: - if(model) - PR_Close(model); - - return nss_fail_connect(connssl, data, result); -} - -static CURLcode nss_do_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - CURLcode result = CURLE_SSL_CONNECT_ERROR; - PRUint32 timeout; - - /* check timeout situation */ - const timediff_t time_left = Curl_timeleft(data, NULL, TRUE); - if(time_left < 0) { - failf(data, "timed out before SSL handshake"); - result = CURLE_OPERATION_TIMEDOUT; - goto error; - } - - DEBUGASSERT(backend); - - /* Force the handshake now */ - timeout = PR_MillisecondsToInterval((PRUint32) time_left); - if(SSL_ForceHandshakeWithTimeout(backend->handle, timeout) != SECSuccess) { - if(PR_GetError() == PR_WOULD_BLOCK_ERROR) - /* blocking direction is updated by nss_update_connecting_state() */ - return CURLE_AGAIN; - else if(SSL_SET_OPTION(certverifyresult) == SSL_ERROR_BAD_CERT_DOMAIN) - result = CURLE_PEER_FAILED_VERIFICATION; - else if(SSL_SET_OPTION(certverifyresult) != 0) - result = CURLE_PEER_FAILED_VERIFICATION; - goto error; - } - - result = display_conn_info(data, backend->handle); - if(result) - goto error; - - if(SSL_CONN_CONFIG(issuercert)) { - SECStatus ret = SECFailure; - char *nickname = dup_nickname(data, SSL_CONN_CONFIG(issuercert)); - if(nickname) { - /* we support only nicknames in case of issuercert for now */ - ret = check_issuer_cert(backend->handle, nickname); - free(nickname); - } - - if(SECFailure == ret) { - infof(data, "SSL certificate issuer check failed"); - result = CURLE_SSL_ISSUER_ERROR; - goto error; - } - else { - infof(data, "SSL certificate issuer check ok"); - } - } - - result = cmp_peer_pubkey(connssl, SSL_PINNED_PUB_KEY()); - if(result) - /* status already printed */ - goto error; - - return CURLE_OK; - -error: - return nss_fail_connect(connssl, data, result); -} - -static CURLcode nss_connect_common(struct Curl_easy *data, - struct connectdata *conn, int sockindex, - bool *done) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - const bool blocking = (done == NULL); - CURLcode result; - - if(connssl->state == ssl_connection_complete) { - if(!blocking) - *done = TRUE; - return CURLE_OK; - } - - if(connssl->connecting_state == ssl_connect_1) { - result = nss_setup_connect(data, conn, sockindex); - if(result) - /* we do not expect CURLE_AGAIN from nss_setup_connect() */ - return result; - - connssl->connecting_state = ssl_connect_2; - } - - /* enable/disable blocking mode before handshake */ - result = nss_set_blocking(connssl, data, blocking); - if(result) - return result; - - result = nss_do_connect(data, conn, sockindex); - switch(result) { - case CURLE_OK: - break; - case CURLE_AGAIN: - /* CURLE_AGAIN in non-blocking mode is not an error */ - if(!blocking) - return CURLE_OK; - else - return result; - default: - return result; - } - - if(blocking) { - /* in blocking mode, set NSS non-blocking mode _after_ SSL handshake */ - result = nss_set_blocking(connssl, data, /* blocking */ FALSE); - if(result) - return result; - } - else - /* signal completed SSL handshake */ - *done = TRUE; - - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = nss_recv; - conn->send[sockindex] = nss_send; - - /* ssl_connect_done is never used outside, go back to the initial state */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode nss_connect(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - return nss_connect_common(data, conn, sockindex, /* blocking */ NULL); -} - -static CURLcode nss_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - return nss_connect_common(data, conn, sockindex, done); -} - -static ssize_t nss_send(struct Curl_easy *data, /* transfer */ - int sockindex, /* socketindex */ - const void *mem, /* send this data */ - size_t len, /* amount to write */ - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - ssize_t rc; - - DEBUGASSERT(backend); - - /* The SelectClientCert() hook uses this for infof() and failf() but the - handle stored in nss_setup_connect() could have already been freed. */ - backend->data = data; - - rc = PR_Send(backend->handle, mem, (int)len, 0, PR_INTERVAL_NO_WAIT); - if(rc < 0) { - PRInt32 err = PR_GetError(); - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(data, "SSL write: error %d (%s)", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_SEND_ERROR; - } - - return -1; - } - - return rc; /* number of bytes */ -} - -static ssize_t nss_recv(struct Curl_easy *data, /* transfer */ - int sockindex, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - ssize_t nread; - - DEBUGASSERT(backend); - - /* The SelectClientCert() hook uses this for infof() and failf() but the - handle stored in nss_setup_connect() could have already been freed. */ - backend->data = data; - - nread = PR_Recv(backend->handle, buf, (int)buffersize, 0, - PR_INTERVAL_NO_WAIT); - if(nread < 0) { - /* failed SSL read */ - PRInt32 err = PR_GetError(); - - if(err == PR_WOULD_BLOCK_ERROR) - *curlcode = CURLE_AGAIN; - else { - /* print the error number and error string */ - const char *err_name = nss_error_to_name(err); - infof(data, "SSL read: errno %d (%s)", err, err_name); - - /* print a human-readable message describing the error if available */ - nss_print_error_message(data, err); - - *curlcode = (is_cc_error(err)) - ? CURLE_SSL_CERTPROBLEM - : CURLE_RECV_ERROR; - } - - return -1; - } - - return nread; -} - -static size_t nss_version(char *buffer, size_t size) -{ - return msnprintf(buffer, size, "NSS/%s", NSS_GetVersion()); -} - -/* data might be NULL */ -static int Curl_nss_seed(struct Curl_easy *data) -{ - /* make sure that NSS is initialized */ - return !!Curl_nss_force_init(data); -} - -/* data might be NULL */ -static CURLcode nss_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length) -{ - Curl_nss_seed(data); /* Initiate the seed if not already done */ - - if(SECSuccess != PK11_GenerateRandom(entropy, curlx_uztosi(length))) - /* signal a failure */ - return CURLE_FAILED_INIT; - - return CURLE_OK; -} - -static CURLcode nss_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len) -{ - PK11Context *SHA256pw = PK11_CreateDigestContext(SEC_OID_SHA256); - unsigned int SHA256out; - - if(!SHA256pw) - return CURLE_NOT_BUILT_IN; - - PK11_DigestOp(SHA256pw, tmp, curlx_uztoui(tmplen)); - PK11_DigestFinal(SHA256pw, sha256sum, &SHA256out, curlx_uztoui(sha256len)); - PK11_DestroyContext(SHA256pw, PR_TRUE); - - return CURLE_OK; -} - -static bool nss_cert_status_request(void) -{ -#ifdef SSL_ENABLE_OCSP_STAPLING - return TRUE; -#else - return FALSE; -#endif -} - -static bool nss_false_start(void) -{ -#if NSSVERNUM >= 0x030f04 /* 3.15.4 */ - return TRUE; -#else - return FALSE; -#endif -} - -static void *nss_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - (void)info; - DEBUGASSERT(backend); - return backend->handle; -} - -const struct Curl_ssl Curl_ssl_nss = { - { CURLSSLBACKEND_NSS, "nss" }, /* info */ - - SSLSUPP_CA_PATH | - SSLSUPP_CERTINFO | - SSLSUPP_PINNEDPUBKEY | - SSLSUPP_HTTPS_PROXY, - - sizeof(struct ssl_backend_data), - - nss_init, /* init */ - nss_cleanup, /* cleanup */ - nss_version, /* version */ - nss_check_cxn, /* check_cxn */ - /* NSS has no shutdown function provided and thus always fail */ - Curl_none_shutdown, /* shutdown */ - Curl_none_data_pending, /* data_pending */ - nss_random, /* random */ - nss_cert_status_request, /* cert_status_request */ - nss_connect, /* connect */ - nss_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - nss_get_internals, /* get_internals */ - nss_close, /* close_one */ - Curl_none_close_all, /* close_all */ - /* NSS has its own session ID cache */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - nss_false_start, /* false_start */ - nss_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif /* USE_NSS */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for all NSS-specific code for the TLS/SSL layer. No code
+ * but vtls.c should ever call or use these functions.
+ */
+
+#include "curl_setup.h"
+
+#ifdef USE_NSS
+
+#include "urldata.h"
+#include "sendf.h"
+#include "formdata.h" /* for the boundary function */
+#include "url.h" /* for the ssl config check function */
+#include "connect.h"
+#include "strcase.h"
+#include "select.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "llist.h"
+#include "multiif.h"
+#include "curl_printf.h"
+#include "nssg.h"
+#include <nspr.h>
+#include <nss.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <secmod.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <prio.h>
+#include <secitem.h>
+#include <secport.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h> /* for SECKEY_DestroyPublicKey() */
+#include <private/pprio.h> /* for PR_ImportTCPSocket */
+
+#define NSSVERNUM ((NSS_VMAJOR<<16)|(NSS_VMINOR<<8)|NSS_VPATCH)
+
+#if NSSVERNUM >= 0x030f00 /* 3.15.0 */
+#include <ocsp.h>
+#endif
+
+#include "warnless.h"
+#include "x509asn1.h"
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define SSL_DIR "/etc/pki/nssdb"
+
+/* enough to fit the string "PEM Token #[0|1]" */
+#define SLOTSIZE 13
+
+struct ssl_backend_data {
+ PRFileDesc *handle;
+ char *client_nickname;
+ struct Curl_easy *data;
+ struct Curl_llist obj_list;
+ PK11GenericObject *obj_clicert;
+};
+
+static PRLock *nss_initlock = NULL;
+static PRLock *nss_crllock = NULL;
+static PRLock *nss_findslot_lock = NULL;
+static PRLock *nss_trustload_lock = NULL;
+static struct Curl_llist nss_crl_list;
+static NSSInitContext *nss_context = NULL;
+static volatile int initialized = 0;
+
+/* type used to wrap pointers as list nodes */
+struct ptr_list_wrap {
+ void *ptr;
+ struct Curl_llist_element node;
+};
+
+struct cipher_s {
+ const char *name;
+ int num;
+};
+
+#define PK11_SETATTRS(_attr, _idx, _type, _val, _len) do { \
+ CK_ATTRIBUTE *ptr = (_attr) + ((_idx)++); \
+ ptr->type = (_type); \
+ ptr->pValue = (_val); \
+ ptr->ulValueLen = (_len); \
+} while(0)
+
+#define CERT_NewTempCertificate __CERT_NewTempCertificate
+
+#define NUM_OF_CIPHERS sizeof(cipherlist)/sizeof(cipherlist[0])
+static const struct cipher_s cipherlist[] = {
+ /* SSL2 cipher suites */
+ {"rc4", SSL_EN_RC4_128_WITH_MD5},
+ {"rc4-md5", SSL_EN_RC4_128_WITH_MD5},
+ {"rc4export", SSL_EN_RC4_128_EXPORT40_WITH_MD5},
+ {"rc2", SSL_EN_RC2_128_CBC_WITH_MD5},
+ {"rc2export", SSL_EN_RC2_128_CBC_EXPORT40_WITH_MD5},
+ {"des", SSL_EN_DES_64_CBC_WITH_MD5},
+ {"desede3", SSL_EN_DES_192_EDE3_CBC_WITH_MD5},
+ /* SSL3/TLS cipher suites */
+ {"rsa_rc4_128_md5", SSL_RSA_WITH_RC4_128_MD5},
+ {"rsa_rc4_128_sha", SSL_RSA_WITH_RC4_128_SHA},
+ {"rsa_3des_sha", SSL_RSA_WITH_3DES_EDE_CBC_SHA},
+ {"rsa_des_sha", SSL_RSA_WITH_DES_CBC_SHA},
+ {"rsa_rc4_40_md5", SSL_RSA_EXPORT_WITH_RC4_40_MD5},
+ {"rsa_rc2_40_md5", SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5},
+ {"rsa_null_md5", SSL_RSA_WITH_NULL_MD5},
+ {"rsa_null_sha", SSL_RSA_WITH_NULL_SHA},
+ {"fips_3des_sha", SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA},
+ {"fips_des_sha", SSL_RSA_FIPS_WITH_DES_CBC_SHA},
+ {"fortezza", SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA},
+ {"fortezza_rc4_128_sha", SSL_FORTEZZA_DMS_WITH_RC4_128_SHA},
+ {"fortezza_null", SSL_FORTEZZA_DMS_WITH_NULL_SHA},
+ {"dhe_rsa_3des_sha", SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+ {"dhe_dss_3des_sha", SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"dhe_rsa_des_sha", SSL_DHE_RSA_WITH_DES_CBC_SHA},
+ {"dhe_dss_des_sha", SSL_DHE_DSS_WITH_DES_CBC_SHA},
+ /* TLS 1.0: Exportable 56-bit Cipher Suites. */
+ {"rsa_des_56_sha", TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA},
+ {"rsa_rc4_56_sha", TLS_RSA_EXPORT1024_WITH_RC4_56_SHA},
+ /* Ephemeral DH with RC4 bulk encryption */
+ {"dhe_dss_rc4_128_sha", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ /* AES ciphers. */
+ {"dhe_dss_aes_128_cbc_sha", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"dhe_dss_aes_256_cbc_sha", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"dhe_rsa_aes_128_cbc_sha", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"dhe_rsa_aes_256_cbc_sha", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"rsa_aes_256_sha", TLS_RSA_WITH_AES_256_CBC_SHA},
+ /* ECC ciphers. */
+ {"ecdh_ecdsa_null_sha", TLS_ECDH_ECDSA_WITH_NULL_SHA},
+ {"ecdh_ecdsa_rc4_128_sha", TLS_ECDH_ECDSA_WITH_RC4_128_SHA},
+ {"ecdh_ecdsa_3des_sha", TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA},
+ {"ecdh_ecdsa_aes_128_sha", TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA},
+ {"ecdh_ecdsa_aes_256_sha", TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA},
+ {"ecdhe_ecdsa_null_sha", TLS_ECDHE_ECDSA_WITH_NULL_SHA},
+ {"ecdhe_ecdsa_rc4_128_sha", TLS_ECDHE_ECDSA_WITH_RC4_128_SHA},
+ {"ecdhe_ecdsa_3des_sha", TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA},
+ {"ecdhe_ecdsa_aes_128_sha", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA},
+ {"ecdhe_ecdsa_aes_256_sha", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA},
+ {"ecdh_rsa_null_sha", TLS_ECDH_RSA_WITH_NULL_SHA},
+ {"ecdh_rsa_128_sha", TLS_ECDH_RSA_WITH_RC4_128_SHA},
+ {"ecdh_rsa_3des_sha", TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA},
+ {"ecdh_rsa_aes_128_sha", TLS_ECDH_RSA_WITH_AES_128_CBC_SHA},
+ {"ecdh_rsa_aes_256_sha", TLS_ECDH_RSA_WITH_AES_256_CBC_SHA},
+ {"ecdhe_rsa_null", TLS_ECDHE_RSA_WITH_NULL_SHA},
+ {"ecdhe_rsa_rc4_128_sha", TLS_ECDHE_RSA_WITH_RC4_128_SHA},
+ {"ecdhe_rsa_3des_sha", TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA},
+ {"ecdhe_rsa_aes_128_sha", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA},
+ {"ecdhe_rsa_aes_256_sha", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA},
+ {"ecdh_anon_null_sha", TLS_ECDH_anon_WITH_NULL_SHA},
+ {"ecdh_anon_rc4_128sha", TLS_ECDH_anon_WITH_RC4_128_SHA},
+ {"ecdh_anon_3des_sha", TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA},
+ {"ecdh_anon_aes_128_sha", TLS_ECDH_anon_WITH_AES_128_CBC_SHA},
+ {"ecdh_anon_aes_256_sha", TLS_ECDH_anon_WITH_AES_256_CBC_SHA},
+#ifdef TLS_RSA_WITH_NULL_SHA256
+ /* new HMAC-SHA256 cipher suites specified in RFC */
+ {"rsa_null_sha_256", TLS_RSA_WITH_NULL_SHA256},
+ {"rsa_aes_128_cbc_sha_256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"rsa_aes_256_cbc_sha_256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+ {"dhe_rsa_aes_128_cbc_sha_256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"dhe_rsa_aes_256_cbc_sha_256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+ {"ecdhe_ecdsa_aes_128_cbc_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256},
+ {"ecdhe_rsa_aes_128_cbc_sha_256", TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256},
+#endif
+#ifdef TLS_RSA_WITH_AES_128_GCM_SHA256
+ /* AES GCM cipher suites in RFC 5288 and RFC 5289 */
+ {"rsa_aes_128_gcm_sha_256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"dhe_rsa_aes_128_gcm_sha_256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"dhe_dss_aes_128_gcm_sha_256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"ecdhe_ecdsa_aes_128_gcm_sha_256", TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
+ {"ecdh_ecdsa_aes_128_gcm_sha_256", TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256},
+ {"ecdhe_rsa_aes_128_gcm_sha_256", TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"ecdh_rsa_aes_128_gcm_sha_256", TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256},
+#endif
+#ifdef TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
+ /* cipher suites using SHA384 */
+ {"rsa_aes_256_gcm_sha_384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"dhe_rsa_aes_256_gcm_sha_384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"dhe_dss_aes_256_gcm_sha_384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+ {"ecdhe_ecdsa_aes_256_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384},
+ {"ecdhe_rsa_aes_256_sha_384", TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384},
+ {"ecdhe_ecdsa_aes_256_gcm_sha_384", TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384},
+ {"ecdhe_rsa_aes_256_gcm_sha_384", TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
+#endif
+#ifdef TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
+ /* chacha20-poly1305 cipher suites */
+ {"ecdhe_rsa_chacha20_poly1305_sha_256",
+ TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+ {"ecdhe_ecdsa_chacha20_poly1305_sha_256",
+ TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256},
+ {"dhe_rsa_chacha20_poly1305_sha_256",
+ TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256},
+#endif
+#ifdef TLS_AES_256_GCM_SHA384
+ {"aes_128_gcm_sha_256", TLS_AES_128_GCM_SHA256},
+ {"aes_256_gcm_sha_384", TLS_AES_256_GCM_SHA384},
+ {"chacha20_poly1305_sha_256", TLS_CHACHA20_POLY1305_SHA256},
+#endif
+#ifdef TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
+ /* AES CBC cipher suites in RFC 5246. Introduced in NSS release 3.20 */
+ {"dhe_dss_aes_128_sha_256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"dhe_dss_aes_256_sha_256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+#endif
+#ifdef TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA
+ /* Camellia cipher suites in RFC 4132/5932.
+ Introduced in NSS release 3.12 */
+ {"dhe_rsa_camellia_128_sha", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"dhe_dss_camellia_128_sha", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"dhe_rsa_camellia_256_sha", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"dhe_dss_camellia_256_sha", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"rsa_camellia_128_sha", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"rsa_camellia_256_sha", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+#endif
+#ifdef TLS_RSA_WITH_SEED_CBC_SHA
+ /* SEED cipher suite in RFC 4162. Introduced in NSS release 3.12.3 */
+ {"rsa_seed_sha", TLS_RSA_WITH_SEED_CBC_SHA},
+#endif
+};
+
+#if defined(WIN32)
+static const char *pem_library = "nsspem.dll";
+static const char *trust_library = "nssckbi.dll";
+#elif defined(__APPLE__)
+static const char *pem_library = "libnsspem.dylib";
+static const char *trust_library = "libnssckbi.dylib";
+#else
+static const char *pem_library = "libnsspem.so";
+static const char *trust_library = "libnssckbi.so";
+#endif
+
+static SECMODModule *pem_module = NULL;
+static SECMODModule *trust_module = NULL;
+
+/* NSPR I/O layer we use to detect blocking direction during SSL handshake */
+static PRDescIdentity nspr_io_identity = PR_INVALID_IO_LAYER;
+static PRIOMethods nspr_io_methods;
+
+static const char *nss_error_to_name(PRErrorCode code)
+{
+ const char *name = PR_ErrorToName(code);
+ if(name)
+ return name;
+
+ return "unknown error";
+}
+
+static void nss_print_error_message(struct Curl_easy *data, PRUint32 err)
+{
+ failf(data, "%s", PR_ErrorToString(err, PR_LANGUAGE_I_DEFAULT));
+}
+
+static char *nss_sslver_to_name(PRUint16 nssver)
+{
+ switch(nssver) {
+ case SSL_LIBRARY_VERSION_2:
+ return strdup("SSLv2");
+ case SSL_LIBRARY_VERSION_3_0:
+ return strdup("SSLv3");
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return strdup("TLSv1.0");
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return strdup("TLSv1.1");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return strdup("TLSv1.2");
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return strdup("TLSv1.3");
+#endif
+ default:
+ return curl_maprintf("0x%04x", nssver);
+ }
+}
+
+/* the longest cipher name this supports */
+#define MAX_CIPHER_LENGTH 128
+
+static SECStatus set_ciphers(struct Curl_easy *data, PRFileDesc *model,
+ const char *cipher_list)
+{
+ unsigned int i;
+ const char *cipher;
+
+ /* use accessors to avoid dynamic linking issues after an update of NSS */
+ const PRUint16 num_implemented_ciphers = SSL_GetNumImplementedCiphers();
+ const PRUint16 *implemented_ciphers = SSL_GetImplementedCiphers();
+ if(!implemented_ciphers)
+ return SECFailure;
+
+ /* First disable all ciphers. This uses a different max value in case
+ * NSS adds more ciphers later we don't want them available by
+ * accident
+ */
+ for(i = 0; i < num_implemented_ciphers; i++) {
+ SSL_CipherPrefSet(model, implemented_ciphers[i], PR_FALSE);
+ }
+
+ cipher = cipher_list;
+
+ while(cipher && cipher[0]) {
+ const char *end;
+ char name[MAX_CIPHER_LENGTH + 1];
+ size_t len;
+ bool found = FALSE;
+ while((*cipher) && (ISBLANK(*cipher)))
+ ++cipher;
+
+ end = strpbrk(cipher, ":, ");
+ if(end)
+ len = end - cipher;
+ else
+ len = strlen(cipher);
+
+ if(len > MAX_CIPHER_LENGTH) {
+ failf(data, "Bad cipher list");
+ return SECFailure;
+ }
+ else if(len) {
+ memcpy(name, cipher, len);
+ name[len] = 0;
+
+ for(i = 0; i<NUM_OF_CIPHERS; i++) {
+ if(strcasecompare(name, cipherlist[i].name)) {
+ /* Enable the selected cipher */
+ if(SSL_CipherPrefSet(model, cipherlist[i].num, PR_TRUE) !=
+ SECSuccess) {
+ failf(data, "cipher-suite not supported by NSS: %s", name);
+ return SECFailure;
+ }
+ found = TRUE;
+ break;
+ }
+ }
+ }
+
+ if(!found && len) {
+ failf(data, "Unknown cipher: %s", name);
+ return SECFailure;
+ }
+ if(end)
+ cipher = ++end;
+ else
+ break;
+ }
+
+ return SECSuccess;
+}
+
+/*
+ * Return true if at least one cipher-suite is enabled. Used to determine
+ * if we need to call NSS_SetDomesticPolicy() to enable the default ciphers.
+ */
+static bool any_cipher_enabled(void)
+{
+ unsigned int i;
+
+ for(i = 0; i<NUM_OF_CIPHERS; i++) {
+ PRInt32 policy = 0;
+ SSL_CipherPolicyGet(cipherlist[i].num, &policy);
+ if(policy)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*
+ * Determine whether the nickname passed in is a filename that needs to
+ * be loaded as a PEM or a regular NSS nickname.
+ *
+ * returns 1 for a file
+ * returns 0 for not a file (NSS nickname)
+ */
+static int is_file(const char *filename)
+{
+ struct_stat st;
+
+ if(!filename)
+ return 0;
+
+ if(stat(filename, &st) == 0)
+ if(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode) || S_ISCHR(st.st_mode))
+ return 1;
+
+ return 0;
+}
+
+/* Check if the given string is filename or nickname of a certificate. If the
+ * given string is recognized as filename, return NULL. If the given string is
+ * recognized as nickname, return a duplicated string. The returned string
+ * should be later deallocated using free(). If the OOM failure occurs, we
+ * return NULL, too.
+ */
+static char *dup_nickname(struct Curl_easy *data, const char *str)
+{
+ const char *n;
+
+ if(!is_file(str))
+ /* no such file exists, use the string as nickname */
+ return strdup(str);
+
+ /* search the first slash; we require at least one slash in a file name */
+ n = strchr(str, '/');
+ if(!n) {
+ infof(data, "WARNING: certificate file name \"%s\" handled as nickname; "
+ "please use \"./%s\" to force file name", str, str);
+ return strdup(str);
+ }
+
+ /* we'll use the PEM reader to read the certificate from file */
+ return NULL;
+}
+
+/* Lock/unlock wrapper for PK11_FindSlotByName() to work around race condition
+ * in nssSlot_IsTokenPresent() causing spurious SEC_ERROR_NO_TOKEN. For more
+ * details, go to <https://bugzilla.mozilla.org/1297397>.
+ */
+static PK11SlotInfo* nss_find_slot_by_name(const char *slot_name)
+{
+ PK11SlotInfo *slot;
+ PR_Lock(nss_findslot_lock);
+ slot = PK11_FindSlotByName(slot_name);
+ PR_Unlock(nss_findslot_lock);
+ return slot;
+}
+
+/* wrap 'ptr' as list node and tail-insert into 'list' */
+static CURLcode insert_wrapped_ptr(struct Curl_llist *list, void *ptr)
+{
+ struct ptr_list_wrap *wrap = malloc(sizeof(*wrap));
+ if(!wrap)
+ return CURLE_OUT_OF_MEMORY;
+
+ wrap->ptr = ptr;
+ Curl_llist_insert_next(list, list->tail, wrap, &wrap->node);
+ return CURLE_OK;
+}
+
+/* Call PK11_CreateGenericObject() with the given obj_class and filename. If
+ * the call succeeds, append the object handle to the list of objects so that
+ * the object can be destroyed in nss_close(). */
+static CURLcode nss_create_object(struct ssl_connect_data *connssl,
+ CK_OBJECT_CLASS obj_class,
+ const char *filename, bool cacert)
+{
+ PK11SlotInfo *slot;
+ PK11GenericObject *obj;
+ CK_BBOOL cktrue = CK_TRUE;
+ CK_BBOOL ckfalse = CK_FALSE;
+ CK_ATTRIBUTE attrs[/* max count of attributes */ 4];
+ int attr_cnt = 0;
+ CURLcode result = (cacert)
+ ? CURLE_SSL_CACERT_BADFILE
+ : CURLE_SSL_CERTPROBLEM;
+
+ const int slot_id = (cacert) ? 0 : 1;
+ char *slot_name = aprintf("PEM Token #%d", slot_id);
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ if(!slot_name)
+ return CURLE_OUT_OF_MEMORY;
+
+ slot = nss_find_slot_by_name(slot_name);
+ free(slot_name);
+ if(!slot)
+ return result;
+
+ PK11_SETATTRS(attrs, attr_cnt, CKA_CLASS, &obj_class, sizeof(obj_class));
+ PK11_SETATTRS(attrs, attr_cnt, CKA_TOKEN, &cktrue, sizeof(CK_BBOOL));
+ PK11_SETATTRS(attrs, attr_cnt, CKA_LABEL, (unsigned char *)filename,
+ (CK_ULONG)strlen(filename) + 1);
+
+ if(CKO_CERTIFICATE == obj_class) {
+ CK_BBOOL *pval = (cacert) ? (&cktrue) : (&ckfalse);
+ PK11_SETATTRS(attrs, attr_cnt, CKA_TRUST, pval, sizeof(*pval));
+ }
+
+ /* PK11_CreateManagedGenericObject() was introduced in NSS 3.34 because
+ * PK11_DestroyGenericObject() does not release resources allocated by
+ * PK11_CreateGenericObject() early enough. */
+ obj =
+#ifdef HAVE_PK11_CREATEMANAGEDGENERICOBJECT
+ PK11_CreateManagedGenericObject
+#else
+ PK11_CreateGenericObject
+#endif
+ (slot, attrs, attr_cnt, PR_FALSE);
+
+ PK11_FreeSlot(slot);
+ if(!obj)
+ return result;
+
+ if(insert_wrapped_ptr(&backend->obj_list, obj) != CURLE_OK) {
+ PK11_DestroyGenericObject(obj);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ if(!cacert && CKO_CERTIFICATE == obj_class)
+ /* store reference to a client certificate */
+ backend->obj_clicert = obj;
+
+ return CURLE_OK;
+}
+
+/* Destroy the NSS object whose handle is given by ptr. This function is
+ * a callback of Curl_llist_alloc() used by Curl_llist_destroy() to destroy
+ * NSS objects in nss_close() */
+static void nss_destroy_object(void *user, void *ptr)
+{
+ struct ptr_list_wrap *wrap = (struct ptr_list_wrap *) ptr;
+ PK11GenericObject *obj = (PK11GenericObject *) wrap->ptr;
+ (void) user;
+ PK11_DestroyGenericObject(obj);
+ free(wrap);
+}
+
+/* same as nss_destroy_object() but for CRL items */
+static void nss_destroy_crl_item(void *user, void *ptr)
+{
+ struct ptr_list_wrap *wrap = (struct ptr_list_wrap *) ptr;
+ SECItem *crl_der = (SECItem *) wrap->ptr;
+ (void) user;
+ SECITEM_FreeItem(crl_der, PR_TRUE);
+ free(wrap);
+}
+
+static CURLcode nss_load_cert(struct ssl_connect_data *ssl,
+ const char *filename, PRBool cacert)
+{
+ CURLcode result = (cacert)
+ ? CURLE_SSL_CACERT_BADFILE
+ : CURLE_SSL_CERTPROBLEM;
+
+ /* libnsspem.so leaks memory if the requested file does not exist. For more
+ * details, go to <https://bugzilla.redhat.com/734760>. */
+ if(is_file(filename))
+ result = nss_create_object(ssl, CKO_CERTIFICATE, filename, cacert);
+
+ if(!result && !cacert) {
+ /* we have successfully loaded a client certificate */
+ char *nickname = NULL;
+ char *n = strrchr(filename, '/');
+ if(n)
+ n++;
+
+ /* The following undocumented magic helps to avoid a SIGSEGV on call
+ * of PK11_ReadRawAttribute() from SelectClientCert() when using an
+ * immature version of libnsspem.so. For more details, go to
+ * <https://bugzilla.redhat.com/733685>. */
+ nickname = aprintf("PEM Token #1:%s", n);
+ if(nickname) {
+ CERTCertificate *cert = PK11_FindCertFromNickname(nickname, NULL);
+ if(cert)
+ CERT_DestroyCertificate(cert);
+
+ free(nickname);
+ }
+ }
+
+ return result;
+}
+
+/* add given CRL to cache if it is not already there */
+static CURLcode nss_cache_crl(SECItem *crl_der)
+{
+ CERTCertDBHandle *db = CERT_GetDefaultCertDB();
+ CERTSignedCrl *crl = SEC_FindCrlByDERCert(db, crl_der, 0);
+ if(crl) {
+ /* CRL already cached */
+ SEC_DestroyCrl(crl);
+ SECITEM_FreeItem(crl_der, PR_TRUE);
+ return CURLE_OK;
+ }
+
+ /* acquire lock before call of CERT_CacheCRL() and accessing nss_crl_list */
+ PR_Lock(nss_crllock);
+
+ if(SECSuccess != CERT_CacheCRL(db, crl_der)) {
+ /* unable to cache CRL */
+ SECITEM_FreeItem(crl_der, PR_TRUE);
+ PR_Unlock(nss_crllock);
+ return CURLE_SSL_CRL_BADFILE;
+ }
+
+ /* store the CRL item so that we can free it in nss_cleanup() */
+ if(insert_wrapped_ptr(&nss_crl_list, crl_der) != CURLE_OK) {
+ if(SECSuccess == CERT_UncacheCRL(db, crl_der))
+ SECITEM_FreeItem(crl_der, PR_TRUE);
+ PR_Unlock(nss_crllock);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ /* we need to clear session cache, so that the CRL could take effect */
+ SSL_ClearSessionCache();
+ PR_Unlock(nss_crllock);
+ return CURLE_OK;
+}
+
+static CURLcode nss_load_crl(const char *crlfilename)
+{
+ PRFileDesc *infile;
+ PRFileInfo info;
+ SECItem filedata = { 0, NULL, 0 };
+ SECItem *crl_der = NULL;
+ char *body;
+
+ infile = PR_Open(crlfilename, PR_RDONLY, 0);
+ if(!infile)
+ return CURLE_SSL_CRL_BADFILE;
+
+ if(PR_SUCCESS != PR_GetOpenFileInfo(infile, &info))
+ goto fail;
+
+ if(!SECITEM_AllocItem(NULL, &filedata, info.size + /* zero ended */ 1))
+ goto fail;
+
+ if(info.size != PR_Read(infile, filedata.data, info.size))
+ goto fail;
+
+ crl_der = SECITEM_AllocItem(NULL, NULL, 0U);
+ if(!crl_der)
+ goto fail;
+
+ /* place a trailing zero right after the visible data */
+ body = (char *)filedata.data;
+ body[--filedata.len] = '\0';
+
+ body = strstr(body, "-----BEGIN");
+ if(body) {
+ /* assume ASCII */
+ char *trailer;
+ char *begin = PORT_Strchr(body, '\n');
+ if(!begin)
+ begin = PORT_Strchr(body, '\r');
+ if(!begin)
+ goto fail;
+
+ trailer = strstr(++begin, "-----END");
+ if(!trailer)
+ goto fail;
+
+ /* retrieve DER from ASCII */
+ *trailer = '\0';
+ if(ATOB_ConvertAsciiToItem(crl_der, begin))
+ goto fail;
+
+ SECITEM_FreeItem(&filedata, PR_FALSE);
+ }
+ else
+ /* assume DER */
+ *crl_der = filedata;
+
+ PR_Close(infile);
+ return nss_cache_crl(crl_der);
+
+fail:
+ PR_Close(infile);
+ SECITEM_FreeItem(crl_der, PR_TRUE);
+ SECITEM_FreeItem(&filedata, PR_FALSE);
+ return CURLE_SSL_CRL_BADFILE;
+}
+
+static CURLcode nss_load_key(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *key_file)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ PK11SlotInfo *slot, *tmp;
+ SECStatus status;
+ CURLcode result;
+
+ (void)data;
+ result = nss_create_object(connssl, CKO_PRIVATE_KEY, key_file, FALSE);
+ if(result) {
+ PR_SetError(SEC_ERROR_BAD_KEY, 0);
+ return result;
+ }
+
+ slot = nss_find_slot_by_name("PEM Token #1");
+ if(!slot)
+ return CURLE_SSL_CERTPROBLEM;
+
+ /* This will force the token to be seen as re-inserted */
+ tmp = SECMOD_WaitForAnyTokenEvent(pem_module, 0, 0);
+ if(tmp)
+ PK11_FreeSlot(tmp);
+ if(!PK11_IsPresent(slot)) {
+ PK11_FreeSlot(slot);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+ status = PK11_Authenticate(slot, PR_TRUE, ssl_config->key_passwd);
+ PK11_FreeSlot(slot);
+
+ return (SECSuccess == status) ? CURLE_OK : CURLE_SSL_CERTPROBLEM;
+}
+
+static int display_error(struct Curl_easy *data, PRInt32 err,
+ const char *filename)
+{
+ switch(err) {
+ case SEC_ERROR_BAD_PASSWORD:
+ failf(data, "Unable to load client key: Incorrect password");
+ return 1;
+ case SEC_ERROR_UNKNOWN_CERT:
+ failf(data, "Unable to load certificate %s", filename);
+ return 1;
+ default:
+ break;
+ }
+ return 0; /* The caller will print a generic error */
+}
+
+static CURLcode cert_stuff(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *cert_file, char *key_file)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ CURLcode result;
+
+ if(cert_file) {
+ result = nss_load_cert(connssl, cert_file, PR_FALSE);
+ if(result) {
+ const PRErrorCode err = PR_GetError();
+ if(!display_error(data, err, cert_file)) {
+ const char *err_name = nss_error_to_name(err);
+ failf(data, "unable to load client cert: %d (%s)", err, err_name);
+ }
+
+ return result;
+ }
+ }
+
+ if(key_file || (is_file(cert_file))) {
+ if(key_file)
+ result = nss_load_key(cf, data, key_file);
+ else
+ /* In case the cert file also has the key */
+ result = nss_load_key(cf, data, cert_file);
+ if(result) {
+ const PRErrorCode err = PR_GetError();
+ if(!display_error(data, err, key_file)) {
+ const char *err_name = nss_error_to_name(err);
+ failf(data, "unable to load client key: %d (%s)", err, err_name);
+ }
+
+ return result;
+ }
+ }
+
+ return CURLE_OK;
+}
+
+static char *nss_get_password(PK11SlotInfo *slot, PRBool retry, void *arg)
+{
+ (void)slot; /* unused */
+
+ if(retry || !arg)
+ return NULL;
+ else
+ return (char *)PORT_Strdup((char *)arg);
+}
+
+/* bypass the default SSL_AuthCertificate() hook in case we do not want to
+ * verify peer */
+static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig,
+ PRBool isServer)
+{
+ struct Curl_cfilter *cf = (struct Curl_cfilter *)arg;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct Curl_easy *data = connssl->backend->data;
+
+ DEBUGASSERT(data);
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ if(conn_config->verifystatus) {
+ SECStatus cacheResult;
+
+ const SECItemArray *csa = SSL_PeerStapledOCSPResponses(fd);
+ if(!csa) {
+ failf(data, "Invalid OCSP response");
+ return SECFailure;
+ }
+
+ if(csa->len == 0) {
+ failf(data, "No OCSP response received");
+ return SECFailure;
+ }
+
+ cacheResult = CERT_CacheOCSPResponseFromSideChannel(
+ CERT_GetDefaultCertDB(), SSL_PeerCertificate(fd),
+ PR_Now(), &csa->items[0], arg
+ );
+
+ if(cacheResult != SECSuccess) {
+ failf(data, "Invalid OCSP response");
+ return cacheResult;
+ }
+ }
+#endif
+
+ if(!conn_config->verifypeer) {
+ infof(data, "skipping SSL peer certificate verification");
+ return SECSuccess;
+ }
+
+ return SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer);
+}
+
+/**
+ * Inform the application that the handshake is complete.
+ */
+static void HandshakeCallback(PRFileDesc *sock, void *arg)
+{
+ struct Curl_cfilter *cf = (struct Curl_cfilter *)arg;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->backend->data;
+ struct connectdata *conn = cf->conn;
+ unsigned int buflenmax = 50;
+ unsigned char buf[50];
+ unsigned int buflen;
+ SSLNextProtoState state;
+
+ DEBUGASSERT(data);
+ if(!conn->bits.tls_enable_alpn) {
+ return;
+ }
+
+ if(SSL_GetNextProto(sock, &state, buf, &buflen, buflenmax) == SECSuccess) {
+
+ switch(state) {
+#if NSSVERNUM >= 0x031a00 /* 3.26.0 */
+ /* used by NSS internally to implement 0-RTT */
+ case SSL_NEXT_PROTO_EARLY_VALUE:
+ /* fall through! */
+#endif
+ case SSL_NEXT_PROTO_NO_SUPPORT:
+ case SSL_NEXT_PROTO_NO_OVERLAP:
+ infof(data, VTLS_INFOF_NO_ALPN);
+ return;
+#ifdef SSL_ENABLE_ALPN
+ case SSL_NEXT_PROTO_SELECTED:
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, buflen, buf);
+ break;
+#endif
+ default:
+ /* ignore SSL_NEXT_PROTO_NEGOTIATED */
+ break;
+ }
+
+#ifdef USE_HTTP2
+ if(buflen == ALPN_H2_LENGTH &&
+ !memcmp(ALPN_H2, buf, ALPN_H2_LENGTH)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+ }
+ else
+#endif
+ if(buflen == ALPN_HTTP_1_1_LENGTH &&
+ !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+ }
+
+ /* This callback might get called when PR_Recv() is used within
+ * close_one() during a connection shutdown. At that point there might not
+ * be any "bundle" associated with the connection anymore.
+ */
+ if(conn->bundle)
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+ }
+}
+
+#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
+static SECStatus CanFalseStartCallback(PRFileDesc *sock, void *client_data,
+ PRBool *canFalseStart)
+{
+ struct Curl_easy *data = (struct Curl_easy *)client_data;
+
+ SSLChannelInfo channelInfo;
+ SSLCipherSuiteInfo cipherInfo;
+
+ SECStatus rv;
+ PRBool negotiatedExtension;
+
+ *canFalseStart = PR_FALSE;
+
+ if(SSL_GetChannelInfo(sock, &channelInfo, sizeof(channelInfo)) != SECSuccess)
+ return SECFailure;
+
+ if(SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
+ sizeof(cipherInfo)) != SECSuccess)
+ return SECFailure;
+
+ /* Prevent version downgrade attacks from TLS 1.2, and avoid False Start for
+ * TLS 1.3 and later. See https://bugzilla.mozilla.org/show_bug.cgi?id=861310
+ */
+ if(channelInfo.protocolVersion != SSL_LIBRARY_VERSION_TLS_1_2)
+ goto end;
+
+ /* Only allow ECDHE key exchange algorithm.
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=952863 */
+ if(cipherInfo.keaType != ssl_kea_ecdh)
+ goto end;
+
+ /* Prevent downgrade attacks on the symmetric cipher. We do not allow CBC
+ * mode due to BEAST, POODLE, and other attacks on the MAC-then-Encrypt
+ * design. See https://bugzilla.mozilla.org/show_bug.cgi?id=1109766 */
+ if(cipherInfo.symCipher != ssl_calg_aes_gcm)
+ goto end;
+
+ /* Enforce ALPN to do False Start, as an indicator of server
+ compatibility. */
+ rv = SSL_HandshakeNegotiatedExtension(sock, ssl_app_layer_protocol_xtn,
+ &negotiatedExtension);
+ if(rv != SECSuccess || !negotiatedExtension) {
+ rv = SSL_HandshakeNegotiatedExtension(sock, ssl_next_proto_nego_xtn,
+ &negotiatedExtension);
+ }
+
+ if(rv != SECSuccess || !negotiatedExtension)
+ goto end;
+
+ *canFalseStart = PR_TRUE;
+
+ infof(data, "Trying TLS False Start");
+
+end:
+ return SECSuccess;
+}
+#endif
+
+static void display_cert_info(struct Curl_easy *data,
+ CERTCertificate *cert)
+{
+ char *subject, *issuer, *common_name;
+ PRExplodedTime printableTime;
+ char timeString[256];
+ PRTime notBefore, notAfter;
+
+ subject = CERT_NameToAscii(&cert->subject);
+ issuer = CERT_NameToAscii(&cert->issuer);
+ common_name = CERT_GetCommonName(&cert->subject);
+ infof(data, "subject: %s", subject);
+
+ CERT_GetCertTimes(cert, ¬Before, ¬After);
+ PR_ExplodeTime(notBefore, PR_GMTParameters, &printableTime);
+ PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime);
+ infof(data, " start date: %s", timeString);
+ PR_ExplodeTime(notAfter, PR_GMTParameters, &printableTime);
+ PR_FormatTime(timeString, 256, "%b %d %H:%M:%S %Y GMT", &printableTime);
+ infof(data, " expire date: %s", timeString);
+ infof(data, " common name: %s", common_name);
+ infof(data, " issuer: %s", issuer);
+
+ PR_Free(subject);
+ PR_Free(issuer);
+ PR_Free(common_name);
+}
+
+/* A number of certs that will never occur in a real server handshake */
+#define TOO_MANY_CERTS 300
+
+static CURLcode display_conn_info(struct Curl_easy *data, PRFileDesc *sock)
+{
+ CURLcode result = CURLE_OK;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+ CERTCertificate *cert;
+ CERTCertificate *cert2;
+ CERTCertificate *cert3;
+ PRTime now;
+
+ if(SSL_GetChannelInfo(sock, &channel, sizeof(channel)) ==
+ SECSuccess && channel.length == sizeof(channel) &&
+ channel.cipherSuite) {
+ if(SSL_GetCipherSuiteInfo(channel.cipherSuite,
+ &suite, sizeof(suite)) == SECSuccess) {
+ infof(data, "SSL connection using %s", suite.cipherSuiteName);
+ }
+ }
+
+ cert = SSL_PeerCertificate(sock);
+ if(cert) {
+ infof(data, "Server certificate:");
+
+ if(!data->set.ssl.certinfo) {
+ display_cert_info(data, cert);
+ CERT_DestroyCertificate(cert);
+ }
+ else {
+ /* Count certificates in chain. */
+ int i = 1;
+ now = PR_Now();
+ if(!cert->isRoot) {
+ cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA);
+ while(cert2) {
+ i++;
+ if(i >= TOO_MANY_CERTS) {
+ CERT_DestroyCertificate(cert2);
+ failf(data, "certificate loop");
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ if(cert2->isRoot) {
+ CERT_DestroyCertificate(cert2);
+ break;
+ }
+ cert3 = CERT_FindCertIssuer(cert2, now, certUsageSSLCA);
+ CERT_DestroyCertificate(cert2);
+ cert2 = cert3;
+ }
+ }
+
+ result = Curl_ssl_init_certinfo(data, i);
+ if(!result) {
+ for(i = 0; cert; cert = cert2) {
+ result = Curl_extract_certinfo(data, i++, (char *)cert->derCert.data,
+ (char *)cert->derCert.data +
+ cert->derCert.len);
+ if(result)
+ break;
+
+ if(cert->isRoot) {
+ CERT_DestroyCertificate(cert);
+ break;
+ }
+
+ cert2 = CERT_FindCertIssuer(cert, now, certUsageSSLCA);
+ CERT_DestroyCertificate(cert);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+static SECStatus BadCertHandler(void *arg, PRFileDesc *sock)
+{
+ struct Curl_cfilter *cf = (struct Curl_cfilter *)arg;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->backend->data;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config;
+ PRErrorCode err = PR_GetError();
+ CERTCertificate *cert;
+
+ DEBUGASSERT(data);
+ ssl_config = Curl_ssl_cf_get_config(cf, data);
+ /* remember the cert verification result */
+ ssl_config->certverifyresult = err;
+
+ if(err == SSL_ERROR_BAD_CERT_DOMAIN && !conn_config->verifyhost)
+ /* we are asked not to verify the host name */
+ return SECSuccess;
+
+ /* print only info about the cert, the error is printed off the callback */
+ cert = SSL_PeerCertificate(sock);
+ if(cert) {
+ infof(data, "Server certificate:");
+ display_cert_info(data, cert);
+ CERT_DestroyCertificate(cert);
+ }
+
+ return SECFailure;
+}
+
+/**
+ *
+ * Check that the Peer certificate's issuer certificate matches the one found
+ * by issuer_nickname. This is not exactly the way OpenSSL and GNU TLS do the
+ * issuer check, so we provide comments that mimic the OpenSSL
+ * X509_check_issued function (in x509v3/v3_purp.c)
+ */
+static SECStatus check_issuer_cert(PRFileDesc *sock,
+ char *issuer_nickname)
+{
+ CERTCertificate *cert, *cert_issuer, *issuer;
+ SECStatus res = SECSuccess;
+ void *proto_win = NULL;
+
+ cert = SSL_PeerCertificate(sock);
+ cert_issuer = CERT_FindCertIssuer(cert, PR_Now(), certUsageObjectSigner);
+
+ proto_win = SSL_RevealPinArg(sock);
+ issuer = PK11_FindCertFromNickname(issuer_nickname, proto_win);
+
+ if((!cert_issuer) || (!issuer))
+ res = SECFailure;
+ else if(SECITEM_CompareItem(&cert_issuer->derCert,
+ &issuer->derCert) != SECEqual)
+ res = SECFailure;
+
+ CERT_DestroyCertificate(cert);
+ CERT_DestroyCertificate(issuer);
+ CERT_DestroyCertificate(cert_issuer);
+ return res;
+}
+
+static CURLcode cmp_peer_pubkey(struct ssl_connect_data *connssl,
+ const char *pinnedpubkey)
+{
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct Curl_easy *data = NULL;
+ CERTCertificate *cert;
+
+ DEBUGASSERT(backend);
+ data = backend->data;
+
+ if(!pinnedpubkey)
+ /* no pinned public key specified */
+ return CURLE_OK;
+
+ /* get peer certificate */
+ cert = SSL_PeerCertificate(backend->handle);
+ if(cert) {
+ /* extract public key from peer certificate */
+ SECKEYPublicKey *pubkey = CERT_ExtractPublicKey(cert);
+ if(pubkey) {
+ /* encode the public key as DER */
+ SECItem *cert_der = PK11_DEREncodePublicKey(pubkey);
+ if(cert_der) {
+ /* compare the public key with the pinned public key */
+ result = Curl_pin_peer_pubkey(data, pinnedpubkey, cert_der->data,
+ cert_der->len);
+ SECITEM_FreeItem(cert_der, PR_TRUE);
+ }
+ SECKEY_DestroyPublicKey(pubkey);
+ }
+ CERT_DestroyCertificate(cert);
+ }
+
+ /* report the resulting status */
+ switch(result) {
+ case CURLE_OK:
+ infof(data, "pinned public key verified successfully");
+ break;
+ case CURLE_SSL_PINNEDPUBKEYNOTMATCH:
+ failf(data, "failed to verify pinned public key");
+ break;
+ default:
+ /* OOM, etc. */
+ break;
+ }
+
+ return result;
+}
+
+/**
+ *
+ * Callback to pick the SSL client certificate.
+ */
+static SECStatus SelectClientCert(void *arg, PRFileDesc *sock,
+ struct CERTDistNamesStr *caNames,
+ struct CERTCertificateStr **pRetCert,
+ struct SECKEYPrivateKeyStr **pRetKey)
+{
+ struct ssl_connect_data *connssl = (struct ssl_connect_data *)arg;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct Curl_easy *data = NULL;
+ const char *nickname = NULL;
+ static const char pem_slotname[] = "PEM Token #1";
+
+ DEBUGASSERT(backend);
+
+ data = backend->data;
+ nickname = backend->client_nickname;
+
+ if(backend->obj_clicert) {
+ /* use the cert/key provided by PEM reader */
+ SECItem cert_der = { 0, NULL, 0 };
+ void *proto_win = SSL_RevealPinArg(sock);
+ struct CERTCertificateStr *cert;
+ struct SECKEYPrivateKeyStr *key;
+
+ PK11SlotInfo *slot = nss_find_slot_by_name(pem_slotname);
+ if(!slot) {
+ failf(data, "NSS: PK11 slot not found: %s", pem_slotname);
+ return SECFailure;
+ }
+
+ if(PK11_ReadRawAttribute(PK11_TypeGeneric, backend->obj_clicert, CKA_VALUE,
+ &cert_der) != SECSuccess) {
+ failf(data, "NSS: CKA_VALUE not found in PK11 generic object");
+ PK11_FreeSlot(slot);
+ return SECFailure;
+ }
+
+ cert = PK11_FindCertFromDERCertItem(slot, &cert_der, proto_win);
+ SECITEM_FreeItem(&cert_der, PR_FALSE);
+ if(!cert) {
+ failf(data, "NSS: client certificate from file not found");
+ PK11_FreeSlot(slot);
+ return SECFailure;
+ }
+
+ key = PK11_FindPrivateKeyFromCert(slot, cert, NULL);
+ PK11_FreeSlot(slot);
+ if(!key) {
+ failf(data, "NSS: private key from file not found");
+ CERT_DestroyCertificate(cert);
+ return SECFailure;
+ }
+
+ infof(data, "NSS: client certificate from file");
+ display_cert_info(data, cert);
+
+ *pRetCert = cert;
+ *pRetKey = key;
+ return SECSuccess;
+ }
+
+ /* use the default NSS hook */
+ if(SECSuccess != NSS_GetClientAuthData((void *)nickname, sock, caNames,
+ pRetCert, pRetKey)
+ || !*pRetCert) {
+
+ if(!nickname)
+ failf(data, "NSS: client certificate not found (nickname not "
+ "specified)");
+ else
+ failf(data, "NSS: client certificate not found: %s", nickname);
+
+ return SECFailure;
+ }
+
+ /* get certificate nickname if any */
+ nickname = (*pRetCert)->nickname;
+ if(!nickname)
+ nickname = "[unknown]";
+
+ if(!strncmp(nickname, pem_slotname, sizeof(pem_slotname) - 1U)) {
+ failf(data, "NSS: refusing previously loaded certificate from file: %s",
+ nickname);
+ return SECFailure;
+ }
+
+ if(!*pRetKey) {
+ failf(data, "NSS: private key not found for certificate: %s", nickname);
+ return SECFailure;
+ }
+
+ infof(data, "NSS: using client certificate: %s", nickname);
+ display_cert_info(data, *pRetCert);
+ return SECSuccess;
+}
+
+/* update blocking direction in case of PR_WOULD_BLOCK_ERROR */
+static void nss_update_connecting_state(ssl_connect_state state, void *secret)
+{
+ struct ssl_connect_data *connssl = (struct ssl_connect_data *)secret;
+ if(PR_GetError() != PR_WOULD_BLOCK_ERROR)
+ /* an unrelated error is passing by */
+ return;
+
+ switch(connssl->connecting_state) {
+ case ssl_connect_2:
+ case ssl_connect_2_reading:
+ case ssl_connect_2_writing:
+ break;
+ default:
+ /* we are not called from an SSL handshake */
+ return;
+ }
+
+ /* update the state accordingly */
+ connssl->connecting_state = state;
+}
+
+/* recv() wrapper we use to detect blocking direction during SSL handshake */
+static PRInt32 nspr_io_recv(PRFileDesc *fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout)
+{
+ const PRRecvFN recv_fn = fd->lower->methods->recv;
+ const PRInt32 rv = recv_fn(fd->lower, buf, amount, flags, timeout);
+ if(rv < 0)
+ /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */
+ nss_update_connecting_state(ssl_connect_2_reading, fd->secret);
+ return rv;
+}
+
+/* send() wrapper we use to detect blocking direction during SSL handshake */
+static PRInt32 nspr_io_send(PRFileDesc *fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout)
+{
+ const PRSendFN send_fn = fd->lower->methods->send;
+ const PRInt32 rv = send_fn(fd->lower, buf, amount, flags, timeout);
+ if(rv < 0)
+ /* check for PR_WOULD_BLOCK_ERROR and update blocking direction */
+ nss_update_connecting_state(ssl_connect_2_writing, fd->secret);
+ return rv;
+}
+
+/* close() wrapper to avoid assertion failure due to fd->secret != NULL */
+static PRStatus nspr_io_close(PRFileDesc *fd)
+{
+ const PRCloseFN close_fn = PR_GetDefaultIOMethods()->close;
+ fd->secret = NULL;
+ return close_fn(fd);
+}
+
+/* load a PKCS #11 module */
+static CURLcode nss_load_module(SECMODModule **pmod, const char *library,
+ const char *name)
+{
+ char *config_string;
+ SECMODModule *module = *pmod;
+ if(module)
+ /* already loaded */
+ return CURLE_OK;
+
+ config_string = aprintf("library=%s name=%s", library, name);
+ if(!config_string)
+ return CURLE_OUT_OF_MEMORY;
+
+ module = SECMOD_LoadUserModule(config_string, NULL, PR_FALSE);
+ free(config_string);
+
+ if(module && module->loaded) {
+ /* loaded successfully */
+ *pmod = module;
+ return CURLE_OK;
+ }
+
+ if(module)
+ SECMOD_DestroyModule(module);
+ return CURLE_FAILED_INIT;
+}
+
+/* unload a PKCS #11 module */
+static void nss_unload_module(SECMODModule **pmod)
+{
+ SECMODModule *module = *pmod;
+ if(!module)
+ /* not loaded */
+ return;
+
+ if(SECMOD_UnloadUserModule(module) != SECSuccess)
+ /* unload failed */
+ return;
+
+ SECMOD_DestroyModule(module);
+ *pmod = NULL;
+}
+
+/* data might be NULL */
+static CURLcode nss_init_core(struct Curl_easy *data, const char *cert_dir)
+{
+ NSSInitParameters initparams;
+ PRErrorCode err;
+ const char *err_name;
+
+ if(nss_context)
+ return CURLE_OK;
+
+ memset((void *) &initparams, '\0', sizeof(initparams));
+ initparams.length = sizeof(initparams);
+
+ if(cert_dir) {
+ char *certpath = aprintf("sql:%s", cert_dir);
+ if(!certpath)
+ return CURLE_OUT_OF_MEMORY;
+
+ infof(data, "Initializing NSS with certpath: %s", certpath);
+ nss_context = NSS_InitContext(certpath, "", "", "", &initparams,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ free(certpath);
+
+ if(nss_context)
+ return CURLE_OK;
+
+ err = PR_GetError();
+ err_name = nss_error_to_name(err);
+ infof(data, "Unable to initialize NSS database: %d (%s)", err, err_name);
+ }
+
+ infof(data, "Initializing NSS with certpath: none");
+ nss_context = NSS_InitContext("", "", "", "", &initparams, NSS_INIT_READONLY
+ | NSS_INIT_NOCERTDB | NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN
+ | NSS_INIT_NOROOTINIT | NSS_INIT_OPTIMIZESPACE | NSS_INIT_PK11RELOAD);
+ if(nss_context)
+ return CURLE_OK;
+
+ err = PR_GetError();
+ err_name = nss_error_to_name(err);
+ failf(data, "Unable to initialize NSS: %d (%s)", err, err_name);
+ return CURLE_SSL_CACERT_BADFILE;
+}
+
+/* data might be NULL */
+static CURLcode nss_setup(struct Curl_easy *data)
+{
+ char *cert_dir;
+ struct_stat st;
+ CURLcode result;
+
+ if(initialized)
+ return CURLE_OK;
+
+ /* list of all CRL items we need to destroy in nss_cleanup() */
+ Curl_llist_init(&nss_crl_list, nss_destroy_crl_item);
+
+ /* First we check if $SSL_DIR points to a valid dir */
+ cert_dir = getenv("SSL_DIR");
+ if(cert_dir) {
+ if((stat(cert_dir, &st) != 0) ||
+ (!S_ISDIR(st.st_mode))) {
+ cert_dir = NULL;
+ }
+ }
+
+ /* Now we check if the default location is a valid dir */
+ if(!cert_dir) {
+ if((stat(SSL_DIR, &st) == 0) &&
+ (S_ISDIR(st.st_mode))) {
+ cert_dir = (char *)SSL_DIR;
+ }
+ }
+
+ if(nspr_io_identity == PR_INVALID_IO_LAYER) {
+ /* allocate an identity for our own NSPR I/O layer */
+ nspr_io_identity = PR_GetUniqueIdentity("libcurl");
+ if(nspr_io_identity == PR_INVALID_IO_LAYER)
+ return CURLE_OUT_OF_MEMORY;
+
+ /* the default methods just call down to the lower I/O layer */
+ memcpy(&nspr_io_methods, PR_GetDefaultIOMethods(),
+ sizeof(nspr_io_methods));
+
+ /* override certain methods in the table by our wrappers */
+ nspr_io_methods.recv = nspr_io_recv;
+ nspr_io_methods.send = nspr_io_send;
+ nspr_io_methods.close = nspr_io_close;
+ }
+
+ result = nss_init_core(data, cert_dir);
+ if(result)
+ return result;
+
+ if(!any_cipher_enabled())
+ NSS_SetDomesticPolicy();
+
+ initialized = 1;
+
+ return CURLE_OK;
+}
+
+/**
+ * Global SSL init
+ *
+ * @retval 0 error initializing SSL
+ * @retval 1 SSL initialized successfully
+ */
+static int nss_init(void)
+{
+ /* curl_global_init() is not thread-safe so this test is ok */
+ if(!nss_initlock) {
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+ nss_initlock = PR_NewLock();
+ nss_crllock = PR_NewLock();
+ nss_findslot_lock = PR_NewLock();
+ nss_trustload_lock = PR_NewLock();
+ }
+
+ /* We will actually initialize NSS later */
+
+ return 1;
+}
+
+/* data might be NULL */
+CURLcode Curl_nss_force_init(struct Curl_easy *data)
+{
+ CURLcode result;
+ if(!nss_initlock) {
+ if(data)
+ failf(data, "unable to initialize NSS, curl_global_init() should have "
+ "been called with CURL_GLOBAL_SSL or CURL_GLOBAL_ALL");
+ return CURLE_FAILED_INIT;
+ }
+
+ PR_Lock(nss_initlock);
+ result = nss_setup(data);
+ PR_Unlock(nss_initlock);
+
+ return result;
+}
+
+/* Global cleanup */
+static void nss_cleanup(void)
+{
+ /* This function isn't required to be threadsafe and this is only done
+ * as a safety feature.
+ */
+ PR_Lock(nss_initlock);
+ if(initialized) {
+ /* Free references to client certificates held in the SSL session cache.
+ * Omitting this hampers destruction of the security module owning
+ * the certificates. */
+ SSL_ClearSessionCache();
+
+ nss_unload_module(&pem_module);
+ nss_unload_module(&trust_module);
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+
+ /* destroy all CRL items */
+ Curl_llist_destroy(&nss_crl_list, NULL);
+
+ PR_Unlock(nss_initlock);
+
+ PR_DestroyLock(nss_initlock);
+ PR_DestroyLock(nss_crllock);
+ PR_DestroyLock(nss_findslot_lock);
+ PR_DestroyLock(nss_trustload_lock);
+ nss_initlock = NULL;
+
+ initialized = 0;
+}
+
+/*
+ * This function uses SSL_peek to determine connection status.
+ *
+ * Return codes:
+ * 1 means the connection is still in place
+ * 0 means the connection has been closed
+ * -1 means the connection status is unknown
+ */
+static int nss_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ int rc;
+ char buf;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ rc =
+ PR_Recv(backend->handle, (void *)&buf, 1, PR_MSG_PEEK,
+ PR_SecondsToInterval(1));
+ if(rc > 0)
+ return 1; /* connection still in place */
+
+ if(rc == 0)
+ return 0; /* connection has been closed */
+
+ return -1; /* connection status unknown */
+}
+
+static void close_one(struct ssl_connect_data *connssl)
+{
+ /* before the cleanup, check whether we are using a client certificate */
+ struct ssl_backend_data *backend = connssl->backend;
+ bool client_cert = true;
+
+ DEBUGASSERT(backend);
+
+ client_cert = (backend->client_nickname != NULL)
+ || (backend->obj_clicert != NULL);
+
+ if(backend->handle) {
+ char buf[32];
+ /* Maybe the server has already sent a close notify alert.
+ Read it to avoid an RST on the TCP connection. */
+ (void)PR_Recv(backend->handle, buf, (int)sizeof(buf), 0,
+ PR_INTERVAL_NO_WAIT);
+ }
+
+ free(backend->client_nickname);
+ backend->client_nickname = NULL;
+
+ /* destroy all NSS objects in order to avoid failure of NSS shutdown */
+ Curl_llist_destroy(&backend->obj_list, NULL);
+ backend->obj_clicert = NULL;
+
+ if(backend->handle) {
+ if(client_cert)
+ /* A server might require different authentication based on the
+ * particular path being requested by the client. To support this
+ * scenario, we must ensure that a connection will never reuse the
+ * authentication data from a previous connection. */
+ SSL_InvalidateSession(backend->handle);
+
+ PR_Close(backend->handle);
+ backend->handle = NULL;
+ }
+}
+
+/*
+ * This function is called when an SSL connection is closed.
+ */
+static void nss_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ (void)data;
+ DEBUGASSERT(backend);
+
+ if(backend->handle) {
+ /* NSS closes the socket we previously handed to it, so we must mark it
+ as closed to avoid double close */
+ fake_sclose(cf->conn->sock[cf->sockindex]);
+ cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
+ }
+
+ close_one(connssl);
+}
+
+/* return true if NSS can provide error code (and possibly msg) for the
+ error */
+static bool is_nss_error(CURLcode err)
+{
+ switch(err) {
+ case CURLE_PEER_FAILED_VERIFICATION:
+ case CURLE_SSL_CERTPROBLEM:
+ case CURLE_SSL_CONNECT_ERROR:
+ case CURLE_SSL_ISSUER_ERROR:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/* return true if the given error code is related to a client certificate */
+static bool is_cc_error(PRInt32 err)
+{
+ switch(err) {
+ case SSL_ERROR_BAD_CERT_ALERT:
+ case SSL_ERROR_EXPIRED_CERT_ALERT:
+ case SSL_ERROR_REVOKED_CERT_ALERT:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static CURLcode nss_load_ca_certificates(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ const char *cafile = conn_config->CAfile;
+ const char *capath = conn_config->CApath;
+ bool use_trust_module;
+ CURLcode result = CURLE_OK;
+
+ /* treat empty string as unset */
+ if(cafile && !cafile[0])
+ cafile = NULL;
+ if(capath && !capath[0])
+ capath = NULL;
+
+ infof(data, " CAfile: %s", cafile ? cafile : "none");
+ infof(data, " CApath: %s", capath ? capath : "none");
+
+ /* load libnssckbi.so if no other trust roots were specified */
+ use_trust_module = !cafile && !capath;
+
+ PR_Lock(nss_trustload_lock);
+ if(use_trust_module && !trust_module) {
+ /* libnssckbi.so needed but not yet loaded --> load it! */
+ result = nss_load_module(&trust_module, trust_library, "trust");
+ infof(data, "%s %s", (result) ? "failed to load" : "loaded",
+ trust_library);
+ if(result == CURLE_FAILED_INIT)
+ /* If libnssckbi.so is not available (or fails to load), one can still
+ use CA certificates stored in NSS database. Ignore the failure. */
+ result = CURLE_OK;
+ }
+ else if(!use_trust_module && trust_module) {
+ /* libnssckbi.so not needed but already loaded --> unload it! */
+ infof(data, "unloading %s", trust_library);
+ nss_unload_module(&trust_module);
+ }
+ PR_Unlock(nss_trustload_lock);
+
+ if(cafile)
+ result = nss_load_cert(connssl, cafile, PR_TRUE);
+
+ if(result)
+ return result;
+
+ if(capath) {
+ struct_stat st;
+ if(stat(capath, &st) == -1)
+ return CURLE_SSL_CACERT_BADFILE;
+
+ if(S_ISDIR(st.st_mode)) {
+ PRDirEntry *entry;
+ PRDir *dir = PR_OpenDir(capath);
+ if(!dir)
+ return CURLE_SSL_CACERT_BADFILE;
+
+ while((entry =
+ PR_ReadDir(dir, (PRDirFlags)(PR_SKIP_BOTH | PR_SKIP_HIDDEN)))) {
+ char *fullpath = aprintf("%s/%s", capath, entry->name);
+ if(!fullpath) {
+ PR_CloseDir(dir);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ if(CURLE_OK != nss_load_cert(connssl, fullpath, PR_TRUE))
+ /* This is purposefully tolerant of errors so non-PEM files can
+ * be in the same directory */
+ infof(data, "failed to load '%s' from CURLOPT_CAPATH", fullpath);
+
+ free(fullpath);
+ }
+
+ PR_CloseDir(dir);
+ }
+ else
+ infof(data, "WARNING: CURLOPT_CAPATH not a directory (%s)", capath);
+ }
+
+ return CURLE_OK;
+}
+
+static CURLcode nss_sslver_from_curl(PRUint16 *nssver, long version)
+{
+ switch(version) {
+ case CURL_SSLVERSION_SSLv2:
+ *nssver = SSL_LIBRARY_VERSION_2;
+ return CURLE_OK;
+
+ case CURL_SSLVERSION_SSLv3:
+ return CURLE_NOT_BUILT_IN;
+
+ case CURL_SSLVERSION_TLSv1_0:
+ *nssver = SSL_LIBRARY_VERSION_TLS_1_0;
+ return CURLE_OK;
+
+ case CURL_SSLVERSION_TLSv1_1:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ *nssver = SSL_LIBRARY_VERSION_TLS_1_1;
+ return CURLE_OK;
+#else
+ return CURLE_SSL_CONNECT_ERROR;
+#endif
+
+ case CURL_SSLVERSION_TLSv1_2:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ *nssver = SSL_LIBRARY_VERSION_TLS_1_2;
+ return CURLE_OK;
+#else
+ return CURLE_SSL_CONNECT_ERROR;
+#endif
+
+ case CURL_SSLVERSION_TLSv1_3:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ *nssver = SSL_LIBRARY_VERSION_TLS_1_3;
+ return CURLE_OK;
+#else
+ return CURLE_SSL_CONNECT_ERROR;
+#endif
+
+ default:
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+}
+
+static CURLcode nss_init_sslver(SSLVersionRange *sslver,
+ struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ CURLcode result;
+ const long min = conn_config->version;
+ const long max = conn_config->version_max;
+ SSLVersionRange vrange;
+
+ switch(min) {
+ case CURL_SSLVERSION_TLSv1:
+ case CURL_SSLVERSION_DEFAULT:
+ /* Bump our minimum TLS version if NSS has stricter requirements. */
+ if(SSL_VersionRangeGetDefault(ssl_variant_stream, &vrange) != SECSuccess)
+ return CURLE_SSL_CONNECT_ERROR;
+ if(sslver->min < vrange.min)
+ sslver->min = vrange.min;
+ break;
+ default:
+ result = nss_sslver_from_curl(&sslver->min, min);
+ if(result) {
+ failf(data, "unsupported min version passed via CURLOPT_SSLVERSION");
+ return result;
+ }
+ }
+
+ switch(max) {
+ case CURL_SSLVERSION_MAX_NONE:
+ case CURL_SSLVERSION_MAX_DEFAULT:
+ break;
+ default:
+ result = nss_sslver_from_curl(&sslver->max, max >> 16);
+ if(result) {
+ failf(data, "unsupported max version passed via CURLOPT_SSLVERSION");
+ return result;
+ }
+ }
+
+ return CURLE_OK;
+}
+
+static CURLcode nss_fail_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ CURLcode curlerr)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ if(is_nss_error(curlerr)) {
+ /* read NSPR error code */
+ PRErrorCode err = PR_GetError();
+ if(is_cc_error(err))
+ curlerr = CURLE_SSL_CERTPROBLEM;
+
+ /* print the error number and error string */
+ infof(data, "NSS error %d (%s)", err, nss_error_to_name(err));
+
+ /* print a human-readable message describing the error if available */
+ nss_print_error_message(data, err);
+ }
+
+ /* cleanup on connection failure */
+ Curl_llist_destroy(&backend->obj_list, NULL);
+
+ return curlerr;
+}
+
+/* Switch the SSL socket into blocking or non-blocking mode. */
+static CURLcode nss_set_blocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ PRSocketOptionData sock_opt;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ sock_opt.option = PR_SockOpt_Nonblocking;
+ sock_opt.value.non_blocking = !blocking;
+
+ if(PR_SetSocketOption(backend->handle, &sock_opt) != PR_SUCCESS)
+ return nss_fail_connect(cf, data, CURLE_SSL_CONNECT_ERROR);
+
+ return CURLE_OK;
+}
+
+static CURLcode nss_setup_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ PRFileDesc *model = NULL;
+ PRFileDesc *nspr_io = NULL;
+ PRFileDesc *nspr_io_stub = NULL;
+ PRBool ssl_no_cache;
+ PRBool ssl_cbc_random_iv;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next);
+ struct ssl_connect_data *connssl_next = cf_ssl_next?
+ cf_ssl_next->ctx : NULL;
+ CURLcode result;
+ bool second_layer = FALSE;
+ SSLVersionRange sslver_supported;
+ SSLVersionRange sslver = {
+ SSL_LIBRARY_VERSION_TLS_1_0, /* min */
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ SSL_LIBRARY_VERSION_TLS_1_3 /* max */
+#elif defined SSL_LIBRARY_VERSION_TLS_1_2
+ SSL_LIBRARY_VERSION_TLS_1_2
+#elif defined SSL_LIBRARY_VERSION_TLS_1_1
+ SSL_LIBRARY_VERSION_TLS_1_1
+#else
+ SSL_LIBRARY_VERSION_TLS_1_0
+#endif
+ };
+ const char *hostname = connssl->hostname;
+ char *snihost;
+
+ snihost = Curl_ssl_snihost(data, hostname, NULL);
+ if(!snihost) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ DEBUGASSERT(backend);
+
+ backend->data = data;
+
+ /* list of all NSS objects we need to destroy in nss_do_close() */
+ Curl_llist_init(&backend->obj_list, nss_destroy_object);
+
+ PR_Lock(nss_initlock);
+ result = nss_setup(data);
+ if(result) {
+ PR_Unlock(nss_initlock);
+ goto error;
+ }
+
+ PK11_SetPasswordFunc(nss_get_password);
+
+ result = nss_load_module(&pem_module, pem_library, "PEM");
+ PR_Unlock(nss_initlock);
+ if(result == CURLE_FAILED_INIT)
+ infof(data, "WARNING: failed to load NSS PEM library %s. Using "
+ "OpenSSL PEM certificates will not work.", pem_library);
+ else if(result)
+ goto error;
+
+ result = CURLE_SSL_CONNECT_ERROR;
+
+ model = PR_NewTCPSocket();
+ if(!model)
+ goto error;
+ model = SSL_ImportFD(NULL, model);
+
+ if(SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ goto error;
+ if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_FALSE) != SECSuccess)
+ goto error;
+ if(SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE) != SECSuccess)
+ goto error;
+
+ /* do not use SSL cache if disabled or we are not going to verify peer */
+ ssl_no_cache = (ssl_config->primary.sessionid
+ && conn_config->verifypeer) ? PR_FALSE : PR_TRUE;
+ if(SSL_OptionSet(model, SSL_NO_CACHE, ssl_no_cache) != SECSuccess)
+ goto error;
+
+ /* enable/disable the requested SSL version(s) */
+ if(nss_init_sslver(&sslver, cf, data) != CURLE_OK)
+ goto error;
+ if(SSL_VersionRangeGetSupported(ssl_variant_stream,
+ &sslver_supported) != SECSuccess)
+ goto error;
+ if(sslver_supported.max < sslver.max && sslver_supported.max >= sslver.min) {
+ char *sslver_req_str, *sslver_supp_str;
+ sslver_req_str = nss_sslver_to_name(sslver.max);
+ sslver_supp_str = nss_sslver_to_name(sslver_supported.max);
+ if(sslver_req_str && sslver_supp_str)
+ infof(data, "Falling back from %s to max supported SSL version (%s)",
+ sslver_req_str, sslver_supp_str);
+ free(sslver_req_str);
+ free(sslver_supp_str);
+ sslver.max = sslver_supported.max;
+ }
+ if(SSL_VersionRangeSet(model, &sslver) != SECSuccess)
+ goto error;
+
+ ssl_cbc_random_iv = !ssl_config->enable_beast;
+#ifdef SSL_CBC_RANDOM_IV
+ /* unless the user explicitly asks to allow the protocol vulnerability, we
+ use the work-around */
+ if(SSL_OptionSet(model, SSL_CBC_RANDOM_IV, ssl_cbc_random_iv) != SECSuccess)
+ infof(data, "WARNING: failed to set SSL_CBC_RANDOM_IV = %d",
+ ssl_cbc_random_iv);
+#else
+ if(ssl_cbc_random_iv)
+ infof(data, "WARNING: support for SSL_CBC_RANDOM_IV not compiled in");
+#endif
+
+ if(conn_config->cipher_list) {
+ if(set_ciphers(data, model, conn_config->cipher_list) != SECSuccess) {
+ result = CURLE_SSL_CIPHER;
+ goto error;
+ }
+ }
+
+ if(!conn_config->verifypeer && conn_config->verifyhost)
+ infof(data, "WARNING: ignoring value of ssl.verifyhost");
+
+ /* bypass the default SSL_AuthCertificate() hook in case we do not want to
+ * verify peer */
+ if(SSL_AuthCertificateHook(model, nss_auth_cert_hook, cf) != SECSuccess)
+ goto error;
+
+ /* not checked yet */
+ ssl_config->certverifyresult = 0;
+
+ if(SSL_BadCertHook(model, BadCertHandler, cf) != SECSuccess)
+ goto error;
+
+ if(SSL_HandshakeCallback(model, HandshakeCallback, cf) != SECSuccess)
+ goto error;
+
+ {
+ const CURLcode rv = nss_load_ca_certificates(cf, data);
+ if((rv == CURLE_SSL_CACERT_BADFILE) && !conn_config->verifypeer)
+ /* not a fatal error because we are not going to verify the peer */
+ infof(data, "WARNING: CA certificates failed to load");
+ else if(rv) {
+ result = rv;
+ goto error;
+ }
+ }
+
+ if(ssl_config->primary.CRLfile) {
+ const CURLcode rv = nss_load_crl(ssl_config->primary.CRLfile);
+ if(rv) {
+ result = rv;
+ goto error;
+ }
+ infof(data, " CRLfile: %s", ssl_config->primary.CRLfile);
+ }
+
+ if(ssl_config->primary.clientcert) {
+ char *nickname = dup_nickname(data, ssl_config->primary.clientcert);
+ if(nickname) {
+ /* we are not going to use libnsspem.so to read the client cert */
+ backend->obj_clicert = NULL;
+ }
+ else {
+ CURLcode rv = cert_stuff(cf, data,
+ ssl_config->primary.clientcert,
+ ssl_config->key);
+ if(rv) {
+ /* failf() is already done in cert_stuff() */
+ result = rv;
+ goto error;
+ }
+ }
+
+ /* store the nickname for SelectClientCert() called during handshake */
+ backend->client_nickname = nickname;
+ }
+ else
+ backend->client_nickname = NULL;
+
+ if(SSL_GetClientAuthDataHook(model, SelectClientCert,
+ (void *)connssl) != SECSuccess) {
+ result = CURLE_SSL_CERTPROBLEM;
+ goto error;
+ }
+
+ /* Is there an SSL filter "in front" of us or are we writing directly
+ * to the socket? */
+ if(connssl_next) {
+ /* The filter should be connected by now, with full handshake */
+ DEBUGASSERT(connssl_next->backend->handle);
+ DEBUGASSERT(ssl_connection_complete == connssl_next->state);
+ /* We tell our NSS instance to use do IO with the 'next' NSS
+ * instance. This NSS instance will take ownership of the next
+ * one, including its destruction. We therefore need to `disown`
+ * the next filter's handle, once import succeeds. */
+ nspr_io = connssl_next->backend->handle;
+ second_layer = TRUE;
+ }
+ else {
+ /* wrap OS file descriptor by NSPR's file descriptor abstraction */
+ nspr_io = PR_ImportTCPSocket(sockfd);
+ if(!nspr_io)
+ goto error;
+ }
+
+ /* create our own NSPR I/O layer */
+ nspr_io_stub = PR_CreateIOLayerStub(nspr_io_identity, &nspr_io_methods);
+ if(!nspr_io_stub) {
+ if(!second_layer)
+ PR_Close(nspr_io);
+ goto error;
+ }
+
+ /* make the per-connection data accessible from NSPR I/O callbacks */
+ nspr_io_stub->secret = (void *)connssl;
+
+ /* push our new layer to the NSPR I/O stack */
+ if(PR_PushIOLayer(nspr_io, PR_TOP_IO_LAYER, nspr_io_stub) != PR_SUCCESS) {
+ if(!second_layer)
+ PR_Close(nspr_io);
+ PR_Close(nspr_io_stub);
+ goto error;
+ }
+
+ /* import our model socket onto the current I/O stack */
+ backend->handle = SSL_ImportFD(model, nspr_io);
+ if(!backend->handle) {
+ if(!second_layer)
+ PR_Close(nspr_io);
+ goto error;
+ }
+
+ PR_Close(model); /* We don't need this any more */
+ model = NULL;
+ if(connssl_next) /* steal the NSS handle we just imported successfully */
+ connssl_next->backend->handle = NULL;
+
+ /* This is the password associated with the cert that we're using */
+ if(ssl_config->key_passwd) {
+ SSL_SetPKCS11PinArg(backend->handle, ssl_config->key_passwd);
+ }
+
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ if(conn_config->verifystatus) {
+ if(SSL_OptionSet(backend->handle, SSL_ENABLE_OCSP_STAPLING, PR_TRUE)
+ != SECSuccess)
+ goto error;
+ }
+#endif
+
+#ifdef SSL_ENABLE_ALPN
+ if(SSL_OptionSet(backend->handle, SSL_ENABLE_ALPN,
+ cf->conn->bits.tls_enable_alpn ? PR_TRUE : PR_FALSE)
+ != SECSuccess)
+ goto error;
+#endif
+
+#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
+ if(data->set.ssl.falsestart) {
+ if(SSL_OptionSet(backend->handle, SSL_ENABLE_FALSE_START, PR_TRUE)
+ != SECSuccess)
+ goto error;
+
+ if(SSL_SetCanFalseStartCallback(backend->handle, CanFalseStartCallback,
+ data) != SECSuccess)
+ goto error;
+ }
+#endif
+
+#if defined(SSL_ENABLE_ALPN)
+ if(cf->conn->bits.tls_enable_alpn) {
+ int cur = 0;
+ unsigned char protocols[128];
+
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2
+#ifndef CURL_DISABLE_PROXY
+ && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
+#endif
+ ) {
+ protocols[cur++] = ALPN_H2_LENGTH;
+ memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
+ cur += ALPN_H2_LENGTH;
+ }
+#endif
+ protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
+ memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
+ cur += ALPN_HTTP_1_1_LENGTH;
+
+ if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess)
+ goto error;
+ }
+#endif
+
+
+ /* Force handshake on next I/O */
+ if(SSL_ResetHandshake(backend->handle, /* asServer */ PR_FALSE)
+ != SECSuccess)
+ goto error;
+
+ /* propagate hostname to the TLS layer */
+ if(SSL_SetURL(backend->handle, snihost) != SECSuccess)
+ goto error;
+
+ /* prevent NSS from re-using the session for a different hostname */
+ if(SSL_SetSockPeerID(backend->handle, snihost) != SECSuccess)
+ goto error;
+
+ return CURLE_OK;
+
+error:
+ if(model)
+ PR_Close(model);
+
+ return nss_fail_connect(cf, data, result);
+}
+
+static CURLcode nss_do_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ CURLcode result = CURLE_SSL_CONNECT_ERROR;
+ PRUint32 timeout;
+
+ /* check timeout situation */
+ const timediff_t time_left = Curl_timeleft(data, NULL, TRUE);
+ if(time_left < 0) {
+ failf(data, "timed out before SSL handshake");
+ result = CURLE_OPERATION_TIMEDOUT;
+ goto error;
+ }
+
+ DEBUGASSERT(backend);
+
+ /* Force the handshake now */
+ timeout = PR_MillisecondsToInterval((PRUint32) time_left);
+ if(SSL_ForceHandshakeWithTimeout(backend->handle, timeout) != SECSuccess) {
+ if(PR_GetError() == PR_WOULD_BLOCK_ERROR)
+ /* blocking direction is updated by nss_update_connecting_state() */
+ return CURLE_AGAIN;
+ else if(ssl_config->certverifyresult == SSL_ERROR_BAD_CERT_DOMAIN)
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ else if(ssl_config->certverifyresult)
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ goto error;
+ }
+
+ result = display_conn_info(data, backend->handle);
+ if(result)
+ goto error;
+
+ if(conn_config->issuercert) {
+ SECStatus ret = SECFailure;
+ char *nickname = dup_nickname(data, conn_config->issuercert);
+ if(nickname) {
+ /* we support only nicknames in case of issuercert for now */
+ ret = check_issuer_cert(backend->handle, nickname);
+ free(nickname);
+ }
+
+ if(SECFailure == ret) {
+ infof(data, "SSL certificate issuer check failed");
+ result = CURLE_SSL_ISSUER_ERROR;
+ goto error;
+ }
+ else {
+ infof(data, "SSL certificate issuer check ok");
+ }
+ }
+
+ result = cmp_peer_pubkey(connssl, Curl_ssl_cf_is_proxy(cf)?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]:
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+ if(result)
+ /* status already printed */
+ goto error;
+
+ return CURLE_OK;
+
+error:
+ return nss_fail_connect(cf, data, result);
+}
+
+static CURLcode nss_connect_common(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ const bool blocking = (done == NULL);
+ CURLcode result;
+
+ if(connssl->state == ssl_connection_complete) {
+ if(!blocking)
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ if(connssl->connecting_state == ssl_connect_1) {
+ result = nss_setup_connect(cf, data);
+ if(result)
+ /* we do not expect CURLE_AGAIN from nss_setup_connect() */
+ return result;
+
+ connssl->connecting_state = ssl_connect_2;
+ }
+
+ /* enable/disable blocking mode before handshake */
+ result = nss_set_blocking(cf, data, blocking);
+ if(result)
+ return result;
+
+ result = nss_do_connect(cf, data);
+ switch(result) {
+ case CURLE_OK:
+ break;
+ case CURLE_AGAIN:
+ /* CURLE_AGAIN in non-blocking mode is not an error */
+ if(!blocking)
+ return CURLE_OK;
+ else
+ return result;
+ default:
+ return result;
+ }
+
+ if(blocking) {
+ /* in blocking mode, set NSS non-blocking mode _after_ SSL handshake */
+ result = nss_set_blocking(cf, data, /* blocking */ FALSE);
+ if(result)
+ return result;
+ }
+ else
+ /* signal completed SSL handshake */
+ *done = TRUE;
+
+ connssl->state = ssl_connection_complete;
+
+ /* ssl_connect_done is never used outside, go back to the initial state */
+ connssl->connecting_state = ssl_connect_1;
+
+ return CURLE_OK;
+}
+
+static CURLcode nss_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ return nss_connect_common(cf, data, /* blocking */ NULL);
+}
+
+static CURLcode nss_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return nss_connect_common(cf, data, done);
+}
+
+static ssize_t nss_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data, /* transfer */
+ const void *mem, /* send this data */
+ size_t len, /* amount to write */
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ ssize_t rc;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ /* The SelectClientCert() hook uses this for infof() and failf() but the
+ handle stored in nss_setup_connect() could have already been freed. */
+ backend->data = data;
+
+ rc = PR_Send(backend->handle, mem, (int)len, 0, PR_INTERVAL_NO_WAIT);
+ if(rc < 0) {
+ PRInt32 err = PR_GetError();
+ if(err == PR_WOULD_BLOCK_ERROR)
+ *curlcode = CURLE_AGAIN;
+ else {
+ /* print the error number and error string */
+ const char *err_name = nss_error_to_name(err);
+ infof(data, "SSL write: error %d (%s)", err, err_name);
+
+ /* print a human-readable message describing the error if available */
+ nss_print_error_message(data, err);
+
+ *curlcode = (is_cc_error(err))
+ ? CURLE_SSL_CERTPROBLEM
+ : CURLE_SEND_ERROR;
+ }
+
+ return -1;
+ }
+
+ return rc; /* number of bytes */
+}
+
+static ssize_t nss_recv(struct Curl_cfilter *cf,
+ struct Curl_easy *data, /* transfer */
+ char *buf, /* store read data here */
+ size_t buffersize, /* max amount to read */
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ ssize_t nread;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ /* The SelectClientCert() hook uses this for infof() and failf() but the
+ handle stored in nss_setup_connect() could have already been freed. */
+ backend->data = data;
+
+ nread = PR_Recv(backend->handle, buf, (int)buffersize, 0,
+ PR_INTERVAL_NO_WAIT);
+ if(nread < 0) {
+ /* failed SSL read */
+ PRInt32 err = PR_GetError();
+
+ if(err == PR_WOULD_BLOCK_ERROR)
+ *curlcode = CURLE_AGAIN;
+ else {
+ /* print the error number and error string */
+ const char *err_name = nss_error_to_name(err);
+ infof(data, "SSL read: errno %d (%s)", err, err_name);
+
+ /* print a human-readable message describing the error if available */
+ nss_print_error_message(data, err);
+
+ *curlcode = (is_cc_error(err))
+ ? CURLE_SSL_CERTPROBLEM
+ : CURLE_RECV_ERROR;
+ }
+
+ return -1;
+ }
+
+ return nread;
+}
+
+static size_t nss_version(char *buffer, size_t size)
+{
+ return msnprintf(buffer, size, "NSS/%s", NSS_GetVersion());
+}
+
+/* data might be NULL */
+static int Curl_nss_seed(struct Curl_easy *data)
+{
+ /* make sure that NSS is initialized */
+ return !!Curl_nss_force_init(data);
+}
+
+/* data might be NULL */
+static CURLcode nss_random(struct Curl_easy *data,
+ unsigned char *entropy,
+ size_t length)
+{
+ Curl_nss_seed(data); /* Initiate the seed if not already done */
+
+ if(SECSuccess != PK11_GenerateRandom(entropy, curlx_uztosi(length)))
+ /* signal a failure */
+ return CURLE_FAILED_INIT;
+
+ return CURLE_OK;
+}
+
+static CURLcode nss_sha256sum(const unsigned char *tmp, /* input */
+ size_t tmplen,
+ unsigned char *sha256sum, /* output */
+ size_t sha256len)
+{
+ PK11Context *SHA256pw = PK11_CreateDigestContext(SEC_OID_SHA256);
+ unsigned int SHA256out;
+
+ if(!SHA256pw)
+ return CURLE_NOT_BUILT_IN;
+
+ PK11_DigestOp(SHA256pw, tmp, curlx_uztoui(tmplen));
+ PK11_DigestFinal(SHA256pw, sha256sum, &SHA256out, curlx_uztoui(sha256len));
+ PK11_DestroyContext(SHA256pw, PR_TRUE);
+
+ return CURLE_OK;
+}
+
+static bool nss_cert_status_request(void)
+{
+#ifdef SSL_ENABLE_OCSP_STAPLING
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+static bool nss_false_start(void)
+{
+#if NSSVERNUM >= 0x030f04 /* 3.15.4 */
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+static void *nss_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ (void)info;
+ DEBUGASSERT(backend);
+ return backend->handle;
+}
+
+static bool nss_attach_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+
+ if(!connssl->backend->data)
+ connssl->backend->data = data;
+ return TRUE;
+}
+
+static void nss_detach_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+
+ if(connssl->backend->data == data)
+ connssl->backend->data = NULL;
+}
+
+const struct Curl_ssl Curl_ssl_nss = {
+ { CURLSSLBACKEND_NSS, "nss" }, /* info */
+
+ SSLSUPP_CA_PATH |
+ SSLSUPP_CERTINFO |
+ SSLSUPP_PINNEDPUBKEY |
+ SSLSUPP_HTTPS_PROXY,
+
+ sizeof(struct ssl_backend_data),
+
+ nss_init, /* init */
+ nss_cleanup, /* cleanup */
+ nss_version, /* version */
+ nss_check_cxn, /* check_cxn */
+ /* NSS has no shutdown function provided and thus always fail */
+ Curl_none_shutdown, /* shutdown */
+ Curl_none_data_pending, /* data_pending */
+ nss_random, /* random */
+ nss_cert_status_request, /* cert_status_request */
+ nss_connect, /* connect */
+ nss_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ nss_get_internals, /* get_internals */
+ nss_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ /* NSS has its own session ID cache */
+ Curl_none_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ nss_false_start, /* false_start */
+ nss_sha256sum, /* sha256sum */
+ nss_attach_data, /* associate_connection */
+ nss_detach_data, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ nss_recv, /* recv decrypted data */
+ nss_send, /* send data to encrypt */
+};
+
+#endif /* USE_NSS */
diff --git a/libs/libcurl/src/vtls/nssg.h b/libs/libcurl/src/vtls/nssg.h index 454a38f1fb..71d3bc3c2c 100644 --- a/libs/libcurl/src/vtls/nssg.h +++ b/libs/libcurl/src/vtls/nssg.h @@ -1,41 +1,41 @@ -#ifndef HEADER_CURL_NSSG_H -#define HEADER_CURL_NSSG_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_NSS -/* - * This header should only be needed to get included by vtls.c and nss.c - */ - -#include "urldata.h" - -/* initialize NSS library if not already */ -CURLcode Curl_nss_force_init(struct Curl_easy *data); - -extern const struct Curl_ssl Curl_ssl_nss; - -#endif /* USE_NSS */ -#endif /* HEADER_CURL_NSSG_H */ +#ifndef HEADER_CURL_NSSG_H
+#define HEADER_CURL_NSSG_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_NSS
+/*
+ * This header should only be needed to get included by vtls.c and nss.c
+ */
+
+#include "urldata.h"
+
+/* initialize NSS library if not already */
+CURLcode Curl_nss_force_init(struct Curl_easy *data);
+
+extern const struct Curl_ssl Curl_ssl_nss;
+
+#endif /* USE_NSS */
+#endif /* HEADER_CURL_NSSG_H */
diff --git a/libs/libcurl/src/vtls/openssl.c b/libs/libcurl/src/vtls/openssl.c index ad2efa5586..2142ff5ccd 100644 --- a/libs/libcurl/src/vtls/openssl.c +++ b/libs/libcurl/src/vtls/openssl.c @@ -1,4617 +1,4984 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for all OpenSSL-specific code for the TLS/SSL layer. No code - * but vtls.c should ever call or use these functions. - */ - -#include "curl_setup.h" - -#if defined(USE_QUICHE) || defined(USE_OPENSSL) - -#include <limits.h> - -/* Wincrypt must be included before anything that could include OpenSSL. */ -#if defined(USE_WIN32_CRYPTO) -#include <wincrypt.h> -/* Undefine wincrypt conflicting symbols for BoringSSL. */ -#undef X509_NAME -#undef X509_EXTENSIONS -#undef PKCS7_ISSUER_AND_SERIAL -#undef PKCS7_SIGNER_INFO -#undef OCSP_REQUEST -#undef OCSP_RESPONSE -#endif - -#include "urldata.h" -#include "sendf.h" -#include "formdata.h" /* for the boundary function */ -#include "url.h" /* for the ssl config check function */ -#include "inet_pton.h" -#include "openssl.h" -#include "connect.h" -#include "slist.h" -#include "select.h" -#include "vtls.h" -#include "vauth/vauth.h" -#include "keylog.h" -#include "strcase.h" -#include "hostcheck.h" -#include "multiif.h" -#include "strerror.h" -#include "curl_printf.h" - -#include <openssl/ssl.h> -#include <openssl/rand.h> -#include <openssl/x509v3.h> -#ifndef OPENSSL_NO_DSA -#include <openssl/dsa.h> -#endif -#include <openssl/dh.h> -#include <openssl/err.h> -#include <openssl/md5.h> -#include <openssl/conf.h> -#include <openssl/bn.h> -#include <openssl/rsa.h> -#include <openssl/bio.h> -#include <openssl/buffer.h> -#include <openssl/pkcs12.h> - -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP) -#include <openssl/ocsp.h> -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x0090700fL) && /* 0.9.7 or later */ \ - !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE) -#define USE_OPENSSL_ENGINE -#include <openssl/engine.h> -#endif - -#include "warnless.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -/* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS - renegotiations when built with BoringSSL. Renegotiating is non-compliant - with HTTP/2 and "an extremely dangerous protocol feature". Beware. - -#define ALLOW_RENEG 1 - */ - -#ifndef OPENSSL_VERSION_NUMBER -#error "OPENSSL_VERSION_NUMBER not defined" -#endif - -#ifdef USE_OPENSSL_ENGINE -#include <openssl/ui.h> -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x00909000L -#define SSL_METHOD_QUAL const -#else -#define SSL_METHOD_QUAL -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10000000L) -#define HAVE_ERR_REMOVE_THREAD_STATE 1 -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && /* OpenSSL 1.1.0+ */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) -#define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER -#define HAVE_X509_GET0_EXTENSIONS 1 /* added in 1.1.0 -pre1 */ -#define HAVE_OPAQUE_EVP_PKEY 1 /* since 1.1.0 -pre3 */ -#define HAVE_OPAQUE_RSA_DSA_DH 1 /* since 1.1.0 -pre5 */ -#define CONST_EXTS const -#define HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED 1 - -/* funny typecast define due to difference in API */ -#ifdef LIBRESSL_VERSION_NUMBER -#define ARG2_X509_signature_print (X509_ALGOR *) -#else -#define ARG2_X509_signature_print -#endif - -#else -/* For OpenSSL before 1.1.0 */ -#define ASN1_STRING_get0_data(x) ASN1_STRING_data(x) -#define X509_get0_notBefore(x) X509_get_notBefore(x) -#define X509_get0_notAfter(x) X509_get_notAfter(x) -#define CONST_EXTS /* nope */ -#ifndef LIBRESSL_VERSION_NUMBER -#define OpenSSL_version_num() SSLeay() -#endif -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* 1.0.2 or later */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) -#define HAVE_X509_GET0_SIGNATURE 1 -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) /* 1.0.2 or later */ -#define HAVE_SSL_GET_SHUTDOWN 1 -#endif - -#if OPENSSL_VERSION_NUMBER >= 0x10002003L && \ - OPENSSL_VERSION_NUMBER <= 0x10002FFFL && \ - !defined(OPENSSL_NO_COMP) -#define HAVE_SSL_COMP_FREE_COMPRESSION_METHODS 1 -#endif - -#if (OPENSSL_VERSION_NUMBER < 0x0090808fL) -/* not present in older OpenSSL */ -#define OPENSSL_load_builtin_modules(x) -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x30000000L) -#define HAVE_EVP_PKEY_GET_PARAMS 1 -#else -#define SSL_get1_peer_certificate SSL_get_peer_certificate -#endif - -#ifdef HAVE_EVP_PKEY_GET_PARAMS -#include <openssl/core_names.h> -#define DECLARE_PKEY_PARAM_BIGNUM(name) BIGNUM *name = NULL -#define FREE_PKEY_PARAM_BIGNUM(name) BN_clear_free(name) -#else -#define DECLARE_PKEY_PARAM_BIGNUM(name) const BIGNUM *name -#define FREE_PKEY_PARAM_BIGNUM(name) -#endif - -/* - * Whether SSL_CTX_set_keylog_callback is available. - * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287 - * BoringSSL: supported since d28f59c27bac (committed 2015-11-19) - * LibreSSL: unsupported in at least 2.7.2 (explicitly check for it since it - * lies and pretends to be OpenSSL 2.0.0). - */ -#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ - !defined(LIBRESSL_VERSION_NUMBER)) || \ - defined(OPENSSL_IS_BORINGSSL) -#define HAVE_KEYLOG_CALLBACK -#endif - -/* Whether SSL_CTX_set_ciphersuites is available. - * OpenSSL: supported since 1.1.1 (commit a53b5be6a05) - * BoringSSL: no - * LibreSSL: no - */ -#if ((OPENSSL_VERSION_NUMBER >= 0x10101000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) && \ - !defined(OPENSSL_IS_BORINGSSL)) -#define HAVE_SSL_CTX_SET_CIPHERSUITES -#define HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH -#endif - -/* - * Whether SSL_CTX_set1_curves_list is available. - * OpenSSL: supported since 1.0.2, see - * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_groups.html - * BoringSSL: supported since 5fd1807d95f7 (committed 2016-09-30) - * LibreSSL: since 2.5.3 (April 12, 2017) - */ -#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) || \ - defined(OPENSSL_IS_BORINGSSL) -#define HAVE_SSL_CTX_SET_EC_CURVES -#endif - -#if defined(LIBRESSL_VERSION_NUMBER) -#define OSSL_PACKAGE "LibreSSL" -#elif defined(OPENSSL_IS_BORINGSSL) -#define OSSL_PACKAGE "BoringSSL" -#else -#define OSSL_PACKAGE "OpenSSL" -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) -/* up2date versions of OpenSSL maintain reasonably secure defaults without - * breaking compatibility, so it is better not to override the defaults in curl - */ -#define DEFAULT_CIPHER_SELECTION NULL -#else -/* ... but it is not the case with old versions of OpenSSL */ -#define DEFAULT_CIPHER_SELECTION \ - "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH" -#endif - -#ifdef HAVE_OPENSSL_SRP -/* the function exists */ -#ifdef USE_TLS_SRP -/* the functionality is not disabled */ -#define USE_OPENSSL_SRP -#endif -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) -#define HAVE_RANDOM_INIT_BY_DEFAULT 1 -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x2070100fL) && \ - !defined(OPENSSL_IS_BORINGSSL) -#define HAVE_OPENSSL_VERSION -#endif - -struct ssl_backend_data { - struct Curl_easy *logger; /* transfer handle to pass trace logs to, only - using sockindex 0 */ - /* these ones requires specific SSL-types */ - SSL_CTX* ctx; - SSL* handle; - X509* server_cert; -#ifndef HAVE_KEYLOG_CALLBACK - /* Set to true once a valid keylog entry has been created to avoid dupes. */ - bool keylog_done; -#endif -}; - -#define push_certinfo(_label, _num) \ -do { \ - long info_len = BIO_get_mem_data(mem, &ptr); \ - Curl_ssl_push_certinfo_len(data, _num, _label, ptr, info_len); \ - if(1 != BIO_reset(mem)) \ - break; \ -} while(0) - -static void pubkey_show(struct Curl_easy *data, - BIO *mem, - int num, - const char *type, - const char *name, - const BIGNUM *bn) -{ - char *ptr; - char namebuf[32]; - - msnprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name); - - if(bn) - BN_print(mem, bn); - push_certinfo(namebuf, num); -} - -#ifdef HAVE_OPAQUE_RSA_DSA_DH -#define print_pubkey_BN(_type, _name, _num) \ - pubkey_show(data, mem, _num, #_type, #_name, _name) - -#else -#define print_pubkey_BN(_type, _name, _num) \ -do { \ - if(_type->_name) { \ - pubkey_show(data, mem, _num, #_type, #_name, _type->_name); \ - } \ -} while(0) -#endif - -static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len) -{ - int i, ilen; - - ilen = (int)len; - if(ilen < 0) - return 1; /* buffer too big */ - - i = i2t_ASN1_OBJECT(buf, ilen, a); - - if(i >= ilen) - return 1; /* buffer too small */ - - return 0; -} - -static void X509V3_ext(struct Curl_easy *data, - int certnum, - CONST_EXTS STACK_OF(X509_EXTENSION) *exts) -{ - int i; - - if((int)sk_X509_EXTENSION_num(exts) <= 0) - /* no extensions, bail out */ - return; - - for(i = 0; i < (int)sk_X509_EXTENSION_num(exts); i++) { - ASN1_OBJECT *obj; - X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i); - BUF_MEM *biomem; - char namebuf[128]; - BIO *bio_out = BIO_new(BIO_s_mem()); - - if(!bio_out) - return; - - obj = X509_EXTENSION_get_object(ext); - - asn1_object_dump(obj, namebuf, sizeof(namebuf)); - - if(!X509V3_EXT_print(bio_out, ext, 0, 0)) - ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext)); - - BIO_get_mem_ptr(bio_out, &biomem); - Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data, - biomem->length); - BIO_free(bio_out); - } -} - -#ifdef OPENSSL_IS_BORINGSSL -typedef size_t numcert_t; -#else -typedef int numcert_t; -#endif - -CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl) -{ - CURLcode result; - STACK_OF(X509) *sk; - int i; - numcert_t numcerts; - BIO *mem; - - DEBUGASSERT(ssl); - - sk = SSL_get_peer_cert_chain(ssl); - if(!sk) { - return CURLE_OUT_OF_MEMORY; - } - - numcerts = sk_X509_num(sk); - - result = Curl_ssl_init_certinfo(data, (int)numcerts); - if(result) { - return result; - } - - mem = BIO_new(BIO_s_mem()); - if(!mem) { - return CURLE_OUT_OF_MEMORY; - } - - for(i = 0; i < (int)numcerts; i++) { - ASN1_INTEGER *num; - X509 *x = sk_X509_value(sk, i); - EVP_PKEY *pubkey = NULL; - int j; - char *ptr; - const ASN1_BIT_STRING *psig = NULL; - - X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Subject", i); - - X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE); - push_certinfo("Issuer", i); - - BIO_printf(mem, "%lx", X509_get_version(x)); - push_certinfo("Version", i); - - num = X509_get_serialNumber(x); - if(num->type == V_ASN1_NEG_INTEGER) - BIO_puts(mem, "-"); - for(j = 0; j < num->length; j++) - BIO_printf(mem, "%02x", num->data[j]); - push_certinfo("Serial Number", i); - -#if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS) - { - const X509_ALGOR *sigalg = NULL; - X509_PUBKEY *xpubkey = NULL; - ASN1_OBJECT *pubkeyoid = NULL; - - X509_get0_signature(&psig, &sigalg, x); - if(sigalg) { - i2a_ASN1_OBJECT(mem, sigalg->algorithm); - push_certinfo("Signature Algorithm", i); - } - - xpubkey = X509_get_X509_PUBKEY(x); - if(xpubkey) { - X509_PUBKEY_get0_param(&pubkeyoid, NULL, NULL, NULL, xpubkey); - if(pubkeyoid) { - i2a_ASN1_OBJECT(mem, pubkeyoid); - push_certinfo("Public Key Algorithm", i); - } - } - - X509V3_ext(data, i, X509_get0_extensions(x)); - } -#else - { - /* before OpenSSL 1.0.2 */ - X509_CINF *cinf = x->cert_info; - - i2a_ASN1_OBJECT(mem, cinf->signature->algorithm); - push_certinfo("Signature Algorithm", i); - - i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm); - push_certinfo("Public Key Algorithm", i); - - X509V3_ext(data, i, cinf->extensions); - - psig = x->signature; - } -#endif - - ASN1_TIME_print(mem, X509_get0_notBefore(x)); - push_certinfo("Start date", i); - - ASN1_TIME_print(mem, X509_get0_notAfter(x)); - push_certinfo("Expire date", i); - - pubkey = X509_get_pubkey(x); - if(!pubkey) - infof(data, " Unable to load public key"); - else { - int pktype; -#ifdef HAVE_OPAQUE_EVP_PKEY - pktype = EVP_PKEY_id(pubkey); -#else - pktype = pubkey->type; -#endif - switch(pktype) { - case EVP_PKEY_RSA: - { -#ifndef HAVE_EVP_PKEY_GET_PARAMS - RSA *rsa; -#ifdef HAVE_OPAQUE_EVP_PKEY - rsa = EVP_PKEY_get0_RSA(pubkey); -#else - rsa = pubkey->pkey.rsa; -#endif /* HAVE_OPAQUE_EVP_PKEY */ -#endif /* !HAVE_EVP_PKEY_GET_PARAMS */ - - { -#ifdef HAVE_OPAQUE_RSA_DSA_DH - DECLARE_PKEY_PARAM_BIGNUM(n); - DECLARE_PKEY_PARAM_BIGNUM(e); -#ifdef HAVE_EVP_PKEY_GET_PARAMS - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_N, &n); - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_E, &e); -#else - RSA_get0_key(rsa, &n, &e, NULL); -#endif /* HAVE_EVP_PKEY_GET_PARAMS */ - BIO_printf(mem, "%d", BN_num_bits(n)); -#else - BIO_printf(mem, "%d", BN_num_bits(rsa->n)); -#endif /* HAVE_OPAQUE_RSA_DSA_DH */ - push_certinfo("RSA Public Key", i); - print_pubkey_BN(rsa, n, i); - print_pubkey_BN(rsa, e, i); - FREE_PKEY_PARAM_BIGNUM(n); - FREE_PKEY_PARAM_BIGNUM(e); - } - - break; - } - case EVP_PKEY_DSA: - { -#ifndef OPENSSL_NO_DSA -#ifndef HAVE_EVP_PKEY_GET_PARAMS - DSA *dsa; -#ifdef HAVE_OPAQUE_EVP_PKEY - dsa = EVP_PKEY_get0_DSA(pubkey); -#else - dsa = pubkey->pkey.dsa; -#endif /* HAVE_OPAQUE_EVP_PKEY */ -#endif /* !HAVE_EVP_PKEY_GET_PARAMS */ - { -#ifdef HAVE_OPAQUE_RSA_DSA_DH - DECLARE_PKEY_PARAM_BIGNUM(p); - DECLARE_PKEY_PARAM_BIGNUM(q); - DECLARE_PKEY_PARAM_BIGNUM(g); - DECLARE_PKEY_PARAM_BIGNUM(pub_key); -#ifdef HAVE_EVP_PKEY_GET_PARAMS - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p); - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q); - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g); - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key); -#else - DSA_get0_pqg(dsa, &p, &q, &g); - DSA_get0_key(dsa, &pub_key, NULL); -#endif /* HAVE_EVP_PKEY_GET_PARAMS */ -#endif /* HAVE_OPAQUE_RSA_DSA_DH */ - print_pubkey_BN(dsa, p, i); - print_pubkey_BN(dsa, q, i); - print_pubkey_BN(dsa, g, i); - print_pubkey_BN(dsa, pub_key, i); - FREE_PKEY_PARAM_BIGNUM(p); - FREE_PKEY_PARAM_BIGNUM(q); - FREE_PKEY_PARAM_BIGNUM(g); - FREE_PKEY_PARAM_BIGNUM(pub_key); - } -#endif /* !OPENSSL_NO_DSA */ - break; - } - case EVP_PKEY_DH: - { -#ifndef HAVE_EVP_PKEY_GET_PARAMS - DH *dh; -#ifdef HAVE_OPAQUE_EVP_PKEY - dh = EVP_PKEY_get0_DH(pubkey); -#else - dh = pubkey->pkey.dh; -#endif /* HAVE_OPAQUE_EVP_PKEY */ -#endif /* !HAVE_EVP_PKEY_GET_PARAMS */ - { -#ifdef HAVE_OPAQUE_RSA_DSA_DH - DECLARE_PKEY_PARAM_BIGNUM(p); - DECLARE_PKEY_PARAM_BIGNUM(q); - DECLARE_PKEY_PARAM_BIGNUM(g); - DECLARE_PKEY_PARAM_BIGNUM(pub_key); -#ifdef HAVE_EVP_PKEY_GET_PARAMS - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p); - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q); - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g); - EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key); -#else - DH_get0_pqg(dh, &p, &q, &g); - DH_get0_key(dh, &pub_key, NULL); -#endif /* HAVE_EVP_PKEY_GET_PARAMS */ - print_pubkey_BN(dh, p, i); - print_pubkey_BN(dh, q, i); - print_pubkey_BN(dh, g, i); -#else - print_pubkey_BN(dh, p, i); - print_pubkey_BN(dh, g, i); -#endif /* HAVE_OPAQUE_RSA_DSA_DH */ - print_pubkey_BN(dh, pub_key, i); - FREE_PKEY_PARAM_BIGNUM(p); - FREE_PKEY_PARAM_BIGNUM(q); - FREE_PKEY_PARAM_BIGNUM(g); - FREE_PKEY_PARAM_BIGNUM(pub_key); - } - break; - } - } - EVP_PKEY_free(pubkey); - } - - if(psig) { - for(j = 0; j < psig->length; j++) - BIO_printf(mem, "%02x:", psig->data[j]); - push_certinfo("Signature", i); - } - - PEM_write_bio_X509(mem, x); - push_certinfo("Cert", i); - } - - BIO_free(mem); - - return CURLE_OK; -} - -#endif /* quiche or OpenSSL */ - -#ifdef USE_OPENSSL - -static bool ossl_associate_connection(struct Curl_easy *data, - struct connectdata *conn, - int sockindex); - -/* - * Number of bytes to read from the random number seed file. This must be - * a finite value (because some entropy "files" like /dev/urandom have - * an infinite length), but must be large enough to provide enough - * entropy to properly seed OpenSSL's PRNG. - */ -#define RAND_LOAD_LENGTH 1024 - -#ifdef HAVE_KEYLOG_CALLBACK -static void ossl_keylog_callback(const SSL *ssl, const char *line) -{ - (void)ssl; - - Curl_tls_keylog_write_line(line); -} -#else -/* - * ossl_log_tls12_secret is called by libcurl to make the CLIENT_RANDOMs if the - * OpenSSL being used doesn't have native support for doing that. - */ -static void -ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done) -{ - const SSL_SESSION *session = SSL_get_session(ssl); - unsigned char client_random[SSL3_RANDOM_SIZE]; - unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH]; - int master_key_length = 0; - - if(!session || *keylog_done) - return; - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER < 0x20700000L) - /* ssl->s3 is not checked in openssl 1.1.0-pre6, but let's assume that - * we have a valid SSL context if we have a non-NULL session. */ - SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE); - master_key_length = (int) - SSL_SESSION_get_master_key(session, master_key, SSL_MAX_MASTER_KEY_LENGTH); -#else - if(ssl->s3 && session->master_key_length > 0) { - master_key_length = session->master_key_length; - memcpy(master_key, session->master_key, session->master_key_length); - memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE); - } -#endif - - /* The handshake has not progressed sufficiently yet, or this is a TLS 1.3 - * session (when curl was built with older OpenSSL headers and running with - * newer OpenSSL runtime libraries). */ - if(master_key_length <= 0) - return; - - *keylog_done = true; - Curl_tls_keylog_write("CLIENT_RANDOM", client_random, - master_key, master_key_length); -} -#endif /* !HAVE_KEYLOG_CALLBACK */ - -static const char *SSL_ERROR_to_str(int err) -{ - switch(err) { - case SSL_ERROR_NONE: - return "SSL_ERROR_NONE"; - case SSL_ERROR_SSL: - return "SSL_ERROR_SSL"; - case SSL_ERROR_WANT_READ: - return "SSL_ERROR_WANT_READ"; - case SSL_ERROR_WANT_WRITE: - return "SSL_ERROR_WANT_WRITE"; - case SSL_ERROR_WANT_X509_LOOKUP: - return "SSL_ERROR_WANT_X509_LOOKUP"; - case SSL_ERROR_SYSCALL: - return "SSL_ERROR_SYSCALL"; - case SSL_ERROR_ZERO_RETURN: - return "SSL_ERROR_ZERO_RETURN"; - case SSL_ERROR_WANT_CONNECT: - return "SSL_ERROR_WANT_CONNECT"; - case SSL_ERROR_WANT_ACCEPT: - return "SSL_ERROR_WANT_ACCEPT"; -#if defined(SSL_ERROR_WANT_ASYNC) - case SSL_ERROR_WANT_ASYNC: - return "SSL_ERROR_WANT_ASYNC"; -#endif -#if defined(SSL_ERROR_WANT_ASYNC_JOB) - case SSL_ERROR_WANT_ASYNC_JOB: - return "SSL_ERROR_WANT_ASYNC_JOB"; -#endif -#if defined(SSL_ERROR_WANT_EARLY) - case SSL_ERROR_WANT_EARLY: - return "SSL_ERROR_WANT_EARLY"; -#endif - default: - return "SSL_ERROR unknown"; - } -} - -/* Return error string for last OpenSSL error - */ -static char *ossl_strerror(unsigned long error, char *buf, size_t size) -{ - if(size) - *buf = '\0'; - -#ifdef OPENSSL_IS_BORINGSSL - ERR_error_string_n((uint32_t)error, buf, size); -#else - ERR_error_string_n(error, buf, size); -#endif - - if(size > 1 && !*buf) { - strncpy(buf, (error ? "Unknown error" : "No error"), size); - buf[size - 1] = '\0'; - } - - return buf; -} - -/* Return an extra data index for the transfer data. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_ssl_data_index(void) -{ - static int ssl_ex_data_data_index = -1; - if(ssl_ex_data_data_index < 0) { - ssl_ex_data_data_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return ssl_ex_data_data_index; -} - -/* Return an extra data index for the connection data. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_ssl_conn_index(void) -{ - static int ssl_ex_data_conn_index = -1; - if(ssl_ex_data_conn_index < 0) { - ssl_ex_data_conn_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return ssl_ex_data_conn_index; -} - -/* Return an extra data index for the sockindex. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_ssl_sockindex_index(void) -{ - static int sockindex_index = -1; - if(sockindex_index < 0) { - sockindex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return sockindex_index; -} - -/* Return an extra data index for proxy boolean. - * This index can be used with SSL_get_ex_data() and SSL_set_ex_data(). - */ -static int ossl_get_proxy_index(void) -{ - static int proxy_index = -1; - if(proxy_index < 0) { - proxy_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL); - } - return proxy_index; -} - -static int passwd_callback(char *buf, int num, int encrypting, - void *global_passwd) -{ - DEBUGASSERT(0 == encrypting); - - if(!encrypting) { - int klen = curlx_uztosi(strlen((char *)global_passwd)); - if(num > klen) { - memcpy(buf, global_passwd, klen + 1); - return klen; - } - } - return 0; -} - -/* - * rand_enough() returns TRUE if we have seeded the random engine properly. - */ -static bool rand_enough(void) -{ - return (0 != RAND_status()) ? TRUE : FALSE; -} - -static CURLcode ossl_seed(struct Curl_easy *data) -{ - /* This might get called before it has been added to a multi handle */ - if(data->multi && data->multi->ssl_seeded) - return CURLE_OK; - - if(rand_enough()) { - /* OpenSSL 1.1.0+ should return here */ - if(data->multi) - data->multi->ssl_seeded = TRUE; - return CURLE_OK; - } -#ifdef HAVE_RANDOM_INIT_BY_DEFAULT - /* with OpenSSL 1.1.0+, a failed RAND_status is a showstopper */ - failf(data, "Insufficient randomness"); - return CURLE_SSL_CONNECT_ERROR; -#else - -#ifdef RANDOM_FILE - RAND_load_file(RANDOM_FILE, RAND_LOAD_LENGTH); - if(rand_enough()) - return CURLE_OK; -#endif - -#if defined(HAVE_RAND_EGD) && defined(EGD_SOCKET) - /* available in OpenSSL 0.9.5 and later */ - /* EGD_SOCKET is set at configure time or not at all */ - { - /* If there's an option and a define, the option overrides the - define */ - int ret = RAND_egd(EGD_SOCKET); - if(-1 != ret) { - if(rand_enough()) - return CURLE_OK; - } - } -#endif - - /* fallback to a custom seeding of the PRNG using a hash based on a current - time */ - do { - unsigned char randb[64]; - size_t len = sizeof(randb); - size_t i, i_max; - for(i = 0, i_max = len / sizeof(struct curltime); i < i_max; ++i) { - struct curltime tv = Curl_now(); - Curl_wait_ms(1); - tv.tv_sec *= i + 1; - tv.tv_usec *= (unsigned int)i + 2; - tv.tv_sec ^= ((Curl_now().tv_sec + Curl_now().tv_usec) * - (i + 3)) << 8; - tv.tv_usec ^= (unsigned int) ((Curl_now().tv_sec + - Curl_now().tv_usec) * - (i + 4)) << 16; - memcpy(&randb[i * sizeof(struct curltime)], &tv, - sizeof(struct curltime)); - } - RAND_add(randb, (int)len, (double)len/2); - } while(!rand_enough()); - - { - /* generates a default path for the random seed file */ - char fname[256]; - fname[0] = 0; /* blank it first */ - RAND_file_name(fname, sizeof(fname)); - if(fname[0]) { - /* we got a file name to try */ - RAND_load_file(fname, RAND_LOAD_LENGTH); - if(rand_enough()) - return CURLE_OK; - } - } - - infof(data, "libcurl is now using a weak random seed"); - return (rand_enough() ? CURLE_OK : - CURLE_SSL_CONNECT_ERROR /* confusing error code */); -#endif -} - -#ifndef SSL_FILETYPE_ENGINE -#define SSL_FILETYPE_ENGINE 42 -#endif -#ifndef SSL_FILETYPE_PKCS12 -#define SSL_FILETYPE_PKCS12 43 -#endif -static int do_file_type(const char *type) -{ - if(!type || !type[0]) - return SSL_FILETYPE_PEM; - if(strcasecompare(type, "PEM")) - return SSL_FILETYPE_PEM; - if(strcasecompare(type, "DER")) - return SSL_FILETYPE_ASN1; - if(strcasecompare(type, "ENG")) - return SSL_FILETYPE_ENGINE; - if(strcasecompare(type, "P12")) - return SSL_FILETYPE_PKCS12; - return -1; -} - -#ifdef USE_OPENSSL_ENGINE -/* - * Supply default password to the engine user interface conversation. - * The password is passed by OpenSSL engine from ENGINE_load_private_key() - * last argument to the ui and can be obtained by UI_get0_user_data(ui) here. - */ -static int ssl_ui_reader(UI *ui, UI_STRING *uis) -{ - const char *password; - switch(UI_get_string_type(uis)) { - case UIT_PROMPT: - case UIT_VERIFY: - password = (const char *)UI_get0_user_data(ui); - if(password && (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) { - UI_set_result(ui, uis, password); - return 1; - } - default: - break; - } - return (UI_method_get_reader(UI_OpenSSL()))(ui, uis); -} - -/* - * Suppress interactive request for a default password if available. - */ -static int ssl_ui_writer(UI *ui, UI_STRING *uis) -{ - switch(UI_get_string_type(uis)) { - case UIT_PROMPT: - case UIT_VERIFY: - if(UI_get0_user_data(ui) && - (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) { - return 1; - } - default: - break; - } - return (UI_method_get_writer(UI_OpenSSL()))(ui, uis); -} - -/* - * Check if a given string is a PKCS#11 URI - */ -static bool is_pkcs11_uri(const char *string) -{ - return (string && strncasecompare(string, "pkcs11:", 7)); -} - -#endif - -static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine); - -static int -SSL_CTX_use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob, - int type, const char *key_passwd) -{ - int ret = 0; - X509 *x = NULL; - /* the typecast of blob->len is fine since it is guaranteed to never be - larger than CURL_MAX_INPUT_LENGTH */ - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); - if(!in) - return CURLE_OUT_OF_MEMORY; - - if(type == SSL_FILETYPE_ASN1) { - /* j = ERR_R_ASN1_LIB; */ - x = d2i_X509_bio(in, NULL); - } - else if(type == SSL_FILETYPE_PEM) { - /* ERR_R_PEM_LIB; */ - x = PEM_read_bio_X509(in, NULL, - passwd_callback, (void *)key_passwd); - } - else { - ret = 0; - goto end; - } - - if(!x) { - ret = 0; - goto end; - } - - ret = SSL_CTX_use_certificate(ctx, x); - end: - X509_free(x); - BIO_free(in); - return ret; -} - -static int -SSL_CTX_use_PrivateKey_blob(SSL_CTX *ctx, const struct curl_blob *blob, - int type, const char *key_passwd) -{ - int ret = 0; - EVP_PKEY *pkey = NULL; - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); - if(!in) - return CURLE_OUT_OF_MEMORY; - - if(type == SSL_FILETYPE_PEM) - pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, - (void *)key_passwd); - else if(type == SSL_FILETYPE_ASN1) - pkey = d2i_PrivateKey_bio(in, NULL); - else { - ret = 0; - goto end; - } - if(!pkey) { - ret = 0; - goto end; - } - ret = SSL_CTX_use_PrivateKey(ctx, pkey); - EVP_PKEY_free(pkey); - end: - BIO_free(in); - return ret; -} - -static int -SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob, - const char *key_passwd) -{ -/* SSL_CTX_add1_chain_cert introduced in OpenSSL 1.0.2 */ -#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* OpenSSL 1.0.2 or later */ \ - !(defined(LIBRESSL_VERSION_NUMBER) && \ - (LIBRESSL_VERSION_NUMBER < 0x2090100fL)) /* LibreSSL 2.9.1 or later */ - int ret = 0; - X509 *x = NULL; - void *passwd_callback_userdata = (void *)key_passwd; - BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len)); - if(!in) - return CURLE_OUT_OF_MEMORY; - - ERR_clear_error(); - - x = PEM_read_bio_X509_AUX(in, NULL, - passwd_callback, (void *)key_passwd); - - if(!x) { - ret = 0; - goto end; - } - - ret = SSL_CTX_use_certificate(ctx, x); - - if(ERR_peek_error() != 0) - ret = 0; - - if(ret) { - X509 *ca; - unsigned long err; - - if(!SSL_CTX_clear_chain_certs(ctx)) { - ret = 0; - goto end; - } - - while((ca = PEM_read_bio_X509(in, NULL, passwd_callback, - passwd_callback_userdata)) - != NULL) { - - if(!SSL_CTX_add0_chain_cert(ctx, ca)) { - X509_free(ca); - ret = 0; - goto end; - } - } - - err = ERR_peek_last_error(); - if((ERR_GET_LIB(err) == ERR_LIB_PEM) && - (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) - ERR_clear_error(); - else - ret = 0; - } - - end: - X509_free(x); - BIO_free(in); - return ret; -#else - (void)ctx; /* unused */ - (void)blob; /* unused */ - (void)key_passwd; /* unused */ - return 0; -#endif -} - -static -int cert_stuff(struct Curl_easy *data, - SSL_CTX* ctx, - char *cert_file, - const struct curl_blob *cert_blob, - const char *cert_type, - char *key_file, - const struct curl_blob *key_blob, - const char *key_type, - char *key_passwd) -{ - char error_buffer[256]; - bool check_privkey = TRUE; - - int file_type = do_file_type(cert_type); - - if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE)) { - SSL *ssl; - X509 *x509; - int cert_done = 0; - int cert_use_result; - - if(key_passwd) { - /* set the password in the callback userdata */ - SSL_CTX_set_default_passwd_cb_userdata(ctx, key_passwd); - /* Set passwd callback: */ - SSL_CTX_set_default_passwd_cb(ctx, passwd_callback); - } - - - switch(file_type) { - case SSL_FILETYPE_PEM: - /* SSL_CTX_use_certificate_chain_file() only works on PEM files */ - cert_use_result = cert_blob ? - SSL_CTX_use_certificate_chain_blob(ctx, cert_blob, key_passwd) : - SSL_CTX_use_certificate_chain_file(ctx, cert_file); - if(cert_use_result != 1) { - failf(data, - "could not load PEM client certificate from %s, " OSSL_PACKAGE - " error %s, " - "(no key found, wrong pass phrase, or wrong file format?)", - (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file), - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - return 0; - } - break; - - case SSL_FILETYPE_ASN1: - /* SSL_CTX_use_certificate_file() works with either PEM or ASN1, but - we use the case above for PEM so this can only be performed with - ASN1 files. */ - - cert_use_result = cert_blob ? - SSL_CTX_use_certificate_blob(ctx, cert_blob, - file_type, key_passwd) : - SSL_CTX_use_certificate_file(ctx, cert_file, file_type); - if(cert_use_result != 1) { - failf(data, - "could not load ASN1 client certificate from %s, " OSSL_PACKAGE - " error %s, " - "(no key found, wrong pass phrase, or wrong file format?)", - (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file), - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - return 0; - } - break; - case SSL_FILETYPE_ENGINE: -#if defined(USE_OPENSSL_ENGINE) && defined(ENGINE_CTRL_GET_CMD_FROM_NAME) - { - /* Implicitly use pkcs11 engine if none was provided and the - * cert_file is a PKCS#11 URI */ - if(!data->state.engine) { - if(is_pkcs11_uri(cert_file)) { - if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } - - if(data->state.engine) { - const char *cmd_name = "LOAD_CERT_CTRL"; - struct { - const char *cert_id; - X509 *cert; - } params; - - params.cert_id = cert_file; - params.cert = NULL; - - /* Does the engine supports LOAD_CERT_CTRL ? */ - if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME, - 0, (void *)cmd_name, NULL)) { - failf(data, "ssl engine does not support loading certificates"); - return 0; - } - - /* Load the certificate from the engine */ - if(!ENGINE_ctrl_cmd(data->state.engine, cmd_name, - 0, ¶ms, NULL, 1)) { - failf(data, "ssl engine cannot load client cert with id" - " '%s' [%s]", cert_file, - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - return 0; - } - - if(!params.cert) { - failf(data, "ssl engine didn't initialized the certificate " - "properly."); - return 0; - } - - if(SSL_CTX_use_certificate(ctx, params.cert) != 1) { - failf(data, "unable to set client certificate [%s]", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - return 0; - } - X509_free(params.cert); /* we don't need the handle any more... */ - } - else { - failf(data, "crypto engine not set, can't load certificate"); - return 0; - } - } - break; -#else - failf(data, "file type ENG for certificate not implemented"); - return 0; -#endif - - case SSL_FILETYPE_PKCS12: - { - BIO *cert_bio = NULL; - PKCS12 *p12 = NULL; - EVP_PKEY *pri; - STACK_OF(X509) *ca = NULL; - if(cert_blob) { - cert_bio = BIO_new_mem_buf(cert_blob->data, (int)(cert_blob->len)); - if(!cert_bio) { - failf(data, - "BIO_new_mem_buf NULL, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - return 0; - } - } - else { - cert_bio = BIO_new(BIO_s_file()); - if(!cert_bio) { - failf(data, - "BIO_new return NULL, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - return 0; - } - - if(BIO_read_filename(cert_bio, cert_file) <= 0) { - failf(data, "could not open PKCS12 file '%s'", cert_file); - BIO_free(cert_bio); - return 0; - } - } - - p12 = d2i_PKCS12_bio(cert_bio, NULL); - BIO_free(cert_bio); - - if(!p12) { - failf(data, "error reading PKCS12 file '%s'", - cert_blob ? "(memory blob)" : cert_file); - return 0; - } - - PKCS12_PBE_add(); - - if(!PKCS12_parse(p12, key_passwd, &pri, &x509, - &ca)) { - failf(data, - "could not parse PKCS12 file, check password, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - PKCS12_free(p12); - return 0; - } - - PKCS12_free(p12); - - if(SSL_CTX_use_certificate(ctx, x509) != 1) { - failf(data, - "could not load PKCS12 client certificate, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - goto fail; - } - - if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) { - failf(data, "unable to use private key from PKCS12 file '%s'", - cert_file); - goto fail; - } - - if(!SSL_CTX_check_private_key (ctx)) { - failf(data, "private key from PKCS12 file '%s' " - "does not match certificate in same file", cert_file); - goto fail; - } - /* Set Certificate Verification chain */ - if(ca) { - while(sk_X509_num(ca)) { - /* - * Note that sk_X509_pop() is used below to make sure the cert is - * removed from the stack properly before getting passed to - * SSL_CTX_add_extra_chain_cert(), which takes ownership. Previously - * we used sk_X509_value() instead, but then we'd clean it in the - * subsequent sk_X509_pop_free() call. - */ - X509 *x = sk_X509_pop(ca); - if(!SSL_CTX_add_client_CA(ctx, x)) { - X509_free(x); - failf(data, "cannot add certificate to client CA list"); - goto fail; - } - if(!SSL_CTX_add_extra_chain_cert(ctx, x)) { - X509_free(x); - failf(data, "cannot add certificate to certificate chain"); - goto fail; - } - } - } - - cert_done = 1; - fail: - EVP_PKEY_free(pri); - X509_free(x509); - sk_X509_pop_free(ca, X509_free); - if(!cert_done) - return 0; /* failure! */ - break; - } - default: - failf(data, "not supported file type '%s' for certificate", cert_type); - return 0; - } - - if((!key_file) && (!key_blob)) { - key_file = cert_file; - key_blob = cert_blob; - } - else - file_type = do_file_type(key_type); - - switch(file_type) { - case SSL_FILETYPE_PEM: - if(cert_done) - break; - /* FALLTHROUGH */ - case SSL_FILETYPE_ASN1: - cert_use_result = key_blob ? - SSL_CTX_use_PrivateKey_blob(ctx, key_blob, file_type, key_passwd) : - SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type); - if(cert_use_result != 1) { - failf(data, "unable to set private key file: '%s' type %s", - key_file?key_file:"(memory blob)", key_type?key_type:"PEM"); - return 0; - } - break; - case SSL_FILETYPE_ENGINE: -#ifdef USE_OPENSSL_ENGINE - { /* XXXX still needs some work */ - EVP_PKEY *priv_key = NULL; - - /* Implicitly use pkcs11 engine if none was provided and the - * key_file is a PKCS#11 URI */ - if(!data->state.engine) { - if(is_pkcs11_uri(key_file)) { - if(ossl_set_engine(data, "pkcs11") != CURLE_OK) { - return 0; - } - } - } - - if(data->state.engine) { - UI_METHOD *ui_method = - UI_create_method((char *)"curl user interface"); - if(!ui_method) { - failf(data, "unable do create " OSSL_PACKAGE - " user-interface method"); - return 0; - } - UI_method_set_opener(ui_method, UI_method_get_opener(UI_OpenSSL())); - UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL())); - UI_method_set_reader(ui_method, ssl_ui_reader); - UI_method_set_writer(ui_method, ssl_ui_writer); - /* the typecast below was added to please mingw32 */ - priv_key = (EVP_PKEY *) - ENGINE_load_private_key(data->state.engine, key_file, - ui_method, - key_passwd); - UI_destroy_method(ui_method); - if(!priv_key) { - failf(data, "failed to load private key from crypto engine"); - return 0; - } - if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) { - failf(data, "unable to set private key"); - EVP_PKEY_free(priv_key); - return 0; - } - EVP_PKEY_free(priv_key); /* we don't need the handle any more... */ - } - else { - failf(data, "crypto engine not set, can't load private key"); - return 0; - } - } - break; -#else - failf(data, "file type ENG for private key not supported"); - return 0; -#endif - case SSL_FILETYPE_PKCS12: - if(!cert_done) { - failf(data, "file type P12 for private key not supported"); - return 0; - } - break; - default: - failf(data, "not supported file type for private key"); - return 0; - } - - ssl = SSL_new(ctx); - if(!ssl) { - failf(data, "unable to create an SSL structure"); - return 0; - } - - x509 = SSL_get_certificate(ssl); - - /* This version was provided by Evan Jordan and is supposed to not - leak memory as the previous version: */ - if(x509) { - EVP_PKEY *pktmp = X509_get_pubkey(x509); - EVP_PKEY_copy_parameters(pktmp, SSL_get_privatekey(ssl)); - EVP_PKEY_free(pktmp); - } - -#if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_IS_BORINGSSL) && \ - !defined(OPENSSL_NO_DEPRECATED_3_0) - { - /* If RSA is used, don't check the private key if its flags indicate - * it doesn't support it. */ - EVP_PKEY *priv_key = SSL_get_privatekey(ssl); - int pktype; -#ifdef HAVE_OPAQUE_EVP_PKEY - pktype = EVP_PKEY_id(priv_key); -#else - pktype = priv_key->type; -#endif - if(pktype == EVP_PKEY_RSA) { - RSA *rsa = EVP_PKEY_get1_RSA(priv_key); - if(RSA_flags(rsa) & RSA_METHOD_FLAG_NO_CHECK) - check_privkey = FALSE; - RSA_free(rsa); /* Decrement reference count */ - } - } -#endif - - SSL_free(ssl); - - /* If we are using DSA, we can copy the parameters from - * the private key */ - - if(check_privkey == TRUE) { - /* Now we know that a key and cert have been set against - * the SSL context */ - if(!SSL_CTX_check_private_key(ctx)) { - failf(data, "Private key does not match the certificate public key"); - return 0; - } - } - } - return 1; -} - -CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, SSL_CTX *ctx, - char *cert_file, - const struct curl_blob *cert_blob, - const char *cert_type, char *key_file, - const struct curl_blob *key_blob, - const char *key_type, char *key_passwd) -{ - int rv = cert_stuff(data, ctx, cert_file, cert_blob, cert_type, key_file, - key_blob, key_type, key_passwd); - if(rv != 1) { - return CURLE_SSL_CERTPROBLEM; - } - - return CURLE_OK; -} - -/* returns non-zero on failure */ -static int x509_name_oneline(X509_NAME *a, char *buf, size_t size) -{ - BIO *bio_out = BIO_new(BIO_s_mem()); - BUF_MEM *biomem; - int rc; - - if(!bio_out) - return 1; /* alloc failed! */ - - rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC); - BIO_get_mem_ptr(bio_out, &biomem); - - if((size_t)biomem->length < size) - size = biomem->length; - else - size--; /* don't overwrite the buffer end */ - - memcpy(buf, biomem->data, size); - buf[size] = 0; - - BIO_free(bio_out); - - return !rc; -} - -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -static int ossl_init(void) -{ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) - const uint64_t flags = -#ifdef OPENSSL_INIT_ENGINE_ALL_BUILTIN - /* not present in BoringSSL */ - OPENSSL_INIT_ENGINE_ALL_BUILTIN | -#endif -#ifdef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG - OPENSSL_INIT_NO_LOAD_CONFIG | -#else - OPENSSL_INIT_LOAD_CONFIG | -#endif - 0; - OPENSSL_init_ssl(flags, NULL); -#else - OPENSSL_load_builtin_modules(); - -#ifdef USE_OPENSSL_ENGINE - ENGINE_load_builtin_engines(); -#endif - -/* CONF_MFLAGS_DEFAULT_SECTION was introduced some time between 0.9.8b and - 0.9.8e */ -#ifndef CONF_MFLAGS_DEFAULT_SECTION -#define CONF_MFLAGS_DEFAULT_SECTION 0x0 -#endif - -#ifndef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG - CONF_modules_load_file(NULL, NULL, - CONF_MFLAGS_DEFAULT_SECTION| - CONF_MFLAGS_IGNORE_MISSING_FILE); -#endif - - /* Let's get nice error messages */ - SSL_load_error_strings(); - - /* Init the global ciphers and digests */ - if(!SSLeay_add_ssl_algorithms()) - return 0; - - OpenSSL_add_all_algorithms(); -#endif - - Curl_tls_keylog_open(); - - /* Initialize the extra data indexes */ - if(ossl_get_ssl_data_index() < 0 || ossl_get_ssl_conn_index() < 0 || - ossl_get_ssl_sockindex_index() < 0 || ossl_get_proxy_index() < 0) - return 0; - - return 1; -} - -/* Global cleanup */ -static void ossl_cleanup(void) -{ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \ - !defined(LIBRESSL_VERSION_NUMBER) - /* OpenSSL 1.1 deprecates all these cleanup functions and - turns them into no-ops in OpenSSL 1.0 compatibility mode */ -#else - /* Free ciphers and digests lists */ - EVP_cleanup(); - -#ifdef USE_OPENSSL_ENGINE - /* Free engine list */ - ENGINE_cleanup(); -#endif - - /* Free OpenSSL error strings */ - ERR_free_strings(); - - /* Free thread local error state, destroying hash upon zero refcount */ -#ifdef HAVE_ERR_REMOVE_THREAD_STATE - ERR_remove_thread_state(NULL); -#else - ERR_remove_state(0); -#endif - - /* Free all memory allocated by all configuration modules */ - CONF_modules_free(); - -#ifdef HAVE_SSL_COMP_FREE_COMPRESSION_METHODS - SSL_COMP_free_compression_methods(); -#endif -#endif - - Curl_tls_keylog_close(); -} - -/* - * This function is used to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -static int ossl_check_cxn(struct connectdata *conn) -{ - /* SSL_peek takes data out of the raw recv buffer without peeking so we use - recv MSG_PEEK instead. Bug #795 */ -#ifdef MSG_PEEK - char buf; - ssize_t nread; - nread = recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf, - (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK); - if(nread == 0) - return 0; /* connection has been closed */ - if(nread == 1) - return 1; /* connection still in place */ - else if(nread == -1) { - int err = SOCKERRNO; - if(err == EINPROGRESS || -#if defined(EAGAIN) && (EAGAIN != EWOULDBLOCK) - err == EAGAIN || -#endif - err == EWOULDBLOCK) - return 1; /* connection still in place */ - if(err == ECONNRESET || -#ifdef ECONNABORTED - err == ECONNABORTED || -#endif -#ifdef ENETDOWN - err == ENETDOWN || -#endif -#ifdef ENETRESET - err == ENETRESET || -#endif -#ifdef ESHUTDOWN - err == ESHUTDOWN || -#endif -#ifdef ETIMEDOUT - err == ETIMEDOUT || -#endif - err == ENOTCONN) - return 0; /* connection has been closed */ - } -#endif - return -1; /* connection status unknown */ -} - -/* Selects an OpenSSL crypto engine - */ -static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine) -{ -#ifdef USE_OPENSSL_ENGINE - ENGINE *e; - -#if OPENSSL_VERSION_NUMBER >= 0x00909000L - e = ENGINE_by_id(engine); -#else - /* avoid memory leak */ - for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { - const char *e_id = ENGINE_get_id(e); - if(!strcmp(engine, e_id)) - break; - } -#endif - - if(!e) { - failf(data, "SSL Engine '%s' not found", engine); - return CURLE_SSL_ENGINE_NOTFOUND; - } - - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; - } - if(!ENGINE_init(e)) { - char buf[256]; - - ENGINE_free(e); - failf(data, "Failed to initialise SSL Engine '%s': %s", - engine, ossl_strerror(ERR_get_error(), buf, sizeof(buf))); - return CURLE_SSL_ENGINE_INITFAILED; - } - data->state.engine = e; - return CURLE_OK; -#else - (void)engine; - failf(data, "SSL Engine not supported"); - return CURLE_SSL_ENGINE_NOTFOUND; -#endif -} - -/* Sets engine as default for all SSL operations - */ -static CURLcode ossl_set_engine_default(struct Curl_easy *data) -{ -#ifdef USE_OPENSSL_ENGINE - if(data->state.engine) { - if(ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) { - infof(data, "set default crypto engine '%s'", - ENGINE_get_id(data->state.engine)); - } - else { - failf(data, "set default crypto engine '%s' failed", - ENGINE_get_id(data->state.engine)); - return CURLE_SSL_ENGINE_SETFAILED; - } - } -#else - (void) data; -#endif - return CURLE_OK; -} - -/* Return list of OpenSSL crypto engine names. - */ -static struct curl_slist *ossl_engines_list(struct Curl_easy *data) -{ - struct curl_slist *list = NULL; -#ifdef USE_OPENSSL_ENGINE - struct curl_slist *beg; - ENGINE *e; - - for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) { - beg = curl_slist_append(list, ENGINE_get_id(e)); - if(!beg) { - curl_slist_free_all(list); - return NULL; - } - list = beg; - } -#endif - (void) data; - return list; -} - -#define set_logger(conn, data) \ - conn->ssl[0].backend->logger = data - -static void ossl_closeone(struct Curl_easy *data, - struct connectdata *conn, - struct ssl_connect_data *connssl) -{ - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(backend->handle) { - char buf[32]; - set_logger(conn, data); - /* - * The conn->sock[0] socket is passed to openssl with SSL_set_fd(). Make - * sure the socket is not closed before calling OpenSSL functions that - * will use it. - */ - DEBUGASSERT(conn->sock[FIRSTSOCKET] != CURL_SOCKET_BAD); - - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)SSL_read(backend->handle, buf, (int)sizeof(buf)); - - (void)SSL_shutdown(backend->handle); - SSL_set_connect_state(backend->handle); - - SSL_free(backend->handle); - backend->handle = NULL; - } - if(backend->ctx) { - SSL_CTX_free(backend->ctx); - backend->ctx = NULL; - } -} - -/* - * This function is called when an SSL connection is closed. - */ -static void ossl_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - ossl_closeone(data, conn, &conn->ssl[sockindex]); -#ifndef CURL_DISABLE_PROXY - ossl_closeone(data, conn, &conn->proxy_ssl[sockindex]); -#endif -} - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int ossl_shutdown(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - char buf[256]; /* We will use this for the OpenSSL error buffer, so it has - to be at least 256 bytes long. */ - unsigned long sslerror; - ssize_t nread; - int buffsize; - int err; - bool done = FALSE; - struct ssl_backend_data *backend = connssl->backend; - int loop = 10; - - DEBUGASSERT(backend); - -#ifndef CURL_DISABLE_FTP - /* This has only been tested on the proftpd server, and the mod_tls code - sends a close notify alert without waiting for a close notify alert in - response. Thus we wait for a close notify alert from the server, but - we do not send one. Let's hope other servers do the same... */ - - if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE) - (void)SSL_shutdown(backend->handle); -#endif - - if(backend->handle) { - buffsize = (int)sizeof(buf); - while(!done && loop--) { - int what = SOCKET_READABLE(conn->sock[sockindex], - SSL_SHUTDOWN_TIMEOUT); - if(what > 0) { - ERR_clear_error(); - - /* Something to read, let's do it and hope that it is the close - notify alert from the server */ - nread = (ssize_t)SSL_read(backend->handle, buf, buffsize); - err = SSL_get_error(backend->handle, (int)nread); - - switch(err) { - case SSL_ERROR_NONE: /* this is not an error */ - case SSL_ERROR_ZERO_RETURN: /* no more data */ - /* This is the expected response. There was no data but only - the close notify alert */ - done = TRUE; - break; - case SSL_ERROR_WANT_READ: - /* there's data pending, re-invoke SSL_read() */ - infof(data, "SSL_ERROR_WANT_READ"); - break; - case SSL_ERROR_WANT_WRITE: - /* SSL wants a write. Really odd. Let's bail out. */ - infof(data, "SSL_ERROR_WANT_WRITE"); - done = TRUE; - break; - default: - /* openssl/ssl.h says "look at error stack/return value/errno" */ - sslerror = ERR_get_error(); - failf(data, OSSL_PACKAGE " SSL_read on shutdown: %s, errno %d", - (sslerror ? - ossl_strerror(sslerror, buf, sizeof(buf)) : - SSL_ERROR_to_str(err)), - SOCKERRNO); - done = TRUE; - break; - } - } - else if(0 == what) { - /* timeout */ - failf(data, "SSL shutdown timeout"); - done = TRUE; - } - else { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - retval = -1; - done = TRUE; - } - } /* while()-loop for the select() */ - - if(data->set.verbose) { -#ifdef HAVE_SSL_GET_SHUTDOWN - switch(SSL_get_shutdown(backend->handle)) { - case SSL_SENT_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN"); - break; - case SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN"); - break; - case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN: - infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|" - "SSL_RECEIVED__SHUTDOWN"); - break; - } -#endif - } - - SSL_free(backend->handle); - backend->handle = NULL; - } - return retval; -} - -static void ossl_session_free(void *ptr) -{ - /* free the ID */ - SSL_SESSION_free(ptr); -} - -/* - * This function is called when the 'data' struct is going away. Close - * down everything and free all resources! - */ -static void ossl_close_all(struct Curl_easy *data) -{ -#ifdef USE_OPENSSL_ENGINE - if(data->state.engine) { - ENGINE_finish(data->state.engine); - ENGINE_free(data->state.engine); - data->state.engine = NULL; - } -#else - (void)data; -#endif -#if !defined(HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED) && \ - defined(HAVE_ERR_REMOVE_THREAD_STATE) - /* OpenSSL 1.0.1 and 1.0.2 build an error queue that is stored per-thread - so we need to clean it here in case the thread will be killed. All OpenSSL - code should extract the error in association with the error so clearing - this queue here should be harmless at worst. */ - ERR_remove_thread_state(NULL); -#endif -} - -/* ====================================================== */ - -/* - * Match subjectAltName against the host name. - */ -static bool subj_alt_hostcheck(struct Curl_easy *data, - const char *match_pattern, - size_t matchlen, - const char *hostname, - size_t hostlen, - const char *dispname) -{ -#ifdef CURL_DISABLE_VERBOSE_STRINGS - (void)dispname; - (void)data; -#endif - if(Curl_cert_hostcheck(match_pattern, matchlen, hostname, hostlen)) { - infof(data, " subjectAltName: host \"%s\" matched cert's \"%s\"", - dispname, match_pattern); - return TRUE; - } - return FALSE; -} - -/* Quote from RFC2818 section 3.1 "Server Identity" - - If a subjectAltName extension of type dNSName is present, that MUST - be used as the identity. Otherwise, the (most specific) Common Name - field in the Subject field of the certificate MUST be used. Although - the use of the Common Name is existing practice, it is deprecated and - Certification Authorities are encouraged to use the dNSName instead. - - Matching is performed using the matching rules specified by - [RFC2459]. If more than one identity of a given type is present in - the certificate (e.g., more than one dNSName name, a match in any one - of the set is considered acceptable.) Names may contain the wildcard - character * which is considered to match any single domain name - component or component fragment. E.g., *.a.com matches foo.a.com but - not bar.foo.a.com. f*.com matches foo.com but not bar.com. - - In some cases, the URI is specified as an IP address rather than a - hostname. In this case, the iPAddress subjectAltName must be present - in the certificate and must exactly match the IP in the URI. - - This function is now used from ngtcp2 (QUIC) as well. -*/ -CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - X509 *server_cert) -{ - bool matched = FALSE; - int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */ - size_t addrlen = 0; - STACK_OF(GENERAL_NAME) *altnames; -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - CURLcode result = CURLE_OK; - bool dNSName = FALSE; /* if a dNSName field exists in the cert */ - bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */ - const char * const hostname = SSL_HOST_NAME(); - const char * const dispname = SSL_HOST_DISPNAME(); - size_t hostlen = strlen(hostname); - -#ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && - Curl_inet_pton(AF_INET6, hostname, &addr)) { - target = GEN_IPADD; - addrlen = sizeof(struct in6_addr); - } - else -#endif - if(Curl_inet_pton(AF_INET, hostname, &addr)) { - target = GEN_IPADD; - addrlen = sizeof(struct in_addr); - } - - /* get a "list" of alternative names */ - altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL); - - if(altnames) { -#ifdef OPENSSL_IS_BORINGSSL - size_t numalts; - size_t i; -#else - int numalts; - int i; -#endif - bool dnsmatched = FALSE; - bool ipmatched = FALSE; - - /* get amount of alternatives, RFC2459 claims there MUST be at least - one, but we don't depend on it... */ - numalts = sk_GENERAL_NAME_num(altnames); - - /* loop through all alternatives - until a dnsmatch */ - for(i = 0; (i < numalts) && !dnsmatched; i++) { - /* get a handle to alternative name number i */ - const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i); - - if(check->type == GEN_DNS) - dNSName = TRUE; - else if(check->type == GEN_IPADD) - iPAddress = TRUE; - - /* only check alternatives of the same type the target is */ - if(check->type == target) { - /* get data and length */ - const char *altptr = (char *)ASN1_STRING_get0_data(check->d.ia5); - size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5); - - switch(target) { - case GEN_DNS: /* name/pattern comparison */ - /* The OpenSSL man page explicitly says: "In general it cannot be - assumed that the data returned by ASN1_STRING_data() is null - terminated or does not contain embedded nulls." But also that - "The actual format of the data will depend on the actual string - type itself: for example for an IA5String the data will be ASCII" - - It has been however verified that in 0.9.6 and 0.9.7, IA5String - is always null-terminated. - */ - if((altlen == strlen(altptr)) && - /* if this isn't true, there was an embedded zero in the name - string and we cannot match it. */ - subj_alt_hostcheck(data, - altptr, - altlen, hostname, hostlen, dispname)) { - dnsmatched = TRUE; - } - break; - - case GEN_IPADD: /* IP address comparison */ - /* compare alternative IP address if the data chunk is the same size - our server IP address is */ - if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) { - ipmatched = TRUE; - infof(data, - " subjectAltName: host \"%s\" matched cert's IP address!", - dispname); - } - break; - } - } - } - GENERAL_NAMES_free(altnames); - - if(dnsmatched || ipmatched) - matched = TRUE; - } - - if(matched) - /* an alternative name matched */ - ; - else if(dNSName || iPAddress) { - infof(data, " subjectAltName does not match %s", dispname); - failf(data, "SSL: no alternative certificate subject name matches " - "target host name '%s'", dispname); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else { - /* we have to look to the last occurrence of a commonName in the - distinguished one to get the most significant one. */ - int i = -1; - unsigned char *peer_CN = NULL; - int peerlen = 0; - - /* The following is done because of a bug in 0.9.6b */ - X509_NAME *name = X509_get_subject_name(server_cert); - if(name) { - int j; - while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0) - i = j; - } - - /* we have the name entry and we will now convert this to a string - that we can use for comparison. Doing this we support BMPstring, - UTF8, etc. */ - - if(i >= 0) { - ASN1_STRING *tmp = - X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i)); - - /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input - is already UTF-8 encoded. We check for this case and copy the raw - string manually to avoid the problem. This code can be made - conditional in the future when OpenSSL has been fixed. */ - if(tmp) { - if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { - peerlen = ASN1_STRING_length(tmp); - if(peerlen >= 0) { - peer_CN = OPENSSL_malloc(peerlen + 1); - if(peer_CN) { - memcpy(peer_CN, ASN1_STRING_get0_data(tmp), peerlen); - peer_CN[peerlen] = '\0'; - } - else - result = CURLE_OUT_OF_MEMORY; - } - } - else /* not a UTF8 name */ - peerlen = ASN1_STRING_to_UTF8(&peer_CN, tmp); - - if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != peerlen)) { - /* there was a terminating zero before the end of string, this - cannot match and we return failure! */ - failf(data, "SSL: illegal cert name field"); - result = CURLE_PEER_FAILED_VERIFICATION; - } - } - } - - if(result) - /* error already detected, pass through */ - ; - else if(!peer_CN) { - failf(data, - "SSL: unable to obtain common name from peer certificate"); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else if(!Curl_cert_hostcheck((const char *)peer_CN, - peerlen, hostname, hostlen)) { - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", peer_CN, dispname); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, " common name: %s (matched)", peer_CN); - } - if(peer_CN) - OPENSSL_free(peer_CN); - } - - return result; -} - -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) -static CURLcode verifystatus(struct Curl_easy *data, - struct ssl_connect_data *connssl) -{ - int i, ocsp_status; - unsigned char *status; - const unsigned char *p; - CURLcode result = CURLE_OK; - OCSP_RESPONSE *rsp = NULL; - OCSP_BASICRESP *br = NULL; - X509_STORE *st = NULL; - STACK_OF(X509) *ch = NULL; - struct ssl_backend_data *backend = connssl->backend; - X509 *cert; - OCSP_CERTID *id = NULL; - int cert_status, crl_reason; - ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; - int ret; - long len; - - DEBUGASSERT(backend); - - len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status); - - if(!status) { - failf(data, "No OCSP response received"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - p = status; - rsp = d2i_OCSP_RESPONSE(NULL, &p, len); - if(!rsp) { - failf(data, "Invalid OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - ocsp_status = OCSP_response_status(rsp); - if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { - failf(data, "Invalid OCSP response status: %s (%d)", - OCSP_response_status_str(ocsp_status), ocsp_status); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - br = OCSP_response_get1_basic(rsp); - if(!br) { - failf(data, "Invalid OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - ch = SSL_get_peer_cert_chain(backend->handle); - if(!ch) { - failf(data, "Could not get peer certificate chain"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - st = SSL_CTX_get_cert_store(backend->ctx); - -#if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \ - (defined(LIBRESSL_VERSION_NUMBER) && \ - LIBRESSL_VERSION_NUMBER <= 0x2040200fL)) - /* The authorized responder cert in the OCSP response MUST be signed by the - peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert, - no problem, but if it's an intermediate cert OpenSSL has a bug where it - expects this issuer to be present in the chain embedded in the OCSP - response. So we add it if necessary. */ - - /* First make sure the peer cert chain includes both a peer and an issuer, - and the OCSP response contains a responder cert. */ - if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) { - X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1); - - /* Find issuer of responder cert and add it to the OCSP response chain */ - for(i = 0; i < sk_X509_num(ch); i++) { - X509 *issuer = sk_X509_value(ch, i); - if(X509_check_issued(issuer, responder) == X509_V_OK) { - if(!OCSP_basic_add1_cert(br, issuer)) { - failf(data, "Could not add issuer cert to OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - } - } - } -#endif - - if(OCSP_basic_verify(br, ch, st, 0) <= 0) { - failf(data, "OCSP response verification failed"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - /* Compute the certificate's ID */ - cert = SSL_get1_peer_certificate(backend->handle); - if(!cert) { - failf(data, "Error getting peer certificate"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - for(i = 0; i < sk_X509_num(ch); i++) { - X509 *issuer = sk_X509_value(ch, i); - if(X509_check_issued(issuer, cert) == X509_V_OK) { - id = OCSP_cert_to_id(EVP_sha1(), cert, issuer); - break; - } - } - X509_free(cert); - - if(!id) { - failf(data, "Error computing OCSP ID"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - /* Find the single OCSP response corresponding to the certificate ID */ - ret = OCSP_resp_find_status(br, id, &cert_status, &crl_reason, &rev, - &thisupd, &nextupd); - OCSP_CERTID_free(id); - if(ret != 1) { - failf(data, "Could not find certificate ID in OCSP response"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - /* Validate the corresponding single OCSP response */ - if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) { - failf(data, "OCSP response has expired"); - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - - infof(data, "SSL certificate status: %s (%d)", - OCSP_cert_status_str(cert_status), cert_status); - - switch(cert_status) { - case V_OCSP_CERTSTATUS_GOOD: - break; - - case V_OCSP_CERTSTATUS_REVOKED: - result = CURLE_SSL_INVALIDCERTSTATUS; - failf(data, "SSL certificate revocation reason: %s (%d)", - OCSP_crl_reason_str(crl_reason), crl_reason); - goto end; - - case V_OCSP_CERTSTATUS_UNKNOWN: - default: - result = CURLE_SSL_INVALIDCERTSTATUS; - goto end; - } - -end: - if(br) - OCSP_BASICRESP_free(br); - OCSP_RESPONSE_free(rsp); - - return result; -} -#endif - -#endif /* USE_OPENSSL */ - -/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions - and thus this cannot be done there. */ -#ifdef SSL_CTRL_SET_MSG_CALLBACK - -static const char *ssl_msg_type(int ssl_ver, int msg) -{ -#ifdef SSL2_VERSION_MAJOR - if(ssl_ver == SSL2_VERSION_MAJOR) { - switch(msg) { - case SSL2_MT_ERROR: - return "Error"; - case SSL2_MT_CLIENT_HELLO: - return "Client hello"; - case SSL2_MT_CLIENT_MASTER_KEY: - return "Client key"; - case SSL2_MT_CLIENT_FINISHED: - return "Client finished"; - case SSL2_MT_SERVER_HELLO: - return "Server hello"; - case SSL2_MT_SERVER_VERIFY: - return "Server verify"; - case SSL2_MT_SERVER_FINISHED: - return "Server finished"; - case SSL2_MT_REQUEST_CERTIFICATE: - return "Request CERT"; - case SSL2_MT_CLIENT_CERTIFICATE: - return "Client CERT"; - } - } - else -#endif - if(ssl_ver == SSL3_VERSION_MAJOR) { - switch(msg) { - case SSL3_MT_HELLO_REQUEST: - return "Hello request"; - case SSL3_MT_CLIENT_HELLO: - return "Client hello"; - case SSL3_MT_SERVER_HELLO: - return "Server hello"; -#ifdef SSL3_MT_NEWSESSION_TICKET - case SSL3_MT_NEWSESSION_TICKET: - return "Newsession Ticket"; -#endif - case SSL3_MT_CERTIFICATE: - return "Certificate"; - case SSL3_MT_SERVER_KEY_EXCHANGE: - return "Server key exchange"; - case SSL3_MT_CLIENT_KEY_EXCHANGE: - return "Client key exchange"; - case SSL3_MT_CERTIFICATE_REQUEST: - return "Request CERT"; - case SSL3_MT_SERVER_DONE: - return "Server finished"; - case SSL3_MT_CERTIFICATE_VERIFY: - return "CERT verify"; - case SSL3_MT_FINISHED: - return "Finished"; -#ifdef SSL3_MT_CERTIFICATE_STATUS - case SSL3_MT_CERTIFICATE_STATUS: - return "Certificate Status"; -#endif -#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS - case SSL3_MT_ENCRYPTED_EXTENSIONS: - return "Encrypted Extensions"; -#endif -#ifdef SSL3_MT_SUPPLEMENTAL_DATA - case SSL3_MT_SUPPLEMENTAL_DATA: - return "Supplemental data"; -#endif -#ifdef SSL3_MT_END_OF_EARLY_DATA - case SSL3_MT_END_OF_EARLY_DATA: - return "End of early data"; -#endif -#ifdef SSL3_MT_KEY_UPDATE - case SSL3_MT_KEY_UPDATE: - return "Key update"; -#endif -#ifdef SSL3_MT_NEXT_PROTO - case SSL3_MT_NEXT_PROTO: - return "Next protocol"; -#endif -#ifdef SSL3_MT_MESSAGE_HASH - case SSL3_MT_MESSAGE_HASH: - return "Message hash"; -#endif - } - } - return "Unknown"; -} - -static const char *tls_rt_type(int type) -{ - switch(type) { -#ifdef SSL3_RT_HEADER - case SSL3_RT_HEADER: - return "TLS header"; -#endif - case SSL3_RT_CHANGE_CIPHER_SPEC: - return "TLS change cipher"; - case SSL3_RT_ALERT: - return "TLS alert"; - case SSL3_RT_HANDSHAKE: - return "TLS handshake"; - case SSL3_RT_APPLICATION_DATA: - return "TLS app data"; - default: - return "TLS Unknown"; - } -} - -/* - * Our callback from the SSL/TLS layers. - */ -static void ossl_trace(int direction, int ssl_ver, int content_type, - const void *buf, size_t len, SSL *ssl, - void *userp) -{ - char unknown[32]; - const char *verstr = NULL; - struct connectdata *conn = userp; - struct ssl_connect_data *connssl = &conn->ssl[0]; - struct ssl_backend_data *backend = connssl->backend; - struct Curl_easy *data = NULL; - - DEBUGASSERT(backend); - data = backend->logger; - - if(!conn || !data || !data->set.fdebug || - (direction != 0 && direction != 1)) - return; - - switch(ssl_ver) { -#ifdef SSL2_VERSION /* removed in recent versions */ - case SSL2_VERSION: - verstr = "SSLv2"; - break; -#endif -#ifdef SSL3_VERSION - case SSL3_VERSION: - verstr = "SSLv3"; - break; -#endif - case TLS1_VERSION: - verstr = "TLSv1.0"; - break; -#ifdef TLS1_1_VERSION - case TLS1_1_VERSION: - verstr = "TLSv1.1"; - break; -#endif -#ifdef TLS1_2_VERSION - case TLS1_2_VERSION: - verstr = "TLSv1.2"; - break; -#endif -#ifdef TLS1_3_VERSION - case TLS1_3_VERSION: - verstr = "TLSv1.3"; - break; -#endif - case 0: - break; - default: - msnprintf(unknown, sizeof(unknown), "(%x)", ssl_ver); - verstr = unknown; - break; - } - - /* Log progress for interesting records only (like Handshake or Alert), skip - * all raw record headers (content_type == SSL3_RT_HEADER or ssl_ver == 0). - * For TLS 1.3, skip notification of the decrypted inner Content-Type. - */ - if(ssl_ver -#ifdef SSL3_RT_INNER_CONTENT_TYPE - && content_type != SSL3_RT_INNER_CONTENT_TYPE -#endif - ) { - const char *msg_name, *tls_rt_name; - char ssl_buf[1024]; - int msg_type, txt_len; - - /* the info given when the version is zero is not that useful for us */ - - ssl_ver >>= 8; /* check the upper 8 bits only below */ - - /* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL - * always pass-up content-type as 0. But the interesting message-type - * is at 'buf[0]'. - */ - if(ssl_ver == SSL3_VERSION_MAJOR && content_type) - tls_rt_name = tls_rt_type(content_type); - else - tls_rt_name = ""; - - if(content_type == SSL3_RT_CHANGE_CIPHER_SPEC) { - msg_type = *(char *)buf; - msg_name = "Change cipher spec"; - } - else if(content_type == SSL3_RT_ALERT) { - msg_type = (((char *)buf)[0] << 8) + ((char *)buf)[1]; - msg_name = SSL_alert_desc_string_long(msg_type); - } - else { - msg_type = *(char *)buf; - msg_name = ssl_msg_type(ssl_ver, msg_type); - } - - txt_len = msnprintf(ssl_buf, sizeof(ssl_buf), "%s (%s), %s, %s (%d):\n", - verstr, direction?"OUT":"IN", - tls_rt_name, msg_name, msg_type); - if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) { - Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len); - } - } - - Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT : - CURLINFO_SSL_DATA_IN, (char *)buf, len); - (void) ssl; -} -#endif - -#ifdef USE_OPENSSL -/* ====================================================== */ - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME -# define use_sni(x) sni = (x) -#else -# define use_sni(x) Curl_nop_stmt -#endif - -/* Check for OpenSSL 1.0.2 which has ALPN support. */ -#undef HAS_ALPN -#if OPENSSL_VERSION_NUMBER >= 0x10002000L \ - && !defined(OPENSSL_NO_TLSEXT) -# define HAS_ALPN 1 -#endif - -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */ -static CURLcode -set_ssl_version_min_max(SSL_CTX *ctx, struct connectdata *conn) -{ - /* first, TLS min version... */ - long curl_ssl_version_min = SSL_CONN_CONFIG(version); - long curl_ssl_version_max; - - /* convert curl min SSL version option to OpenSSL constant */ -#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) - uint16_t ossl_ssl_version_min = 0; - uint16_t ossl_ssl_version_max = 0; -#else - long ossl_ssl_version_min = 0; - long ossl_ssl_version_max = 0; -#endif - switch(curl_ssl_version_min) { - case CURL_SSLVERSION_TLSv1: /* TLS 1.x */ - case CURL_SSLVERSION_TLSv1_0: - ossl_ssl_version_min = TLS1_VERSION; - break; - case CURL_SSLVERSION_TLSv1_1: - ossl_ssl_version_min = TLS1_1_VERSION; - break; - case CURL_SSLVERSION_TLSv1_2: - ossl_ssl_version_min = TLS1_2_VERSION; - break; - case CURL_SSLVERSION_TLSv1_3: -#ifdef TLS1_3_VERSION - ossl_ssl_version_min = TLS1_3_VERSION; - break; -#else - return CURLE_NOT_BUILT_IN; -#endif - } - - /* CURL_SSLVERSION_DEFAULT means that no option was selected. - We don't want to pass 0 to SSL_CTX_set_min_proto_version as - it would enable all versions down to the lowest supported by - the library. - So we skip this, and stay with the library default - */ - if(curl_ssl_version_min != CURL_SSLVERSION_DEFAULT) { - if(!SSL_CTX_set_min_proto_version(ctx, ossl_ssl_version_min)) { - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* ... then, TLS max version */ - curl_ssl_version_max = SSL_CONN_CONFIG(version_max); - - /* convert curl max SSL version option to OpenSSL constant */ - switch(curl_ssl_version_max) { - case CURL_SSLVERSION_MAX_TLSv1_0: - ossl_ssl_version_max = TLS1_VERSION; - break; - case CURL_SSLVERSION_MAX_TLSv1_1: - ossl_ssl_version_max = TLS1_1_VERSION; - break; - case CURL_SSLVERSION_MAX_TLSv1_2: - ossl_ssl_version_max = TLS1_2_VERSION; - break; -#ifdef TLS1_3_VERSION - case CURL_SSLVERSION_MAX_TLSv1_3: - ossl_ssl_version_max = TLS1_3_VERSION; - break; -#endif - case CURL_SSLVERSION_MAX_NONE: /* none selected */ - case CURL_SSLVERSION_MAX_DEFAULT: /* max selected */ - default: - /* SSL_CTX_set_max_proto_version states that: - setting the maximum to 0 will enable - protocol versions up to the highest version - supported by the library */ - ossl_ssl_version_max = 0; - break; - } - - if(!SSL_CTX_set_max_proto_version(ctx, ossl_ssl_version_max)) { - return CURLE_SSL_CONNECT_ERROR; - } - - return CURLE_OK; -} -#endif - -#ifdef OPENSSL_IS_BORINGSSL -typedef uint32_t ctx_option_t; -#elif OPENSSL_VERSION_NUMBER >= 0x30000000L -typedef uint64_t ctx_option_t; -#else -typedef long ctx_option_t; -#endif - -#if (OPENSSL_VERSION_NUMBER < 0x10100000L) /* 1.1.0 */ -static CURLcode -set_ssl_version_min_max_legacy(ctx_option_t *ctx_options, - struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - long ssl_version = SSL_CONN_CONFIG(version); - long ssl_version_max = SSL_CONN_CONFIG(version_max); - - (void) data; /* In case it's unused. */ - - switch(ssl_version) { - case CURL_SSLVERSION_TLSv1_3: -#ifdef TLS1_3_VERSION - { - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - SSL_CTX_set_max_proto_version(backend->ctx, TLS1_3_VERSION); - *ctx_options |= SSL_OP_NO_TLSv1_2; - } -#else - (void)sockindex; - (void)ctx_options; - failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); - return CURLE_NOT_BUILT_IN; -#endif - /* FALLTHROUGH */ - case CURL_SSLVERSION_TLSv1_2: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - *ctx_options |= SSL_OP_NO_TLSv1_1; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.2 support"); - return CURLE_NOT_BUILT_IN; -#endif - /* FALLTHROUGH */ - case CURL_SSLVERSION_TLSv1_1: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - *ctx_options |= SSL_OP_NO_TLSv1; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.1 support"); - return CURLE_NOT_BUILT_IN; -#endif - /* FALLTHROUGH */ - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1: - break; - } - - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_TLSv1_0: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - *ctx_options |= SSL_OP_NO_TLSv1_1; -#endif - /* FALLTHROUGH */ - case CURL_SSLVERSION_MAX_TLSv1_1: -#if OPENSSL_VERSION_NUMBER >= 0x1000100FL - *ctx_options |= SSL_OP_NO_TLSv1_2; -#endif - /* FALLTHROUGH */ - case CURL_SSLVERSION_MAX_TLSv1_2: -#ifdef TLS1_3_VERSION - *ctx_options |= SSL_OP_NO_TLSv1_3; -#endif - break; - case CURL_SSLVERSION_MAX_TLSv1_3: -#ifdef TLS1_3_VERSION - break; -#else - failf(data, OSSL_PACKAGE " was built without TLS 1.3 support"); - return CURLE_NOT_BUILT_IN; -#endif - } - return CURLE_OK; -} -#endif - -/* The "new session" callback must return zero if the session can be removed - * or non-zero if the session has been put into the session cache. - */ -static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) -{ - int res = 0; - struct connectdata *conn; - struct Curl_easy *data; - int sockindex; - curl_socket_t *sockindex_ptr; - int data_idx = ossl_get_ssl_data_index(); - int connectdata_idx = ossl_get_ssl_conn_index(); - int sockindex_idx = ossl_get_ssl_sockindex_index(); - int proxy_idx = ossl_get_proxy_index(); - bool isproxy; - - if(data_idx < 0 || connectdata_idx < 0 || sockindex_idx < 0 || proxy_idx < 0) - return 0; - - conn = (struct connectdata*) SSL_get_ex_data(ssl, connectdata_idx); - data = (struct Curl_easy *) SSL_get_ex_data(ssl, data_idx); - /* The sockindex has been stored as a pointer to an array element */ - sockindex_ptr = (curl_socket_t*) SSL_get_ex_data(ssl, sockindex_idx); - if(!conn || !data || !sockindex_ptr) - return 0; - - sockindex = (int)(sockindex_ptr - conn->sock); - - isproxy = SSL_get_ex_data(ssl, proxy_idx) ? TRUE : FALSE; - - if(SSL_SET_OPTION(primary.sessionid)) { - bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; - - Curl_ssl_sessionid_lock(data); - if(isproxy) - incache = FALSE; - else - incache = !(Curl_ssl_getsessionid(data, conn, isproxy, - &old_ssl_sessionid, NULL, sockindex)); - if(incache) { - if(old_ssl_sessionid != ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } - } - - if(!incache) { - if(!Curl_ssl_addsessionid(data, conn, isproxy, ssl_sessionid, - 0 /* unknown size */, sockindex, &added)) { - if(added) { - /* the session has been put into the session cache */ - res = 1; - } - } - else - failf(data, "failed to store ssl session"); - } - Curl_ssl_sessionid_unlock(data); - } - - return res; -} - -static CURLcode load_cacert_from_memory(SSL_CTX *ctx, - const struct curl_blob *ca_info_blob) -{ - /* these need to be 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) -{ - CURLcode result = CURLE_OK; - char *ciphers; - SSL_METHOD_QUAL SSL_METHOD *req_method = NULL; - X509_LOOKUP *lookup = NULL; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - ctx_option_t ctx_options = 0; - void *ssl_sessionid = NULL; - -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - bool sni; - const char * const hostname = SSL_HOST_NAME(); - -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif -#endif - const long int ssl_version = SSL_CONN_CONFIG(version); -#ifdef USE_OPENSSL_SRP - const enum CURL_TLSAUTH ssl_authtype = SSL_SET_OPTION(primary.authtype); -#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 = - /* 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(primary.CRLfile); - char error_buffer[256]; - struct ssl_backend_data *backend = connssl->backend; - bool imported_native_ca = false; - - DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); - DEBUGASSERT(backend); - - /* Make funny stuff to get random input */ - result = ossl_seed(data); - if(result) - return result; - - SSL_SET_OPTION_LVALUE(certverifyresult) = !X509_V_OK; - - /* check to see if we've been told to use an explicit SSL/TLS version */ - - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - /* it will be handled later with the context options */ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) - req_method = TLS_client_method(); -#else - req_method = SSLv23_client_method(); -#endif - use_sni(TRUE); - break; - case CURL_SSLVERSION_SSLv2: - failf(data, "No SSLv2 support"); - return CURLE_NOT_BUILT_IN; - case CURL_SSLVERSION_SSLv3: - failf(data, "No SSLv3 support"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - DEBUGASSERT(!backend->ctx); - backend->ctx = SSL_CTX_new(req_method); - - if(!backend->ctx) { - failf(data, "SSL: couldn't create a context: %s", - ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer))); - return CURLE_OUT_OF_MEMORY; - } - -#ifdef SSL_MODE_RELEASE_BUFFERS - SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS); -#endif - -#ifdef SSL_CTRL_SET_MSG_CALLBACK - if(data->set.fdebug && data->set.verbose) { - /* the SSL trace callback is only used for verbose logging */ - SSL_CTX_set_msg_callback(backend->ctx, ossl_trace); - SSL_CTX_set_msg_callback_arg(backend->ctx, conn); - set_logger(conn, data); - } -#endif - - /* OpenSSL contains code to work around lots of bugs and flaws in various - SSL-implementations. SSL_CTX_set_options() is used to enabled those - work-arounds. The man page for this option states that SSL_OP_ALL enables - all the work-arounds and that "It is usually safe to use SSL_OP_ALL to - enable the bug workaround options if compatibility with somewhat broken - implementations is desired." - - The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to - disable "rfc4507bis session ticket support". rfc4507bis was later turned - into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077 - - The enabled extension concerns the session management. I wonder how often - libcurl stops a connection and then resumes a TLS session. Also, sending - the session data is some overhead. I suggest that you just use your - proposed patch (which explicitly disables TICKET). - - If someone writes an application with libcurl and OpenSSL who wants to - enable the feature, one can do this in the SSL callback. - - SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper - interoperability with web server Netscape Enterprise Server 2.0.1 which - was released back in 1996. - - Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has - become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate - CVE-2010-4180 when using previous OpenSSL versions we no longer enable - this option regardless of OpenSSL version and SSL_OP_ALL definition. - - OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability - (https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to - SSL_OP_ALL that _disables_ that work-around despite the fact that - SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to - keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit - must not be set. - */ - - ctx_options = SSL_OP_ALL; - -#ifdef SSL_OP_NO_TICKET - ctx_options |= SSL_OP_NO_TICKET; -#endif - -#ifdef SSL_OP_NO_COMPRESSION - ctx_options |= SSL_OP_NO_COMPRESSION; -#endif - -#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG - /* mitigate CVE-2010-4180 */ - ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; -#endif - -#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS - /* unless the user explicitly asks to allow the protocol vulnerability we - use the work-around */ - if(!SSL_SET_OPTION(enable_beast)) - ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; -#endif - - switch(ssl_version) { - case CURL_SSLVERSION_SSLv2: - case CURL_SSLVERSION_SSLv3: - return CURLE_NOT_BUILT_IN; - - /* "--tlsv<x.y>" options mean TLS >= version <x.y> */ - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */ - case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */ - case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */ - case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */ - case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */ - /* asking for any TLS version as the minimum, means no SSL versions - allowed */ - ctx_options |= SSL_OP_NO_SSLv2; - ctx_options |= SSL_OP_NO_SSLv3; - -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */ - result = set_ssl_version_min_max(backend->ctx, conn); -#else - result = set_ssl_version_min_max_legacy(&ctx_options, data, conn, - sockindex); -#endif - if(result != CURLE_OK) - return result; - break; - - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - SSL_CTX_set_options(backend->ctx, ctx_options); - -#ifdef HAS_ALPN - if(conn->bits.tls_enable_alpn) { - int cur = 0; - unsigned char protocols[128]; - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!SSL_IS_PROXY() || !conn->bits.tunnel_proxy) -#endif - ) { - protocols[cur++] = ALPN_H2_LENGTH; - - memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - protocols[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - /* expects length prefixed preference ordered list of protocols in wire - * format - */ - if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) { - failf(data, "Error setting ALPN"); - return CURLE_SSL_CONNECT_ERROR; - } - } -#endif - - if(ssl_cert || ssl_cert_blob || ssl_cert_type) { - if(!result && - !cert_stuff(data, backend->ctx, - ssl_cert, ssl_cert_blob, ssl_cert_type, - SSL_SET_OPTION(key), SSL_SET_OPTION(key_blob), - SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd))) - result = CURLE_SSL_CERTPROBLEM; - if(result) - /* failf() is already done in cert_stuff() */ - return result; - } - - ciphers = SSL_CONN_CONFIG(cipher_list); - if(!ciphers) - ciphers = (char *)DEFAULT_CIPHER_SELECTION; - if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { - failf(data, "failed setting cipher list: %s", ciphers); - return CURLE_SSL_CIPHER; - } - infof(data, "Cipher selection: %s", ciphers); - } - -#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES - { - char *ciphers13 = SSL_CONN_CONFIG(cipher_list13); - if(ciphers13) { - if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) { - failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13); - return CURLE_SSL_CIPHER; - } - infof(data, "TLS 1.3 cipher selection: %s", ciphers13); - } - } -#endif - -#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH - /* OpenSSL 1.1.1 requires clients to opt-in for PHA */ - SSL_CTX_set_post_handshake_auth(backend->ctx, 1); -#endif - -#ifdef HAVE_SSL_CTX_SET_EC_CURVES - { - char *curves = SSL_CONN_CONFIG(curves); - if(curves) { - if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { - failf(data, "failed setting curves list: '%s'", curves); - return CURLE_SSL_CIPHER; - } - } - } -#endif - -#ifdef USE_OPENSSL_SRP - if((ssl_authtype == CURL_TLSAUTH_SRP) && - Curl_auth_allowed_to_host(data)) { - char * const ssl_username = SSL_SET_OPTION(primary.username); - char * const ssl_password = SSL_SET_OPTION(primary.password); - infof(data, "Using TLS-SRP username: %s", ssl_username); - - if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) { - failf(data, "Unable to set SRP user name"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - if(!SSL_CTX_set_srp_password(backend->ctx, ssl_password)) { - failf(data, "failed setting SRP password"); - return CURLE_BAD_FUNCTION_ARGUMENT; - } - if(!SSL_CONN_CONFIG(cipher_list)) { - infof(data, "Setting cipher list SRP"); - - if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) { - failf(data, "failed setting SRP cipher list"); - return CURLE_SSL_CIPHER; - } - } - } -#endif - - -#if defined(USE_WIN32_CRYPTO) - /* Import certificates from the Windows root certificate store if requested. - https://stackoverflow.com/questions/9507184/ - https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037 - https://datatracker.ietf.org/doc/html/rfc5280 */ - if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) && - (SSL_SET_OPTION(native_ca_store))) { - X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx); - HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT")); - - if(hStore) { - PCCERT_CONTEXT pContext = NULL; - /* The array of enhanced key usage OIDs will vary per certificate and is - declared outside of the loop so that rather than malloc/free each - iteration we can grow it with realloc, when necessary. */ - CERT_ENHKEY_USAGE *enhkey_usage = NULL; - DWORD enhkey_usage_size = 0; - - /* This loop makes a best effort to import all valid certificates from - the MS root store. If a certificate cannot be imported it is skipped. - 'result' is used to store only hard-fail conditions (such as out of - memory) that cause an early break. */ - result = CURLE_OK; - for(;;) { - X509 *x509; - FILETIME now; - BYTE key_usage[2]; - DWORD req_size; - const unsigned char *encoded_cert; -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - char cert_name[256]; -#endif - - pContext = CertEnumCertificatesInStore(hStore, pContext); - if(!pContext) - break; - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, - NULL, cert_name, sizeof(cert_name))) { - strcpy(cert_name, "Unknown"); - } - infof(data, "SSL: Checking cert \"%s\"", cert_name); -#endif - - encoded_cert = (const unsigned char *)pContext->pbCertEncoded; - if(!encoded_cert) - continue; - - GetSystemTimeAsFileTime(&now); - if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 || - CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0) - continue; - - /* If key usage exists check for signing attribute */ - if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType, - pContext->pCertInfo, - key_usage, sizeof(key_usage))) { - if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE)) - continue; - } - else if(GetLastError()) - continue; - - /* If enhanced key usage exists check for server auth attribute. - * - * Note "In a Microsoft environment, a certificate might also have EKU - * extended properties that specify valid uses for the certificate." - * The call below checks both, and behavior varies depending on what is - * found. For more details see CertGetEnhancedKeyUsage doc. - */ - if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) { - if(req_size && req_size > enhkey_usage_size) { - void *tmp = realloc(enhkey_usage, req_size); - - if(!tmp) { - failf(data, "SSL: Out of memory allocating for OID list"); - result = CURLE_OUT_OF_MEMORY; - break; - } - - enhkey_usage = (CERT_ENHKEY_USAGE *)tmp; - enhkey_usage_size = req_size; - } - - if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) { - if(!enhkey_usage->cUsageIdentifier) { - /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is - good for all uses. If it returns zero, the certificate has no - valid uses." */ - if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND) - continue; - } - else { - DWORD i; - bool found = false; - - for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) { - if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */, - enhkey_usage->rgpszUsageIdentifier[i])) { - found = true; - break; - } - } - - if(!found) - continue; - } - } - else - continue; - } - else - continue; - - x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); - if(!x509) - continue; - - /* Try to import the certificate. This may fail for legitimate reasons - such as duplicate certificate, which is allowed by MS but not - OpenSSL. */ - if(X509_STORE_add_cert(store, x509) == 1) { -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - infof(data, "SSL: Imported cert \"%s\"", cert_name); -#endif - imported_native_ca = true; - } - X509_free(x509); - } - - free(enhkey_usage); - CertFreeCertificateContext(pContext); - CertCloseStore(hStore, 0); - - if(result) - return result; - } - if(imported_native_ca) - infof(data, "successfully imported Windows CA store"); - else - infof(data, "error importing Windows CA store, continuing anyway"); - } -#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 warn if no certificate verification is required. */ - infof(data, "error importing CA certificate blob, continuing anyway"); - } - } - - if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) { -#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) - /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ - if(ssl_cafile && - !SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate file: %s", ssl_cafile); - return CURLE_SSL_CACERT_BADFILE; - } - if(ssl_capath && - !SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate path: %s", ssl_capath); - return CURLE_SSL_CACERT_BADFILE; - } -#else - /* tell OpenSSL where to find CA certificates that are used to verify the - server's certificate. */ - if(!SSL_CTX_load_verify_locations(backend->ctx, ssl_cafile, ssl_capath)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - ssl_cafile ? ssl_cafile : "none", - ssl_capath ? ssl_capath : "none"); - return CURLE_SSL_CACERT_BADFILE; - } -#endif - infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none"); - infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); - } - -#ifdef CURL_CA_FALLBACK - 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); - } -#endif - - if(ssl_crlfile) { - /* tell OpenSSL where to find CRL file that is used to check certificate - * revocation */ - lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(backend->ctx), - X509_LOOKUP_file()); - if(!lookup || - (!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) { - failf(data, "error loading CRL file: %s", ssl_crlfile); - return CURLE_SSL_CRL_BADFILE; - } - /* Everything is fine. */ - infof(data, "successfully loaded CRL file:"); - X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx), - X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); - - infof(data, " CRLfile: %s", ssl_crlfile); - } - - if(verifypeer) { - /* Try building a chain using issuers in the trusted store first to avoid - problems with server-sent legacy intermediates. Newer versions of - OpenSSL do alternate chain checking by default but we do not know how to - determine that in a reliable manner. - https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest - */ -#if defined(X509_V_FLAG_TRUSTED_FIRST) - X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx), - X509_V_FLAG_TRUSTED_FIRST); -#endif -#ifdef X509_V_FLAG_PARTIAL_CHAIN - if(!SSL_SET_OPTION(no_partialchain) && !ssl_crlfile) { - /* Have intermediate certificates in the trust store be treated as - trust-anchors, in the same way as self-signed root CA certificates - are. This allows users to verify servers using the intermediate cert - only, instead of needing the whole chain. - - Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we - cannot do partial chains with a CRL check. - */ - X509_STORE_set_flags(SSL_CTX_get_cert_store(backend->ctx), - X509_V_FLAG_PARTIAL_CHAIN); - } -#endif - } - - /* OpenSSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(backend->ctx, - verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); - - /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */ -#ifdef HAVE_KEYLOG_CALLBACK - if(Curl_tls_keylog_enabled()) { - SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback); - } -#endif - - /* Enable the session cache because it's a prerequisite for the "new session" - * callback. Use the "external storage" mode to prevent OpenSSL from creating - * an internal session cache. - */ - SSL_CTX_set_session_cache_mode(backend->ctx, - SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL); - SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb); - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - Curl_set_in_callback(data, true); - result = (*data->set.ssl.fsslctx)(data, backend->ctx, - data->set.ssl.fsslctxp); - Curl_set_in_callback(data, false); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - return result; - } - } - - /* Let's make an SSL structure */ - if(backend->handle) - SSL_free(backend->handle); - backend->handle = SSL_new(backend->ctx); - if(!backend->handle) { - failf(data, "SSL: couldn't create a context (handle)"); - return CURLE_OUT_OF_MEMORY; - } - -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) - if(SSL_CONN_CONFIG(verifystatus)) - SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp); -#endif - -#if defined(OPENSSL_IS_BORINGSSL) && defined(ALLOW_RENEG) - SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely); -#endif - - SSL_set_connect_state(backend->handle); - - backend->server_cert = 0x0; -#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME - if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) && -#ifdef ENABLE_IPV6 - (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) && -#endif - sni) { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost || !SSL_set_tlsext_host_name(backend->handle, snihost)) { - failf(data, "Failed set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - } -#endif - - if(!ossl_associate_connection(data, conn, sockindex)) { - /* Maybe the internal errors of SSL_get_ex_new_index or SSL_set_ex_data */ - failf(data, "SSL: ossl_associate_connection failed: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - return CURLE_SSL_CONNECT_ERROR; - } - - if(SSL_SET_OPTION(primary.sessionid)) { - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, SSL_IS_PROXY() ? TRUE : FALSE, - &ssl_sessionid, NULL, sockindex)) { - /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { - Curl_ssl_sessionid_unlock(data); - failf(data, "SSL: SSL_set_session failed: %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer))); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof(data, "SSL re-using session ID"); - } - Curl_ssl_sessionid_unlock(data); - } - -#ifndef CURL_DISABLE_PROXY - if(conn->proxy_ssl[sockindex].use) { - BIO *const bio = BIO_new(BIO_f_ssl()); - struct ssl_backend_data *proxy_backend; - SSL* handle = NULL; - proxy_backend = conn->proxy_ssl[sockindex].backend; - DEBUGASSERT(proxy_backend); - handle = proxy_backend->handle; - DEBUGASSERT(ssl_connection_complete == conn->proxy_ssl[sockindex].state); - DEBUGASSERT(handle != NULL); - DEBUGASSERT(bio != NULL); - BIO_set_ssl(bio, handle, FALSE); - SSL_set_bio(backend->handle, bio, bio); - } - else -#endif - if(!SSL_set_fd(backend->handle, (int)sockfd)) { - /* pass the raw socket into the SSL layers */ - failf(data, "SSL: SSL_set_fd failed: %s", - ossl_strerror(ERR_get_error(), error_buffer, sizeof(error_buffer))); - return CURLE_SSL_CONNECT_ERROR; - } - - connssl->connecting_state = ssl_connect_2; - - return CURLE_OK; -} - -static CURLcode ossl_connect_step2(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - int err; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); - DEBUGASSERT(backend); - - ERR_clear_error(); - - err = SSL_connect(backend->handle); -#ifndef HAVE_KEYLOG_CALLBACK - if(Curl_tls_keylog_enabled()) { - /* If key logging is enabled, wait for the handshake to complete and then - * proceed with logging secrets (for TLS 1.2 or older). - */ - ossl_log_tls12_secret(backend->handle, &backend->keylog_done); - } -#endif - - /* 1 is fine - 0 is "not successful but was shut down controlled" - <0 is "handshake was not successful, because a fatal error occurred" */ - if(1 != err) { - int detail = SSL_get_error(backend->handle, err); - - if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - } -#ifdef SSL_ERROR_WANT_ASYNC - if(SSL_ERROR_WANT_ASYNC == detail) { - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; - } -#endif - else { - /* untreated error */ - unsigned long errdetail; - char error_buffer[256]=""; - CURLcode result; - long lerr; - int lib; - int reason; - - /* the connection failed, we're not waiting for anything else. */ - connssl->connecting_state = ssl_connect_2; - - /* Get the earliest error code from the thread's error queue and remove - the entry. */ - errdetail = ERR_get_error(); - - /* Extract which lib and reason */ - lib = ERR_GET_LIB(errdetail); - reason = ERR_GET_REASON(errdetail); - - if((lib == ERR_LIB_SSL) && - ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) || - (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) { - result = CURLE_PEER_FAILED_VERIFICATION; - - lerr = SSL_get_verify_result(backend->handle); - if(lerr != X509_V_OK) { - SSL_SET_OPTION_LVALUE(certverifyresult) = lerr; - msnprintf(error_buffer, sizeof(error_buffer), - "SSL certificate problem: %s", - X509_verify_cert_error_string(lerr)); - } - else - /* strcpy() is fine here as long as the string fits within - error_buffer */ - strcpy(error_buffer, "SSL certificate verification failed"); - } -#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \ - !defined(LIBRESSL_VERSION_NUMBER) && \ - !defined(OPENSSL_IS_BORINGSSL)) - /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on - OpenSSL version above v1.1.1, not LibreSSL nor BoringSSL */ - else if((lib == ERR_LIB_SSL) && - (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) { - /* If client certificate is required, communicate the - error to client */ - result = CURLE_SSL_CLIENTCERT; - ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); - } -#endif - else { - result = CURLE_SSL_CONNECT_ERROR; - ossl_strerror(errdetail, error_buffer, sizeof(error_buffer)); - } - - /* detail is already set to the SSL error above */ - - /* If we e.g. use SSLv2 request-method and the server doesn't like us - * (RST connection, etc.), OpenSSL gives no explanation whatsoever and - * the SO_ERROR is also lost. - */ - if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) { - const char * const hostname = SSL_HOST_NAME(); - const long int port = SSL_HOST_PORT(); - char extramsg[80]=""; - int sockerr = SOCKERRNO; - if(sockerr && detail == SSL_ERROR_SYSCALL) - Curl_strerror(sockerr, extramsg, sizeof(extramsg)); - failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%ld ", - extramsg[0] ? extramsg : SSL_ERROR_to_str(detail), - hostname, port); - return result; - } - - /* Could be a CERT problem */ - failf(data, "%s", error_buffer); - - return result; - } - } - else { - /* we connected fine, we're not waiting for anything else. */ - connssl->connecting_state = ssl_connect_3; - - /* Informational message */ - infof(data, "SSL connection using %s / %s", - SSL_get_version(backend->handle), - SSL_get_cipher(backend->handle)); - -#ifdef HAS_ALPN - /* Sets data and len to negotiated protocol, len is 0 if no protocol was - * negotiated - */ - if(conn->bits.tls_enable_alpn) { - const unsigned char *neg_protocol; - unsigned int len; - SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len); - if(len) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, len, neg_protocol); - -#ifdef USE_HTTP2 - if(len == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, neg_protocol, len)) { - conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(len == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) { - conn->alpn = CURL_HTTP_VERSION_1_1; - } - } - else - infof(data, VTLS_INFOF_NO_ALPN); - - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } -#endif - - return CURLE_OK; - } -} - -/* - * Heavily modified from: - * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL - */ -static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert, - const char *pinnedpubkey) -{ - /* Scratch */ - int len1 = 0, len2 = 0; - unsigned char *buff1 = NULL, *temp = NULL; - - /* Result is returned to caller */ - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - - /* if a path wasn't specified, don't pin */ - if(!pinnedpubkey) - return CURLE_OK; - - if(!cert) - return result; - - do { - /* Begin Gyrations to get the subjectPublicKeyInfo */ - /* Thanks to Viktor Dukhovni on the OpenSSL mailing list */ - - /* https://groups.google.com/group/mailing.openssl.users/browse_thread - /thread/d61858dae102c6c7 */ - len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL); - if(len1 < 1) - break; /* failed */ - - buff1 = temp = malloc(len1); - if(!buff1) - break; /* failed */ - - /* https://www.openssl.org/docs/crypto/d2i_X509.html */ - len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp); - - /* - * These checks are verifying we got back the same values as when we - * sized the buffer. It's pretty weak since they should always be the - * same. But it gives us something to test. - */ - if((len1 != len2) || !temp || ((temp - buff1) != len1)) - break; /* failed */ - - /* End Gyrations */ - - /* The one good exit point */ - result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1); - } while(0); - - if(buff1) - free(buff1); - - return result; -} - -/* - * Get the server cert, verify it and show it, etc., only call failf() if the - * 'strict' argument is TRUE as otherwise all this is for informational - * purposes only! - * - * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack. - */ -static CURLcode servercert(struct Curl_easy *data, - struct connectdata *conn, - struct ssl_connect_data *connssl, - bool strict) -{ - CURLcode result = CURLE_OK; - int rc; - long lerr; - X509 *issuer; - BIO *fp = NULL; - char error_buffer[256]=""; - char buffer[2048]; - const char *ptr; - BIO *mem = BIO_new(BIO_s_mem()); - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(!mem) { - failf(data, - "BIO_new return NULL, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - return CURLE_OUT_OF_MEMORY; - } - - if(data->set.ssl.certinfo) - /* asked to gather certificate info */ - (void)Curl_ossl_certchain(data, connssl->backend->handle); - - backend->server_cert = SSL_get1_peer_certificate(backend->handle); - if(!backend->server_cert) { - BIO_free(mem); - if(!strict) - return CURLE_OK; - - failf(data, "SSL: couldn't get peer certificate"); - return CURLE_PEER_FAILED_VERIFICATION; - } - - infof(data, "%s certificate:", SSL_IS_PROXY() ? "Proxy" : "Server"); - - rc = x509_name_oneline(X509_get_subject_name(backend->server_cert), - buffer, sizeof(buffer)); - infof(data, " subject: %s", rc?"[NONE]":buffer); - -#ifndef CURL_DISABLE_VERBOSE_STRINGS - { - long len; - ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert)); - len = BIO_get_mem_data(mem, (char **) &ptr); - infof(data, " start date: %.*s", (int)len, ptr); - (void)BIO_reset(mem); - - ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert)); - len = BIO_get_mem_data(mem, (char **) &ptr); - infof(data, " expire date: %.*s", (int)len, ptr); - (void)BIO_reset(mem); - } -#endif - - BIO_free(mem); - - if(SSL_CONN_CONFIG(verifyhost)) { - result = Curl_ossl_verifyhost(data, conn, backend->server_cert); - if(result) { - X509_free(backend->server_cert); - backend->server_cert = NULL; - return result; - } - } - - rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert), - buffer, sizeof(buffer)); - if(rc) { - if(strict) - failf(data, "SSL: couldn't get X509-issuer name"); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, " issuer: %s", buffer); - - /* We could do all sorts of certificate verification stuff here before - deallocating the certificate. */ - - /* e.g. match issuer name with provided issuer certificate */ - if(SSL_CONN_CONFIG(issuercert) || SSL_CONN_CONFIG(issuercert_blob)) { - if(SSL_CONN_CONFIG(issuercert_blob)) { - fp = BIO_new_mem_buf(SSL_CONN_CONFIG(issuercert_blob)->data, - (int)SSL_CONN_CONFIG(issuercert_blob)->len); - if(!fp) { - failf(data, - "BIO_new_mem_buf NULL, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; - return CURLE_OUT_OF_MEMORY; - } - } - else { - fp = BIO_new(BIO_s_file()); - if(!fp) { - failf(data, - "BIO_new return NULL, " OSSL_PACKAGE - " error %s", - ossl_strerror(ERR_get_error(), error_buffer, - sizeof(error_buffer)) ); - X509_free(backend->server_cert); - backend->server_cert = NULL; - return CURLE_OUT_OF_MEMORY; - } - - if(BIO_read_filename(fp, SSL_CONN_CONFIG(issuercert)) <= 0) { - if(strict) - failf(data, "SSL: Unable to open issuer cert (%s)", - SSL_CONN_CONFIG(issuercert)); - BIO_free(fp); - X509_free(backend->server_cert); - backend->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } - } - - issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL); - if(!issuer) { - if(strict) - failf(data, "SSL: Unable to read issuer cert (%s)", - SSL_CONN_CONFIG(issuercert)); - BIO_free(fp); - X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } - - if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) { - if(strict) - failf(data, "SSL: Certificate issuer check failed (%s)", - SSL_CONN_CONFIG(issuercert)); - BIO_free(fp); - X509_free(issuer); - X509_free(backend->server_cert); - backend->server_cert = NULL; - return CURLE_SSL_ISSUER_ERROR; - } - - infof(data, " SSL certificate issuer check ok (%s)", - SSL_CONN_CONFIG(issuercert)); - BIO_free(fp); - X509_free(issuer); - } - - lerr = SSL_get_verify_result(backend->handle); - SSL_SET_OPTION_LVALUE(certverifyresult) = lerr; - if(lerr != X509_V_OK) { - if(SSL_CONN_CONFIG(verifypeer)) { - /* We probably never reach this, because SSL_connect() will fail - and we return earlier if verifypeer is set? */ - if(strict) - failf(data, "SSL certificate verify result: %s (%ld)", - X509_verify_cert_error_string(lerr), lerr); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else - infof(data, " SSL certificate verify result: %s (%ld)," - " continuing anyway.", - X509_verify_cert_error_string(lerr), lerr); - } - else - infof(data, " SSL certificate verify ok."); - } - -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) - if(SSL_CONN_CONFIG(verifystatus)) { - result = verifystatus(data, connssl); - if(result) { - X509_free(backend->server_cert); - backend->server_cert = NULL; - return result; - } - } -#endif - - if(!strict) - /* when not strict, we don't bother about the verify cert problems */ - result = CURLE_OK; - - ptr = SSL_PINNED_PUB_KEY(); - if(!result && ptr) { - result = pkp_pin_peer_pubkey(data, backend->server_cert, ptr); - if(result) - failf(data, "SSL: public key does not match pinned public key"); - } - - X509_free(backend->server_cert); - backend->server_cert = NULL; - connssl->connecting_state = ssl_connect_done; - - return result; -} - -static CURLcode ossl_connect_step3(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - CURLcode result = CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - - /* - * We check certificates to authenticate the server; otherwise we risk - * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to - * verify the peer, ignore faults and failures from the server cert - * operations. - */ - - result = servercert(data, conn, connssl, (SSL_CONN_CONFIG(verifypeer) || - SSL_CONN_CONFIG(verifyhost))); - - if(!result) - connssl->connecting_state = ssl_connect_done; - - return result; -} - -static Curl_recv ossl_recv; -static Curl_send ossl_send; - -static CURLcode ossl_connect_common(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode result; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time is already up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - result = ossl_connect_step1(data, conn, sockindex); - if(result) - return result; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading || - connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ - result = ossl_connect_step2(data, conn, sockindex); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return result; - - } /* repeat step2 until all transactions are done. */ - - if(ssl_connect_3 == connssl->connecting_state) { - result = ossl_connect_step3(data, conn, sockindex); - if(result) - return result; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = ossl_recv; - conn->send[sockindex] = ossl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode ossl_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool *done) -{ - return ossl_connect_common(data, conn, sockindex, TRUE, done); -} - -static CURLcode ossl_connect(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = ossl_connect_common(data, conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static bool ossl_data_pending(const struct connectdata *conn, - int connindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[connindex]; - DEBUGASSERT(connssl->backend); - if(connssl->backend->handle && SSL_pending(connssl->backend->handle)) - return TRUE; -#ifndef CURL_DISABLE_PROXY - { - const struct ssl_connect_data *proxyssl = &conn->proxy_ssl[connindex]; - DEBUGASSERT(proxyssl->backend); - if(proxyssl->backend->handle && SSL_pending(proxyssl->backend->handle)) - return TRUE; - } -#endif - return FALSE; -} - -static size_t ossl_version(char *buffer, size_t size); - -static ssize_t ossl_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - /* SSL_write() is said to return 'int' while write() and send() returns - 'size_t' */ - int err; - char error_buffer[256]; - unsigned long sslerror; - int memlen; - int rc; - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - ERR_clear_error(); - - memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - set_logger(conn, data); - rc = SSL_write(backend->handle, mem, memlen); - - if(rc <= 0) { - err = SSL_get_error(backend->handle, rc); - - switch(err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* The operation did not complete; the same TLS/SSL I/O function - should be called again later. This is basically an EWOULDBLOCK - equivalent. */ - *curlcode = CURLE_AGAIN; - return -1; - case SSL_ERROR_SYSCALL: - { - int sockerr = SOCKERRNO; - sslerror = ERR_get_error(); - if(sslerror) - ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); - else if(sockerr) - Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } - failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", - error_buffer, sockerr); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - case SSL_ERROR_SSL: - /* A failure in the SSL library occurred, usually a protocol error. - The OpenSSL error queue contains more information on the error. */ - sslerror = ERR_get_error(); - if(ERR_GET_LIB(sslerror) == ERR_LIB_SSL && - ERR_GET_REASON(sslerror) == SSL_R_BIO_NOT_SET && - conn->ssl[sockindex].state == ssl_connection_complete -#ifndef CURL_DISABLE_PROXY - && conn->proxy_ssl[sockindex].state == ssl_connection_complete -#endif - ) { - char ver[120]; - (void)ossl_version(ver, sizeof(ver)); - failf(data, "Error: %s does not support double SSL tunneling.", ver); - } - else - failf(data, "SSL_write() error: %s", - ossl_strerror(sslerror, error_buffer, sizeof(error_buffer))); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - /* a true error */ - failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d", - SSL_ERROR_to_str(err), SOCKERRNO); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - *curlcode = CURLE_OK; - return (ssize_t)rc; /* number of bytes */ -} - -static ssize_t ossl_recv(struct Curl_easy *data, /* transfer */ - int num, /* socketindex */ - char *buf, /* store read data here */ - size_t buffersize, /* max amount to read */ - CURLcode *curlcode) -{ - char error_buffer[256]; - unsigned long sslerror; - ssize_t nread; - int buffsize; - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[num]; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - ERR_clear_error(); - - buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - set_logger(conn, data); - nread = (ssize_t)SSL_read(backend->handle, buf, buffsize); - if(nread <= 0) { - /* failed SSL_read */ - int err = SSL_get_error(backend->handle, (int)nread); - - switch(err) { - case SSL_ERROR_NONE: /* this is not an error */ - break; - case SSL_ERROR_ZERO_RETURN: /* no more data */ - /* close_notify alert */ - if(num == FIRSTSOCKET) - /* mark the connection for close if it is indeed the control - connection */ - connclose(conn, "TLS close_notify"); - break; - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - /* openssl/ssl.h for SSL_ERROR_SYSCALL says "look at error stack/return - value/errno" */ - /* https://www.openssl.org/docs/crypto/ERR_get_error.html */ - sslerror = ERR_get_error(); - if((nread < 0) || sslerror) { - /* If the return code was negative or there actually is an error in the - queue */ - int sockerr = SOCKERRNO; - if(sslerror) - ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)); - else if(sockerr && err == SSL_ERROR_SYSCALL) - Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer)); - error_buffer[sizeof(error_buffer) - 1] = '\0'; - } - failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d", - error_buffer, sockerr); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - /* For debug builds be a little stricter and error on any - SSL_ERROR_SYSCALL. For example a server may have closed the connection - abruptly without a close_notify alert. For compatibility with older - peers we don't do this by default. #4624 - - We can use this to gauge how many users may be affected, and - if it goes ok eventually transition to allow in dev and release with - the newest OpenSSL: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) */ -#ifdef DEBUGBUILD - if(err == SSL_ERROR_SYSCALL) { - int sockerr = SOCKERRNO; - if(sockerr) - Curl_strerror(sockerr, error_buffer, sizeof(error_buffer)); - else { - msnprintf(error_buffer, sizeof(error_buffer), - "Connection closed abruptly"); - } - failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d" - " (Fatal because this is a curl debug build)", - error_buffer, sockerr); - *curlcode = CURLE_RECV_ERROR; - return -1; - } -#endif - } - } - return nread; -} - -static size_t ossl_version(char *buffer, size_t size) -{ -#ifdef LIBRESSL_VERSION_NUMBER -#ifdef HAVE_OPENSSL_VERSION - char *p; - int count; - const char *ver = OpenSSL_version(OPENSSL_VERSION); - const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */ - if(Curl_strncasecompare(ver, expected, sizeof(expected) - 1)) { - ver += sizeof(expected) - 1; - } - count = msnprintf(buffer, size, "%s/%s", OSSL_PACKAGE, ver); - for(p = buffer; *p; ++p) { - if(ISBLANK(*p)) - *p = '_'; - } - return count; -#else - return msnprintf(buffer, size, "%s/%lx.%lx.%lx", - OSSL_PACKAGE, - (LIBRESSL_VERSION_NUMBER>>28)&0xf, - (LIBRESSL_VERSION_NUMBER>>20)&0xff, - (LIBRESSL_VERSION_NUMBER>>12)&0xff); -#endif -#elif defined(OPENSSL_IS_BORINGSSL) -#ifdef CURL_BORINGSSL_VERSION - return msnprintf(buffer, size, "%s/%s", - OSSL_PACKAGE, - CURL_BORINGSSL_VERSION); -#else - return msnprintf(buffer, size, OSSL_PACKAGE); -#endif -#elif defined(HAVE_OPENSSL_VERSION) && defined(OPENSSL_VERSION_STRING) - return msnprintf(buffer, size, "%s/%s", - OSSL_PACKAGE, OpenSSL_version(OPENSSL_VERSION_STRING)); -#else - /* not LibreSSL, BoringSSL and not using OpenSSL_version */ - - char sub[3]; - unsigned long ssleay_value; - sub[2]='\0'; - sub[1]='\0'; - ssleay_value = OpenSSL_version_num(); - if(ssleay_value < 0x906000) { - ssleay_value = SSLEAY_VERSION_NUMBER; - sub[0]='\0'; - } - else { - if(ssleay_value&0xff0) { - int minor_ver = (ssleay_value >> 4) & 0xff; - if(minor_ver > 26) { - /* handle extended version introduced for 0.9.8za */ - sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1); - sub[0] = 'z'; - } - else { - sub[0] = (char) (minor_ver + 'a' - 1); - } - } - else - sub[0]='\0'; - } - - return msnprintf(buffer, size, "%s/%lx.%lx.%lx%s" -#ifdef OPENSSL_FIPS - "-fips" -#endif - , - OSSL_PACKAGE, - (ssleay_value>>28)&0xf, - (ssleay_value>>20)&0xff, - (ssleay_value>>12)&0xff, - sub); -#endif /* OPENSSL_IS_BORINGSSL */ -} - -/* can be called with data == NULL */ -static CURLcode ossl_random(struct Curl_easy *data, - unsigned char *entropy, size_t length) -{ - int rc; - if(data) { - if(ossl_seed(data)) /* Initiate the seed if not already done */ - return CURLE_FAILED_INIT; /* couldn't seed for some reason */ - } - else { - if(!rand_enough()) - return CURLE_FAILED_INIT; - } - /* RAND_bytes() returns 1 on success, 0 otherwise. */ - rc = RAND_bytes(entropy, curlx_uztosi(length)); - return (rc == 1 ? CURLE_OK : CURLE_FAILED_INIT); -} - -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) -static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum /* output */, - size_t unused) -{ - EVP_MD_CTX *mdctx; - unsigned int len = 0; - (void) unused; - - mdctx = EVP_MD_CTX_create(); - if(!mdctx) - return CURLE_OUT_OF_MEMORY; - EVP_DigestInit(mdctx, EVP_sha256()); - EVP_DigestUpdate(mdctx, tmp, tmplen); - EVP_DigestFinal_ex(mdctx, sha256sum, &len); - EVP_MD_CTX_destroy(mdctx); - return CURLE_OK; -} -#endif - -static bool ossl_cert_status_request(void) -{ -#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \ - !defined(OPENSSL_NO_OCSP) - return TRUE; -#else - return FALSE; -#endif -} - -static void *ossl_get_internals(struct ssl_connect_data *connssl, - CURLINFO info) -{ - /* Legacy: CURLINFO_TLS_SESSION must return an SSL_CTX pointer. */ - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - return info == CURLINFO_TLS_SESSION ? - (void *)backend->ctx : (void *)backend->handle; -} - -static bool ossl_associate_connection(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - - /* If we don't have SSL context, do nothing. */ - if(!backend->handle) - return FALSE; - - if(SSL_SET_OPTION(primary.sessionid)) { - int data_idx = ossl_get_ssl_data_index(); - int connectdata_idx = ossl_get_ssl_conn_index(); - int sockindex_idx = ossl_get_ssl_sockindex_index(); - int proxy_idx = ossl_get_proxy_index(); - - if(data_idx >= 0 && connectdata_idx >= 0 && sockindex_idx >= 0 && - proxy_idx >= 0) { - int data_status, conn_status, sockindex_status, proxy_status; - - /* Store the data needed for the "new session" callback. - * The sockindex is stored as a pointer to an array element. */ - data_status = SSL_set_ex_data(backend->handle, data_idx, data); - conn_status = SSL_set_ex_data(backend->handle, connectdata_idx, conn); - sockindex_status = SSL_set_ex_data(backend->handle, sockindex_idx, - conn->sock + sockindex); -#ifndef CURL_DISABLE_PROXY - proxy_status = SSL_set_ex_data(backend->handle, proxy_idx, - SSL_IS_PROXY() ? (void *) 1 : NULL); -#else - proxy_status = SSL_set_ex_data(backend->handle, proxy_idx, NULL); -#endif - if(data_status && conn_status && sockindex_status && proxy_status) - return TRUE; - } - return FALSE; - } - return TRUE; -} - -/* - * Starting with TLS 1.3, the ossl_new_session_cb callback gets called after - * the handshake. If the transfer that sets up the callback gets killed before - * this callback arrives, we must make sure to properly clear the data to - * avoid UAF problems. A future optimization could be to instead store another - * transfer that might still be using the same connection. - */ - -static void ossl_disassociate_connection(struct Curl_easy *data, - int sockindex) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - - /* If we don't have SSL context, do nothing. */ - if(!backend->handle) - return; - - if(SSL_SET_OPTION(primary.sessionid)) { - int data_idx = ossl_get_ssl_data_index(); - int connectdata_idx = ossl_get_ssl_conn_index(); - int sockindex_idx = ossl_get_ssl_sockindex_index(); - int proxy_idx = ossl_get_proxy_index(); - - if(data_idx >= 0 && connectdata_idx >= 0 && sockindex_idx >= 0 && - proxy_idx >= 0) { - /* Disable references to data in "new session" callback to avoid - * accessing a stale pointer. */ - SSL_set_ex_data(backend->handle, data_idx, NULL); - SSL_set_ex_data(backend->handle, connectdata_idx, NULL); - SSL_set_ex_data(backend->handle, sockindex_idx, NULL); - SSL_set_ex_data(backend->handle, proxy_idx, NULL); - } - } -} - -const struct Curl_ssl Curl_ssl_openssl = { - { CURLSSLBACKEND_OPENSSL, "openssl" }, /* info */ - - SSLSUPP_CA_PATH | - SSLSUPP_CAINFO_BLOB | - SSLSUPP_CERTINFO | - SSLSUPP_PINNEDPUBKEY | - SSLSUPP_SSL_CTX | -#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES - SSLSUPP_TLS13_CIPHERSUITES | -#endif - SSLSUPP_HTTPS_PROXY, - - sizeof(struct ssl_backend_data), - - ossl_init, /* init */ - ossl_cleanup, /* cleanup */ - ossl_version, /* version */ - ossl_check_cxn, /* check_cxn */ - ossl_shutdown, /* shutdown */ - ossl_data_pending, /* data_pending */ - ossl_random, /* random */ - ossl_cert_status_request, /* cert_status_request */ - ossl_connect, /* connect */ - ossl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - ossl_get_internals, /* get_internals */ - ossl_close, /* close_one */ - ossl_close_all, /* close_all */ - ossl_session_free, /* session_free */ - ossl_set_engine, /* set_engine */ - ossl_set_engine_default, /* set_engine_default */ - ossl_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ -#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) - ossl_sha256sum, /* sha256sum */ -#else - NULL, /* sha256sum */ -#endif - ossl_associate_connection, /* associate_connection */ - ossl_disassociate_connection /* disassociate_connection */ -}; - -#endif /* USE_OPENSSL */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for all OpenSSL-specific code for the TLS/SSL layer. No code
+ * but vtls.c should ever call or use these functions.
+ */
+
+#include "curl_setup.h"
+
+#if defined(USE_QUICHE) || defined(USE_OPENSSL)
+
+#include <limits.h>
+
+/* Wincrypt must be included before anything that could include OpenSSL. */
+#if defined(USE_WIN32_CRYPTO)
+#include <wincrypt.h>
+/* Undefine wincrypt conflicting symbols for BoringSSL. */
+#undef X509_NAME
+#undef X509_EXTENSIONS
+#undef PKCS7_ISSUER_AND_SERIAL
+#undef PKCS7_SIGNER_INFO
+#undef OCSP_REQUEST
+#undef OCSP_RESPONSE
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "formdata.h" /* for the boundary function */
+#include "url.h" /* for the ssl config check function */
+#include "inet_pton.h"
+#include "openssl.h"
+#include "connect.h"
+#include "slist.h"
+#include "select.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "vauth/vauth.h"
+#include "keylog.h"
+#include "strcase.h"
+#include "hostcheck.h"
+#include "multiif.h"
+#include "strerror.h"
+#include "curl_printf.h"
+
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+#include <openssl/x509v3.h>
+#ifndef OPENSSL_NO_DSA
+#include <openssl/dsa.h>
+#endif
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/md5.h>
+#include <openssl/conf.h>
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/pkcs12.h>
+
+#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP)
+#include <openssl/ocsp.h>
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x0090700fL) && /* 0.9.7 or later */ \
+ !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_UI_CONSOLE)
+#define USE_OPENSSL_ENGINE
+#include <openssl/engine.h>
+#endif
+
+#include "warnless.h"
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS
+ renegotiations when built with BoringSSL. Renegotiating is non-compliant
+ with HTTP/2 and "an extremely dangerous protocol feature". Beware.
+
+#define ALLOW_RENEG 1
+ */
+
+#ifndef OPENSSL_VERSION_NUMBER
+#error "OPENSSL_VERSION_NUMBER not defined"
+#endif
+
+#ifdef USE_OPENSSL_ENGINE
+#include <openssl/ui.h>
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x00909000L
+#define SSL_METHOD_QUAL const
+#else
+#define SSL_METHOD_QUAL
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
+#define HAVE_ERR_REMOVE_THREAD_STATE 1
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && /* OpenSSL 1.1.0+ */ \
+ !(defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER < 0x20700000L)
+#define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER
+#define HAVE_X509_GET0_EXTENSIONS 1 /* added in 1.1.0 -pre1 */
+#define HAVE_OPAQUE_EVP_PKEY 1 /* since 1.1.0 -pre3 */
+#define HAVE_OPAQUE_RSA_DSA_DH 1 /* since 1.1.0 -pre5 */
+#define CONST_EXTS const
+#define HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED 1
+
+/* funny typecast define due to difference in API */
+#ifdef LIBRESSL_VERSION_NUMBER
+#define ARG2_X509_signature_print (X509_ALGOR *)
+#else
+#define ARG2_X509_signature_print
+#endif
+
+#else
+/* For OpenSSL before 1.1.0 */
+#define ASN1_STRING_get0_data(x) ASN1_STRING_data(x)
+#define X509_get0_notBefore(x) X509_get_notBefore(x)
+#define X509_get0_notAfter(x) X509_get_notAfter(x)
+#define CONST_EXTS /* nope */
+#ifndef LIBRESSL_VERSION_NUMBER
+#define OpenSSL_version_num() SSLeay()
+#endif
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* 1.0.2 or later */ \
+ !(defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER < 0x20700000L)
+#define HAVE_X509_GET0_SIGNATURE 1
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) /* 1.0.2 or later */
+#define HAVE_SSL_GET_SHUTDOWN 1
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002003L && \
+ OPENSSL_VERSION_NUMBER <= 0x10002FFFL && \
+ !defined(OPENSSL_NO_COMP)
+#define HAVE_SSL_COMP_FREE_COMPRESSION_METHODS 1
+#endif
+
+#if (OPENSSL_VERSION_NUMBER < 0x0090808fL)
+/* not present in older OpenSSL */
+#define OPENSSL_load_builtin_modules(x)
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x30000000L)
+#define HAVE_EVP_PKEY_GET_PARAMS 1
+#else
+#define SSL_get1_peer_certificate SSL_get_peer_certificate
+#endif
+
+#ifdef HAVE_EVP_PKEY_GET_PARAMS
+#include <openssl/core_names.h>
+#define DECLARE_PKEY_PARAM_BIGNUM(name) BIGNUM *name = NULL
+#define FREE_PKEY_PARAM_BIGNUM(name) BN_clear_free(name)
+#else
+#define DECLARE_PKEY_PARAM_BIGNUM(name) const BIGNUM *name
+#define FREE_PKEY_PARAM_BIGNUM(name)
+#endif
+
+/*
+ * Whether SSL_CTX_set_keylog_callback is available.
+ * OpenSSL: supported since 1.1.1 https://github.com/openssl/openssl/pull/2287
+ * BoringSSL: supported since d28f59c27bac (committed 2015-11-19)
+ * LibreSSL: unsupported in at least 2.7.2 (explicitly check for it since it
+ * lies and pretends to be OpenSSL 2.0.0).
+ */
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \
+ !defined(LIBRESSL_VERSION_NUMBER)) || \
+ defined(OPENSSL_IS_BORINGSSL)
+#define HAVE_KEYLOG_CALLBACK
+#endif
+
+/* Whether SSL_CTX_set_ciphersuites is available.
+ * OpenSSL: supported since 1.1.1 (commit a53b5be6a05)
+ * BoringSSL: no
+ * LibreSSL: no
+ */
+#if ((OPENSSL_VERSION_NUMBER >= 0x10101000L) && \
+ !defined(LIBRESSL_VERSION_NUMBER) && \
+ !defined(OPENSSL_IS_BORINGSSL))
+#define HAVE_SSL_CTX_SET_CIPHERSUITES
+#define HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
+#endif
+
+/*
+ * Whether SSL_CTX_set1_curves_list is available.
+ * OpenSSL: supported since 1.0.2, see
+ * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set1_groups.html
+ * BoringSSL: supported since 5fd1807d95f7 (committed 2016-09-30)
+ * LibreSSL: since 2.5.3 (April 12, 2017)
+ */
+#if (OPENSSL_VERSION_NUMBER >= 0x10002000L) || \
+ defined(OPENSSL_IS_BORINGSSL)
+#define HAVE_SSL_CTX_SET_EC_CURVES
+#endif
+
+#if defined(LIBRESSL_VERSION_NUMBER)
+#define OSSL_PACKAGE "LibreSSL"
+#elif defined(OPENSSL_IS_BORINGSSL)
+#define OSSL_PACKAGE "BoringSSL"
+#else
+#define OSSL_PACKAGE "OpenSSL"
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+/* up2date versions of OpenSSL maintain reasonably secure defaults without
+ * breaking compatibility, so it is better not to override the defaults in curl
+ */
+#define DEFAULT_CIPHER_SELECTION NULL
+#else
+/* ... but it is not the case with old versions of OpenSSL */
+#define DEFAULT_CIPHER_SELECTION \
+ "ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH"
+#endif
+
+#ifdef HAVE_OPENSSL_SRP
+/* the function exists */
+#ifdef USE_TLS_SRP
+/* the functionality is not disabled */
+#define USE_OPENSSL_SRP
+#endif
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+#define HAVE_RANDOM_INIT_BY_DEFAULT 1
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \
+ !(defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER < 0x2070100fL) && \
+ !defined(OPENSSL_IS_BORINGSSL)
+#define HAVE_OPENSSL_VERSION
+#endif
+
+/*
+ * Whether the OpenSSL version has the API needed to support sharing an
+ * X509_STORE between connections. The API is:
+ * * `X509_STORE_up_ref` -- Introduced: OpenSSL 1.1.0.
+ */
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* OpenSSL >= 1.1.0 */
+#define HAVE_SSL_X509_STORE_SHARE
+#endif
+
+/* What API version do we use? */
+#if defined(LIBRESSL_VERSION_NUMBER)
+#define USE_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f)
+#else /* !LIBRESSL_VERSION_NUMBER */
+#define USE_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#endif /* !LIBRESSL_VERSION_NUMBER */
+
+struct ssl_backend_data {
+ struct Curl_easy *logger; /* transfer handle to pass trace logs to, only
+ using sockindex 0 */
+ /* these ones requires specific SSL-types */
+ SSL_CTX* ctx;
+ SSL* handle;
+ X509* server_cert;
+ CURLcode io_result; /* result of last BIO cfilter operation */
+#ifndef HAVE_KEYLOG_CALLBACK
+ /* Set to true once a valid keylog entry has been created to avoid dupes. */
+ bool keylog_done;
+#endif
+};
+
+#if defined(HAVE_SSL_X509_STORE_SHARE)
+struct multi_ssl_backend_data {
+ char *CAfile; /* CAfile path used to generate X509 store */
+ X509_STORE *store; /* cached X509 store or NULL if none */
+ struct curltime time; /* when the cached store was created */
+};
+#endif /* HAVE_SSL_X509_STORE_SHARE */
+
+#define push_certinfo(_label, _num) \
+do { \
+ long info_len = BIO_get_mem_data(mem, &ptr); \
+ Curl_ssl_push_certinfo_len(data, _num, _label, ptr, info_len); \
+ if(1 != BIO_reset(mem)) \
+ break; \
+} while(0)
+
+static void pubkey_show(struct Curl_easy *data,
+ BIO *mem,
+ int num,
+ const char *type,
+ const char *name,
+ const BIGNUM *bn)
+{
+ char *ptr;
+ char namebuf[32];
+
+ msnprintf(namebuf, sizeof(namebuf), "%s(%s)", type, name);
+
+ if(bn)
+ BN_print(mem, bn);
+ push_certinfo(namebuf, num);
+}
+
+#ifdef HAVE_OPAQUE_RSA_DSA_DH
+#define print_pubkey_BN(_type, _name, _num) \
+ pubkey_show(data, mem, _num, #_type, #_name, _name)
+
+#else
+#define print_pubkey_BN(_type, _name, _num) \
+do { \
+ if(_type->_name) { \
+ pubkey_show(data, mem, _num, #_type, #_name, _type->_name); \
+ } \
+} while(0)
+#endif
+
+static int asn1_object_dump(ASN1_OBJECT *a, char *buf, size_t len)
+{
+ int i, ilen;
+
+ ilen = (int)len;
+ if(ilen < 0)
+ return 1; /* buffer too big */
+
+ i = i2t_ASN1_OBJECT(buf, ilen, a);
+
+ if(i >= ilen)
+ return 1; /* buffer too small */
+
+ return 0;
+}
+
+static void X509V3_ext(struct Curl_easy *data,
+ int certnum,
+ CONST_EXTS STACK_OF(X509_EXTENSION) *exts)
+{
+ int i;
+
+ if((int)sk_X509_EXTENSION_num(exts) <= 0)
+ /* no extensions, bail out */
+ return;
+
+ for(i = 0; i < (int)sk_X509_EXTENSION_num(exts); i++) {
+ ASN1_OBJECT *obj;
+ X509_EXTENSION *ext = sk_X509_EXTENSION_value(exts, i);
+ BUF_MEM *biomem;
+ char namebuf[128];
+ BIO *bio_out = BIO_new(BIO_s_mem());
+
+ if(!bio_out)
+ return;
+
+ obj = X509_EXTENSION_get_object(ext);
+
+ asn1_object_dump(obj, namebuf, sizeof(namebuf));
+
+ if(!X509V3_EXT_print(bio_out, ext, 0, 0))
+ ASN1_STRING_print(bio_out, (ASN1_STRING *)X509_EXTENSION_get_data(ext));
+
+ BIO_get_mem_ptr(bio_out, &biomem);
+ Curl_ssl_push_certinfo_len(data, certnum, namebuf, biomem->data,
+ biomem->length);
+ BIO_free(bio_out);
+ }
+}
+
+#ifdef OPENSSL_IS_BORINGSSL
+typedef size_t numcert_t;
+#else
+typedef int numcert_t;
+#endif
+
+CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl)
+{
+ CURLcode result;
+ STACK_OF(X509) *sk;
+ int i;
+ numcert_t numcerts;
+ BIO *mem;
+
+ DEBUGASSERT(ssl);
+
+ sk = SSL_get_peer_cert_chain(ssl);
+ if(!sk) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ numcerts = sk_X509_num(sk);
+
+ result = Curl_ssl_init_certinfo(data, (int)numcerts);
+ if(result) {
+ return result;
+ }
+
+ mem = BIO_new(BIO_s_mem());
+ if(!mem) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ for(i = 0; i < (int)numcerts; i++) {
+ ASN1_INTEGER *num;
+ X509 *x = sk_X509_value(sk, i);
+ EVP_PKEY *pubkey = NULL;
+ int j;
+ char *ptr;
+ const ASN1_BIT_STRING *psig = NULL;
+
+ X509_NAME_print_ex(mem, X509_get_subject_name(x), 0, XN_FLAG_ONELINE);
+ push_certinfo("Subject", i);
+
+ X509_NAME_print_ex(mem, X509_get_issuer_name(x), 0, XN_FLAG_ONELINE);
+ push_certinfo("Issuer", i);
+
+ BIO_printf(mem, "%lx", X509_get_version(x));
+ push_certinfo("Version", i);
+
+ num = X509_get_serialNumber(x);
+ if(num->type == V_ASN1_NEG_INTEGER)
+ BIO_puts(mem, "-");
+ for(j = 0; j < num->length; j++)
+ BIO_printf(mem, "%02x", num->data[j]);
+ push_certinfo("Serial Number", i);
+
+#if defined(HAVE_X509_GET0_SIGNATURE) && defined(HAVE_X509_GET0_EXTENSIONS)
+ {
+ const X509_ALGOR *sigalg = NULL;
+ X509_PUBKEY *xpubkey = NULL;
+ ASN1_OBJECT *pubkeyoid = NULL;
+
+ X509_get0_signature(&psig, &sigalg, x);
+ if(sigalg) {
+ i2a_ASN1_OBJECT(mem, sigalg->algorithm);
+ push_certinfo("Signature Algorithm", i);
+ }
+
+ xpubkey = X509_get_X509_PUBKEY(x);
+ if(xpubkey) {
+ X509_PUBKEY_get0_param(&pubkeyoid, NULL, NULL, NULL, xpubkey);
+ if(pubkeyoid) {
+ i2a_ASN1_OBJECT(mem, pubkeyoid);
+ push_certinfo("Public Key Algorithm", i);
+ }
+ }
+
+ X509V3_ext(data, i, X509_get0_extensions(x));
+ }
+#else
+ {
+ /* before OpenSSL 1.0.2 */
+ X509_CINF *cinf = x->cert_info;
+
+ i2a_ASN1_OBJECT(mem, cinf->signature->algorithm);
+ push_certinfo("Signature Algorithm", i);
+
+ i2a_ASN1_OBJECT(mem, cinf->key->algor->algorithm);
+ push_certinfo("Public Key Algorithm", i);
+
+ X509V3_ext(data, i, cinf->extensions);
+
+ psig = x->signature;
+ }
+#endif
+
+ ASN1_TIME_print(mem, X509_get0_notBefore(x));
+ push_certinfo("Start date", i);
+
+ ASN1_TIME_print(mem, X509_get0_notAfter(x));
+ push_certinfo("Expire date", i);
+
+ pubkey = X509_get_pubkey(x);
+ if(!pubkey)
+ infof(data, " Unable to load public key");
+ else {
+ int pktype;
+#ifdef HAVE_OPAQUE_EVP_PKEY
+ pktype = EVP_PKEY_id(pubkey);
+#else
+ pktype = pubkey->type;
+#endif
+ switch(pktype) {
+ case EVP_PKEY_RSA:
+ {
+#ifndef HAVE_EVP_PKEY_GET_PARAMS
+ RSA *rsa;
+#ifdef HAVE_OPAQUE_EVP_PKEY
+ rsa = EVP_PKEY_get0_RSA(pubkey);
+#else
+ rsa = pubkey->pkey.rsa;
+#endif /* HAVE_OPAQUE_EVP_PKEY */
+#endif /* !HAVE_EVP_PKEY_GET_PARAMS */
+
+ {
+#ifdef HAVE_OPAQUE_RSA_DSA_DH
+ DECLARE_PKEY_PARAM_BIGNUM(n);
+ DECLARE_PKEY_PARAM_BIGNUM(e);
+#ifdef HAVE_EVP_PKEY_GET_PARAMS
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_N, &n);
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_RSA_E, &e);
+#else
+ RSA_get0_key(rsa, &n, &e, NULL);
+#endif /* HAVE_EVP_PKEY_GET_PARAMS */
+ BIO_printf(mem, "%d", BN_num_bits(n));
+#else
+ BIO_printf(mem, "%d", BN_num_bits(rsa->n));
+#endif /* HAVE_OPAQUE_RSA_DSA_DH */
+ push_certinfo("RSA Public Key", i);
+ print_pubkey_BN(rsa, n, i);
+ print_pubkey_BN(rsa, e, i);
+ FREE_PKEY_PARAM_BIGNUM(n);
+ FREE_PKEY_PARAM_BIGNUM(e);
+ }
+
+ break;
+ }
+ case EVP_PKEY_DSA:
+ {
+#ifndef OPENSSL_NO_DSA
+#ifndef HAVE_EVP_PKEY_GET_PARAMS
+ DSA *dsa;
+#ifdef HAVE_OPAQUE_EVP_PKEY
+ dsa = EVP_PKEY_get0_DSA(pubkey);
+#else
+ dsa = pubkey->pkey.dsa;
+#endif /* HAVE_OPAQUE_EVP_PKEY */
+#endif /* !HAVE_EVP_PKEY_GET_PARAMS */
+ {
+#ifdef HAVE_OPAQUE_RSA_DSA_DH
+ DECLARE_PKEY_PARAM_BIGNUM(p);
+ DECLARE_PKEY_PARAM_BIGNUM(q);
+ DECLARE_PKEY_PARAM_BIGNUM(g);
+ DECLARE_PKEY_PARAM_BIGNUM(pub_key);
+#ifdef HAVE_EVP_PKEY_GET_PARAMS
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p);
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q);
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g);
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key);
+#else
+ DSA_get0_pqg(dsa, &p, &q, &g);
+ DSA_get0_key(dsa, &pub_key, NULL);
+#endif /* HAVE_EVP_PKEY_GET_PARAMS */
+#endif /* HAVE_OPAQUE_RSA_DSA_DH */
+ print_pubkey_BN(dsa, p, i);
+ print_pubkey_BN(dsa, q, i);
+ print_pubkey_BN(dsa, g, i);
+ print_pubkey_BN(dsa, pub_key, i);
+ FREE_PKEY_PARAM_BIGNUM(p);
+ FREE_PKEY_PARAM_BIGNUM(q);
+ FREE_PKEY_PARAM_BIGNUM(g);
+ FREE_PKEY_PARAM_BIGNUM(pub_key);
+ }
+#endif /* !OPENSSL_NO_DSA */
+ break;
+ }
+ case EVP_PKEY_DH:
+ {
+#ifndef HAVE_EVP_PKEY_GET_PARAMS
+ DH *dh;
+#ifdef HAVE_OPAQUE_EVP_PKEY
+ dh = EVP_PKEY_get0_DH(pubkey);
+#else
+ dh = pubkey->pkey.dh;
+#endif /* HAVE_OPAQUE_EVP_PKEY */
+#endif /* !HAVE_EVP_PKEY_GET_PARAMS */
+ {
+#ifdef HAVE_OPAQUE_RSA_DSA_DH
+ DECLARE_PKEY_PARAM_BIGNUM(p);
+ DECLARE_PKEY_PARAM_BIGNUM(q);
+ DECLARE_PKEY_PARAM_BIGNUM(g);
+ DECLARE_PKEY_PARAM_BIGNUM(pub_key);
+#ifdef HAVE_EVP_PKEY_GET_PARAMS
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_P, &p);
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_Q, &q);
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_FFC_G, &g);
+ EVP_PKEY_get_bn_param(pubkey, OSSL_PKEY_PARAM_PUB_KEY, &pub_key);
+#else
+ DH_get0_pqg(dh, &p, &q, &g);
+ DH_get0_key(dh, &pub_key, NULL);
+#endif /* HAVE_EVP_PKEY_GET_PARAMS */
+ print_pubkey_BN(dh, p, i);
+ print_pubkey_BN(dh, q, i);
+ print_pubkey_BN(dh, g, i);
+#else
+ print_pubkey_BN(dh, p, i);
+ print_pubkey_BN(dh, g, i);
+#endif /* HAVE_OPAQUE_RSA_DSA_DH */
+ print_pubkey_BN(dh, pub_key, i);
+ FREE_PKEY_PARAM_BIGNUM(p);
+ FREE_PKEY_PARAM_BIGNUM(q);
+ FREE_PKEY_PARAM_BIGNUM(g);
+ FREE_PKEY_PARAM_BIGNUM(pub_key);
+ }
+ break;
+ }
+ }
+ EVP_PKEY_free(pubkey);
+ }
+
+ if(psig) {
+ for(j = 0; j < psig->length; j++)
+ BIO_printf(mem, "%02x:", psig->data[j]);
+ push_certinfo("Signature", i);
+ }
+
+ PEM_write_bio_X509(mem, x);
+ push_certinfo("Cert", i);
+ }
+
+ BIO_free(mem);
+
+ return CURLE_OK;
+}
+
+#endif /* quiche or OpenSSL */
+
+#ifdef USE_OPENSSL
+
+#if USE_PRE_1_1_API
+#if !defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER < 0x2070000fL
+#define BIO_set_init(x,v) ((x)->init=(v))
+#define BIO_get_data(x) ((x)->ptr)
+#define BIO_set_data(x,v) ((x)->ptr=(v))
+#endif
+#define BIO_get_shutdown(x) ((x)->shutdown)
+#define BIO_set_shutdown(x,v) ((x)->shutdown=(v))
+#endif /* USE_PRE_1_1_API */
+
+static int bio_cf_create(BIO *bio)
+{
+ BIO_set_shutdown(bio, 1);
+ BIO_set_init(bio, 1);
+#if USE_PRE_1_1_API
+ bio->num = -1;
+#endif
+ BIO_set_data(bio, NULL);
+ return 1;
+}
+
+static int bio_cf_destroy(BIO *bio)
+{
+ if(!bio)
+ return 0;
+ return 1;
+}
+
+static long bio_cf_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ struct Curl_cfilter *cf = BIO_get_data(bio);
+ long ret = 1;
+
+ (void)cf;
+ (void)ptr;
+ switch(cmd) {
+ case BIO_CTRL_GET_CLOSE:
+ ret = (long)BIO_get_shutdown(bio);
+ break;
+ case BIO_CTRL_SET_CLOSE:
+ BIO_set_shutdown(bio, (int)num);
+ break;
+ case BIO_CTRL_FLUSH:
+ /* we do no delayed writes, but if we ever would, this
+ * needs to trigger it. */
+ ret = 1;
+ break;
+ case BIO_CTRL_DUP:
+ ret = 1;
+ break;
+#ifdef BIO_CTRL_EOF
+ case BIO_CTRL_EOF:
+ /* EOF has been reached on input? */
+ return (!cf->next || !cf->next->connected);
+#endif
+ default:
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static int bio_cf_out_write(BIO *bio, const char *buf, int blen)
+{
+ struct Curl_cfilter *cf = BIO_get_data(bio);
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nwritten;
+ CURLcode result = CURLE_SEND_ERROR;
+
+ DEBUGASSERT(data);
+ nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
+ /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
+ blen, (int)nwritten, result)); */
+ BIO_clear_retry_flags(bio);
+ connssl->backend->io_result = result;
+ if(nwritten < 0) {
+ if(CURLE_AGAIN == result)
+ BIO_set_retry_write(bio);
+ }
+ return (int)nwritten;
+}
+
+static int bio_cf_in_read(BIO *bio, char *buf, int blen)
+{
+ struct Curl_cfilter *cf = BIO_get_data(bio);
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nread;
+ CURLcode result = CURLE_RECV_ERROR;
+
+ DEBUGASSERT(data);
+ /* OpenSSL catches this case, so should we. */
+ if(!buf)
+ return 0;
+
+ nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
+ /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
+ blen, (int)nread, result)); */
+ BIO_clear_retry_flags(bio);
+ connssl->backend->io_result = result;
+ if(nread < 0) {
+ if(CURLE_AGAIN == result)
+ BIO_set_retry_read(bio);
+ }
+ return (int)nread;
+}
+
+static BIO_METHOD *bio_cf_method = NULL;
+
+#if USE_PRE_1_1_API
+
+static BIO_METHOD bio_cf_meth_1_0 = {
+ BIO_TYPE_MEM,
+ "OpenSSL CF BIO",
+ bio_cf_out_write,
+ bio_cf_in_read,
+ NULL, /* puts is never called */
+ NULL, /* gets is never called */
+ bio_cf_ctrl,
+ bio_cf_create,
+ bio_cf_destroy,
+ NULL
+};
+
+static void bio_cf_init_methods(void)
+{
+ bio_cf_method = &bio_cf_meth_1_0;
+}
+
+#define bio_cf_free_methods() Curl_nop_stmt
+
+#else
+
+static void bio_cf_init_methods(void)
+{
+ bio_cf_method = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO");
+ BIO_meth_set_write(bio_cf_method, &bio_cf_out_write);
+ BIO_meth_set_read(bio_cf_method, &bio_cf_in_read);
+ BIO_meth_set_ctrl(bio_cf_method, &bio_cf_ctrl);
+ BIO_meth_set_create(bio_cf_method, &bio_cf_create);
+ BIO_meth_set_destroy(bio_cf_method, &bio_cf_destroy);
+}
+
+static void bio_cf_free_methods(void)
+{
+ BIO_meth_free(bio_cf_method);
+}
+
+#endif
+
+
+static bool ossl_attach_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+
+/*
+ * Number of bytes to read from the random number seed file. This must be
+ * a finite value (because some entropy "files" like /dev/urandom have
+ * an infinite length), but must be large enough to provide enough
+ * entropy to properly seed OpenSSL's PRNG.
+ */
+#define RAND_LOAD_LENGTH 1024
+
+#ifdef HAVE_KEYLOG_CALLBACK
+static void ossl_keylog_callback(const SSL *ssl, const char *line)
+{
+ (void)ssl;
+
+ Curl_tls_keylog_write_line(line);
+}
+#else
+/*
+ * ossl_log_tls12_secret is called by libcurl to make the CLIENT_RANDOMs if the
+ * OpenSSL being used doesn't have native support for doing that.
+ */
+static void
+ossl_log_tls12_secret(const SSL *ssl, bool *keylog_done)
+{
+ const SSL_SESSION *session = SSL_get_session(ssl);
+ unsigned char client_random[SSL3_RANDOM_SIZE];
+ unsigned char master_key[SSL_MAX_MASTER_KEY_LENGTH];
+ int master_key_length = 0;
+
+ if(!session || *keylog_done)
+ return;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
+ !(defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER < 0x20700000L)
+ /* ssl->s3 is not checked in openssl 1.1.0-pre6, but let's assume that
+ * we have a valid SSL context if we have a non-NULL session. */
+ SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE);
+ master_key_length = (int)
+ SSL_SESSION_get_master_key(session, master_key, SSL_MAX_MASTER_KEY_LENGTH);
+#else
+ if(ssl->s3 && session->master_key_length > 0) {
+ master_key_length = session->master_key_length;
+ memcpy(master_key, session->master_key, session->master_key_length);
+ memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE);
+ }
+#endif
+
+ /* The handshake has not progressed sufficiently yet, or this is a TLS 1.3
+ * session (when curl was built with older OpenSSL headers and running with
+ * newer OpenSSL runtime libraries). */
+ if(master_key_length <= 0)
+ return;
+
+ *keylog_done = true;
+ Curl_tls_keylog_write("CLIENT_RANDOM", client_random,
+ master_key, master_key_length);
+}
+#endif /* !HAVE_KEYLOG_CALLBACK */
+
+static const char *SSL_ERROR_to_str(int err)
+{
+ switch(err) {
+ case SSL_ERROR_NONE:
+ return "SSL_ERROR_NONE";
+ case SSL_ERROR_SSL:
+ return "SSL_ERROR_SSL";
+ case SSL_ERROR_WANT_READ:
+ return "SSL_ERROR_WANT_READ";
+ case SSL_ERROR_WANT_WRITE:
+ return "SSL_ERROR_WANT_WRITE";
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ return "SSL_ERROR_WANT_X509_LOOKUP";
+ case SSL_ERROR_SYSCALL:
+ return "SSL_ERROR_SYSCALL";
+ case SSL_ERROR_ZERO_RETURN:
+ return "SSL_ERROR_ZERO_RETURN";
+ case SSL_ERROR_WANT_CONNECT:
+ return "SSL_ERROR_WANT_CONNECT";
+ case SSL_ERROR_WANT_ACCEPT:
+ return "SSL_ERROR_WANT_ACCEPT";
+#if defined(SSL_ERROR_WANT_ASYNC)
+ case SSL_ERROR_WANT_ASYNC:
+ return "SSL_ERROR_WANT_ASYNC";
+#endif
+#if defined(SSL_ERROR_WANT_ASYNC_JOB)
+ case SSL_ERROR_WANT_ASYNC_JOB:
+ return "SSL_ERROR_WANT_ASYNC_JOB";
+#endif
+#if defined(SSL_ERROR_WANT_EARLY)
+ case SSL_ERROR_WANT_EARLY:
+ return "SSL_ERROR_WANT_EARLY";
+#endif
+ default:
+ return "SSL_ERROR unknown";
+ }
+}
+
+static size_t ossl_version(char *buffer, size_t size);
+
+/* Return error string for last OpenSSL error
+ */
+static char *ossl_strerror(unsigned long error, char *buf, size_t size)
+{
+ size_t len;
+ DEBUGASSERT(size);
+ *buf = '\0';
+
+ len = ossl_version(buf, size);
+ DEBUGASSERT(len < (size - 2));
+ if(len < (size - 2)) {
+ buf += len;
+ size -= (len + 2);
+ *buf++ = ':';
+ *buf++ = ' ';
+ *buf = '\0';
+ }
+
+#ifdef OPENSSL_IS_BORINGSSL
+ ERR_error_string_n((uint32_t)error, buf, size);
+#else
+ ERR_error_string_n(error, buf, size);
+#endif
+
+ if(!*buf) {
+ strncpy(buf, (error ? "Unknown error" : "No error"), size);
+ buf[size - 1] = '\0';
+ }
+
+ return buf;
+}
+
+/* Return an extra data index for the transfer data.
+ * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
+ */
+static int ossl_get_ssl_data_index(void)
+{
+ static int ssl_ex_data_data_index = -1;
+ if(ssl_ex_data_data_index < 0) {
+ ssl_ex_data_data_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ }
+ return ssl_ex_data_data_index;
+}
+
+/* Return an extra data index for the associated Curl_cfilter instance.
+ * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
+ */
+static int ossl_get_ssl_cf_index(void)
+{
+ static int ssl_ex_data_cf_index = -1;
+ if(ssl_ex_data_cf_index < 0) {
+ ssl_ex_data_cf_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ }
+ return ssl_ex_data_cf_index;
+}
+
+/* Return an extra data index for the sockindex.
+ * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
+ */
+static int ossl_get_ssl_sockindex_index(void)
+{
+ static int sockindex_index = -1;
+ if(sockindex_index < 0) {
+ sockindex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ }
+ return sockindex_index;
+}
+
+/* Return an extra data index for proxy boolean.
+ * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
+ */
+static int ossl_get_proxy_index(void)
+{
+ static int proxy_index = -1;
+ if(proxy_index < 0) {
+ proxy_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
+ }
+ return proxy_index;
+}
+
+static int passwd_callback(char *buf, int num, int encrypting,
+ void *global_passwd)
+{
+ DEBUGASSERT(0 == encrypting);
+
+ if(!encrypting) {
+ int klen = curlx_uztosi(strlen((char *)global_passwd));
+ if(num > klen) {
+ memcpy(buf, global_passwd, klen + 1);
+ return klen;
+ }
+ }
+ return 0;
+}
+
+/*
+ * rand_enough() returns TRUE if we have seeded the random engine properly.
+ */
+static bool rand_enough(void)
+{
+ return (0 != RAND_status()) ? TRUE : FALSE;
+}
+
+static CURLcode ossl_seed(struct Curl_easy *data)
+{
+ /* This might get called before it has been added to a multi handle */
+ if(data->multi && data->multi->ssl_seeded)
+ return CURLE_OK;
+
+ if(rand_enough()) {
+ /* OpenSSL 1.1.0+ should return here */
+ if(data->multi)
+ data->multi->ssl_seeded = TRUE;
+ return CURLE_OK;
+ }
+#ifdef HAVE_RANDOM_INIT_BY_DEFAULT
+ /* with OpenSSL 1.1.0+, a failed RAND_status is a showstopper */
+ failf(data, "Insufficient randomness");
+ return CURLE_SSL_CONNECT_ERROR;
+#else
+
+#ifdef RANDOM_FILE
+ RAND_load_file(RANDOM_FILE, RAND_LOAD_LENGTH);
+ if(rand_enough())
+ return CURLE_OK;
+#endif
+
+#if defined(HAVE_RAND_EGD) && defined(EGD_SOCKET)
+ /* available in OpenSSL 0.9.5 and later */
+ /* EGD_SOCKET is set at configure time or not at all */
+ {
+ /* If there's an option and a define, the option overrides the
+ define */
+ int ret = RAND_egd(EGD_SOCKET);
+ if(-1 != ret) {
+ if(rand_enough())
+ return CURLE_OK;
+ }
+ }
+#endif
+
+ /* fallback to a custom seeding of the PRNG using a hash based on a current
+ time */
+ do {
+ unsigned char randb[64];
+ size_t len = sizeof(randb);
+ size_t i, i_max;
+ for(i = 0, i_max = len / sizeof(struct curltime); i < i_max; ++i) {
+ struct curltime tv = Curl_now();
+ Curl_wait_ms(1);
+ tv.tv_sec *= i + 1;
+ tv.tv_usec *= (unsigned int)i + 2;
+ tv.tv_sec ^= ((Curl_now().tv_sec + Curl_now().tv_usec) *
+ (i + 3)) << 8;
+ tv.tv_usec ^= (unsigned int) ((Curl_now().tv_sec +
+ Curl_now().tv_usec) *
+ (i + 4)) << 16;
+ memcpy(&randb[i * sizeof(struct curltime)], &tv,
+ sizeof(struct curltime));
+ }
+ RAND_add(randb, (int)len, (double)len/2);
+ } while(!rand_enough());
+
+ {
+ /* generates a default path for the random seed file */
+ char fname[256];
+ fname[0] = 0; /* blank it first */
+ RAND_file_name(fname, sizeof(fname));
+ if(fname[0]) {
+ /* we got a file name to try */
+ RAND_load_file(fname, RAND_LOAD_LENGTH);
+ if(rand_enough())
+ return CURLE_OK;
+ }
+ }
+
+ infof(data, "libcurl is now using a weak random seed");
+ return (rand_enough() ? CURLE_OK :
+ CURLE_SSL_CONNECT_ERROR /* confusing error code */);
+#endif
+}
+
+#ifndef SSL_FILETYPE_ENGINE
+#define SSL_FILETYPE_ENGINE 42
+#endif
+#ifndef SSL_FILETYPE_PKCS12
+#define SSL_FILETYPE_PKCS12 43
+#endif
+static int do_file_type(const char *type)
+{
+ if(!type || !type[0])
+ return SSL_FILETYPE_PEM;
+ if(strcasecompare(type, "PEM"))
+ return SSL_FILETYPE_PEM;
+ if(strcasecompare(type, "DER"))
+ return SSL_FILETYPE_ASN1;
+ if(strcasecompare(type, "ENG"))
+ return SSL_FILETYPE_ENGINE;
+ if(strcasecompare(type, "P12"))
+ return SSL_FILETYPE_PKCS12;
+ return -1;
+}
+
+#ifdef USE_OPENSSL_ENGINE
+/*
+ * Supply default password to the engine user interface conversation.
+ * The password is passed by OpenSSL engine from ENGINE_load_private_key()
+ * last argument to the ui and can be obtained by UI_get0_user_data(ui) here.
+ */
+static int ssl_ui_reader(UI *ui, UI_STRING *uis)
+{
+ const char *password;
+ switch(UI_get_string_type(uis)) {
+ case UIT_PROMPT:
+ case UIT_VERIFY:
+ password = (const char *)UI_get0_user_data(ui);
+ if(password && (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) {
+ UI_set_result(ui, uis, password);
+ return 1;
+ }
+ default:
+ break;
+ }
+ return (UI_method_get_reader(UI_OpenSSL()))(ui, uis);
+}
+
+/*
+ * Suppress interactive request for a default password if available.
+ */
+static int ssl_ui_writer(UI *ui, UI_STRING *uis)
+{
+ switch(UI_get_string_type(uis)) {
+ case UIT_PROMPT:
+ case UIT_VERIFY:
+ if(UI_get0_user_data(ui) &&
+ (UI_get_input_flags(uis) & UI_INPUT_FLAG_DEFAULT_PWD)) {
+ return 1;
+ }
+ default:
+ break;
+ }
+ return (UI_method_get_writer(UI_OpenSSL()))(ui, uis);
+}
+
+/*
+ * Check if a given string is a PKCS#11 URI
+ */
+static bool is_pkcs11_uri(const char *string)
+{
+ return (string && strncasecompare(string, "pkcs11:", 7));
+}
+
+#endif
+
+static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine);
+
+static int
+SSL_CTX_use_certificate_blob(SSL_CTX *ctx, const struct curl_blob *blob,
+ int type, const char *key_passwd)
+{
+ int ret = 0;
+ X509 *x = NULL;
+ /* the typecast of blob->len is fine since it is guaranteed to never be
+ larger than CURL_MAX_INPUT_LENGTH */
+ BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len));
+ if(!in)
+ return CURLE_OUT_OF_MEMORY;
+
+ if(type == SSL_FILETYPE_ASN1) {
+ /* j = ERR_R_ASN1_LIB; */
+ x = d2i_X509_bio(in, NULL);
+ }
+ else if(type == SSL_FILETYPE_PEM) {
+ /* ERR_R_PEM_LIB; */
+ x = PEM_read_bio_X509(in, NULL,
+ passwd_callback, (void *)key_passwd);
+ }
+ else {
+ ret = 0;
+ goto end;
+ }
+
+ if(!x) {
+ ret = 0;
+ goto end;
+ }
+
+ ret = SSL_CTX_use_certificate(ctx, x);
+ end:
+ X509_free(x);
+ BIO_free(in);
+ return ret;
+}
+
+static int
+SSL_CTX_use_PrivateKey_blob(SSL_CTX *ctx, const struct curl_blob *blob,
+ int type, const char *key_passwd)
+{
+ int ret = 0;
+ EVP_PKEY *pkey = NULL;
+ BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len));
+ if(!in)
+ return CURLE_OUT_OF_MEMORY;
+
+ if(type == SSL_FILETYPE_PEM)
+ pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback,
+ (void *)key_passwd);
+ else if(type == SSL_FILETYPE_ASN1)
+ pkey = d2i_PrivateKey_bio(in, NULL);
+ else {
+ ret = 0;
+ goto end;
+ }
+ if(!pkey) {
+ ret = 0;
+ goto end;
+ }
+ ret = SSL_CTX_use_PrivateKey(ctx, pkey);
+ EVP_PKEY_free(pkey);
+ end:
+ BIO_free(in);
+ return ret;
+}
+
+static int
+SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob,
+ const char *key_passwd)
+{
+/* SSL_CTX_add1_chain_cert introduced in OpenSSL 1.0.2 */
+#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL) && /* OpenSSL 1.0.2 or later */ \
+ !(defined(LIBRESSL_VERSION_NUMBER) && \
+ (LIBRESSL_VERSION_NUMBER < 0x2090100fL)) /* LibreSSL 2.9.1 or later */
+ int ret = 0;
+ X509 *x = NULL;
+ void *passwd_callback_userdata = (void *)key_passwd;
+ BIO *in = BIO_new_mem_buf(blob->data, (int)(blob->len));
+ if(!in)
+ return CURLE_OUT_OF_MEMORY;
+
+ ERR_clear_error();
+
+ x = PEM_read_bio_X509_AUX(in, NULL,
+ passwd_callback, (void *)key_passwd);
+
+ if(!x) {
+ ret = 0;
+ goto end;
+ }
+
+ ret = SSL_CTX_use_certificate(ctx, x);
+
+ if(ERR_peek_error() != 0)
+ ret = 0;
+
+ if(ret) {
+ X509 *ca;
+ unsigned long err;
+
+ if(!SSL_CTX_clear_chain_certs(ctx)) {
+ ret = 0;
+ goto end;
+ }
+
+ while((ca = PEM_read_bio_X509(in, NULL, passwd_callback,
+ passwd_callback_userdata))
+ != NULL) {
+
+ if(!SSL_CTX_add0_chain_cert(ctx, ca)) {
+ X509_free(ca);
+ ret = 0;
+ goto end;
+ }
+ }
+
+ err = ERR_peek_last_error();
+ if((ERR_GET_LIB(err) == ERR_LIB_PEM) &&
+ (ERR_GET_REASON(err) == PEM_R_NO_START_LINE))
+ ERR_clear_error();
+ else
+ ret = 0;
+ }
+
+ end:
+ X509_free(x);
+ BIO_free(in);
+ return ret;
+#else
+ (void)ctx; /* unused */
+ (void)blob; /* unused */
+ (void)key_passwd; /* unused */
+ return 0;
+#endif
+}
+
+static
+int cert_stuff(struct Curl_easy *data,
+ SSL_CTX* ctx,
+ char *cert_file,
+ const struct curl_blob *cert_blob,
+ const char *cert_type,
+ char *key_file,
+ const struct curl_blob *key_blob,
+ const char *key_type,
+ char *key_passwd)
+{
+ char error_buffer[256];
+ bool check_privkey = TRUE;
+
+ int file_type = do_file_type(cert_type);
+
+ if(cert_file || cert_blob || (file_type == SSL_FILETYPE_ENGINE)) {
+ SSL *ssl;
+ X509 *x509;
+ int cert_done = 0;
+ int cert_use_result;
+
+ if(key_passwd) {
+ /* set the password in the callback userdata */
+ SSL_CTX_set_default_passwd_cb_userdata(ctx, key_passwd);
+ /* Set passwd callback: */
+ SSL_CTX_set_default_passwd_cb(ctx, passwd_callback);
+ }
+
+
+ switch(file_type) {
+ case SSL_FILETYPE_PEM:
+ /* SSL_CTX_use_certificate_chain_file() only works on PEM files */
+ cert_use_result = cert_blob ?
+ SSL_CTX_use_certificate_chain_blob(ctx, cert_blob, key_passwd) :
+ SSL_CTX_use_certificate_chain_file(ctx, cert_file);
+ if(cert_use_result != 1) {
+ failf(data,
+ "could not load PEM client certificate from %s, " OSSL_PACKAGE
+ " error %s, "
+ "(no key found, wrong pass phrase, or wrong file format?)",
+ (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file),
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ return 0;
+ }
+ break;
+
+ case SSL_FILETYPE_ASN1:
+ /* SSL_CTX_use_certificate_file() works with either PEM or ASN1, but
+ we use the case above for PEM so this can only be performed with
+ ASN1 files. */
+
+ cert_use_result = cert_blob ?
+ SSL_CTX_use_certificate_blob(ctx, cert_blob,
+ file_type, key_passwd) :
+ SSL_CTX_use_certificate_file(ctx, cert_file, file_type);
+ if(cert_use_result != 1) {
+ failf(data,
+ "could not load ASN1 client certificate from %s, " OSSL_PACKAGE
+ " error %s, "
+ "(no key found, wrong pass phrase, or wrong file format?)",
+ (cert_blob ? "CURLOPT_SSLCERT_BLOB" : cert_file),
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ return 0;
+ }
+ break;
+ case SSL_FILETYPE_ENGINE:
+#if defined(USE_OPENSSL_ENGINE) && defined(ENGINE_CTRL_GET_CMD_FROM_NAME)
+ {
+ /* Implicitly use pkcs11 engine if none was provided and the
+ * cert_file is a PKCS#11 URI */
+ if(!data->state.engine) {
+ if(is_pkcs11_uri(cert_file)) {
+ if(ossl_set_engine(data, "pkcs11") != CURLE_OK) {
+ return 0;
+ }
+ }
+ }
+
+ if(data->state.engine) {
+ const char *cmd_name = "LOAD_CERT_CTRL";
+ struct {
+ const char *cert_id;
+ X509 *cert;
+ } params;
+
+ params.cert_id = cert_file;
+ params.cert = NULL;
+
+ /* Does the engine supports LOAD_CERT_CTRL ? */
+ if(!ENGINE_ctrl(data->state.engine, ENGINE_CTRL_GET_CMD_FROM_NAME,
+ 0, (void *)cmd_name, NULL)) {
+ failf(data, "ssl engine does not support loading certificates");
+ return 0;
+ }
+
+ /* Load the certificate from the engine */
+ if(!ENGINE_ctrl_cmd(data->state.engine, cmd_name,
+ 0, ¶ms, NULL, 1)) {
+ failf(data, "ssl engine cannot load client cert with id"
+ " '%s' [%s]", cert_file,
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)));
+ return 0;
+ }
+
+ if(!params.cert) {
+ failf(data, "ssl engine didn't initialized the certificate "
+ "properly.");
+ return 0;
+ }
+
+ if(SSL_CTX_use_certificate(ctx, params.cert) != 1) {
+ failf(data, "unable to set client certificate [%s]",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)));
+ return 0;
+ }
+ X509_free(params.cert); /* we don't need the handle any more... */
+ }
+ else {
+ failf(data, "crypto engine not set, can't load certificate");
+ return 0;
+ }
+ }
+ break;
+#else
+ failf(data, "file type ENG for certificate not implemented");
+ return 0;
+#endif
+
+ case SSL_FILETYPE_PKCS12:
+ {
+ BIO *cert_bio = NULL;
+ PKCS12 *p12 = NULL;
+ EVP_PKEY *pri;
+ STACK_OF(X509) *ca = NULL;
+ if(cert_blob) {
+ cert_bio = BIO_new_mem_buf(cert_blob->data, (int)(cert_blob->len));
+ if(!cert_bio) {
+ failf(data,
+ "BIO_new_mem_buf NULL, " OSSL_PACKAGE
+ " error %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ return 0;
+ }
+ }
+ else {
+ cert_bio = BIO_new(BIO_s_file());
+ if(!cert_bio) {
+ failf(data,
+ "BIO_new return NULL, " OSSL_PACKAGE
+ " error %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ return 0;
+ }
+
+ if(BIO_read_filename(cert_bio, cert_file) <= 0) {
+ failf(data, "could not open PKCS12 file '%s'", cert_file);
+ BIO_free(cert_bio);
+ return 0;
+ }
+ }
+
+ p12 = d2i_PKCS12_bio(cert_bio, NULL);
+ BIO_free(cert_bio);
+
+ if(!p12) {
+ failf(data, "error reading PKCS12 file '%s'",
+ cert_blob ? "(memory blob)" : cert_file);
+ return 0;
+ }
+
+ PKCS12_PBE_add();
+
+ if(!PKCS12_parse(p12, key_passwd, &pri, &x509,
+ &ca)) {
+ failf(data,
+ "could not parse PKCS12 file, check password, " OSSL_PACKAGE
+ " error %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ PKCS12_free(p12);
+ return 0;
+ }
+
+ PKCS12_free(p12);
+
+ if(SSL_CTX_use_certificate(ctx, x509) != 1) {
+ failf(data,
+ "could not load PKCS12 client certificate, " OSSL_PACKAGE
+ " error %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ goto fail;
+ }
+
+ if(SSL_CTX_use_PrivateKey(ctx, pri) != 1) {
+ failf(data, "unable to use private key from PKCS12 file '%s'",
+ cert_file);
+ goto fail;
+ }
+
+ if(!SSL_CTX_check_private_key (ctx)) {
+ failf(data, "private key from PKCS12 file '%s' "
+ "does not match certificate in same file", cert_file);
+ goto fail;
+ }
+ /* Set Certificate Verification chain */
+ if(ca) {
+ while(sk_X509_num(ca)) {
+ /*
+ * Note that sk_X509_pop() is used below to make sure the cert is
+ * removed from the stack properly before getting passed to
+ * SSL_CTX_add_extra_chain_cert(), which takes ownership. Previously
+ * we used sk_X509_value() instead, but then we'd clean it in the
+ * subsequent sk_X509_pop_free() call.
+ */
+ X509 *x = sk_X509_pop(ca);
+ if(!SSL_CTX_add_client_CA(ctx, x)) {
+ X509_free(x);
+ failf(data, "cannot add certificate to client CA list");
+ goto fail;
+ }
+ if(!SSL_CTX_add_extra_chain_cert(ctx, x)) {
+ X509_free(x);
+ failf(data, "cannot add certificate to certificate chain");
+ goto fail;
+ }
+ }
+ }
+
+ cert_done = 1;
+ fail:
+ EVP_PKEY_free(pri);
+ X509_free(x509);
+ sk_X509_pop_free(ca, X509_free);
+ if(!cert_done)
+ return 0; /* failure! */
+ break;
+ }
+ default:
+ failf(data, "not supported file type '%s' for certificate", cert_type);
+ return 0;
+ }
+
+ if((!key_file) && (!key_blob)) {
+ key_file = cert_file;
+ key_blob = cert_blob;
+ }
+ else
+ file_type = do_file_type(key_type);
+
+ switch(file_type) {
+ case SSL_FILETYPE_PEM:
+ if(cert_done)
+ break;
+ /* FALLTHROUGH */
+ case SSL_FILETYPE_ASN1:
+ cert_use_result = key_blob ?
+ SSL_CTX_use_PrivateKey_blob(ctx, key_blob, file_type, key_passwd) :
+ SSL_CTX_use_PrivateKey_file(ctx, key_file, file_type);
+ if(cert_use_result != 1) {
+ failf(data, "unable to set private key file: '%s' type %s",
+ key_file?key_file:"(memory blob)", key_type?key_type:"PEM");
+ return 0;
+ }
+ break;
+ case SSL_FILETYPE_ENGINE:
+#ifdef USE_OPENSSL_ENGINE
+ { /* XXXX still needs some work */
+ EVP_PKEY *priv_key = NULL;
+
+ /* Implicitly use pkcs11 engine if none was provided and the
+ * key_file is a PKCS#11 URI */
+ if(!data->state.engine) {
+ if(is_pkcs11_uri(key_file)) {
+ if(ossl_set_engine(data, "pkcs11") != CURLE_OK) {
+ return 0;
+ }
+ }
+ }
+
+ if(data->state.engine) {
+ UI_METHOD *ui_method =
+ UI_create_method((char *)"curl user interface");
+ if(!ui_method) {
+ failf(data, "unable do create " OSSL_PACKAGE
+ " user-interface method");
+ return 0;
+ }
+ UI_method_set_opener(ui_method, UI_method_get_opener(UI_OpenSSL()));
+ UI_method_set_closer(ui_method, UI_method_get_closer(UI_OpenSSL()));
+ UI_method_set_reader(ui_method, ssl_ui_reader);
+ UI_method_set_writer(ui_method, ssl_ui_writer);
+ /* the typecast below was added to please mingw32 */
+ priv_key = (EVP_PKEY *)
+ ENGINE_load_private_key(data->state.engine, key_file,
+ ui_method,
+ key_passwd);
+ UI_destroy_method(ui_method);
+ if(!priv_key) {
+ failf(data, "failed to load private key from crypto engine");
+ return 0;
+ }
+ if(SSL_CTX_use_PrivateKey(ctx, priv_key) != 1) {
+ failf(data, "unable to set private key");
+ EVP_PKEY_free(priv_key);
+ return 0;
+ }
+ EVP_PKEY_free(priv_key); /* we don't need the handle any more... */
+ }
+ else {
+ failf(data, "crypto engine not set, can't load private key");
+ return 0;
+ }
+ }
+ break;
+#else
+ failf(data, "file type ENG for private key not supported");
+ return 0;
+#endif
+ case SSL_FILETYPE_PKCS12:
+ if(!cert_done) {
+ failf(data, "file type P12 for private key not supported");
+ return 0;
+ }
+ break;
+ default:
+ failf(data, "not supported file type for private key");
+ return 0;
+ }
+
+ ssl = SSL_new(ctx);
+ if(!ssl) {
+ failf(data, "unable to create an SSL structure");
+ return 0;
+ }
+
+ x509 = SSL_get_certificate(ssl);
+
+ /* This version was provided by Evan Jordan and is supposed to not
+ leak memory as the previous version: */
+ if(x509) {
+ EVP_PKEY *pktmp = X509_get_pubkey(x509);
+ EVP_PKEY_copy_parameters(pktmp, SSL_get_privatekey(ssl));
+ EVP_PKEY_free(pktmp);
+ }
+
+#if !defined(OPENSSL_NO_RSA) && !defined(OPENSSL_IS_BORINGSSL) && \
+ !defined(OPENSSL_NO_DEPRECATED_3_0)
+ {
+ /* If RSA is used, don't check the private key if its flags indicate
+ * it doesn't support it. */
+ EVP_PKEY *priv_key = SSL_get_privatekey(ssl);
+ int pktype;
+#ifdef HAVE_OPAQUE_EVP_PKEY
+ pktype = EVP_PKEY_id(priv_key);
+#else
+ pktype = priv_key->type;
+#endif
+ if(pktype == EVP_PKEY_RSA) {
+ RSA *rsa = EVP_PKEY_get1_RSA(priv_key);
+ if(RSA_flags(rsa) & RSA_METHOD_FLAG_NO_CHECK)
+ check_privkey = FALSE;
+ RSA_free(rsa); /* Decrement reference count */
+ }
+ }
+#endif
+
+ SSL_free(ssl);
+
+ /* If we are using DSA, we can copy the parameters from
+ * the private key */
+
+ if(check_privkey == TRUE) {
+ /* Now we know that a key and cert have been set against
+ * the SSL context */
+ if(!SSL_CTX_check_private_key(ctx)) {
+ failf(data, "Private key does not match the certificate public key");
+ return 0;
+ }
+ }
+ }
+ return 1;
+}
+
+CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, SSL_CTX *ctx,
+ char *cert_file,
+ const struct curl_blob *cert_blob,
+ const char *cert_type, char *key_file,
+ const struct curl_blob *key_blob,
+ const char *key_type, char *key_passwd)
+{
+ int rv = cert_stuff(data, ctx, cert_file, cert_blob, cert_type, key_file,
+ key_blob, key_type, key_passwd);
+ if(rv != 1) {
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+ return CURLE_OK;
+}
+
+/* returns non-zero on failure */
+static int x509_name_oneline(X509_NAME *a, char *buf, size_t size)
+{
+ BIO *bio_out = BIO_new(BIO_s_mem());
+ BUF_MEM *biomem;
+ int rc;
+
+ if(!bio_out)
+ return 1; /* alloc failed! */
+
+ rc = X509_NAME_print_ex(bio_out, a, 0, XN_FLAG_SEP_SPLUS_SPC);
+ BIO_get_mem_ptr(bio_out, &biomem);
+
+ if((size_t)biomem->length < size)
+ size = biomem->length;
+ else
+ size--; /* don't overwrite the buffer end */
+
+ memcpy(buf, biomem->data, size);
+ buf[size] = 0;
+
+ BIO_free(bio_out);
+
+ return !rc;
+}
+
+/**
+ * Global SSL init
+ *
+ * @retval 0 error initializing SSL
+ * @retval 1 SSL initialized successfully
+ */
+static int ossl_init(void)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+ const uint64_t flags =
+#ifdef OPENSSL_INIT_ENGINE_ALL_BUILTIN
+ /* not present in BoringSSL */
+ OPENSSL_INIT_ENGINE_ALL_BUILTIN |
+#endif
+#ifdef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG
+ OPENSSL_INIT_NO_LOAD_CONFIG |
+#else
+ OPENSSL_INIT_LOAD_CONFIG |
+#endif
+ 0;
+ OPENSSL_init_ssl(flags, NULL);
+#else
+ OPENSSL_load_builtin_modules();
+
+#ifdef USE_OPENSSL_ENGINE
+ ENGINE_load_builtin_engines();
+#endif
+
+/* CONF_MFLAGS_DEFAULT_SECTION was introduced some time between 0.9.8b and
+ 0.9.8e */
+#ifndef CONF_MFLAGS_DEFAULT_SECTION
+#define CONF_MFLAGS_DEFAULT_SECTION 0x0
+#endif
+
+#ifndef CURL_DISABLE_OPENSSL_AUTO_LOAD_CONFIG
+ CONF_modules_load_file(NULL, NULL,
+ CONF_MFLAGS_DEFAULT_SECTION|
+ CONF_MFLAGS_IGNORE_MISSING_FILE);
+#endif
+
+ /* Let's get nice error messages */
+ SSL_load_error_strings();
+
+ /* Init the global ciphers and digests */
+ if(!SSLeay_add_ssl_algorithms())
+ return 0;
+
+ OpenSSL_add_all_algorithms();
+#endif
+
+ bio_cf_init_methods();
+ Curl_tls_keylog_open();
+
+ /* Initialize the extra data indexes */
+ if(ossl_get_ssl_data_index() < 0 || ossl_get_ssl_cf_index() < 0 ||
+ ossl_get_ssl_sockindex_index() < 0 || ossl_get_proxy_index() < 0)
+ return 0;
+
+ return 1;
+}
+
+/* Global cleanup */
+static void ossl_cleanup(void)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && \
+ !defined(LIBRESSL_VERSION_NUMBER)
+ /* OpenSSL 1.1 deprecates all these cleanup functions and
+ turns them into no-ops in OpenSSL 1.0 compatibility mode */
+#else
+ /* Free ciphers and digests lists */
+ EVP_cleanup();
+
+#ifdef USE_OPENSSL_ENGINE
+ /* Free engine list */
+ ENGINE_cleanup();
+#endif
+
+ /* Free OpenSSL error strings */
+ ERR_free_strings();
+
+ /* Free thread local error state, destroying hash upon zero refcount */
+#ifdef HAVE_ERR_REMOVE_THREAD_STATE
+ ERR_remove_thread_state(NULL);
+#else
+ ERR_remove_state(0);
+#endif
+
+ /* Free all memory allocated by all configuration modules */
+ CONF_modules_free();
+
+#ifdef HAVE_SSL_COMP_FREE_COMPRESSION_METHODS
+ SSL_COMP_free_compression_methods();
+#endif
+#endif
+
+ Curl_tls_keylog_close();
+ bio_cf_free_methods();
+}
+
+/*
+ * This function is used to determine connection status.
+ *
+ * Return codes:
+ * 1 means the connection is still in place
+ * 0 means the connection has been closed
+ * -1 means the connection status is unknown
+ */
+static int ossl_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ /* SSL_peek takes data out of the raw recv buffer without peeking so we use
+ recv MSG_PEEK instead. Bug #795 */
+#ifdef MSG_PEEK
+ char buf;
+ ssize_t nread;
+ nread = recv((RECV_TYPE_ARG1)cf->conn->sock[cf->sockindex],
+ (RECV_TYPE_ARG2)&buf, (RECV_TYPE_ARG3)1,
+ (RECV_TYPE_ARG4)MSG_PEEK);
+ if(nread == 0)
+ return 0; /* connection has been closed */
+ if(nread == 1)
+ return 1; /* connection still in place */
+ else if(nread == -1) {
+ int err = SOCKERRNO;
+ if(err == EINPROGRESS ||
+#if defined(EAGAIN) && (EAGAIN != EWOULDBLOCK)
+ err == EAGAIN ||
+#endif
+ err == EWOULDBLOCK)
+ return 1; /* connection still in place */
+ if(err == ECONNRESET ||
+#ifdef ECONNABORTED
+ err == ECONNABORTED ||
+#endif
+#ifdef ENETDOWN
+ err == ENETDOWN ||
+#endif
+#ifdef ENETRESET
+ err == ENETRESET ||
+#endif
+#ifdef ESHUTDOWN
+ err == ESHUTDOWN ||
+#endif
+#ifdef ETIMEDOUT
+ err == ETIMEDOUT ||
+#endif
+ err == ENOTCONN)
+ return 0; /* connection has been closed */
+ }
+#endif
+ (void)data;
+ return -1; /* connection status unknown */
+}
+
+/* Selects an OpenSSL crypto engine
+ */
+static CURLcode ossl_set_engine(struct Curl_easy *data, const char *engine)
+{
+#ifdef USE_OPENSSL_ENGINE
+ ENGINE *e;
+
+#if OPENSSL_VERSION_NUMBER >= 0x00909000L
+ e = ENGINE_by_id(engine);
+#else
+ /* avoid memory leak */
+ for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) {
+ const char *e_id = ENGINE_get_id(e);
+ if(!strcmp(engine, e_id))
+ break;
+ }
+#endif
+
+ if(!e) {
+ failf(data, "SSL Engine '%s' not found", engine);
+ return CURLE_SSL_ENGINE_NOTFOUND;
+ }
+
+ if(data->state.engine) {
+ ENGINE_finish(data->state.engine);
+ ENGINE_free(data->state.engine);
+ data->state.engine = NULL;
+ }
+ if(!ENGINE_init(e)) {
+ char buf[256];
+
+ ENGINE_free(e);
+ failf(data, "Failed to initialise SSL Engine '%s': %s",
+ engine, ossl_strerror(ERR_get_error(), buf, sizeof(buf)));
+ return CURLE_SSL_ENGINE_INITFAILED;
+ }
+ data->state.engine = e;
+ return CURLE_OK;
+#else
+ (void)engine;
+ failf(data, "SSL Engine not supported");
+ return CURLE_SSL_ENGINE_NOTFOUND;
+#endif
+}
+
+/* Sets engine as default for all SSL operations
+ */
+static CURLcode ossl_set_engine_default(struct Curl_easy *data)
+{
+#ifdef USE_OPENSSL_ENGINE
+ if(data->state.engine) {
+ if(ENGINE_set_default(data->state.engine, ENGINE_METHOD_ALL) > 0) {
+ infof(data, "set default crypto engine '%s'",
+ ENGINE_get_id(data->state.engine));
+ }
+ else {
+ failf(data, "set default crypto engine '%s' failed",
+ ENGINE_get_id(data->state.engine));
+ return CURLE_SSL_ENGINE_SETFAILED;
+ }
+ }
+#else
+ (void) data;
+#endif
+ return CURLE_OK;
+}
+
+/* Return list of OpenSSL crypto engine names.
+ */
+static struct curl_slist *ossl_engines_list(struct Curl_easy *data)
+{
+ struct curl_slist *list = NULL;
+#ifdef USE_OPENSSL_ENGINE
+ struct curl_slist *beg;
+ ENGINE *e;
+
+ for(e = ENGINE_get_first(); e; e = ENGINE_get_next(e)) {
+ beg = curl_slist_append(list, ENGINE_get_id(e));
+ if(!beg) {
+ curl_slist_free_all(list);
+ return NULL;
+ }
+ list = beg;
+ }
+#endif
+ (void) data;
+ return list;
+}
+
+#define set_logger(connssl, data) \
+ connssl->backend->logger = data
+
+static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ if(backend->handle) {
+ set_logger(connssl, data);
+
+ if(cf->next && cf->next->connected) {
+ char buf[32];
+ /* Maybe the server has already sent a close notify alert.
+ Read it to avoid an RST on the TCP connection. */
+ (void)SSL_read(backend->handle, buf, (int)sizeof(buf));
+
+ (void)SSL_shutdown(backend->handle);
+ SSL_set_connect_state(backend->handle);
+ }
+
+ SSL_free(backend->handle);
+ backend->handle = NULL;
+ }
+ if(backend->ctx) {
+ SSL_CTX_free(backend->ctx);
+ backend->ctx = NULL;
+ }
+}
+
+/*
+ * This function is called to shut down the SSL layer but keep the
+ * socket open (CCC - Clear Command Channel)
+ */
+static int ossl_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ int retval = 0;
+ struct ssl_connect_data *connssl = cf->ctx;
+ char buf[256]; /* We will use this for the OpenSSL error buffer, so it has
+ to be at least 256 bytes long. */
+ unsigned long sslerror;
+ int nread;
+ int buffsize;
+ int err;
+ bool done = FALSE;
+ struct ssl_backend_data *backend = connssl->backend;
+ int loop = 10;
+
+ DEBUGASSERT(backend);
+
+#ifndef CURL_DISABLE_FTP
+ /* This has only been tested on the proftpd server, and the mod_tls code
+ sends a close notify alert without waiting for a close notify alert in
+ response. Thus we wait for a close notify alert from the server, but
+ we do not send one. Let's hope other servers do the same... */
+
+ if(data->set.ftp_ccc == CURLFTPSSL_CCC_ACTIVE)
+ (void)SSL_shutdown(backend->handle);
+#endif
+
+ if(backend->handle) {
+ buffsize = (int)sizeof(buf);
+ while(!done && loop--) {
+ int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+ SSL_SHUTDOWN_TIMEOUT);
+ if(what > 0) {
+ ERR_clear_error();
+
+ /* Something to read, let's do it and hope that it is the close
+ notify alert from the server */
+ nread = SSL_read(backend->handle, buf, buffsize);
+ err = SSL_get_error(backend->handle, nread);
+
+ switch(err) {
+ case SSL_ERROR_NONE: /* this is not an error */
+ case SSL_ERROR_ZERO_RETURN: /* no more data */
+ /* This is the expected response. There was no data but only
+ the close notify alert */
+ done = TRUE;
+ break;
+ case SSL_ERROR_WANT_READ:
+ /* there's data pending, re-invoke SSL_read() */
+ infof(data, "SSL_ERROR_WANT_READ");
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ /* SSL wants a write. Really odd. Let's bail out. */
+ infof(data, "SSL_ERROR_WANT_WRITE");
+ done = TRUE;
+ break;
+ default:
+ /* openssl/ssl.h says "look at error stack/return value/errno" */
+ sslerror = ERR_get_error();
+ failf(data, OSSL_PACKAGE " SSL_read on shutdown: %s, errno %d",
+ (sslerror ?
+ ossl_strerror(sslerror, buf, sizeof(buf)) :
+ SSL_ERROR_to_str(err)),
+ SOCKERRNO);
+ done = TRUE;
+ break;
+ }
+ }
+ else if(0 == what) {
+ /* timeout */
+ failf(data, "SSL shutdown timeout");
+ done = TRUE;
+ }
+ else {
+ /* anything that gets here is fatally bad */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ retval = -1;
+ done = TRUE;
+ }
+ } /* while()-loop for the select() */
+
+ if(data->set.verbose) {
+#ifdef HAVE_SSL_GET_SHUTDOWN
+ switch(SSL_get_shutdown(backend->handle)) {
+ case SSL_SENT_SHUTDOWN:
+ infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN");
+ break;
+ case SSL_RECEIVED_SHUTDOWN:
+ infof(data, "SSL_get_shutdown() returned SSL_RECEIVED_SHUTDOWN");
+ break;
+ case SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN:
+ infof(data, "SSL_get_shutdown() returned SSL_SENT_SHUTDOWN|"
+ "SSL_RECEIVED__SHUTDOWN");
+ break;
+ }
+#endif
+ }
+
+ SSL_free(backend->handle);
+ backend->handle = NULL;
+ }
+ return retval;
+}
+
+static void ossl_session_free(void *ptr)
+{
+ /* free the ID */
+ SSL_SESSION_free(ptr);
+}
+
+/*
+ * This function is called when the 'data' struct is going away. Close
+ * down everything and free all resources!
+ */
+static void ossl_close_all(struct Curl_easy *data)
+{
+#ifdef USE_OPENSSL_ENGINE
+ if(data->state.engine) {
+ ENGINE_finish(data->state.engine);
+ ENGINE_free(data->state.engine);
+ data->state.engine = NULL;
+ }
+#else
+ (void)data;
+#endif
+#if !defined(HAVE_ERR_REMOVE_THREAD_STATE_DEPRECATED) && \
+ defined(HAVE_ERR_REMOVE_THREAD_STATE)
+ /* OpenSSL 1.0.1 and 1.0.2 build an error queue that is stored per-thread
+ so we need to clean it here in case the thread will be killed. All OpenSSL
+ code should extract the error in association with the error so clearing
+ this queue here should be harmless at worst. */
+ ERR_remove_thread_state(NULL);
+#endif
+}
+
+/* ====================================================== */
+
+/*
+ * Match subjectAltName against the host name.
+ */
+static bool subj_alt_hostcheck(struct Curl_easy *data,
+ const char *match_pattern,
+ size_t matchlen,
+ const char *hostname,
+ size_t hostlen,
+ const char *dispname)
+{
+#ifdef CURL_DISABLE_VERBOSE_STRINGS
+ (void)dispname;
+ (void)data;
+#endif
+ if(Curl_cert_hostcheck(match_pattern, matchlen, hostname, hostlen)) {
+ infof(data, " subjectAltName: host \"%s\" matched cert's \"%s\"",
+ dispname, match_pattern);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/* Quote from RFC2818 section 3.1 "Server Identity"
+
+ If a subjectAltName extension of type dNSName is present, that MUST
+ be used as the identity. Otherwise, the (most specific) Common Name
+ field in the Subject field of the certificate MUST be used. Although
+ the use of the Common Name is existing practice, it is deprecated and
+ Certification Authorities are encouraged to use the dNSName instead.
+
+ Matching is performed using the matching rules specified by
+ [RFC2459]. If more than one identity of a given type is present in
+ the certificate (e.g., more than one dNSName name, a match in any one
+ of the set is considered acceptable.) Names may contain the wildcard
+ character * which is considered to match any single domain name
+ component or component fragment. E.g., *.a.com matches foo.a.com but
+ not bar.foo.a.com. f*.com matches foo.com but not bar.com.
+
+ In some cases, the URI is specified as an IP address rather than a
+ hostname. In this case, the iPAddress subjectAltName must be present
+ in the certificate and must exactly match the IP in the URI.
+
+ This function is now used from ngtcp2 (QUIC) as well.
+*/
+CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+ X509 *server_cert)
+{
+ bool matched = FALSE;
+ int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */
+ size_t addrlen = 0;
+ STACK_OF(GENERAL_NAME) *altnames;
+#ifdef ENABLE_IPV6
+ struct in6_addr addr;
+#else
+ struct in_addr addr;
+#endif
+ CURLcode result = CURLE_OK;
+ bool dNSName = FALSE; /* if a dNSName field exists in the cert */
+ bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */
+ const char *hostname, *dispname;
+ int port;
+ size_t hostlen;
+
+ (void)conn;
+ Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port);
+ hostlen = strlen(hostname);
+
+#ifndef ENABLE_IPV6
+ /* Silence compiler warnings for unused params */
+ (void) conn;
+#endif
+
+#ifdef ENABLE_IPV6
+ if(conn->bits.ipv6_ip &&
+ Curl_inet_pton(AF_INET6, hostname, &addr)) {
+ target = GEN_IPADD;
+ addrlen = sizeof(struct in6_addr);
+ }
+ else
+#endif
+ if(Curl_inet_pton(AF_INET, hostname, &addr)) {
+ target = GEN_IPADD;
+ addrlen = sizeof(struct in_addr);
+ }
+
+ /* get a "list" of alternative names */
+ altnames = X509_get_ext_d2i(server_cert, NID_subject_alt_name, NULL, NULL);
+
+ if(altnames) {
+#ifdef OPENSSL_IS_BORINGSSL
+ size_t numalts;
+ size_t i;
+#else
+ int numalts;
+ int i;
+#endif
+ bool dnsmatched = FALSE;
+ bool ipmatched = FALSE;
+
+ /* get amount of alternatives, RFC2459 claims there MUST be at least
+ one, but we don't depend on it... */
+ numalts = sk_GENERAL_NAME_num(altnames);
+
+ /* loop through all alternatives - until a dnsmatch */
+ for(i = 0; (i < numalts) && !dnsmatched; i++) {
+ /* get a handle to alternative name number i */
+ const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
+
+ if(check->type == GEN_DNS)
+ dNSName = TRUE;
+ else if(check->type == GEN_IPADD)
+ iPAddress = TRUE;
+
+ /* only check alternatives of the same type the target is */
+ if(check->type == target) {
+ /* get data and length */
+ const char *altptr = (char *)ASN1_STRING_get0_data(check->d.ia5);
+ size_t altlen = (size_t) ASN1_STRING_length(check->d.ia5);
+
+ switch(target) {
+ case GEN_DNS: /* name/pattern comparison */
+ /* The OpenSSL man page explicitly says: "In general it cannot be
+ assumed that the data returned by ASN1_STRING_data() is null
+ terminated or does not contain embedded nulls." But also that
+ "The actual format of the data will depend on the actual string
+ type itself: for example for an IA5String the data will be ASCII"
+
+ It has been however verified that in 0.9.6 and 0.9.7, IA5String
+ is always null-terminated.
+ */
+ if((altlen == strlen(altptr)) &&
+ /* if this isn't true, there was an embedded zero in the name
+ string and we cannot match it. */
+ subj_alt_hostcheck(data,
+ altptr,
+ altlen, hostname, hostlen, dispname)) {
+ dnsmatched = TRUE;
+ }
+ break;
+
+ case GEN_IPADD: /* IP address comparison */
+ /* compare alternative IP address if the data chunk is the same size
+ our server IP address is */
+ if((altlen == addrlen) && !memcmp(altptr, &addr, altlen)) {
+ ipmatched = TRUE;
+ infof(data,
+ " subjectAltName: host \"%s\" matched cert's IP address!",
+ dispname);
+ }
+ break;
+ }
+ }
+ }
+ GENERAL_NAMES_free(altnames);
+
+ if(dnsmatched || ipmatched)
+ matched = TRUE;
+ }
+
+ if(matched)
+ /* an alternative name matched */
+ ;
+ else if(dNSName || iPAddress) {
+ infof(data, " subjectAltName does not match %s", dispname);
+ failf(data, "SSL: no alternative certificate subject name matches "
+ "target host name '%s'", dispname);
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else {
+ /* we have to look to the last occurrence of a commonName in the
+ distinguished one to get the most significant one. */
+ int i = -1;
+ unsigned char *peer_CN = NULL;
+ int peerlen = 0;
+
+ /* The following is done because of a bug in 0.9.6b */
+ X509_NAME *name = X509_get_subject_name(server_cert);
+ if(name) {
+ int j;
+ while((j = X509_NAME_get_index_by_NID(name, NID_commonName, i)) >= 0)
+ i = j;
+ }
+
+ /* we have the name entry and we will now convert this to a string
+ that we can use for comparison. Doing this we support BMPstring,
+ UTF8, etc. */
+
+ if(i >= 0) {
+ ASN1_STRING *tmp =
+ X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
+
+ /* In OpenSSL 0.9.7d and earlier, ASN1_STRING_to_UTF8 fails if the input
+ is already UTF-8 encoded. We check for this case and copy the raw
+ string manually to avoid the problem. This code can be made
+ conditional in the future when OpenSSL has been fixed. */
+ if(tmp) {
+ if(ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) {
+ peerlen = ASN1_STRING_length(tmp);
+ if(peerlen >= 0) {
+ peer_CN = OPENSSL_malloc(peerlen + 1);
+ if(peer_CN) {
+ memcpy(peer_CN, ASN1_STRING_get0_data(tmp), peerlen);
+ peer_CN[peerlen] = '\0';
+ }
+ else
+ result = CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else /* not a UTF8 name */
+ peerlen = ASN1_STRING_to_UTF8(&peer_CN, tmp);
+
+ if(peer_CN && (curlx_uztosi(strlen((char *)peer_CN)) != peerlen)) {
+ /* there was a terminating zero before the end of string, this
+ cannot match and we return failure! */
+ failf(data, "SSL: illegal cert name field");
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ }
+ }
+
+ if(result)
+ /* error already detected, pass through */
+ ;
+ else if(!peer_CN) {
+ failf(data,
+ "SSL: unable to obtain common name from peer certificate");
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else if(!Curl_cert_hostcheck((const char *)peer_CN,
+ peerlen, hostname, hostlen)) {
+ failf(data, "SSL: certificate subject name '%s' does not match "
+ "target host name '%s'", peer_CN, dispname);
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else {
+ infof(data, " common name: %s (matched)", peer_CN);
+ }
+ if(peer_CN)
+ OPENSSL_free(peer_CN);
+ }
+
+ return result;
+}
+
+#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(OPENSSL_NO_OCSP)
+static CURLcode verifystatus(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ int i, ocsp_status;
+ unsigned char *status;
+ const unsigned char *p;
+ CURLcode result = CURLE_OK;
+ OCSP_RESPONSE *rsp = NULL;
+ OCSP_BASICRESP *br = NULL;
+ X509_STORE *st = NULL;
+ STACK_OF(X509) *ch = NULL;
+ struct ssl_backend_data *backend = connssl->backend;
+ X509 *cert;
+ OCSP_CERTID *id = NULL;
+ int cert_status, crl_reason;
+ ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+ int ret;
+ long len;
+
+ DEBUGASSERT(backend);
+
+ len = SSL_get_tlsext_status_ocsp_resp(backend->handle, &status);
+
+ if(!status) {
+ failf(data, "No OCSP response received");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+ p = status;
+ rsp = d2i_OCSP_RESPONSE(NULL, &p, len);
+ if(!rsp) {
+ failf(data, "Invalid OCSP response");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ ocsp_status = OCSP_response_status(rsp);
+ if(ocsp_status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ failf(data, "Invalid OCSP response status: %s (%d)",
+ OCSP_response_status_str(ocsp_status), ocsp_status);
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ br = OCSP_response_get1_basic(rsp);
+ if(!br) {
+ failf(data, "Invalid OCSP response");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ ch = SSL_get_peer_cert_chain(backend->handle);
+ if(!ch) {
+ failf(data, "Could not get peer certificate chain");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+ st = SSL_CTX_get_cert_store(backend->ctx);
+
+#if ((OPENSSL_VERSION_NUMBER <= 0x1000201fL) /* Fixed after 1.0.2a */ || \
+ (defined(LIBRESSL_VERSION_NUMBER) && \
+ LIBRESSL_VERSION_NUMBER <= 0x2040200fL))
+ /* The authorized responder cert in the OCSP response MUST be signed by the
+ peer cert's issuer (see RFC6960 section 4.2.2.2). If that's a root cert,
+ no problem, but if it's an intermediate cert OpenSSL has a bug where it
+ expects this issuer to be present in the chain embedded in the OCSP
+ response. So we add it if necessary. */
+
+ /* First make sure the peer cert chain includes both a peer and an issuer,
+ and the OCSP response contains a responder cert. */
+ if(sk_X509_num(ch) >= 2 && sk_X509_num(br->certs) >= 1) {
+ X509 *responder = sk_X509_value(br->certs, sk_X509_num(br->certs) - 1);
+
+ /* Find issuer of responder cert and add it to the OCSP response chain */
+ for(i = 0; i < sk_X509_num(ch); i++) {
+ X509 *issuer = sk_X509_value(ch, i);
+ if(X509_check_issued(issuer, responder) == X509_V_OK) {
+ if(!OCSP_basic_add1_cert(br, issuer)) {
+ failf(data, "Could not add issuer cert to OCSP response");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+ }
+ }
+ }
+#endif
+
+ if(OCSP_basic_verify(br, ch, st, 0) <= 0) {
+ failf(data, "OCSP response verification failed");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ /* Compute the certificate's ID */
+ cert = SSL_get1_peer_certificate(backend->handle);
+ if(!cert) {
+ failf(data, "Error getting peer certificate");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ for(i = 0; i < sk_X509_num(ch); i++) {
+ X509 *issuer = sk_X509_value(ch, i);
+ if(X509_check_issued(issuer, cert) == X509_V_OK) {
+ id = OCSP_cert_to_id(EVP_sha1(), cert, issuer);
+ break;
+ }
+ }
+ X509_free(cert);
+
+ if(!id) {
+ failf(data, "Error computing OCSP ID");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ /* Find the single OCSP response corresponding to the certificate ID */
+ ret = OCSP_resp_find_status(br, id, &cert_status, &crl_reason, &rev,
+ &thisupd, &nextupd);
+ OCSP_CERTID_free(id);
+ if(ret != 1) {
+ failf(data, "Could not find certificate ID in OCSP response");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ /* Validate the corresponding single OCSP response */
+ if(!OCSP_check_validity(thisupd, nextupd, 300L, -1L)) {
+ failf(data, "OCSP response has expired");
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+ infof(data, "SSL certificate status: %s (%d)",
+ OCSP_cert_status_str(cert_status), cert_status);
+
+ switch(cert_status) {
+ case V_OCSP_CERTSTATUS_GOOD:
+ break;
+
+ case V_OCSP_CERTSTATUS_REVOKED:
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ failf(data, "SSL certificate revocation reason: %s (%d)",
+ OCSP_crl_reason_str(crl_reason), crl_reason);
+ goto end;
+
+ case V_OCSP_CERTSTATUS_UNKNOWN:
+ default:
+ result = CURLE_SSL_INVALIDCERTSTATUS;
+ goto end;
+ }
+
+end:
+ if(br)
+ OCSP_BASICRESP_free(br);
+ OCSP_RESPONSE_free(rsp);
+
+ return result;
+}
+#endif
+
+#endif /* USE_OPENSSL */
+
+/* The SSL_CTRL_SET_MSG_CALLBACK doesn't exist in ancient OpenSSL versions
+ and thus this cannot be done there. */
+#ifdef SSL_CTRL_SET_MSG_CALLBACK
+
+static const char *ssl_msg_type(int ssl_ver, int msg)
+{
+#ifdef SSL2_VERSION_MAJOR
+ if(ssl_ver == SSL2_VERSION_MAJOR) {
+ switch(msg) {
+ case SSL2_MT_ERROR:
+ return "Error";
+ case SSL2_MT_CLIENT_HELLO:
+ return "Client hello";
+ case SSL2_MT_CLIENT_MASTER_KEY:
+ return "Client key";
+ case SSL2_MT_CLIENT_FINISHED:
+ return "Client finished";
+ case SSL2_MT_SERVER_HELLO:
+ return "Server hello";
+ case SSL2_MT_SERVER_VERIFY:
+ return "Server verify";
+ case SSL2_MT_SERVER_FINISHED:
+ return "Server finished";
+ case SSL2_MT_REQUEST_CERTIFICATE:
+ return "Request CERT";
+ case SSL2_MT_CLIENT_CERTIFICATE:
+ return "Client CERT";
+ }
+ }
+ else
+#endif
+ if(ssl_ver == SSL3_VERSION_MAJOR) {
+ switch(msg) {
+ case SSL3_MT_HELLO_REQUEST:
+ return "Hello request";
+ case SSL3_MT_CLIENT_HELLO:
+ return "Client hello";
+ case SSL3_MT_SERVER_HELLO:
+ return "Server hello";
+#ifdef SSL3_MT_NEWSESSION_TICKET
+ case SSL3_MT_NEWSESSION_TICKET:
+ return "Newsession Ticket";
+#endif
+ case SSL3_MT_CERTIFICATE:
+ return "Certificate";
+ case SSL3_MT_SERVER_KEY_EXCHANGE:
+ return "Server key exchange";
+ case SSL3_MT_CLIENT_KEY_EXCHANGE:
+ return "Client key exchange";
+ case SSL3_MT_CERTIFICATE_REQUEST:
+ return "Request CERT";
+ case SSL3_MT_SERVER_DONE:
+ return "Server finished";
+ case SSL3_MT_CERTIFICATE_VERIFY:
+ return "CERT verify";
+ case SSL3_MT_FINISHED:
+ return "Finished";
+#ifdef SSL3_MT_CERTIFICATE_STATUS
+ case SSL3_MT_CERTIFICATE_STATUS:
+ return "Certificate Status";
+#endif
+#ifdef SSL3_MT_ENCRYPTED_EXTENSIONS
+ case SSL3_MT_ENCRYPTED_EXTENSIONS:
+ return "Encrypted Extensions";
+#endif
+#ifdef SSL3_MT_SUPPLEMENTAL_DATA
+ case SSL3_MT_SUPPLEMENTAL_DATA:
+ return "Supplemental data";
+#endif
+#ifdef SSL3_MT_END_OF_EARLY_DATA
+ case SSL3_MT_END_OF_EARLY_DATA:
+ return "End of early data";
+#endif
+#ifdef SSL3_MT_KEY_UPDATE
+ case SSL3_MT_KEY_UPDATE:
+ return "Key update";
+#endif
+#ifdef SSL3_MT_NEXT_PROTO
+ case SSL3_MT_NEXT_PROTO:
+ return "Next protocol";
+#endif
+#ifdef SSL3_MT_MESSAGE_HASH
+ case SSL3_MT_MESSAGE_HASH:
+ return "Message hash";
+#endif
+ }
+ }
+ return "Unknown";
+}
+
+static const char *tls_rt_type(int type)
+{
+ switch(type) {
+#ifdef SSL3_RT_HEADER
+ case SSL3_RT_HEADER:
+ return "TLS header";
+#endif
+ case SSL3_RT_CHANGE_CIPHER_SPEC:
+ return "TLS change cipher";
+ case SSL3_RT_ALERT:
+ return "TLS alert";
+ case SSL3_RT_HANDSHAKE:
+ return "TLS handshake";
+ case SSL3_RT_APPLICATION_DATA:
+ return "TLS app data";
+ default:
+ return "TLS Unknown";
+ }
+}
+
+/*
+ * Our callback from the SSL/TLS layers.
+ */
+static void ossl_trace(int direction, int ssl_ver, int content_type,
+ const void *buf, size_t len, SSL *ssl,
+ void *userp)
+{
+ char unknown[32];
+ const char *verstr = NULL;
+ struct connectdata *conn = userp;
+ int cf_idx = ossl_get_ssl_cf_index();
+ struct ssl_connect_data *connssl;
+ struct Curl_easy *data = NULL;
+ struct Curl_cfilter *cf;
+
+ DEBUGASSERT(cf_idx >= 0);
+ cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx);
+ DEBUGASSERT(cf);
+ connssl = cf->ctx;
+ DEBUGASSERT(connssl);
+ DEBUGASSERT(connssl->backend);
+ data = connssl->backend->logger;
+
+ if(!conn || !data || !data->set.fdebug ||
+ (direction != 0 && direction != 1))
+ return;
+
+ switch(ssl_ver) {
+#ifdef SSL2_VERSION /* removed in recent versions */
+ case SSL2_VERSION:
+ verstr = "SSLv2";
+ break;
+#endif
+#ifdef SSL3_VERSION
+ case SSL3_VERSION:
+ verstr = "SSLv3";
+ break;
+#endif
+ case TLS1_VERSION:
+ verstr = "TLSv1.0";
+ break;
+#ifdef TLS1_1_VERSION
+ case TLS1_1_VERSION:
+ verstr = "TLSv1.1";
+ break;
+#endif
+#ifdef TLS1_2_VERSION
+ case TLS1_2_VERSION:
+ verstr = "TLSv1.2";
+ break;
+#endif
+#ifdef TLS1_3_VERSION
+ case TLS1_3_VERSION:
+ verstr = "TLSv1.3";
+ break;
+#endif
+ case 0:
+ break;
+ default:
+ msnprintf(unknown, sizeof(unknown), "(%x)", ssl_ver);
+ verstr = unknown;
+ break;
+ }
+
+ /* Log progress for interesting records only (like Handshake or Alert), skip
+ * all raw record headers (content_type == SSL3_RT_HEADER or ssl_ver == 0).
+ * For TLS 1.3, skip notification of the decrypted inner Content-Type.
+ */
+ if(ssl_ver
+#ifdef SSL3_RT_INNER_CONTENT_TYPE
+ && content_type != SSL3_RT_INNER_CONTENT_TYPE
+#endif
+ ) {
+ const char *msg_name, *tls_rt_name;
+ char ssl_buf[1024];
+ int msg_type, txt_len;
+
+ /* the info given when the version is zero is not that useful for us */
+
+ ssl_ver >>= 8; /* check the upper 8 bits only below */
+
+ /* SSLv2 doesn't seem to have TLS record-type headers, so OpenSSL
+ * always pass-up content-type as 0. But the interesting message-type
+ * is at 'buf[0]'.
+ */
+ if(ssl_ver == SSL3_VERSION_MAJOR && content_type)
+ tls_rt_name = tls_rt_type(content_type);
+ else
+ tls_rt_name = "";
+
+ if(content_type == SSL3_RT_CHANGE_CIPHER_SPEC) {
+ msg_type = *(char *)buf;
+ msg_name = "Change cipher spec";
+ }
+ else if(content_type == SSL3_RT_ALERT) {
+ msg_type = (((char *)buf)[0] << 8) + ((char *)buf)[1];
+ msg_name = SSL_alert_desc_string_long(msg_type);
+ }
+ else {
+ msg_type = *(char *)buf;
+ msg_name = ssl_msg_type(ssl_ver, msg_type);
+ }
+
+ txt_len = msnprintf(ssl_buf, sizeof(ssl_buf),
+ CFMSG(cf, "%s (%s), %s, %s (%d):\n"),
+ verstr, direction?"OUT":"IN",
+ tls_rt_name, msg_name, msg_type);
+ if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) {
+ Curl_debug(data, CURLINFO_TEXT, ssl_buf, (size_t)txt_len);
+ }
+ }
+
+ Curl_debug(data, (direction == 1) ? CURLINFO_SSL_DATA_OUT :
+ CURLINFO_SSL_DATA_IN, (char *)buf, len);
+ (void) ssl;
+}
+#endif
+
+#ifdef USE_OPENSSL
+/* ====================================================== */
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+# define use_sni(x) sni = (x)
+#else
+# define use_sni(x) Curl_nop_stmt
+#endif
+
+/* Check for OpenSSL 1.0.2 which has ALPN support. */
+#undef HAS_ALPN
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L \
+ && !defined(OPENSSL_NO_TLSEXT)
+# define HAS_ALPN 1
+#endif
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */
+static CURLcode
+set_ssl_version_min_max(struct Curl_cfilter *cf, SSL_CTX *ctx)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ /* first, TLS min version... */
+ long curl_ssl_version_min = conn_config->version;
+ long curl_ssl_version_max;
+
+ /* convert curl min SSL version option to OpenSSL constant */
+#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
+ uint16_t ossl_ssl_version_min = 0;
+ uint16_t ossl_ssl_version_max = 0;
+#else
+ long ossl_ssl_version_min = 0;
+ long ossl_ssl_version_max = 0;
+#endif
+ switch(curl_ssl_version_min) {
+ case CURL_SSLVERSION_TLSv1: /* TLS 1.x */
+ case CURL_SSLVERSION_TLSv1_0:
+ ossl_ssl_version_min = TLS1_VERSION;
+ break;
+ case CURL_SSLVERSION_TLSv1_1:
+ ossl_ssl_version_min = TLS1_1_VERSION;
+ break;
+ case CURL_SSLVERSION_TLSv1_2:
+ ossl_ssl_version_min = TLS1_2_VERSION;
+ break;
+ case CURL_SSLVERSION_TLSv1_3:
+#ifdef TLS1_3_VERSION
+ ossl_ssl_version_min = TLS1_3_VERSION;
+ break;
+#else
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+
+ /* CURL_SSLVERSION_DEFAULT means that no option was selected.
+ We don't want to pass 0 to SSL_CTX_set_min_proto_version as
+ it would enable all versions down to the lowest supported by
+ the library.
+ So we skip this, and stay with the library default
+ */
+ if(curl_ssl_version_min != CURL_SSLVERSION_DEFAULT) {
+ if(!SSL_CTX_set_min_proto_version(ctx, ossl_ssl_version_min)) {
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ /* ... then, TLS max version */
+ curl_ssl_version_max = conn_config->version_max;
+
+ /* convert curl max SSL version option to OpenSSL constant */
+ switch(curl_ssl_version_max) {
+ case CURL_SSLVERSION_MAX_TLSv1_0:
+ ossl_ssl_version_max = TLS1_VERSION;
+ break;
+ case CURL_SSLVERSION_MAX_TLSv1_1:
+ ossl_ssl_version_max = TLS1_1_VERSION;
+ break;
+ case CURL_SSLVERSION_MAX_TLSv1_2:
+ ossl_ssl_version_max = TLS1_2_VERSION;
+ break;
+#ifdef TLS1_3_VERSION
+ case CURL_SSLVERSION_MAX_TLSv1_3:
+ ossl_ssl_version_max = TLS1_3_VERSION;
+ break;
+#endif
+ case CURL_SSLVERSION_MAX_NONE: /* none selected */
+ case CURL_SSLVERSION_MAX_DEFAULT: /* max selected */
+ default:
+ /* SSL_CTX_set_max_proto_version states that:
+ setting the maximum to 0 will enable
+ protocol versions up to the highest version
+ supported by the library */
+ ossl_ssl_version_max = 0;
+ break;
+ }
+
+ if(!SSL_CTX_set_max_proto_version(ctx, ossl_ssl_version_max)) {
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ return CURLE_OK;
+}
+#endif
+
+#ifdef OPENSSL_IS_BORINGSSL
+typedef uint32_t ctx_option_t;
+#elif OPENSSL_VERSION_NUMBER >= 0x30000000L
+typedef uint64_t ctx_option_t;
+#else
+typedef long ctx_option_t;
+#endif
+
+#if (OPENSSL_VERSION_NUMBER < 0x10100000L) /* 1.1.0 */
+static CURLcode
+set_ssl_version_min_max_legacy(ctx_option_t *ctx_options,
+ struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ long ssl_version = conn_config->version;
+ long ssl_version_max = conn_config->version_max;
+
+ (void) data; /* In case it's unused. */
+
+ switch(ssl_version) {
+ case CURL_SSLVERSION_TLSv1_3:
+#ifdef TLS1_3_VERSION
+ {
+ struct ssl_connect_data *connssl = cf->ctx;
+ DEBUGASSERT(connssl->backend);
+ SSL_CTX_set_max_proto_version(connssl->backend->ctx, TLS1_3_VERSION);
+ *ctx_options |= SSL_OP_NO_TLSv1_2;
+ }
+#else
+ (void)ctx_options;
+ failf(data, OSSL_PACKAGE " was built without TLS 1.3 support");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ /* FALLTHROUGH */
+ case CURL_SSLVERSION_TLSv1_2:
+#if OPENSSL_VERSION_NUMBER >= 0x1000100FL
+ *ctx_options |= SSL_OP_NO_TLSv1_1;
+#else
+ failf(data, OSSL_PACKAGE " was built without TLS 1.2 support");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ /* FALLTHROUGH */
+ case CURL_SSLVERSION_TLSv1_1:
+#if OPENSSL_VERSION_NUMBER >= 0x1000100FL
+ *ctx_options |= SSL_OP_NO_TLSv1;
+#else
+ failf(data, OSSL_PACKAGE " was built without TLS 1.1 support");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ /* FALLTHROUGH */
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1:
+ break;
+ }
+
+ switch(ssl_version_max) {
+ case CURL_SSLVERSION_MAX_TLSv1_0:
+#if OPENSSL_VERSION_NUMBER >= 0x1000100FL
+ *ctx_options |= SSL_OP_NO_TLSv1_1;
+#endif
+ /* FALLTHROUGH */
+ case CURL_SSLVERSION_MAX_TLSv1_1:
+#if OPENSSL_VERSION_NUMBER >= 0x1000100FL
+ *ctx_options |= SSL_OP_NO_TLSv1_2;
+#endif
+ /* FALLTHROUGH */
+ case CURL_SSLVERSION_MAX_TLSv1_2:
+#ifdef TLS1_3_VERSION
+ *ctx_options |= SSL_OP_NO_TLSv1_3;
+#endif
+ break;
+ case CURL_SSLVERSION_MAX_TLSv1_3:
+#ifdef TLS1_3_VERSION
+ break;
+#else
+ failf(data, OSSL_PACKAGE " was built without TLS 1.3 support");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+ return CURLE_OK;
+}
+#endif
+
+/* The "new session" callback must return zero if the session can be removed
+ * or non-zero if the session has been put into the session cache.
+ */
+static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
+{
+ int res = 0;
+ struct Curl_easy *data;
+ struct Curl_cfilter *cf;
+ const struct ssl_config_data *config;
+ curl_socket_t *sockindex_ptr;
+ int data_idx = ossl_get_ssl_data_index();
+ int cf_idx = ossl_get_ssl_cf_index();
+ int sockindex_idx = ossl_get_ssl_sockindex_index();
+ int proxy_idx = ossl_get_proxy_index();
+ bool isproxy;
+
+ if(data_idx < 0 || cf_idx < 0 || sockindex_idx < 0 || proxy_idx < 0)
+ return 0;
+
+ cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx);
+ data = (struct Curl_easy *) SSL_get_ex_data(ssl, data_idx);
+ /* The sockindex has been stored as a pointer to an array element */
+ sockindex_ptr = (curl_socket_t*) SSL_get_ex_data(ssl, sockindex_idx);
+ if(!cf || !data || !sockindex_ptr)
+ return 0;
+
+ isproxy = Curl_ssl_cf_is_proxy(cf);
+
+ config = Curl_ssl_cf_get_config(cf, data);
+ if(config->primary.sessionid) {
+ bool incache;
+ bool added = FALSE;
+ void *old_ssl_sessionid = NULL;
+
+ Curl_ssl_sessionid_lock(data);
+ if(isproxy)
+ incache = FALSE;
+ else
+ incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL));
+ if(incache) {
+ if(old_ssl_sessionid != ssl_sessionid) {
+ infof(data, "old SSL session ID is stale, removing");
+ Curl_ssl_delsessionid(data, old_ssl_sessionid);
+ incache = FALSE;
+ }
+ }
+
+ if(!incache) {
+ if(!Curl_ssl_addsessionid(cf, data, ssl_sessionid,
+ 0 /* unknown size */, &added)) {
+ if(added) {
+ /* the session has been put into the session cache */
+ res = 1;
+ }
+ }
+ else
+ failf(data, "failed to store ssl session");
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+ return res;
+}
+
+static CURLcode load_cacert_from_memory(X509_STORE *store,
+ const struct curl_blob *ca_info_blob)
+{
+ /* these need to be freed at the end */
+ BIO *cbio = NULL;
+ STACK_OF(X509_INFO) *inf = NULL;
+
+ /* everything else is just a reference */
+ int i, count = 0;
+ X509_INFO *itmp = NULL;
+
+ if(ca_info_blob->len > (size_t)INT_MAX)
+ return CURLE_SSL_CACERT_BADFILE;
+
+ 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(store, itmp->x509)) {
+ ++count;
+ }
+ else {
+ /* set count to 0 to return an error */
+ count = 0;
+ break;
+ }
+ }
+ if(itmp->crl) {
+ if(X509_STORE_add_crl(store, 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 populate_x509_store(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ X509_STORE *store)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ CURLcode result = CURLE_OK;
+ X509_LOOKUP *lookup = NULL;
+ const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
+ const char * const ssl_cafile =
+ /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+ (ca_info_blob ? NULL : conn_config->CAfile);
+ const char * const ssl_capath = conn_config->CApath;
+ const char * const ssl_crlfile = ssl_config->primary.CRLfile;
+ const bool verifypeer = conn_config->verifypeer;
+ bool imported_native_ca = false;
+
+ if(!store)
+ return CURLE_OUT_OF_MEMORY;
+
+#if defined(USE_WIN32_CRYPTO)
+ /* Import certificates from the Windows root certificate store if requested.
+ https://stackoverflow.com/questions/9507184/
+ https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
+ https://datatracker.ietf.org/doc/html/rfc5280 */
+ if((conn_config->verifypeer || conn_config->verifyhost) &&
+ (ssl_config->native_ca_store)) {
+ HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
+
+ if(hStore) {
+ PCCERT_CONTEXT pContext = NULL;
+ /* The array of enhanced key usage OIDs will vary per certificate and is
+ declared outside of the loop so that rather than malloc/free each
+ iteration we can grow it with realloc, when necessary. */
+ CERT_ENHKEY_USAGE *enhkey_usage = NULL;
+ DWORD enhkey_usage_size = 0;
+
+ /* This loop makes a best effort to import all valid certificates from
+ the MS root store. If a certificate cannot be imported it is skipped.
+ 'result' is used to store only hard-fail conditions (such as out of
+ memory) that cause an early break. */
+ result = CURLE_OK;
+ for(;;) {
+ X509 *x509;
+ FILETIME now;
+ BYTE key_usage[2];
+ DWORD req_size;
+ const unsigned char *encoded_cert;
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ char cert_name[256];
+#endif
+
+ pContext = CertEnumCertificatesInStore(hStore, pContext);
+ if(!pContext)
+ break;
+
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
+ NULL, cert_name, sizeof(cert_name))) {
+ strcpy(cert_name, "Unknown");
+ }
+ infof(data, "SSL: Checking cert \"%s\"", cert_name);
+#endif
+
+ encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
+ if(!encoded_cert)
+ continue;
+
+ GetSystemTimeAsFileTime(&now);
+ if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
+ CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
+ continue;
+
+ /* If key usage exists check for signing attribute */
+ if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
+ pContext->pCertInfo,
+ key_usage, sizeof(key_usage))) {
+ if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
+ continue;
+ }
+ else if(GetLastError())
+ continue;
+
+ /* If enhanced key usage exists check for server auth attribute.
+ *
+ * Note "In a Microsoft environment, a certificate might also have EKU
+ * extended properties that specify valid uses for the certificate."
+ * The call below checks both, and behavior varies depending on what is
+ * found. For more details see CertGetEnhancedKeyUsage doc.
+ */
+ if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
+ if(req_size && req_size > enhkey_usage_size) {
+ void *tmp = realloc(enhkey_usage, req_size);
+
+ if(!tmp) {
+ failf(data, "SSL: Out of memory allocating for OID list");
+ result = CURLE_OUT_OF_MEMORY;
+ break;
+ }
+
+ enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
+ enhkey_usage_size = req_size;
+ }
+
+ if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
+ if(!enhkey_usage->cUsageIdentifier) {
+ /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
+ good for all uses. If it returns zero, the certificate has no
+ valid uses." */
+ if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
+ continue;
+ }
+ else {
+ DWORD i;
+ bool found = false;
+
+ for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
+ if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
+ enhkey_usage->rgpszUsageIdentifier[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if(!found)
+ continue;
+ }
+ }
+ else
+ continue;
+ }
+ else
+ continue;
+
+ x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+ if(!x509)
+ continue;
+
+ /* Try to import the certificate. This may fail for legitimate reasons
+ such as duplicate certificate, which is allowed by MS but not
+ OpenSSL. */
+ if(X509_STORE_add_cert(store, x509) == 1) {
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+ infof(data, "SSL: Imported cert \"%s\"", cert_name);
+#endif
+ imported_native_ca = true;
+ }
+ X509_free(x509);
+ }
+
+ free(enhkey_usage);
+ CertFreeCertificateContext(pContext);
+ CertCloseStore(hStore, 0);
+
+ if(result)
+ return result;
+ }
+ if(imported_native_ca)
+ infof(data, "successfully imported Windows CA store");
+ else
+ infof(data, "error importing Windows CA store, continuing anyway");
+ }
+#endif
+
+ if(ca_info_blob) {
+ result = load_cacert_from_memory(store, 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 warn if no certificate verification is required. */
+ infof(data, "error importing CA certificate blob, continuing anyway");
+ }
+ }
+
+ if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) {
+#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
+ /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
+ if(ssl_cafile &&
+ !X509_STORE_load_file(store, ssl_cafile)) {
+ /* Fail if we insist on successfully verifying the server. */
+ failf(data, "error setting certificate file: %s", ssl_cafile);
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ if(ssl_capath &&
+ !X509_STORE_load_path(store, ssl_capath)) {
+ /* Fail if we insist on successfully verifying the server. */
+ failf(data, "error setting certificate path: %s", ssl_capath);
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+#else
+ /* tell OpenSSL where to find CA certificates that are used to verify the
+ server's certificate. */
+ if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) {
+ /* Fail if we insist on successfully verifying the server. */
+ failf(data, "error setting certificate verify locations:"
+ " CAfile: %s CApath: %s",
+ ssl_cafile ? ssl_cafile : "none",
+ ssl_capath ? ssl_capath : "none");
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+#endif
+ infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+ infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+ }
+
+#ifdef CURL_CA_FALLBACK
+ 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 */
+ X509_STORE_set_default_paths(store);
+ }
+#endif
+
+ if(ssl_crlfile) {
+ /* tell OpenSSL where to find CRL file that is used to check certificate
+ * revocation */
+ lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
+ if(!lookup ||
+ (!X509_load_crl_file(lookup, ssl_crlfile, X509_FILETYPE_PEM)) ) {
+ failf(data, "error loading CRL file: %s", ssl_crlfile);
+ return CURLE_SSL_CRL_BADFILE;
+ }
+ /* Everything is fine. */
+ infof(data, "successfully loaded CRL file:");
+ X509_STORE_set_flags(store,
+ X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
+
+ infof(data, " CRLfile: %s", ssl_crlfile);
+ }
+
+ if(verifypeer) {
+ /* Try building a chain using issuers in the trusted store first to avoid
+ problems with server-sent legacy intermediates. Newer versions of
+ OpenSSL do alternate chain checking by default but we do not know how to
+ determine that in a reliable manner.
+ https://rt.openssl.org/Ticket/Display.html?id=3621&user=guest&pass=guest
+ */
+#if defined(X509_V_FLAG_TRUSTED_FIRST)
+ X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST);
+#endif
+#ifdef X509_V_FLAG_PARTIAL_CHAIN
+ if(!ssl_config->no_partialchain && !ssl_crlfile) {
+ /* Have intermediate certificates in the trust store be treated as
+ trust-anchors, in the same way as self-signed root CA certificates
+ are. This allows users to verify servers using the intermediate cert
+ only, instead of needing the whole chain.
+
+ Due to OpenSSL bug https://github.com/openssl/openssl/issues/5081 we
+ cannot do partial chains with a CRL check.
+ */
+ X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN);
+ }
+#endif
+ }
+
+ return result;
+}
+
+#if defined(HAVE_SSL_X509_STORE_SHARE)
+static bool cached_x509_store_expired(const struct Curl_easy *data,
+ const struct multi_ssl_backend_data *mb)
+{
+ const struct ssl_general_config *cfg = &data->set.general_ssl;
+ struct curltime now = Curl_now();
+ timediff_t elapsed_ms = Curl_timediff(now, mb->time);
+ timediff_t timeout_ms = cfg->ca_cache_timeout * (timediff_t)1000;
+
+ if(timeout_ms < 0)
+ return false;
+
+ return elapsed_ms >= timeout_ms;
+}
+
+static bool cached_x509_store_different(
+ struct Curl_cfilter *cf,
+ const struct multi_ssl_backend_data *mb)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ if(!mb->CAfile || !conn_config->CAfile)
+ return mb->CAfile != conn_config->CAfile;
+
+ return strcmp(mb->CAfile, conn_config->CAfile);
+}
+
+static X509_STORE *get_cached_x509_store(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi;
+ X509_STORE *store = NULL;
+
+ if(multi &&
+ multi->ssl_backend_data &&
+ multi->ssl_backend_data->store &&
+ !cached_x509_store_expired(data, multi->ssl_backend_data) &&
+ !cached_x509_store_different(cf, multi->ssl_backend_data)) {
+ store = multi->ssl_backend_data->store;
+ }
+
+ return store;
+}
+
+static void set_cached_x509_store(struct Curl_cfilter *cf,
+ const struct Curl_easy *data,
+ X509_STORE *store)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct Curl_multi *multi = data->multi_easy ? data->multi_easy : data->multi;
+ struct multi_ssl_backend_data *mbackend;
+
+ if(!multi)
+ return;
+
+ if(!multi->ssl_backend_data) {
+ multi->ssl_backend_data = calloc(1, sizeof(struct multi_ssl_backend_data));
+ if(!multi->ssl_backend_data)
+ return;
+ }
+
+ mbackend = multi->ssl_backend_data;
+
+ if(X509_STORE_up_ref(store)) {
+ char *CAfile = NULL;
+
+ if(conn_config->CAfile) {
+ CAfile = strdup(conn_config->CAfile);
+ if(!CAfile) {
+ X509_STORE_free(store);
+ return;
+ }
+ }
+
+ if(mbackend->store) {
+ X509_STORE_free(mbackend->store);
+ free(mbackend->CAfile);
+ }
+
+ mbackend->time = Curl_now();
+ mbackend->store = store;
+ mbackend->CAfile = CAfile;
+ }
+}
+
+static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct ssl_backend_data *backend)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ CURLcode result = CURLE_OK;
+ X509_STORE *cached_store;
+ bool cache_criteria_met;
+
+ /* Consider the X509 store cacheable if it comes exclusively from a CAfile,
+ or no source is provided and we are falling back to openssl's built-in
+ default. */
+ cache_criteria_met = (data->set.general_ssl.ca_cache_timeout != 0) &&
+ conn_config->verifypeer &&
+ !conn_config->CApath &&
+ !conn_config->ca_info_blob &&
+ !ssl_config->primary.CRLfile &&
+ !ssl_config->native_ca_store;
+
+ cached_store = get_cached_x509_store(cf, data);
+ if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) {
+ SSL_CTX_set_cert_store(backend->ctx, cached_store);
+ }
+ else {
+ X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
+
+ result = populate_x509_store(cf, data, store);
+ if(result == CURLE_OK && cache_criteria_met) {
+ set_cached_x509_store(cf, data, store);
+ }
+ }
+
+ return result;
+}
+#else /* HAVE_SSL_X509_STORE_SHARE */
+static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct ssl_backend_data *backend)
+{
+ X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
+
+ return populate_x509_store(cf, data, store);
+}
+#endif /* HAVE_SSL_X509_STORE_SHARE */
+
+static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode result = CURLE_OK;
+ char *ciphers;
+ SSL_METHOD_QUAL SSL_METHOD *req_method = NULL;
+ struct ssl_connect_data *connssl = cf->ctx;
+ ctx_option_t ctx_options = 0;
+ void *ssl_sessionid = NULL;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ BIO *bio;
+
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ bool sni;
+ const char *hostname = connssl->hostname;
+
+#ifdef ENABLE_IPV6
+ struct in6_addr addr;
+#else
+ struct in_addr addr;
+#endif
+#endif
+ const long int ssl_version = conn_config->version;
+#ifdef USE_OPENSSL_SRP
+ const enum CURL_TLSAUTH ssl_authtype = ssl_config->primary.authtype;
+#endif
+ char * const ssl_cert = ssl_config->primary.clientcert;
+ const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
+ const char * const ssl_cert_type = ssl_config->cert_type;
+ const bool verifypeer = conn_config->verifypeer;
+ char error_buffer[256];
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(ssl_connect_1 == connssl->connecting_state);
+ DEBUGASSERT(backend);
+
+ /* Make funny stuff to get random input */
+ result = ossl_seed(data);
+ if(result)
+ return result;
+
+ ssl_config->certverifyresult = !X509_V_OK;
+
+ /* check to see if we've been told to use an explicit SSL/TLS version */
+
+ switch(ssl_version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1_1:
+ case CURL_SSLVERSION_TLSv1_2:
+ case CURL_SSLVERSION_TLSv1_3:
+ /* it will be handled later with the context options */
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ req_method = TLS_client_method();
+#else
+ req_method = SSLv23_client_method();
+#endif
+ use_sni(TRUE);
+ break;
+ case CURL_SSLVERSION_SSLv2:
+ failf(data, "No SSLv2 support");
+ return CURLE_NOT_BUILT_IN;
+ case CURL_SSLVERSION_SSLv3:
+ failf(data, "No SSLv3 support");
+ return CURLE_NOT_BUILT_IN;
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if(backend->ctx) {
+ /* This happens when an error was encountered before in this
+ * step and we are called to do it again. Get rid of any leftover
+ * from the previous call. */
+ ossl_close(cf, data);
+ }
+ backend->ctx = SSL_CTX_new(req_method);
+
+ if(!backend->ctx) {
+ failf(data, "SSL: couldn't create a context: %s",
+ ossl_strerror(ERR_peek_error(), error_buffer, sizeof(error_buffer)));
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ SSL_CTX_set_mode(backend->ctx, SSL_MODE_RELEASE_BUFFERS);
+#endif
+
+#ifdef SSL_CTRL_SET_MSG_CALLBACK
+ if(data->set.fdebug && data->set.verbose) {
+ /* the SSL trace callback is only used for verbose logging */
+ SSL_CTX_set_msg_callback(backend->ctx, ossl_trace);
+ SSL_CTX_set_msg_callback_arg(backend->ctx, cf->conn);
+ set_logger(connssl, data);
+ }
+#endif
+
+ /* OpenSSL contains code to work around lots of bugs and flaws in various
+ SSL-implementations. SSL_CTX_set_options() is used to enabled those
+ work-arounds. The man page for this option states that SSL_OP_ALL enables
+ all the work-arounds and that "It is usually safe to use SSL_OP_ALL to
+ enable the bug workaround options if compatibility with somewhat broken
+ implementations is desired."
+
+ The "-no_ticket" option was introduced in OpenSSL 0.9.8j. It's a flag to
+ disable "rfc4507bis session ticket support". rfc4507bis was later turned
+ into the proper RFC5077: https://datatracker.ietf.org/doc/html/rfc5077
+
+ The enabled extension concerns the session management. I wonder how often
+ libcurl stops a connection and then resumes a TLS session. Also, sending
+ the session data is some overhead. I suggest that you just use your
+ proposed patch (which explicitly disables TICKET).
+
+ If someone writes an application with libcurl and OpenSSL who wants to
+ enable the feature, one can do this in the SSL callback.
+
+ SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG option enabling allowed proper
+ interoperability with web server Netscape Enterprise Server 2.0.1 which
+ was released back in 1996.
+
+ Due to CVE-2010-4180, option SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG has
+ become ineffective as of OpenSSL 0.9.8q and 1.0.0c. In order to mitigate
+ CVE-2010-4180 when using previous OpenSSL versions we no longer enable
+ this option regardless of OpenSSL version and SSL_OP_ALL definition.
+
+ OpenSSL added a work-around for a SSL 3.0/TLS 1.0 CBC vulnerability
+ (https://www.openssl.org/~bodo/tls-cbc.txt). In 0.9.6e they added a bit to
+ SSL_OP_ALL that _disables_ that work-around despite the fact that
+ SSL_OP_ALL is documented to do "rather harmless" workarounds. In order to
+ keep the secure work-around, the SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS bit
+ must not be set.
+ */
+
+ ctx_options = SSL_OP_ALL;
+
+#ifdef SSL_OP_NO_TICKET
+ ctx_options |= SSL_OP_NO_TICKET;
+#endif
+
+#ifdef SSL_OP_NO_COMPRESSION
+ ctx_options |= SSL_OP_NO_COMPRESSION;
+#endif
+
+#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+ /* mitigate CVE-2010-4180 */
+ ctx_options &= ~SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG;
+#endif
+
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+ /* unless the user explicitly asks to allow the protocol vulnerability we
+ use the work-around */
+ if(!ssl_config->enable_beast)
+ ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
+#endif
+
+ switch(ssl_version) {
+ case CURL_SSLVERSION_SSLv2:
+ case CURL_SSLVERSION_SSLv3:
+ return CURLE_NOT_BUILT_IN;
+
+ /* "--tlsv<x.y>" options mean TLS >= version <x.y> */
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1: /* TLS >= version 1.0 */
+ case CURL_SSLVERSION_TLSv1_0: /* TLS >= version 1.0 */
+ case CURL_SSLVERSION_TLSv1_1: /* TLS >= version 1.1 */
+ case CURL_SSLVERSION_TLSv1_2: /* TLS >= version 1.2 */
+ case CURL_SSLVERSION_TLSv1_3: /* TLS >= version 1.3 */
+ /* asking for any TLS version as the minimum, means no SSL versions
+ allowed */
+ ctx_options |= SSL_OP_NO_SSLv2;
+ ctx_options |= SSL_OP_NO_SSLv3;
+
+#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) /* 1.1.0 */
+ result = set_ssl_version_min_max(cf, backend->ctx);
+#else
+ result = set_ssl_version_min_max_legacy(&ctx_options, cf, data);
+#endif
+ if(result != CURLE_OK)
+ return result;
+ break;
+
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ SSL_CTX_set_options(backend->ctx, ctx_options);
+
+#ifdef HAS_ALPN
+ if(cf->conn->bits.tls_enable_alpn) {
+ int cur = 0;
+ unsigned char protocols[128];
+
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2
+#ifndef CURL_DISABLE_PROXY
+ && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
+#endif
+ ) {
+ protocols[cur++] = ALPN_H2_LENGTH;
+
+ memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
+ cur += ALPN_H2_LENGTH;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+ }
+#endif
+
+ protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
+ memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
+ cur += ALPN_HTTP_1_1_LENGTH;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+
+ /* expects length prefixed preference ordered list of protocols in wire
+ * format
+ */
+ if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) {
+ failf(data, "Error setting ALPN");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+#endif
+
+ if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
+ if(!result &&
+ !cert_stuff(data, backend->ctx,
+ ssl_cert, ssl_cert_blob, ssl_cert_type,
+ ssl_config->key, ssl_config->key_blob,
+ ssl_config->key_type, ssl_config->key_passwd))
+ result = CURLE_SSL_CERTPROBLEM;
+ if(result)
+ /* failf() is already done in cert_stuff() */
+ return result;
+ }
+
+ ciphers = conn_config->cipher_list;
+ if(!ciphers)
+ ciphers = (char *)DEFAULT_CIPHER_SELECTION;
+ if(ciphers) {
+ if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) {
+ failf(data, "failed setting cipher list: %s", ciphers);
+ return CURLE_SSL_CIPHER;
+ }
+ infof(data, "Cipher selection: %s", ciphers);
+ }
+
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+ {
+ char *ciphers13 = conn_config->cipher_list13;
+ if(ciphers13) {
+ if(!SSL_CTX_set_ciphersuites(backend->ctx, ciphers13)) {
+ failf(data, "failed setting TLS 1.3 cipher suite: %s", ciphers13);
+ return CURLE_SSL_CIPHER;
+ }
+ infof(data, "TLS 1.3 cipher selection: %s", ciphers13);
+ }
+ }
+#endif
+
+#ifdef HAVE_SSL_CTX_SET_POST_HANDSHAKE_AUTH
+ /* OpenSSL 1.1.1 requires clients to opt-in for PHA */
+ SSL_CTX_set_post_handshake_auth(backend->ctx, 1);
+#endif
+
+#ifdef HAVE_SSL_CTX_SET_EC_CURVES
+ {
+ char *curves = conn_config->curves;
+ if(curves) {
+ if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) {
+ failf(data, "failed setting curves list: '%s'", curves);
+ return CURLE_SSL_CIPHER;
+ }
+ }
+ }
+#endif
+
+#ifdef USE_OPENSSL_SRP
+ if((ssl_authtype == CURL_TLSAUTH_SRP) &&
+ Curl_auth_allowed_to_host(data)) {
+ char * const ssl_username = ssl_config->primary.username;
+ char * const ssl_password = ssl_config->primary.password;
+ infof(data, "Using TLS-SRP username: %s", ssl_username);
+
+ if(!SSL_CTX_set_srp_username(backend->ctx, ssl_username)) {
+ failf(data, "Unable to set SRP user name");
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+ if(!SSL_CTX_set_srp_password(backend->ctx, ssl_password)) {
+ failf(data, "failed setting SRP password");
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+ if(!conn_config->cipher_list) {
+ infof(data, "Setting cipher list SRP");
+
+ if(!SSL_CTX_set_cipher_list(backend->ctx, "SRP")) {
+ failf(data, "failed setting SRP cipher list");
+ return CURLE_SSL_CIPHER;
+ }
+ }
+ }
+#endif
+
+ result = set_up_x509_store(cf, data, backend);
+ if(result)
+ return result;
+
+ /* OpenSSL always tries to verify the peer, this only says whether it should
+ * fail to connect if the verification fails, or if it should continue
+ * anyway. In the latter case the result of the verification is checked with
+ * SSL_get_verify_result() below. */
+ SSL_CTX_set_verify(backend->ctx,
+ verifypeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
+
+ /* Enable logging of secrets to the file specified in env SSLKEYLOGFILE. */
+#ifdef HAVE_KEYLOG_CALLBACK
+ if(Curl_tls_keylog_enabled()) {
+ SSL_CTX_set_keylog_callback(backend->ctx, ossl_keylog_callback);
+ }
+#endif
+
+ /* Enable the session cache because it's a prerequisite for the "new session"
+ * callback. Use the "external storage" mode to prevent OpenSSL from creating
+ * an internal session cache.
+ */
+ SSL_CTX_set_session_cache_mode(backend->ctx,
+ SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL);
+ SSL_CTX_sess_set_new_cb(backend->ctx, ossl_new_session_cb);
+
+ /* give application a chance to interfere with SSL set up. */
+ if(data->set.ssl.fsslctx) {
+ Curl_set_in_callback(data, true);
+ result = (*data->set.ssl.fsslctx)(data, backend->ctx,
+ data->set.ssl.fsslctxp);
+ Curl_set_in_callback(data, false);
+ if(result) {
+ failf(data, "error signaled by ssl ctx callback");
+ return result;
+ }
+ }
+
+ /* Let's make an SSL structure */
+ if(backend->handle)
+ SSL_free(backend->handle);
+ backend->handle = SSL_new(backend->ctx);
+ if(!backend->handle) {
+ failf(data, "SSL: couldn't create a context (handle)");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(OPENSSL_NO_OCSP)
+ if(conn_config->verifystatus)
+ SSL_set_tlsext_status_type(backend->handle, TLSEXT_STATUSTYPE_ocsp);
+#endif
+
+#if defined(OPENSSL_IS_BORINGSSL) && defined(ALLOW_RENEG)
+ SSL_set_renegotiate_mode(backend->handle, ssl_renegotiate_freely);
+#endif
+
+ SSL_set_connect_state(backend->handle);
+
+ backend->server_cert = 0x0;
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+ if((0 == Curl_inet_pton(AF_INET, hostname, &addr)) &&
+#ifdef ENABLE_IPV6
+ (0 == Curl_inet_pton(AF_INET6, hostname, &addr)) &&
+#endif
+ sni) {
+ char *snihost = Curl_ssl_snihost(data, hostname, NULL);
+ if(!snihost || !SSL_set_tlsext_host_name(backend->handle, snihost)) {
+ failf(data, "Failed set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+#endif
+
+ if(!ossl_attach_data(cf, data)) {
+ /* Maybe the internal errors of SSL_get_ex_new_index or SSL_set_ex_data */
+ failf(data, "SSL: ossl_attach_data failed: %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if(ssl_config->primary.sessionid) {
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) {
+ /* we got a session id, use it! */
+ if(!SSL_set_session(backend->handle, ssl_sessionid)) {
+ Curl_ssl_sessionid_unlock(data);
+ failf(data, "SSL: SSL_set_session failed: %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ /* Informational message */
+ infof(data, "SSL re-using session ID");
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+ bio = BIO_new(bio_cf_method);
+ if(!bio)
+ return CURLE_OUT_OF_MEMORY;
+
+ BIO_set_data(bio, cf);
+ SSL_set_bio(backend->handle, bio, bio);
+
+ connssl->connecting_state = ssl_connect_2;
+
+ return CURLE_OK;
+}
+
+static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ int err;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ DEBUGASSERT(ssl_connect_2 == connssl->connecting_state
+ || ssl_connect_2_reading == connssl->connecting_state
+ || ssl_connect_2_writing == connssl->connecting_state);
+ DEBUGASSERT(backend);
+
+ ERR_clear_error();
+
+ err = SSL_connect(backend->handle);
+#ifndef HAVE_KEYLOG_CALLBACK
+ if(Curl_tls_keylog_enabled()) {
+ /* If key logging is enabled, wait for the handshake to complete and then
+ * proceed with logging secrets (for TLS 1.2 or older).
+ */
+ ossl_log_tls12_secret(backend->handle, &backend->keylog_done);
+ }
+#endif
+
+ /* 1 is fine
+ 0 is "not successful but was shut down controlled"
+ <0 is "handshake was not successful, because a fatal error occurred" */
+ if(1 != err) {
+ int detail = SSL_get_error(backend->handle, err);
+
+ if(SSL_ERROR_WANT_READ == detail) {
+ connssl->connecting_state = ssl_connect_2_reading;
+ return CURLE_OK;
+ }
+ if(SSL_ERROR_WANT_WRITE == detail) {
+ connssl->connecting_state = ssl_connect_2_writing;
+ return CURLE_OK;
+ }
+#ifdef SSL_ERROR_WANT_ASYNC
+ if(SSL_ERROR_WANT_ASYNC == detail) {
+ connssl->connecting_state = ssl_connect_2;
+ return CURLE_OK;
+ }
+#endif
+ else if(backend->io_result == CURLE_AGAIN) {
+ return CURLE_OK;
+ }
+ else {
+ /* untreated error */
+ unsigned long errdetail;
+ char error_buffer[256]="";
+ CURLcode result;
+ long lerr;
+ int lib;
+ int reason;
+
+ /* the connection failed, we're not waiting for anything else. */
+ connssl->connecting_state = ssl_connect_2;
+
+ /* Get the earliest error code from the thread's error queue and remove
+ the entry. */
+ errdetail = ERR_get_error();
+
+ /* Extract which lib and reason */
+ lib = ERR_GET_LIB(errdetail);
+ reason = ERR_GET_REASON(errdetail);
+
+ if((lib == ERR_LIB_SSL) &&
+ ((reason == SSL_R_CERTIFICATE_VERIFY_FAILED) ||
+ (reason == SSL_R_SSLV3_ALERT_CERTIFICATE_EXPIRED))) {
+ result = CURLE_PEER_FAILED_VERIFICATION;
+
+ lerr = SSL_get_verify_result(backend->handle);
+ if(lerr != X509_V_OK) {
+ ssl_config->certverifyresult = lerr;
+ msnprintf(error_buffer, sizeof(error_buffer),
+ "SSL certificate problem: %s",
+ X509_verify_cert_error_string(lerr));
+ }
+ else
+ /* strcpy() is fine here as long as the string fits within
+ error_buffer */
+ strcpy(error_buffer, "SSL certificate verification failed");
+ }
+#if (OPENSSL_VERSION_NUMBER >= 0x10101000L && \
+ !defined(LIBRESSL_VERSION_NUMBER) && \
+ !defined(OPENSSL_IS_BORINGSSL))
+ /* SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED is only available on
+ OpenSSL version above v1.1.1, not LibreSSL nor BoringSSL */
+ else if((lib == ERR_LIB_SSL) &&
+ (reason == SSL_R_TLSV13_ALERT_CERTIFICATE_REQUIRED)) {
+ /* If client certificate is required, communicate the
+ error to client */
+ result = CURLE_SSL_CLIENTCERT;
+ ossl_strerror(errdetail, error_buffer, sizeof(error_buffer));
+ }
+#endif
+ else {
+ result = CURLE_SSL_CONNECT_ERROR;
+ ossl_strerror(errdetail, error_buffer, sizeof(error_buffer));
+ }
+
+ /* detail is already set to the SSL error above */
+
+ /* If we e.g. use SSLv2 request-method and the server doesn't like us
+ * (RST connection, etc.), OpenSSL gives no explanation whatsoever and
+ * the SO_ERROR is also lost.
+ */
+ if(CURLE_SSL_CONNECT_ERROR == result && errdetail == 0) {
+ char extramsg[80]="";
+ int sockerr = SOCKERRNO;
+
+ if(sockerr && detail == SSL_ERROR_SYSCALL)
+ Curl_strerror(sockerr, extramsg, sizeof(extramsg));
+ failf(data, OSSL_PACKAGE " SSL_connect: %s in connection to %s:%d ",
+ extramsg[0] ? extramsg : SSL_ERROR_to_str(detail),
+ connssl->hostname, connssl->port);
+ return result;
+ }
+
+ /* Could be a CERT problem */
+ failf(data, "%s", error_buffer);
+
+ return result;
+ }
+ }
+ else {
+ /* we connected fine, we're not waiting for anything else. */
+ connssl->connecting_state = ssl_connect_3;
+
+ /* Informational message */
+ infof(data, "SSL connection using %s / %s",
+ SSL_get_version(backend->handle),
+ SSL_get_cipher(backend->handle));
+
+#ifdef HAS_ALPN
+ /* Sets data and len to negotiated protocol, len is 0 if no protocol was
+ * negotiated
+ */
+ if(cf->conn->bits.tls_enable_alpn) {
+ const unsigned char *neg_protocol;
+ unsigned int len;
+ SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len);
+ if(len) {
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, len, neg_protocol);
+
+#ifdef USE_HTTP2
+ if(len == ALPN_H2_LENGTH &&
+ !memcmp(ALPN_H2, neg_protocol, len)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+ }
+ else
+#endif
+ if(len == ALPN_HTTP_1_1_LENGTH &&
+ !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+ }
+ }
+ else
+ infof(data, VTLS_INFOF_NO_ALPN);
+
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+ }
+#endif
+
+ return CURLE_OK;
+ }
+}
+
+/*
+ * Heavily modified from:
+ * https://www.owasp.org/index.php/Certificate_and_Public_Key_Pinning#OpenSSL
+ */
+static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, X509* cert,
+ const char *pinnedpubkey)
+{
+ /* Scratch */
+ int len1 = 0, len2 = 0;
+ unsigned char *buff1 = NULL, *temp = NULL;
+
+ /* Result is returned to caller */
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+ /* if a path wasn't specified, don't pin */
+ if(!pinnedpubkey)
+ return CURLE_OK;
+
+ if(!cert)
+ return result;
+
+ do {
+ /* Begin Gyrations to get the subjectPublicKeyInfo */
+ /* Thanks to Viktor Dukhovni on the OpenSSL mailing list */
+
+ /* https://groups.google.com/group/mailing.openssl.users/browse_thread
+ /thread/d61858dae102c6c7 */
+ len1 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), NULL);
+ if(len1 < 1)
+ break; /* failed */
+
+ buff1 = temp = malloc(len1);
+ if(!buff1)
+ break; /* failed */
+
+ /* https://www.openssl.org/docs/crypto/d2i_X509.html */
+ len2 = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &temp);
+
+ /*
+ * These checks are verifying we got back the same values as when we
+ * sized the buffer. It's pretty weak since they should always be the
+ * same. But it gives us something to test.
+ */
+ if((len1 != len2) || !temp || ((temp - buff1) != len1))
+ break; /* failed */
+
+ /* End Gyrations */
+
+ /* The one good exit point */
+ result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1);
+ } while(0);
+
+ if(buff1)
+ free(buff1);
+
+ return result;
+}
+
+/*
+ * Get the server cert, verify it and show it, etc., only call failf() if the
+ * 'strict' argument is TRUE as otherwise all this is for informational
+ * purposes only!
+ *
+ * We check certificates to authenticate the server; otherwise we risk
+ * man-in-the-middle attack.
+ */
+static CURLcode servercert(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool strict)
+{
+ struct connectdata *conn = cf->conn;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ CURLcode result = CURLE_OK;
+ int rc;
+ long lerr;
+ X509 *issuer;
+ BIO *fp = NULL;
+ char error_buffer[256]="";
+ char buffer[2048];
+ const char *ptr;
+ BIO *mem = BIO_new(BIO_s_mem());
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ if(!mem) {
+ failf(data,
+ "BIO_new return NULL, " OSSL_PACKAGE
+ " error %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ if(data->set.ssl.certinfo)
+ /* asked to gather certificate info */
+ (void)Curl_ossl_certchain(data, connssl->backend->handle);
+
+ backend->server_cert = SSL_get1_peer_certificate(backend->handle);
+ if(!backend->server_cert) {
+ BIO_free(mem);
+ if(!strict)
+ return CURLE_OK;
+
+ failf(data, "SSL: couldn't get peer certificate");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ infof(data, "%s certificate:",
+ Curl_ssl_cf_is_proxy(cf)? "Proxy" : "Server");
+
+ rc = x509_name_oneline(X509_get_subject_name(backend->server_cert),
+ buffer, sizeof(buffer));
+ infof(data, " subject: %s", rc?"[NONE]":buffer);
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ {
+ long len;
+ ASN1_TIME_print(mem, X509_get0_notBefore(backend->server_cert));
+ len = BIO_get_mem_data(mem, (char **) &ptr);
+ infof(data, " start date: %.*s", (int)len, ptr);
+ (void)BIO_reset(mem);
+
+ ASN1_TIME_print(mem, X509_get0_notAfter(backend->server_cert));
+ len = BIO_get_mem_data(mem, (char **) &ptr);
+ infof(data, " expire date: %.*s", (int)len, ptr);
+ (void)BIO_reset(mem);
+ }
+#endif
+
+ BIO_free(mem);
+
+ if(conn_config->verifyhost) {
+ result = Curl_ossl_verifyhost(data, conn, backend->server_cert);
+ if(result) {
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ return result;
+ }
+ }
+
+ rc = x509_name_oneline(X509_get_issuer_name(backend->server_cert),
+ buffer, sizeof(buffer));
+ if(rc) {
+ if(strict)
+ failf(data, "SSL: couldn't get X509-issuer name");
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else {
+ infof(data, " issuer: %s", buffer);
+
+ /* We could do all sorts of certificate verification stuff here before
+ deallocating the certificate. */
+
+ /* e.g. match issuer name with provided issuer certificate */
+ if(conn_config->issuercert || conn_config->issuercert_blob) {
+ if(conn_config->issuercert_blob) {
+ fp = BIO_new_mem_buf(conn_config->issuercert_blob->data,
+ (int)conn_config->issuercert_blob->len);
+ if(!fp) {
+ failf(data,
+ "BIO_new_mem_buf NULL, " OSSL_PACKAGE
+ " error %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ fp = BIO_new(BIO_s_file());
+ if(!fp) {
+ failf(data,
+ "BIO_new return NULL, " OSSL_PACKAGE
+ " error %s",
+ ossl_strerror(ERR_get_error(), error_buffer,
+ sizeof(error_buffer)) );
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ if(BIO_read_filename(fp, conn_config->issuercert) <= 0) {
+ if(strict)
+ failf(data, "SSL: Unable to open issuer cert (%s)",
+ conn_config->issuercert);
+ BIO_free(fp);
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ return CURLE_SSL_ISSUER_ERROR;
+ }
+ }
+
+ issuer = PEM_read_bio_X509(fp, NULL, ZERO_NULL, NULL);
+ if(!issuer) {
+ if(strict)
+ failf(data, "SSL: Unable to read issuer cert (%s)",
+ conn_config->issuercert);
+ BIO_free(fp);
+ X509_free(issuer);
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ return CURLE_SSL_ISSUER_ERROR;
+ }
+
+ if(X509_check_issued(issuer, backend->server_cert) != X509_V_OK) {
+ if(strict)
+ failf(data, "SSL: Certificate issuer check failed (%s)",
+ conn_config->issuercert);
+ BIO_free(fp);
+ X509_free(issuer);
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ return CURLE_SSL_ISSUER_ERROR;
+ }
+
+ infof(data, " SSL certificate issuer check ok (%s)",
+ conn_config->issuercert);
+ BIO_free(fp);
+ X509_free(issuer);
+ }
+
+ lerr = SSL_get_verify_result(backend->handle);
+ ssl_config->certverifyresult = lerr;
+ if(lerr != X509_V_OK) {
+ if(conn_config->verifypeer) {
+ /* We probably never reach this, because SSL_connect() will fail
+ and we return earlier if verifypeer is set? */
+ if(strict)
+ failf(data, "SSL certificate verify result: %s (%ld)",
+ X509_verify_cert_error_string(lerr), lerr);
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else
+ infof(data, " SSL certificate verify result: %s (%ld),"
+ " continuing anyway.",
+ X509_verify_cert_error_string(lerr), lerr);
+ }
+ else
+ infof(data, " SSL certificate verify ok.");
+ }
+
+#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(OPENSSL_NO_OCSP)
+ if(conn_config->verifystatus) {
+ result = verifystatus(cf, data);
+ if(result) {
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ return result;
+ }
+ }
+#endif
+
+ if(!strict)
+ /* when not strict, we don't bother about the verify cert problems */
+ result = CURLE_OK;
+
+ ptr = Curl_ssl_cf_is_proxy(cf)?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]:
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+ if(!result && ptr) {
+ result = pkp_pin_peer_pubkey(data, backend->server_cert, ptr);
+ if(result)
+ failf(data, "SSL: public key does not match pinned public key");
+ }
+
+ X509_free(backend->server_cert);
+ backend->server_cert = NULL;
+ connssl->connecting_state = ssl_connect_done;
+
+ return result;
+}
+
+static CURLcode ossl_connect_step3(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode result = CURLE_OK;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+
+ DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
+
+ /*
+ * We check certificates to authenticate the server; otherwise we risk
+ * man-in-the-middle attack; NEVERTHELESS, if we're told explicitly not to
+ * verify the peer, ignore faults and failures from the server cert
+ * operations.
+ */
+
+ result = servercert(cf, data, conn_config->verifypeer ||
+ conn_config->verifyhost);
+
+ if(!result)
+ connssl->connecting_state = ssl_connect_done;
+
+ return result;
+}
+
+static CURLcode ossl_connect_common(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool nonblocking,
+ bool *done)
+{
+ CURLcode result = CURLE_OK;
+ struct ssl_connect_data *connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ int what;
+
+ /* check if the connection has already been established */
+ if(ssl_connection_complete == connssl->state) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ if(ssl_connect_1 == connssl->connecting_state) {
+ /* Find out how much more time we're allowed */
+ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time is already up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ result = ossl_connect_step1(cf, data);
+ if(result)
+ goto out;
+ }
+
+ while(ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state) {
+
+ /* check allowed time left */
+ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ result = CURLE_OPERATION_TIMEDOUT;
+ goto out;
+ }
+
+ /* if ssl is expecting something, check if it's available. */
+ if(connssl->connecting_state == ssl_connect_2_reading ||
+ connssl->connecting_state == ssl_connect_2_writing) {
+
+ curl_socket_t writefd = ssl_connect_2_writing ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ nonblocking?0:timeout_ms);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ result = CURLE_SSL_CONNECT_ERROR;
+ goto out;
+ }
+ if(0 == what) {
+ if(nonblocking) {
+ *done = FALSE;
+ result = CURLE_OK;
+ goto out;
+ }
+ /* timeout */
+ failf(data, "SSL connection timeout");
+ result = CURLE_OPERATION_TIMEDOUT;
+ goto out;
+ }
+ /* socket is readable or writable */
+ }
+
+ /* Run transaction, and return to the caller if it failed or if this
+ * connection is done nonblocking and this loop would execute again. This
+ * permits the owner of a multi handle to abort a connection attempt
+ * before step2 has completed while ensuring that a client using select()
+ * or epoll() will always have a valid fdset to wait on.
+ */
+ result = ossl_connect_step2(cf, data);
+ if(result || (nonblocking &&
+ (ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state)))
+ goto out;
+
+ } /* repeat step2 until all transactions are done. */
+
+ if(ssl_connect_3 == connssl->connecting_state) {
+ result = ossl_connect_step3(cf, data);
+ if(result)
+ goto out;
+ }
+
+ if(ssl_connect_done == connssl->connecting_state) {
+ connssl->state = ssl_connection_complete;
+ *done = TRUE;
+ }
+ else
+ *done = FALSE;
+
+ /* Reset our connect state machine */
+ connssl->connecting_state = ssl_connect_1;
+
+out:
+ return result;
+}
+
+static CURLcode ossl_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return ossl_connect_common(cf, data, TRUE, done);
+}
+
+static CURLcode ossl_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode result;
+ bool done = FALSE;
+
+ result = ossl_connect_common(cf, data, FALSE, &done);
+ if(result)
+ return result;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+static bool ossl_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx = cf->ctx;
+
+ (void)data;
+ DEBUGASSERT(ctx && ctx->backend);
+ if(ctx->backend->handle && SSL_pending(ctx->backend->handle))
+ return TRUE;
+ return FALSE;
+}
+
+static ssize_t ossl_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ /* SSL_write() is said to return 'int' while write() and send() returns
+ 'size_t' */
+ int err;
+ char error_buffer[256];
+ unsigned long sslerror;
+ int memlen;
+ int rc;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ ERR_clear_error();
+
+ memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
+ set_logger(connssl, data);
+ rc = SSL_write(backend->handle, mem, memlen);
+
+ if(rc <= 0) {
+ err = SSL_get_error(backend->handle, rc);
+
+ switch(err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ /* The operation did not complete; the same TLS/SSL I/O function
+ should be called again later. This is basically an EWOULDBLOCK
+ equivalent. */
+ *curlcode = CURLE_AGAIN;
+ rc = -1;
+ goto out;
+ case SSL_ERROR_SYSCALL:
+ {
+ int sockerr = SOCKERRNO;
+
+ if(backend->io_result == CURLE_AGAIN) {
+ *curlcode = CURLE_AGAIN;
+ rc = -1;
+ goto out;
+ }
+ sslerror = ERR_get_error();
+ if(sslerror)
+ ossl_strerror(sslerror, error_buffer, sizeof(error_buffer));
+ else if(sockerr)
+ Curl_strerror(sockerr, error_buffer, sizeof(error_buffer));
+ else {
+ strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer));
+ error_buffer[sizeof(error_buffer) - 1] = '\0';
+ }
+ failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d",
+ error_buffer, sockerr);
+ *curlcode = CURLE_SEND_ERROR;
+ rc = -1;
+ goto out;
+ }
+ case SSL_ERROR_SSL: {
+ /* A failure in the SSL library occurred, usually a protocol error.
+ The OpenSSL error queue contains more information on the error. */
+ struct Curl_cfilter *cf_ssl_next = Curl_ssl_cf_get_ssl(cf->next);
+ struct ssl_connect_data *connssl_next = cf_ssl_next?
+ cf_ssl_next->ctx : NULL;
+ sslerror = ERR_get_error();
+ if(ERR_GET_LIB(sslerror) == ERR_LIB_SSL &&
+ ERR_GET_REASON(sslerror) == SSL_R_BIO_NOT_SET &&
+ connssl->state == ssl_connection_complete &&
+ (connssl_next && connssl_next->state == ssl_connection_complete)
+ ) {
+ char ver[120];
+ (void)ossl_version(ver, sizeof(ver));
+ failf(data, "Error: %s does not support double SSL tunneling.", ver);
+ }
+ else
+ failf(data, "SSL_write() error: %s",
+ ossl_strerror(sslerror, error_buffer, sizeof(error_buffer)));
+ *curlcode = CURLE_SEND_ERROR;
+ rc = -1;
+ goto out;
+ }
+ default:
+ /* a true error */
+ failf(data, OSSL_PACKAGE " SSL_write: %s, errno %d",
+ SSL_ERROR_to_str(err), SOCKERRNO);
+ *curlcode = CURLE_SEND_ERROR;
+ rc = -1;
+ goto out;
+ }
+ }
+ *curlcode = CURLE_OK;
+
+out:
+ return (ssize_t)rc; /* number of bytes */
+}
+
+static ssize_t ossl_recv(struct Curl_cfilter *cf,
+ struct Curl_easy *data, /* transfer */
+ char *buf, /* store read data here */
+ size_t buffersize, /* max amount to read */
+ CURLcode *curlcode)
+{
+ char error_buffer[256];
+ unsigned long sslerror;
+ ssize_t nread;
+ int buffsize;
+ struct connectdata *conn = cf->conn;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ ERR_clear_error();
+
+ buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
+ set_logger(connssl, data);
+ nread = (ssize_t)SSL_read(backend->handle, buf, buffsize);
+
+ if(nread <= 0) {
+ /* failed SSL_read */
+ int err = SSL_get_error(backend->handle, (int)nread);
+
+ switch(err) {
+ case SSL_ERROR_NONE: /* this is not an error */
+ break;
+ case SSL_ERROR_ZERO_RETURN: /* no more data */
+ /* close_notify alert */
+ if(cf->sockindex == FIRSTSOCKET)
+ /* mark the connection for close if it is indeed the control
+ connection */
+ connclose(conn, "TLS close_notify");
+ break;
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ /* there's data pending, re-invoke SSL_read() */
+ *curlcode = CURLE_AGAIN;
+ nread = -1;
+ goto out;
+ default:
+ /* openssl/ssl.h for SSL_ERROR_SYSCALL says "look at error stack/return
+ value/errno" */
+ /* https://www.openssl.org/docs/crypto/ERR_get_error.html */
+ if(backend->io_result == CURLE_AGAIN) {
+ *curlcode = CURLE_AGAIN;
+ nread = -1;
+ goto out;
+ }
+ sslerror = ERR_get_error();
+ if((nread < 0) || sslerror) {
+ /* If the return code was negative or there actually is an error in the
+ queue */
+ int sockerr = SOCKERRNO;
+ if(sslerror)
+ ossl_strerror(sslerror, error_buffer, sizeof(error_buffer));
+ else if(sockerr && err == SSL_ERROR_SYSCALL)
+ Curl_strerror(sockerr, error_buffer, sizeof(error_buffer));
+ else {
+ strncpy(error_buffer, SSL_ERROR_to_str(err), sizeof(error_buffer));
+ error_buffer[sizeof(error_buffer) - 1] = '\0';
+ }
+ failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d",
+ error_buffer, sockerr);
+ *curlcode = CURLE_RECV_ERROR;
+ nread = -1;
+ goto out;
+ }
+ /* For debug builds be a little stricter and error on any
+ SSL_ERROR_SYSCALL. For example a server may have closed the connection
+ abruptly without a close_notify alert. For compatibility with older
+ peers we don't do this by default. #4624
+
+ We can use this to gauge how many users may be affected, and
+ if it goes ok eventually transition to allow in dev and release with
+ the newest OpenSSL: #if (OPENSSL_VERSION_NUMBER >= 0x10101000L) */
+#ifdef DEBUGBUILD
+ if(err == SSL_ERROR_SYSCALL) {
+ int sockerr = SOCKERRNO;
+ if(sockerr)
+ Curl_strerror(sockerr, error_buffer, sizeof(error_buffer));
+ else {
+ msnprintf(error_buffer, sizeof(error_buffer),
+ "Connection closed abruptly");
+ }
+ failf(data, OSSL_PACKAGE " SSL_read: %s, errno %d"
+ " (Fatal because this is a curl debug build)",
+ error_buffer, sockerr);
+ *curlcode = CURLE_RECV_ERROR;
+ nread = -1;
+ goto out;
+ }
+#endif
+ }
+ }
+
+out:
+ return nread;
+}
+
+static size_t ossl_version(char *buffer, size_t size)
+{
+#ifdef LIBRESSL_VERSION_NUMBER
+#ifdef HAVE_OPENSSL_VERSION
+ char *p;
+ int count;
+ const char *ver = OpenSSL_version(OPENSSL_VERSION);
+ const char expected[] = OSSL_PACKAGE " "; /* ie "LibreSSL " */
+ if(strncasecompare(ver, expected, sizeof(expected) - 1)) {
+ ver += sizeof(expected) - 1;
+ }
+ count = msnprintf(buffer, size, "%s/%s", OSSL_PACKAGE, ver);
+ for(p = buffer; *p; ++p) {
+ if(ISBLANK(*p))
+ *p = '_';
+ }
+ return count;
+#else
+ return msnprintf(buffer, size, "%s/%lx.%lx.%lx",
+ OSSL_PACKAGE,
+ (LIBRESSL_VERSION_NUMBER>>28)&0xf,
+ (LIBRESSL_VERSION_NUMBER>>20)&0xff,
+ (LIBRESSL_VERSION_NUMBER>>12)&0xff);
+#endif
+#elif defined(OPENSSL_IS_BORINGSSL)
+#ifdef CURL_BORINGSSL_VERSION
+ return msnprintf(buffer, size, "%s/%s",
+ OSSL_PACKAGE,
+ CURL_BORINGSSL_VERSION);
+#else
+ return msnprintf(buffer, size, OSSL_PACKAGE);
+#endif
+#elif defined(HAVE_OPENSSL_VERSION) && defined(OPENSSL_VERSION_STRING)
+ return msnprintf(buffer, size, "%s/%s",
+ OSSL_PACKAGE, OpenSSL_version(OPENSSL_VERSION_STRING));
+#else
+ /* not LibreSSL, BoringSSL and not using OpenSSL_version */
+
+ char sub[3];
+ unsigned long ssleay_value;
+ sub[2]='\0';
+ sub[1]='\0';
+ ssleay_value = OpenSSL_version_num();
+ if(ssleay_value < 0x906000) {
+ ssleay_value = SSLEAY_VERSION_NUMBER;
+ sub[0]='\0';
+ }
+ else {
+ if(ssleay_value&0xff0) {
+ int minor_ver = (ssleay_value >> 4) & 0xff;
+ if(minor_ver > 26) {
+ /* handle extended version introduced for 0.9.8za */
+ sub[1] = (char) ((minor_ver - 1) % 26 + 'a' + 1);
+ sub[0] = 'z';
+ }
+ else {
+ sub[0] = (char) (minor_ver + 'a' - 1);
+ }
+ }
+ else
+ sub[0]='\0';
+ }
+
+ return msnprintf(buffer, size, "%s/%lx.%lx.%lx%s"
+#ifdef OPENSSL_FIPS
+ "-fips"
+#endif
+ ,
+ OSSL_PACKAGE,
+ (ssleay_value>>28)&0xf,
+ (ssleay_value>>20)&0xff,
+ (ssleay_value>>12)&0xff,
+ sub);
+#endif /* OPENSSL_IS_BORINGSSL */
+}
+
+/* can be called with data == NULL */
+static CURLcode ossl_random(struct Curl_easy *data,
+ unsigned char *entropy, size_t length)
+{
+ int rc;
+ if(data) {
+ if(ossl_seed(data)) /* Initiate the seed if not already done */
+ return CURLE_FAILED_INIT; /* couldn't seed for some reason */
+ }
+ else {
+ if(!rand_enough())
+ return CURLE_FAILED_INIT;
+ }
+ /* RAND_bytes() returns 1 on success, 0 otherwise. */
+ rc = RAND_bytes(entropy, curlx_uztosi(length));
+ return (rc == 1 ? CURLE_OK : CURLE_FAILED_INIT);
+}
+
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
+static CURLcode ossl_sha256sum(const unsigned char *tmp, /* input */
+ size_t tmplen,
+ unsigned char *sha256sum /* output */,
+ size_t unused)
+{
+ EVP_MD_CTX *mdctx;
+ unsigned int len = 0;
+ (void) unused;
+
+ mdctx = EVP_MD_CTX_create();
+ if(!mdctx)
+ return CURLE_OUT_OF_MEMORY;
+ EVP_DigestInit(mdctx, EVP_sha256());
+ EVP_DigestUpdate(mdctx, tmp, tmplen);
+ EVP_DigestFinal_ex(mdctx, sha256sum, &len);
+ EVP_MD_CTX_destroy(mdctx);
+ return CURLE_OK;
+}
+#endif
+
+static bool ossl_cert_status_request(void)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
+ !defined(OPENSSL_NO_OCSP)
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+static void *ossl_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info)
+{
+ /* Legacy: CURLINFO_TLS_SESSION must return an SSL_CTX pointer. */
+ struct ssl_backend_data *backend = connssl->backend;
+ DEBUGASSERT(backend);
+ return info == CURLINFO_TLS_SESSION ?
+ (void *)backend->ctx : (void *)backend->handle;
+}
+
+static bool ossl_attach_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ const struct ssl_config_data *config;
+
+ DEBUGASSERT(backend);
+
+ /* If we don't have SSL context, do nothing. */
+ if(!backend->handle)
+ return FALSE;
+
+ config = Curl_ssl_cf_get_config(cf, data);
+ if(config->primary.sessionid) {
+ int data_idx = ossl_get_ssl_data_index();
+ int cf_idx = ossl_get_ssl_cf_index();
+ int sockindex_idx = ossl_get_ssl_sockindex_index();
+ int proxy_idx = ossl_get_proxy_index();
+
+ if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 &&
+ proxy_idx >= 0) {
+ int data_status, cf_status, sockindex_status, proxy_status;
+
+ /* Store the data needed for the "new session" callback.
+ * The sockindex is stored as a pointer to an array element. */
+ data_status = SSL_set_ex_data(backend->handle, data_idx, data);
+ cf_status = SSL_set_ex_data(backend->handle, cf_idx, cf);
+ sockindex_status = SSL_set_ex_data(backend->handle, sockindex_idx,
+ cf->conn->sock + cf->sockindex);
+#ifndef CURL_DISABLE_PROXY
+ proxy_status = SSL_set_ex_data(backend->handle, proxy_idx,
+ Curl_ssl_cf_is_proxy(cf)?
+ (void *) 1 : NULL);
+#else
+ proxy_status = SSL_set_ex_data(backend->handle, proxy_idx, NULL);
+#endif
+ if(data_status && cf_status && sockindex_status && proxy_status)
+ return TRUE;
+ }
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * Starting with TLS 1.3, the ossl_new_session_cb callback gets called after
+ * the handshake. If the transfer that sets up the callback gets killed before
+ * this callback arrives, we must make sure to properly clear the data to
+ * avoid UAF problems. A future optimization could be to instead store another
+ * transfer that might still be using the same connection.
+ */
+
+static void ossl_detach_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ DEBUGASSERT(backend);
+
+ /* If we don't have SSL context, do nothing. */
+ if(!backend->handle)
+ return;
+
+ if(ssl_config->primary.sessionid) {
+ int data_idx = ossl_get_ssl_data_index();
+ int cf_idx = ossl_get_ssl_cf_index();
+ int sockindex_idx = ossl_get_ssl_sockindex_index();
+ int proxy_idx = ossl_get_proxy_index();
+
+ if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 &&
+ proxy_idx >= 0) {
+ /* Disable references to data in "new session" callback to avoid
+ * accessing a stale pointer. */
+ SSL_set_ex_data(backend->handle, data_idx, NULL);
+ SSL_set_ex_data(backend->handle, cf_idx, NULL);
+ SSL_set_ex_data(backend->handle, sockindex_idx, NULL);
+ SSL_set_ex_data(backend->handle, proxy_idx, NULL);
+ }
+ }
+}
+
+static void ossl_free_multi_ssl_backend_data(
+ struct multi_ssl_backend_data *mbackend)
+{
+#if defined(HAVE_SSL_X509_STORE_SHARE)
+ if(mbackend->store) {
+ X509_STORE_free(mbackend->store);
+ }
+ free(mbackend->CAfile);
+ free(mbackend);
+#else /* HAVE_SSL_X509_STORE_SHARE */
+ (void)mbackend;
+#endif /* HAVE_SSL_X509_STORE_SHARE */
+}
+
+const struct Curl_ssl Curl_ssl_openssl = {
+ { CURLSSLBACKEND_OPENSSL, "openssl" }, /* info */
+
+ SSLSUPP_CA_PATH |
+ SSLSUPP_CAINFO_BLOB |
+ SSLSUPP_CERTINFO |
+ SSLSUPP_PINNEDPUBKEY |
+ SSLSUPP_SSL_CTX |
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+ SSLSUPP_TLS13_CIPHERSUITES |
+#endif
+ SSLSUPP_HTTPS_PROXY,
+
+ sizeof(struct ssl_backend_data),
+
+ ossl_init, /* init */
+ ossl_cleanup, /* cleanup */
+ ossl_version, /* version */
+ ossl_check_cxn, /* check_cxn */
+ ossl_shutdown, /* shutdown */
+ ossl_data_pending, /* data_pending */
+ ossl_random, /* random */
+ ossl_cert_status_request, /* cert_status_request */
+ ossl_connect, /* connect */
+ ossl_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks,/* getsock */
+ ossl_get_internals, /* get_internals */
+ ossl_close, /* close_one */
+ ossl_close_all, /* close_all */
+ ossl_session_free, /* session_free */
+ ossl_set_engine, /* set_engine */
+ ossl_set_engine_default, /* set_engine_default */
+ ossl_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
+ ossl_sha256sum, /* sha256sum */
+#else
+ NULL, /* sha256sum */
+#endif
+ ossl_attach_data, /* use of data in this connection */
+ ossl_detach_data, /* remote of data from this connection */
+ ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */
+ ossl_recv, /* recv decrypted data */
+ ossl_send, /* send data to encrypt */
+};
+
+#endif /* USE_OPENSSL */
diff --git a/libs/libcurl/src/vtls/openssl.h b/libs/libcurl/src/vtls/openssl.h index 9df4ecddba..d8cd5bee4a 100644 --- a/libs/libcurl/src/vtls/openssl.h +++ b/libs/libcurl/src/vtls/openssl.h @@ -1,60 +1,60 @@ -#ifndef HEADER_CURL_SSLUSE_H -#define HEADER_CURL_SSLUSE_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#ifdef USE_OPENSSL -/* - * This header should only be needed to get included by vtls.c, openssl.c - * and ngtcp2.c - */ -#include <openssl/ssl.h> - -#include "urldata.h" - -/* - * In an effort to avoid using 'X509 *' here, we instead use the struct - * x509_st version of the type so that we can forward-declare it here without - * having to include <openssl/x509v3.h>. Including that header causes name - * conflicts when libcurl is built with both Schannel and OpenSSL support. - */ -struct x509_st; -CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - struct x509_st *server_cert); -extern const struct Curl_ssl Curl_ssl_openssl; - -struct ssl_ctx_st; -CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data, - struct ssl_ctx_st *ctx, char *cert_file, - const struct curl_blob *cert_blob, - const char *cert_type, char *key_file, - const struct curl_blob *key_blob, - const char *key_type, char *key_passwd); - -CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl); - -#endif /* USE_OPENSSL */ -#endif /* HEADER_CURL_SSLUSE_H */ +#ifndef HEADER_CURL_SSLUSE_H
+#define HEADER_CURL_SSLUSE_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_OPENSSL
+/*
+ * This header should only be needed to get included by vtls.c, openssl.c
+ * and ngtcp2.c
+ */
+#include <openssl/ssl.h>
+
+#include "urldata.h"
+
+/*
+ * In an effort to avoid using 'X509 *' here, we instead use the struct
+ * x509_st version of the type so that we can forward-declare it here without
+ * having to include <openssl/x509v3.h>. Including that header causes name
+ * conflicts when libcurl is built with both Schannel and OpenSSL support.
+ */
+struct x509_st;
+CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+ struct x509_st *server_cert);
+extern const struct Curl_ssl Curl_ssl_openssl;
+
+struct ssl_ctx_st;
+CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data,
+ struct ssl_ctx_st *ctx, char *cert_file,
+ const struct curl_blob *cert_blob,
+ const char *cert_type, char *key_file,
+ const struct curl_blob *key_blob,
+ const char *key_type, char *key_passwd);
+
+CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl);
+
+#endif /* USE_OPENSSL */
+#endif /* HEADER_CURL_SSLUSE_H */
diff --git a/libs/libcurl/src/vtls/rustls.c b/libs/libcurl/src/vtls/rustls.c index 77a49f1ab4..49b5869ac2 100644 --- a/libs/libcurl/src/vtls/rustls.c +++ b/libs/libcurl/src/vtls/rustls.c @@ -1,636 +1,671 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews, - * <github@hoffman-andrews.com> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_RUSTLS - -#include "curl_printf.h" - -#include <errno.h> -#include <rustls.h> - -#include "inet_pton.h" -#include "urldata.h" -#include "sendf.h" -#include "vtls.h" -#include "select.h" -#include "strerror.h" -#include "multiif.h" - -struct ssl_backend_data -{ - const struct rustls_client_config *config; - struct rustls_connection *conn; - bool data_pending; -}; - -/* For a given rustls_result error code, return the best-matching CURLcode. */ -static CURLcode map_error(rustls_result r) -{ - if(rustls_result_is_cert_error(r)) { - return CURLE_PEER_FAILED_VERIFICATION; - } - switch(r) { - case RUSTLS_RESULT_OK: - return CURLE_OK; - case RUSTLS_RESULT_NULL_PARAMETER: - return CURLE_BAD_FUNCTION_ARGUMENT; - default: - return CURLE_READ_ERROR; - } -} - -static bool -cr_data_pending(const struct connectdata *conn, int sockindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - return backend->data_pending; -} - -static CURLcode -cr_connect(struct Curl_easy *data UNUSED_PARAM, - struct connectdata *conn UNUSED_PARAM, - int sockindex UNUSED_PARAM) -{ - infof(data, "rustls_connect: unimplemented"); - return CURLE_SSL_CONNECT_ERROR; -} - -static int -read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n) -{ - ssize_t n = sread(*(int *)userdata, buf, len); - if(n < 0) { - return SOCKERRNO; - } - *out_n = n; - return 0; -} - -static int -write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n) -{ - ssize_t n = swrite(*(int *)userdata, buf, len); - if(n < 0) { - return SOCKERRNO; - } - *out_n = n; - return 0; -} - -/* - * On each run: - * - Read a chunk of bytes from the socket into rustls' TLS input buffer. - * - Tell rustls to process any new packets. - * - Read out as many plaintext bytes from rustls as possible, until hitting - * error, EOF, or EAGAIN/EWOULDBLOCK, or plainbuf/plainlen is filled up. - * - * It's okay to call this function with plainbuf == NULL and plainlen == 0. - * In that case, it will copy bytes from the socket into rustls' TLS input - * buffer, and process packets, but won't consume bytes from rustls' plaintext - * output buffer. - */ -static ssize_t -cr_recv(struct Curl_easy *data, int sockindex, - char *plainbuf, size_t plainlen, CURLcode *err) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *const connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *const backend = connssl->backend; - struct rustls_connection *rconn = NULL; - - size_t n = 0; - size_t tls_bytes_read = 0; - size_t plain_bytes_copied = 0; - rustls_result rresult = 0; - char errorbuf[255]; - rustls_io_result io_error; - - DEBUGASSERT(backend); - rconn = backend->conn; - - io_error = rustls_connection_read_tls(rconn, read_cb, - &conn->sock[sockindex], &tls_bytes_read); - if(io_error == EAGAIN || io_error == EWOULDBLOCK) { - infof(data, "sread: EAGAIN or EWOULDBLOCK"); - } - else if(io_error) { - char buffer[STRERROR_LEN]; - failf(data, "reading from socket: %s", - Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_READ_ERROR; - return -1; - } - - infof(data, "cr_recv read %ld bytes from the network", tls_bytes_read); - - rresult = rustls_connection_process_new_packets(rconn); - if(rresult != RUSTLS_RESULT_OK) { - rustls_error(rresult, errorbuf, sizeof(errorbuf), &n); - failf(data, "%.*s", n, errorbuf); - *err = map_error(rresult); - return -1; - } - - backend->data_pending = TRUE; - - while(plain_bytes_copied < plainlen) { - rresult = rustls_connection_read(rconn, - (uint8_t *)plainbuf + plain_bytes_copied, - plainlen - plain_bytes_copied, - &n); - if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) { - infof(data, "cr_recv got PLAINTEXT_EMPTY. will try again later."); - backend->data_pending = FALSE; - break; - } - else if(rresult != RUSTLS_RESULT_OK) { - /* n always equals 0 in this case, don't need to check it */ - failf(data, "error in rustls_connection_read: %d", rresult); - *err = CURLE_READ_ERROR; - return -1; - } - else if(n == 0) { - /* n == 0 indicates clean EOF, but we may have read some other - plaintext bytes before we reached this. Break out of the loop - so we can figure out whether to return success or EOF. */ - break; - } - else { - infof(data, "cr_recv copied out %ld bytes of plaintext", n); - plain_bytes_copied += n; - } - } - - if(plain_bytes_copied) { - *err = CURLE_OK; - return plain_bytes_copied; - } - - /* If we wrote out 0 plaintext bytes, that means either we hit a clean EOF, - OR we got a RUSTLS_RESULT_PLAINTEXT_EMPTY. - If the latter, return CURLE_AGAIN so curl doesn't treat this as EOF. */ - if(!backend->data_pending) { - *err = CURLE_AGAIN; - return -1; - } - - /* Zero bytes read, and no RUSTLS_RESULT_PLAINTEXT_EMPTY, means the TCP - connection was cleanly closed (with a close_notify alert). */ - *err = CURLE_OK; - return 0; -} - -/* - * On each call: - * - Copy `plainlen` bytes into rustls' plaintext input buffer (if > 0). - * - Fully drain rustls' plaintext output buffer into the socket until - * we get either an error or EAGAIN/EWOULDBLOCK. - * - * It's okay to call this function with plainbuf == NULL and plainlen == 0. - * In that case, it won't read anything into rustls' plaintext input buffer. - * It will only drain rustls' plaintext output buffer into the socket. - */ -static ssize_t -cr_send(struct Curl_easy *data, int sockindex, - const void *plainbuf, size_t plainlen, CURLcode *err) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *const connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *const backend = connssl->backend; - struct rustls_connection *rconn = NULL; - size_t plainwritten = 0; - size_t tlswritten = 0; - size_t tlswritten_total = 0; - rustls_result rresult; - rustls_io_result io_error; - - DEBUGASSERT(backend); - rconn = backend->conn; - - infof(data, "cr_send %ld bytes of plaintext", plainlen); - - if(plainlen > 0) { - rresult = rustls_connection_write(rconn, plainbuf, plainlen, - &plainwritten); - if(rresult != RUSTLS_RESULT_OK) { - failf(data, "error in rustls_connection_write"); - *err = CURLE_WRITE_ERROR; - return -1; - } - else if(plainwritten == 0) { - failf(data, "EOF in rustls_connection_write"); - *err = CURLE_WRITE_ERROR; - return -1; - } - } - - while(rustls_connection_wants_write(rconn)) { - io_error = rustls_connection_write_tls(rconn, write_cb, - &conn->sock[sockindex], &tlswritten); - if(io_error == EAGAIN || io_error == EWOULDBLOCK) { - infof(data, "swrite: EAGAIN after %ld bytes", tlswritten_total); - *err = CURLE_AGAIN; - return -1; - } - else if(io_error) { - char buffer[STRERROR_LEN]; - failf(data, "writing to socket: %s", - Curl_strerror(io_error, buffer, sizeof(buffer))); - *err = CURLE_WRITE_ERROR; - return -1; - } - if(tlswritten == 0) { - failf(data, "EOF in swrite"); - *err = CURLE_WRITE_ERROR; - return -1; - } - infof(data, "cr_send wrote %ld bytes to network", tlswritten); - tlswritten_total += tlswritten; - } - - return plainwritten; -} - -/* A server certificate verify callback for rustls that always returns - RUSTLS_RESULT_OK, or in other words disable certificate verification. */ -static enum rustls_result -cr_verify_none(void *userdata UNUSED_PARAM, - const rustls_verify_server_cert_params *params UNUSED_PARAM) -{ - return RUSTLS_RESULT_OK; -} - -static bool -cr_hostname_is_ip(const char *hostname) -{ - struct in_addr in; -#ifdef ENABLE_IPV6 - struct in6_addr in6; - if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) { - return true; - } -#endif /* ENABLE_IPV6 */ - if(Curl_inet_pton(AF_INET, hostname, &in) > 0) { - return true; - } - return false; -} - -static CURLcode -cr_init_backend(struct Curl_easy *data, struct connectdata *conn, - struct ssl_backend_data *const backend) -{ - struct rustls_connection *rconn = NULL; - struct rustls_client_config_builder *config_builder = NULL; - struct rustls_root_cert_store *roots = NULL; - const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob); - const char * const ssl_cafile = - /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */ - (ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile)); - const bool verifypeer = SSL_CONN_CONFIG(verifypeer); - const char *hostname = conn->host.name; - char errorbuf[256]; - size_t errorlen; - int result; - rustls_slice_bytes alpn[2] = { - { (const uint8_t *)ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH }, - { (const uint8_t *)ALPN_H2, ALPN_H2_LENGTH }, - }; - - DEBUGASSERT(backend); - rconn = backend->conn; - - config_builder = rustls_client_config_builder_new(); -#ifdef USE_HTTP2 - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 2); -#else - rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1); -#endif - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - if(!verifypeer) { - rustls_client_config_builder_dangerous_set_certificate_verifier( - config_builder, cr_verify_none); - /* rustls doesn't support IP addresses (as of 0.19.0), and will reject - * connections created with an IP address, even when certificate - * verification is turned off. Set a placeholder hostname and disable - * SNI. */ - if(cr_hostname_is_ip(hostname)) { - rustls_client_config_builder_set_enable_sni(config_builder, false); - hostname = "example.invalid"; - } - } - else if(ca_info_blob) { - roots = rustls_root_cert_store_new(); - - /* Enable strict parsing only if verification isn't disabled. */ - result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data, - ca_info_blob->len, verifypeer); - if(result != RUSTLS_RESULT_OK) { - failf(data, "failed to parse trusted certificates from blob"); - rustls_root_cert_store_free(roots); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); - return CURLE_SSL_CACERT_BADFILE; - } - - result = rustls_client_config_builder_use_roots(config_builder, roots); - rustls_root_cert_store_free(roots); - if(result != RUSTLS_RESULT_OK) { - failf(data, "failed to load trusted certificates"); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); - return CURLE_SSL_CACERT_BADFILE; - } - } - else if(ssl_cafile) { - result = rustls_client_config_builder_load_roots_from_file( - config_builder, ssl_cafile); - if(result != RUSTLS_RESULT_OK) { - failf(data, "failed to load trusted certificates"); - rustls_client_config_free( - rustls_client_config_builder_build(config_builder)); - return CURLE_SSL_CACERT_BADFILE; - } - } - - backend->config = rustls_client_config_builder_build(config_builder); - DEBUGASSERT(rconn == NULL); - { - char *snihost = Curl_ssl_snihost(data, hostname, NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - result = rustls_client_connection_new(backend->config, snihost, &rconn); - } - if(result != RUSTLS_RESULT_OK) { - rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen); - failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf); - return CURLE_COULDNT_CONNECT; - } - rustls_connection_set_userdata(rconn, backend); - backend->conn = rconn; - return CURLE_OK; -} - -static void -cr_set_negotiated_alpn(struct Curl_easy *data, struct connectdata *conn, - const struct rustls_connection *rconn) -{ - const uint8_t *protocol = NULL; - size_t len = 0; - - rustls_connection_get_alpn_protocol(rconn, &protocol, &len); - if(!protocol) { - infof(data, VTLS_INFOF_NO_ALPN); - return; - } - -#ifdef USE_HTTP2 - if(len == ALPN_H2_LENGTH && 0 == memcmp(ALPN_H2, protocol, len)) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_H2); - conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(len == ALPN_HTTP_1_1_LENGTH && - 0 == memcmp(ALPN_HTTP_1_1, protocol, len)) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_HTTP_1_1); - conn->alpn = CURL_HTTP_VERSION_1_1; - } - else { - infof(data, "ALPN, negotiated an unrecognized protocol"); - } - - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); -} - -static CURLcode -cr_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, - int sockindex, bool *done) -{ - struct ssl_connect_data *const connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_backend_data *const backend = connssl->backend; - struct rustls_connection *rconn = NULL; - CURLcode tmperr = CURLE_OK; - int result; - int what; - bool wants_read; - bool wants_write; - curl_socket_t writefd; - curl_socket_t readfd; - - DEBUGASSERT(backend); - - if(ssl_connection_none == connssl->state) { - result = cr_init_backend(data, conn, connssl->backend); - if(result != CURLE_OK) { - return result; - } - connssl->state = ssl_connection_negotiating; - } - - rconn = backend->conn; - - /* Read/write data until the handshake is done or the socket would block. */ - for(;;) { - /* - * Connection has been established according to rustls. Set send/recv - * handlers, and update the state machine. - */ - if(!rustls_connection_is_handshaking(rconn)) { - infof(data, "Done handshaking"); - /* Done with the handshake. Set up callbacks to send/receive data. */ - connssl->state = ssl_connection_complete; - - cr_set_negotiated_alpn(data, conn, rconn); - - conn->recv[sockindex] = cr_recv; - conn->send[sockindex] = cr_send; - *done = TRUE; - return CURLE_OK; - } - - wants_read = rustls_connection_wants_read(rconn); - wants_write = rustls_connection_wants_write(rconn); - DEBUGASSERT(wants_read || wants_write); - writefd = wants_write?sockfd:CURL_SOCKET_BAD; - readfd = wants_read?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, 0); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - if(0 == what) { - infof(data, "Curl_socket_check: %s would block", - wants_read&&wants_write ? "writing and reading" : - wants_write ? "writing" : "reading"); - *done = FALSE; - return CURLE_OK; - } - /* socket is readable or writable */ - - if(wants_write) { - infof(data, "rustls_connection wants us to write_tls."); - cr_send(data, sockindex, NULL, 0, &tmperr); - if(tmperr == CURLE_AGAIN) { - infof(data, "writing would block"); - /* fall through */ - } - else if(tmperr != CURLE_OK) { - return tmperr; - } - } - - if(wants_read) { - infof(data, "rustls_connection wants us to read_tls."); - - cr_recv(data, sockindex, NULL, 0, &tmperr); - if(tmperr == CURLE_AGAIN) { - infof(data, "reading would block"); - /* fall through */ - } - else if(tmperr != CURLE_OK) { - if(tmperr == CURLE_READ_ERROR) { - return CURLE_SSL_CONNECT_ERROR; - } - else { - return tmperr; - } - } - } - } - - /* We should never fall through the loop. We should return either because - the handshake is done or because we can't read/write without blocking. */ - DEBUGASSERT(false); -} - -/* returns a bitmap of flags for this connection's first socket indicating - whether we want to read or write */ -static int -cr_getsock(struct connectdata *conn, curl_socket_t *socks) -{ - struct ssl_connect_data *const connssl = &conn->ssl[FIRSTSOCKET]; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - struct ssl_backend_data *const backend = connssl->backend; - struct rustls_connection *rconn = NULL; - - DEBUGASSERT(backend); - rconn = backend->conn; - - if(rustls_connection_wants_write(rconn)) { - socks[0] = sockfd; - return GETSOCK_WRITESOCK(0); - } - if(rustls_connection_wants_read(rconn)) { - socks[0] = sockfd; - return GETSOCK_READSOCK(0); - } - - return GETSOCK_BLANK; -} - -static void * -cr_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - return &backend->conn; -} - -static void -cr_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - CURLcode tmperr = CURLE_OK; - ssize_t n = 0; - - DEBUGASSERT(backend); - - if(backend->conn) { - rustls_connection_send_close_notify(backend->conn); - n = cr_send(data, sockindex, NULL, 0, &tmperr); - if(n < 0) { - failf(data, "error sending close notify: %d", tmperr); - } - - rustls_connection_free(backend->conn); - backend->conn = NULL; - } - if(backend->config) { - rustls_client_config_free(backend->config); - backend->config = NULL; - } -} - -static size_t cr_version(char *buffer, size_t size) -{ - struct rustls_str ver = rustls_version(); - return msnprintf(buffer, size, "%.*s", (int)ver.len, ver.data); -} - -const struct Curl_ssl Curl_ssl_rustls = { - { CURLSSLBACKEND_RUSTLS, "rustls" }, - SSLSUPP_CAINFO_BLOB | /* supports */ - SSLSUPP_TLS13_CIPHERSUITES, - sizeof(struct ssl_backend_data), - - Curl_none_init, /* init */ - Curl_none_cleanup, /* cleanup */ - cr_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ - cr_data_pending, /* data_pending */ - Curl_none_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - cr_connect, /* connect */ - cr_connect_nonblocking, /* connect_nonblocking */ - cr_getsock, /* cr_getsock */ - cr_get_internals, /* get_internals */ - cr_close, /* close_one */ - Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - NULL, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif /* USE_RUSTLS */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews,
+ * <github@hoffman-andrews.com>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_RUSTLS
+
+#include "curl_printf.h"
+
+#include <errno.h>
+#include <rustls.h>
+
+#include "inet_pton.h"
+#include "urldata.h"
+#include "sendf.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "select.h"
+#include "strerror.h"
+#include "multiif.h"
+
+struct ssl_backend_data
+{
+ const struct rustls_client_config *config;
+ struct rustls_connection *conn;
+ bool data_pending;
+};
+
+/* For a given rustls_result error code, return the best-matching CURLcode. */
+static CURLcode map_error(rustls_result r)
+{
+ if(rustls_result_is_cert_error(r)) {
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ switch(r) {
+ case RUSTLS_RESULT_OK:
+ return CURLE_OK;
+ case RUSTLS_RESULT_NULL_PARAMETER:
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ default:
+ return CURLE_READ_ERROR;
+ }
+}
+
+static bool
+cr_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx = cf->ctx;
+
+ (void)data;
+ DEBUGASSERT(ctx && ctx->backend);
+ return ctx->backend->data_pending;
+}
+
+static CURLcode
+cr_connect(struct Curl_cfilter *cf UNUSED_PARAM,
+ struct Curl_easy *data UNUSED_PARAM)
+{
+ infof(data, "rustls_connect: unimplemented");
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+struct io_ctx {
+ struct Curl_cfilter *cf;
+ struct Curl_easy *data;
+};
+
+static int
+read_cb(void *userdata, uint8_t *buf, uintptr_t len, uintptr_t *out_n)
+{
+ struct io_ctx *io_ctx = userdata;
+ CURLcode result;
+ int ret = 0;
+ ssize_t nread = Curl_conn_cf_recv(io_ctx->cf->next, io_ctx->data,
+ (char *)buf, len, &result);
+ if(nread < 0) {
+ nread = 0;
+ if(CURLE_AGAIN == result)
+ ret = EAGAIN;
+ else
+ ret = EINVAL;
+ }
+ *out_n = (int)nread;
+ return ret;
+}
+
+static int
+write_cb(void *userdata, const uint8_t *buf, uintptr_t len, uintptr_t *out_n)
+{
+ struct io_ctx *io_ctx = userdata;
+ CURLcode result;
+ int ret = 0;
+ ssize_t nwritten = Curl_conn_cf_send(io_ctx->cf->next, io_ctx->data,
+ (const char *)buf, len, &result);
+ if(nwritten < 0) {
+ nwritten = 0;
+ if(CURLE_AGAIN == result)
+ ret = EAGAIN;
+ else
+ ret = EINVAL;
+ }
+ *out_n = (int)nwritten;
+ return ret;
+}
+
+/*
+ * On each run:
+ * - Read a chunk of bytes from the socket into rustls' TLS input buffer.
+ * - Tell rustls to process any new packets.
+ * - Read out as many plaintext bytes from rustls as possible, until hitting
+ * error, EOF, or EAGAIN/EWOULDBLOCK, or plainbuf/plainlen is filled up.
+ *
+ * It's okay to call this function with plainbuf == NULL and plainlen == 0.
+ * In that case, it will copy bytes from the socket into rustls' TLS input
+ * buffer, and process packets, but won't consume bytes from rustls' plaintext
+ * output buffer.
+ */
+static ssize_t
+cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *plainbuf, size_t plainlen, CURLcode *err)
+{
+ struct ssl_connect_data *const connssl = cf->ctx;
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_connection *rconn = NULL;
+ struct io_ctx io_ctx;
+
+ size_t n = 0;
+ size_t tls_bytes_read = 0;
+ size_t plain_bytes_copied = 0;
+ rustls_result rresult = 0;
+ char errorbuf[255];
+ rustls_io_result io_error;
+
+ DEBUGASSERT(backend);
+ rconn = backend->conn;
+
+ io_ctx.cf = cf;
+ io_ctx.data = data;
+
+ io_error = rustls_connection_read_tls(rconn, read_cb, &io_ctx,
+ &tls_bytes_read);
+ if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
+ infof(data, CFMSG(cf, "cr_recv: EAGAIN or EWOULDBLOCK"));
+ }
+ else if(io_error) {
+ char buffer[STRERROR_LEN];
+ failf(data, "reading from socket: %s",
+ Curl_strerror(io_error, buffer, sizeof(buffer)));
+ *err = CURLE_READ_ERROR;
+ return -1;
+ }
+
+ infof(data, CFMSG(cf, "cr_recv: read %ld TLS bytes"), tls_bytes_read);
+
+ rresult = rustls_connection_process_new_packets(rconn);
+ if(rresult != RUSTLS_RESULT_OK) {
+ rustls_error(rresult, errorbuf, sizeof(errorbuf), &n);
+ failf(data, "%.*s", n, errorbuf);
+ *err = map_error(rresult);
+ return -1;
+ }
+
+ backend->data_pending = TRUE;
+
+ while(plain_bytes_copied < plainlen) {
+ rresult = rustls_connection_read(rconn,
+ (uint8_t *)plainbuf + plain_bytes_copied,
+ plainlen - plain_bytes_copied,
+ &n);
+ if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) {
+ infof(data, CFMSG(cf, "cr_recv: got PLAINTEXT_EMPTY. "
+ "will try again later."));
+ backend->data_pending = FALSE;
+ break;
+ }
+ else if(rresult != RUSTLS_RESULT_OK) {
+ /* n always equals 0 in this case, don't need to check it */
+ failf(data, "error in rustls_connection_read: %d", rresult);
+ *err = CURLE_READ_ERROR;
+ return -1;
+ }
+ else if(n == 0) {
+ /* n == 0 indicates clean EOF, but we may have read some other
+ plaintext bytes before we reached this. Break out of the loop
+ so we can figure out whether to return success or EOF. */
+ break;
+ }
+ else {
+ infof(data, CFMSG(cf, "cr_recv: got %ld plain bytes"), n);
+ plain_bytes_copied += n;
+ }
+ }
+
+ if(plain_bytes_copied) {
+ *err = CURLE_OK;
+ return plain_bytes_copied;
+ }
+
+ /* If we wrote out 0 plaintext bytes, that means either we hit a clean EOF,
+ OR we got a RUSTLS_RESULT_PLAINTEXT_EMPTY.
+ If the latter, return CURLE_AGAIN so curl doesn't treat this as EOF. */
+ if(!backend->data_pending) {
+ *err = CURLE_AGAIN;
+ return -1;
+ }
+
+ /* Zero bytes read, and no RUSTLS_RESULT_PLAINTEXT_EMPTY, means the TCP
+ connection was cleanly closed (with a close_notify alert). */
+ *err = CURLE_OK;
+ return 0;
+}
+
+/*
+ * On each call:
+ * - Copy `plainlen` bytes into rustls' plaintext input buffer (if > 0).
+ * - Fully drain rustls' plaintext output buffer into the socket until
+ * we get either an error or EAGAIN/EWOULDBLOCK.
+ *
+ * It's okay to call this function with plainbuf == NULL and plainlen == 0.
+ * In that case, it won't read anything into rustls' plaintext input buffer.
+ * It will only drain rustls' plaintext output buffer into the socket.
+ */
+static ssize_t
+cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *plainbuf, size_t plainlen, CURLcode *err)
+{
+ struct ssl_connect_data *const connssl = cf->ctx;
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_connection *rconn = NULL;
+ struct io_ctx io_ctx;
+ size_t plainwritten = 0;
+ size_t tlswritten = 0;
+ size_t tlswritten_total = 0;
+ rustls_result rresult;
+ rustls_io_result io_error;
+
+ DEBUGASSERT(backend);
+ rconn = backend->conn;
+
+ infof(data, CFMSG(cf, "cr_send: %ld plain bytes"), plainlen);
+
+ if(plainlen > 0) {
+ rresult = rustls_connection_write(rconn, plainbuf, plainlen,
+ &plainwritten);
+ if(rresult != RUSTLS_RESULT_OK) {
+ failf(data, "error in rustls_connection_write");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ else if(plainwritten == 0) {
+ failf(data, "EOF in rustls_connection_write");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ }
+
+ io_ctx.cf = cf;
+ io_ctx.data = data;
+
+ while(rustls_connection_wants_write(rconn)) {
+ io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx,
+ &tlswritten);
+ if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
+ infof(data, CFMSG(cf, "cr_send: EAGAIN after %ld bytes"),
+ tlswritten_total);
+ *err = CURLE_AGAIN;
+ return -1;
+ }
+ else if(io_error) {
+ char buffer[STRERROR_LEN];
+ failf(data, "writing to socket: %s",
+ Curl_strerror(io_error, buffer, sizeof(buffer)));
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ if(tlswritten == 0) {
+ failf(data, "EOF in swrite");
+ *err = CURLE_WRITE_ERROR;
+ return -1;
+ }
+ infof(data, CFMSG(cf, "cr_send: wrote %ld TLS bytes"), tlswritten);
+ tlswritten_total += tlswritten;
+ }
+
+ return plainwritten;
+}
+
+/* A server certificate verify callback for rustls that always returns
+ RUSTLS_RESULT_OK, or in other words disable certificate verification. */
+static enum rustls_result
+cr_verify_none(void *userdata UNUSED_PARAM,
+ const rustls_verify_server_cert_params *params UNUSED_PARAM)
+{
+ return RUSTLS_RESULT_OK;
+}
+
+static bool
+cr_hostname_is_ip(const char *hostname)
+{
+ struct in_addr in;
+#ifdef ENABLE_IPV6
+ struct in6_addr in6;
+ if(Curl_inet_pton(AF_INET6, hostname, &in6) > 0) {
+ return true;
+ }
+#endif /* ENABLE_IPV6 */
+ if(Curl_inet_pton(AF_INET, hostname, &in) > 0) {
+ return true;
+ }
+ return false;
+}
+
+static CURLcode
+cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
+ struct ssl_backend_data *const backend)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct rustls_connection *rconn = NULL;
+ struct rustls_client_config_builder *config_builder = NULL;
+ struct rustls_root_cert_store *roots = NULL;
+ const struct curl_blob *ca_info_blob = conn_config->ca_info_blob;
+ const char * const ssl_cafile =
+ /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+ (ca_info_blob ? NULL : conn_config->CAfile);
+ const bool verifypeer = conn_config->verifypeer;
+ const char *hostname = connssl->hostname;
+ char errorbuf[256];
+ size_t errorlen;
+ int result;
+ rustls_slice_bytes alpn[2] = {
+ { (const uint8_t *)ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH },
+ { (const uint8_t *)ALPN_H2, ALPN_H2_LENGTH },
+ };
+
+ DEBUGASSERT(backend);
+ rconn = backend->conn;
+
+ config_builder = rustls_client_config_builder_new();
+#ifdef USE_HTTP2
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+ rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 2);
+#else
+ rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1);
+#endif
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+ if(!verifypeer) {
+ rustls_client_config_builder_dangerous_set_certificate_verifier(
+ config_builder, cr_verify_none);
+ /* rustls doesn't support IP addresses (as of 0.19.0), and will reject
+ * connections created with an IP address, even when certificate
+ * verification is turned off. Set a placeholder hostname and disable
+ * SNI. */
+ if(cr_hostname_is_ip(hostname)) {
+ rustls_client_config_builder_set_enable_sni(config_builder, false);
+ hostname = "example.invalid";
+ }
+ }
+ else if(ca_info_blob) {
+ roots = rustls_root_cert_store_new();
+
+ /* Enable strict parsing only if verification isn't disabled. */
+ result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data,
+ ca_info_blob->len, verifypeer);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "failed to parse trusted certificates from blob");
+ rustls_root_cert_store_free(roots);
+ rustls_client_config_free(
+ rustls_client_config_builder_build(config_builder));
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+
+ result = rustls_client_config_builder_use_roots(config_builder, roots);
+ rustls_root_cert_store_free(roots);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "failed to load trusted certificates");
+ rustls_client_config_free(
+ rustls_client_config_builder_build(config_builder));
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+ else if(ssl_cafile) {
+ result = rustls_client_config_builder_load_roots_from_file(
+ config_builder, ssl_cafile);
+ if(result != RUSTLS_RESULT_OK) {
+ failf(data, "failed to load trusted certificates");
+ rustls_client_config_free(
+ rustls_client_config_builder_build(config_builder));
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+
+ backend->config = rustls_client_config_builder_build(config_builder);
+ DEBUGASSERT(rconn == NULL);
+ {
+ char *snihost = Curl_ssl_snihost(data, hostname, NULL);
+ if(!snihost) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ result = rustls_client_connection_new(backend->config, snihost, &rconn);
+ }
+ if(result != RUSTLS_RESULT_OK) {
+ rustls_error(result, errorbuf, sizeof(errorbuf), &errorlen);
+ failf(data, "rustls_client_connection_new: %.*s", errorlen, errorbuf);
+ return CURLE_COULDNT_CONNECT;
+ }
+ rustls_connection_set_userdata(rconn, backend);
+ backend->conn = rconn;
+ return CURLE_OK;
+}
+
+static void
+cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const struct rustls_connection *rconn)
+{
+ const uint8_t *protocol = NULL;
+ size_t len = 0;
+
+ rustls_connection_get_alpn_protocol(rconn, &protocol, &len);
+ if(!protocol) {
+ infof(data, VTLS_INFOF_NO_ALPN);
+ return;
+ }
+
+#ifdef USE_HTTP2
+ if(len == ALPN_H2_LENGTH && 0 == memcmp(ALPN_H2, protocol, len)) {
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_H2);
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+ }
+ else
+#endif
+ if(len == ALPN_HTTP_1_1_LENGTH &&
+ 0 == memcmp(ALPN_HTTP_1_1, protocol, len)) {
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_HTTP_1_1);
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+ }
+ else {
+ infof(data, "ALPN, negotiated an unrecognized protocol");
+ }
+
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+}
+
+static CURLcode
+cr_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data, bool *done)
+{
+ struct ssl_connect_data *const connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_connection *rconn = NULL;
+ CURLcode tmperr = CURLE_OK;
+ int result;
+ int what;
+ bool wants_read;
+ bool wants_write;
+ curl_socket_t writefd;
+ curl_socket_t readfd;
+
+ DEBUGASSERT(backend);
+
+ if(ssl_connection_none == connssl->state) {
+ result = cr_init_backend(cf, data, connssl->backend);
+ if(result != CURLE_OK) {
+ return result;
+ }
+ connssl->state = ssl_connection_negotiating;
+ }
+
+ rconn = backend->conn;
+
+ /* Read/write data until the handshake is done or the socket would block. */
+ for(;;) {
+ /*
+ * Connection has been established according to rustls. Set send/recv
+ * handlers, and update the state machine.
+ */
+ if(!rustls_connection_is_handshaking(rconn)) {
+ infof(data, "Done handshaking");
+ /* Done with the handshake. Set up callbacks to send/receive data. */
+ connssl->state = ssl_connection_complete;
+
+ cr_set_negotiated_alpn(cf, data, rconn);
+
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ wants_read = rustls_connection_wants_read(rconn);
+ wants_write = rustls_connection_wants_write(rconn);
+ DEBUGASSERT(wants_read || wants_write);
+ writefd = wants_write?sockfd:CURL_SOCKET_BAD;
+ readfd = wants_read?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, 0);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ if(0 == what) {
+ infof(data, "Curl_socket_check: %s would block",
+ wants_read&&wants_write ? "writing and reading" :
+ wants_write ? "writing" : "reading");
+ *done = FALSE;
+ return CURLE_OK;
+ }
+ /* socket is readable or writable */
+
+ if(wants_write) {
+ infof(data, "rustls_connection wants us to write_tls.");
+ cr_send(cf, data, NULL, 0, &tmperr);
+ if(tmperr == CURLE_AGAIN) {
+ infof(data, "writing would block");
+ /* fall through */
+ }
+ else if(tmperr != CURLE_OK) {
+ return tmperr;
+ }
+ }
+
+ if(wants_read) {
+ infof(data, "rustls_connection wants us to read_tls.");
+
+ cr_recv(cf, data, NULL, 0, &tmperr);
+ if(tmperr == CURLE_AGAIN) {
+ infof(data, "reading would block");
+ /* fall through */
+ }
+ else if(tmperr != CURLE_OK) {
+ if(tmperr == CURLE_READ_ERROR) {
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else {
+ return tmperr;
+ }
+ }
+ }
+ }
+
+ /* We should never fall through the loop. We should return either because
+ the handshake is done or because we can't read/write without blocking. */
+ DEBUGASSERT(false);
+}
+
+/* returns a bitmap of flags for this connection's first socket indicating
+ whether we want to read or write */
+static int
+cr_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data,
+ curl_socket_t *socks)
+{
+ struct ssl_connect_data *const connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ struct ssl_backend_data *const backend = connssl->backend;
+ struct rustls_connection *rconn = NULL;
+
+ (void)data;
+ DEBUGASSERT(backend);
+ rconn = backend->conn;
+
+ if(rustls_connection_wants_write(rconn)) {
+ socks[0] = sockfd;
+ return GETSOCK_WRITESOCK(0);
+ }
+ if(rustls_connection_wants_read(rconn)) {
+ socks[0] = sockfd;
+ return GETSOCK_READSOCK(0);
+ }
+
+ return GETSOCK_BLANK;
+}
+
+static void *
+cr_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ DEBUGASSERT(backend);
+ return &backend->conn;
+}
+
+static void
+cr_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ CURLcode tmperr = CURLE_OK;
+ ssize_t n = 0;
+
+ DEBUGASSERT(backend);
+
+ if(backend->conn) {
+ rustls_connection_send_close_notify(backend->conn);
+ n = cr_send(cf, data, NULL, 0, &tmperr);
+ if(n < 0) {
+ failf(data, "error sending close notify: %d", tmperr);
+ }
+
+ rustls_connection_free(backend->conn);
+ backend->conn = NULL;
+ }
+ if(backend->config) {
+ rustls_client_config_free(backend->config);
+ backend->config = NULL;
+ }
+}
+
+static size_t cr_version(char *buffer, size_t size)
+{
+ struct rustls_str ver = rustls_version();
+ return msnprintf(buffer, size, "%.*s", (int)ver.len, ver.data);
+}
+
+const struct Curl_ssl Curl_ssl_rustls = {
+ { CURLSSLBACKEND_RUSTLS, "rustls" },
+ SSLSUPP_CAINFO_BLOB | /* supports */
+ SSLSUPP_TLS13_CIPHERSUITES |
+ SSLSUPP_HTTPS_PROXY,
+ sizeof(struct ssl_backend_data),
+
+ Curl_none_init, /* init */
+ Curl_none_cleanup, /* cleanup */
+ cr_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ Curl_none_shutdown, /* shutdown */
+ cr_data_pending, /* data_pending */
+ Curl_none_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ cr_connect, /* connect */
+ cr_connect_nonblocking, /* connect_nonblocking */
+ cr_get_select_socks, /* get_select_socks */
+ cr_get_internals, /* get_internals */
+ cr_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ Curl_none_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ NULL, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ cr_recv, /* recv decrypted data */
+ cr_send, /* send data to encrypt */
+};
+
+#endif /* USE_RUSTLS */
diff --git a/libs/libcurl/src/vtls/rustls.h b/libs/libcurl/src/vtls/rustls.h index 6b393dd639..2739b458bd 100644 --- a/libs/libcurl/src/vtls/rustls.h +++ b/libs/libcurl/src/vtls/rustls.h @@ -1,35 +1,35 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews, - * <github@hoffman-andrews.com> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#ifndef HEADER_CURL_RUSTLS_H -#define HEADER_CURL_RUSTLS_H - -#include "curl_setup.h" - -#ifdef USE_RUSTLS - -extern const struct Curl_ssl Curl_ssl_rustls; - -#endif /* USE_RUSTLS */ -#endif /* HEADER_CURL_RUSTLS_H */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews,
+ * <github@hoffman-andrews.com>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#ifndef HEADER_CURL_RUSTLS_H
+#define HEADER_CURL_RUSTLS_H
+
+#include "curl_setup.h"
+
+#ifdef USE_RUSTLS
+
+extern const struct Curl_ssl Curl_ssl_rustls;
+
+#endif /* USE_RUSTLS */
+#endif /* HEADER_CURL_RUSTLS_H */
diff --git a/libs/libcurl/src/vtls/schannel.c b/libs/libcurl/src/vtls/schannel.c index 454eb79674..48dc6651ab 100644 --- a/libs/libcurl/src/vtls/schannel.c +++ b/libs/libcurl/src/vtls/schannel.c @@ -1,2815 +1,2801 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de> - * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com> - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for all Schannel-specific code for the TLS/SSL layer. No code - * but vtls.c should ever call or use these functions. - */ - -#include "curl_setup.h" - -#ifdef USE_SCHANNEL - -#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS - -#ifndef USE_WINDOWS_SSPI -# error "Can't compile SCHANNEL support without SSPI." -#endif - -#include "schannel.h" -#include "vtls.h" -#include "strcase.h" -#include "sendf.h" -#include "connect.h" /* for the connect timeout */ -#include "strerror.h" -#include "select.h" /* for the socket readiness */ -#include "inet_pton.h" /* for IP addr SNI check */ -#include "curl_multibyte.h" -#include "warnless.h" -#include "x509asn1.h" -#include "curl_printf.h" -#include "multiif.h" -#include "version_win32.h" -#include "rand.h" - -/* The last #include file should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -/* ALPN requires version 8.1 of the Windows SDK, which was - shipped with Visual Studio 2013, aka _MSC_VER 1800: - - https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx -*/ -#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_) -# define HAS_ALPN 1 -#endif - -#ifndef UNISP_NAME_A -#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME_W -#define UNISP_NAME_W L"Microsoft Unified Security Protocol Provider" -#endif - -#ifndef UNISP_NAME -#ifdef UNICODE -#define UNISP_NAME UNISP_NAME_W -#else -#define UNISP_NAME UNISP_NAME_A -#endif -#endif - -#ifndef BCRYPT_CHACHA20_POLY1305_ALGORITHM -#define BCRYPT_CHACHA20_POLY1305_ALGORITHM L"CHACHA20_POLY1305" -#endif - -#ifndef BCRYPT_CHAIN_MODE_CCM -#define BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM" -#endif - -#ifndef BCRYPT_CHAIN_MODE_GCM -#define BCRYPT_CHAIN_MODE_GCM L"ChainingModeGCM" -#endif - -#ifndef BCRYPT_AES_ALGORITHM -#define BCRYPT_AES_ALGORITHM L"AES" -#endif - -#ifndef BCRYPT_SHA256_ALGORITHM -#define BCRYPT_SHA256_ALGORITHM L"SHA256" -#endif - -#ifndef BCRYPT_SHA384_ALGORITHM -#define BCRYPT_SHA384_ALGORITHM L"SHA384" -#endif - -/* Workaround broken compilers like MinGW. - Return the number of elements in a statically sized array. -*/ -#ifndef ARRAYSIZE -#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0])) -#endif - -#ifdef HAS_CLIENT_CERT_PATH -#ifdef UNICODE -#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W -#else -#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_A -#endif -#endif - -#ifndef SP_PROT_SSL2_CLIENT -#define SP_PROT_SSL2_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_SSL3_CLIENT -#define SP_PROT_SSL3_CLIENT 0x00000008 -#endif - -#ifndef SP_PROT_TLS1_CLIENT -#define SP_PROT_TLS1_CLIENT 0x00000080 -#endif - -#ifndef SP_PROT_TLS1_0_CLIENT -#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT -#endif - -#ifndef SP_PROT_TLS1_1_CLIENT -#define SP_PROT_TLS1_1_CLIENT 0x00000200 -#endif - -#ifndef SP_PROT_TLS1_2_CLIENT -#define SP_PROT_TLS1_2_CLIENT 0x00000800 -#endif - -#ifndef SP_PROT_TLS1_3_CLIENT -#define SP_PROT_TLS1_3_CLIENT 0x00002000 -#endif - -#ifndef SCH_USE_STRONG_CRYPTO -#define SCH_USE_STRONG_CRYPTO 0x00400000 -#endif - -#ifndef SECBUFFER_ALERT -#define SECBUFFER_ALERT 17 -#endif - -/* Both schannel buffer sizes must be > 0 */ -#define CURL_SCHANNEL_BUFFER_INIT_SIZE 4096 -#define CURL_SCHANNEL_BUFFER_FREE_SIZE 1024 - -#define CERT_THUMBPRINT_STR_LEN 40 -#define CERT_THUMBPRINT_DATA_LEN 20 - -/* Uncomment to force verbose output - * #define infof(x, y, ...) printf(y, __VA_ARGS__) - * #define failf(x, y, ...) printf(y, __VA_ARGS__) - */ - -#ifndef CALG_SHA_256 -# define CALG_SHA_256 0x0000800c -#endif - -/* Work around typo in classic MinGW's w32api up to version 5.0, - see https://osdn.net/projects/mingw/ticket/38391 */ -#if !defined(ALG_CLASS_DHASH) && defined(ALG_CLASS_HASH) -#define ALG_CLASS_DHASH ALG_CLASS_HASH -#endif - -#ifndef PKCS12_NO_PERSIST_KEY -#define PKCS12_NO_PERSIST_KEY 0x00008000 -#endif - -static Curl_recv schannel_recv; -static Curl_send schannel_send; - -static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, - struct connectdata *conn, int sockindex, - const char *pinnedpubkey); - -static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType, - void *BufDataPtr, unsigned long BufByteSize) -{ - buffer->cbBuffer = BufByteSize; - buffer->BufferType = BufType; - buffer->pvBuffer = BufDataPtr; -} - -static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr, - unsigned long NumArrElem) -{ - desc->ulVersion = SECBUFFER_VERSION; - desc->pBuffers = BufArr; - desc->cBuffers = NumArrElem; -} - -static CURLcode -set_ssl_version_min_max(DWORD *enabled_protocols, struct Curl_easy *data, - struct connectdata *conn) -{ - long ssl_version = SSL_CONN_CONFIG(version); - long ssl_version_max = SSL_CONN_CONFIG(version_max); - long i = ssl_version; - - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - - /* Windows Server 2022 and newer (including Windows 11) support TLS 1.3 - built-in. Previous builds of Windows 10 had broken TLS 1.3 - implementations that could be enabled via registry. - */ - if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) { - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3; - } - else /* Windows 10 and older */ - ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2; - - break; - } - - for(; i <= (ssl_version_max >> 16); ++i) { - switch(i) { - case CURL_SSLVERSION_TLSv1_0: - (*enabled_protocols) |= SP_PROT_TLS1_0_CLIENT; - break; - case CURL_SSLVERSION_TLSv1_1: - (*enabled_protocols) |= SP_PROT_TLS1_1_CLIENT; - break; - case CURL_SSLVERSION_TLSv1_2: - (*enabled_protocols) |= SP_PROT_TLS1_2_CLIENT; - break; - case CURL_SSLVERSION_TLSv1_3: - - /* Windows Server 2022 and newer */ - if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) { - (*enabled_protocols) |= SP_PROT_TLS1_3_CLIENT; - break; - } - else { /* Windows 10 and older */ - failf(data, "schannel: TLS 1.3 not supported on Windows prior to 11"); - return CURLE_SSL_CONNECT_ERROR; - } - } - } - return CURLE_OK; -} - -/*longest is 26, buffer is slightly bigger*/ -#define LONGEST_ALG_ID 32 -#define CIPHEROPTION(X) \ - if(strcmp(#X, tmp) == 0) \ - return X - -static int -get_alg_id_by_name(char *name) -{ - char tmp[LONGEST_ALG_ID] = { 0 }; - char *nameEnd = strchr(name, ':'); - size_t n = nameEnd ? (size_t)(nameEnd - name) : strlen(name); - - /* reject too-long alg names */ - if(n > (LONGEST_ALG_ID - 1)) - return 0; - - strncpy(tmp, name, n); - tmp[n] = 0; - CIPHEROPTION(CALG_MD2); - CIPHEROPTION(CALG_MD4); - CIPHEROPTION(CALG_MD5); - CIPHEROPTION(CALG_SHA); - CIPHEROPTION(CALG_SHA1); - CIPHEROPTION(CALG_MAC); - CIPHEROPTION(CALG_RSA_SIGN); - CIPHEROPTION(CALG_DSS_SIGN); -/*ifdefs for the options that are defined conditionally in wincrypt.h*/ -#ifdef CALG_NO_SIGN - CIPHEROPTION(CALG_NO_SIGN); -#endif - CIPHEROPTION(CALG_RSA_KEYX); - CIPHEROPTION(CALG_DES); -#ifdef CALG_3DES_112 - CIPHEROPTION(CALG_3DES_112); -#endif - CIPHEROPTION(CALG_3DES); - CIPHEROPTION(CALG_DESX); - CIPHEROPTION(CALG_RC2); - CIPHEROPTION(CALG_RC4); - CIPHEROPTION(CALG_SEAL); -#ifdef CALG_DH_SF - CIPHEROPTION(CALG_DH_SF); -#endif - CIPHEROPTION(CALG_DH_EPHEM); -#ifdef CALG_AGREEDKEY_ANY - CIPHEROPTION(CALG_AGREEDKEY_ANY); -#endif -#ifdef CALG_HUGHES_MD5 - CIPHEROPTION(CALG_HUGHES_MD5); -#endif - CIPHEROPTION(CALG_SKIPJACK); -#ifdef CALG_TEK - CIPHEROPTION(CALG_TEK); -#endif - CIPHEROPTION(CALG_CYLINK_MEK); - CIPHEROPTION(CALG_SSL3_SHAMD5); -#ifdef CALG_SSL3_MASTER - CIPHEROPTION(CALG_SSL3_MASTER); -#endif -#ifdef CALG_SCHANNEL_MASTER_HASH - CIPHEROPTION(CALG_SCHANNEL_MASTER_HASH); -#endif -#ifdef CALG_SCHANNEL_MAC_KEY - CIPHEROPTION(CALG_SCHANNEL_MAC_KEY); -#endif -#ifdef CALG_SCHANNEL_ENC_KEY - CIPHEROPTION(CALG_SCHANNEL_ENC_KEY); -#endif -#ifdef CALG_PCT1_MASTER - CIPHEROPTION(CALG_PCT1_MASTER); -#endif -#ifdef CALG_SSL2_MASTER - CIPHEROPTION(CALG_SSL2_MASTER); -#endif -#ifdef CALG_TLS1_MASTER - CIPHEROPTION(CALG_TLS1_MASTER); -#endif -#ifdef CALG_RC5 - CIPHEROPTION(CALG_RC5); -#endif -#ifdef CALG_HMAC - CIPHEROPTION(CALG_HMAC); -#endif -#ifdef CALG_TLS1PRF - CIPHEROPTION(CALG_TLS1PRF); -#endif -#ifdef CALG_HASH_REPLACE_OWF - CIPHEROPTION(CALG_HASH_REPLACE_OWF); -#endif -#ifdef CALG_AES_128 - CIPHEROPTION(CALG_AES_128); -#endif -#ifdef CALG_AES_192 - CIPHEROPTION(CALG_AES_192); -#endif -#ifdef CALG_AES_256 - CIPHEROPTION(CALG_AES_256); -#endif -#ifdef CALG_AES - CIPHEROPTION(CALG_AES); -#endif -#ifdef CALG_SHA_256 - CIPHEROPTION(CALG_SHA_256); -#endif -#ifdef CALG_SHA_384 - CIPHEROPTION(CALG_SHA_384); -#endif -#ifdef CALG_SHA_512 - CIPHEROPTION(CALG_SHA_512); -#endif -#ifdef CALG_ECDH - CIPHEROPTION(CALG_ECDH); -#endif -#ifdef CALG_ECMQV - CIPHEROPTION(CALG_ECMQV); -#endif -#ifdef CALG_ECDSA - CIPHEROPTION(CALG_ECDSA); -#endif -#ifdef CALG_ECDH_EPHEM - CIPHEROPTION(CALG_ECDH_EPHEM); -#endif - return 0; -} - -#define NUM_CIPHERS 47 /* There are 47 options listed above */ - -static CURLcode -set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers, - ALG_ID *algIds) -{ - char *startCur = ciphers; - int algCount = 0; - while(startCur && (0 != *startCur) && (algCount < NUM_CIPHERS)) { - long alg = strtol(startCur, 0, 0); - if(!alg) - alg = get_alg_id_by_name(startCur); - if(alg) - algIds[algCount++] = alg; - else if(!strncmp(startCur, "USE_STRONG_CRYPTO", - sizeof("USE_STRONG_CRYPTO") - 1) || - !strncmp(startCur, "SCH_USE_STRONG_CRYPTO", - sizeof("SCH_USE_STRONG_CRYPTO") - 1)) - schannel_cred->dwFlags |= SCH_USE_STRONG_CRYPTO; - else - return CURLE_SSL_CIPHER; - startCur = strchr(startCur, ':'); - if(startCur) - startCur++; - } - schannel_cred->palgSupportedAlgs = algIds; - schannel_cred->cSupportedAlgs = algCount; - return CURLE_OK; -} - -#ifdef HAS_CLIENT_CERT_PATH - -/* Function allocates memory for store_path only if CURLE_OK is returned */ -static CURLcode -get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path, - TCHAR **thumbprint) -{ - TCHAR *sep; - TCHAR *store_path_start; - size_t store_name_len; - - sep = _tcschr(path, TEXT('\\')); - if(!sep) - return CURLE_SSL_CERTPROBLEM; - - store_name_len = sep - path; - - if(_tcsncmp(path, TEXT("CurrentUser"), store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_CURRENT_USER; - else if(_tcsncmp(path, TEXT("LocalMachine"), store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE; - else if(_tcsncmp(path, TEXT("CurrentService"), store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_CURRENT_SERVICE; - else if(_tcsncmp(path, TEXT("Services"), store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_SERVICES; - else if(_tcsncmp(path, TEXT("Users"), store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_USERS; - else if(_tcsncmp(path, TEXT("CurrentUserGroupPolicy"), - store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineGroupPolicy"), - store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY; - else if(_tcsncmp(path, TEXT("LocalMachineEnterprise"), - store_name_len) == 0) - *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE; - else - return CURLE_SSL_CERTPROBLEM; - - store_path_start = sep + 1; - - sep = _tcschr(store_path_start, TEXT('\\')); - if(!sep) - return CURLE_SSL_CERTPROBLEM; - - *thumbprint = sep + 1; - if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN) - return CURLE_SSL_CERTPROBLEM; - - *sep = TEXT('\0'); - *store_path = _tcsdup(store_path_start); - *sep = TEXT('\\'); - if(!*store_path) - return CURLE_OUT_OF_MEMORY; - - return CURLE_OK; -} -#endif -static CURLcode -schannel_acquire_credential_handle(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - -#ifdef HAS_CLIENT_CERT_PATH - PCCERT_CONTEXT client_certs[1] = { NULL }; - HCERTSTORE client_cert_store = NULL; -#endif - SECURITY_STATUS sspi_status = SEC_E_OK; - CURLcode result; - - /* setup Schannel API options */ - DWORD flags = 0; - DWORD enabled_protocols = 0; - - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(conn->ssl_config.verifypeer) { -#ifdef HAS_MANUAL_VERIFY_API - if(backend->use_manual_cred_validation) - flags = SCH_CRED_MANUAL_CRED_VALIDATION; - else -#endif - flags = SCH_CRED_AUTO_CRED_VALIDATION; - - if(SSL_SET_OPTION(no_revoke)) { - flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE; - - DEBUGF(infof(data, "schannel: disabled server certificate revocation " - "checks")); - } - else if(SSL_SET_OPTION(revoke_best_effort)) { - flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_REVOCATION_CHECK_CHAIN; - - DEBUGF(infof(data, "schannel: ignore revocation offline errors")); - } - else { - flags |= SCH_CRED_REVOCATION_CHECK_CHAIN; - - DEBUGF(infof(data, - "schannel: checking server certificate revocation")); - } - } - else { - flags = SCH_CRED_MANUAL_CRED_VALIDATION | - SCH_CRED_IGNORE_NO_REVOCATION_CHECK | - SCH_CRED_IGNORE_REVOCATION_OFFLINE; - DEBUGF(infof(data, - "schannel: disabled server cert revocation checks")); - } - - if(!conn->ssl_config.verifyhost) { - flags |= SCH_CRED_NO_SERVERNAME_CHECK; - DEBUGF(infof(data, "schannel: verifyhost setting prevents Schannel from " - "comparing the supplied target name with the subject " - "names in server certificates.")); - } - - if(!SSL_SET_OPTION(auto_client_cert)) { - flags &= ~SCH_CRED_USE_DEFAULT_CREDS; - flags |= SCH_CRED_NO_DEFAULT_CREDS; - infof(data, "schannel: disabled automatic use of client certificate"); - } - else - infof(data, "schannel: enabled automatic use of client certificate"); - - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - result = set_ssl_version_min_max(&enabled_protocols, data, conn); - if(result != CURLE_OK) - return result; - break; - } - case CURL_SSLVERSION_SSLv3: - case CURL_SSLVERSION_SSLv2: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef HAS_CLIENT_CERT_PATH - /* client certificate */ - if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) { - DWORD cert_store_name = 0; - TCHAR *cert_store_path = NULL; - TCHAR *cert_thumbprint_str = NULL; - CRYPT_HASH_BLOB cert_thumbprint; - BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN]; - HCERTSTORE cert_store = NULL; - FILE *fInCert = NULL; - void *certdata = NULL; - size_t certsize = 0; - bool blob = data->set.ssl.primary.cert_blob != NULL; - TCHAR *cert_path = NULL; - if(blob) { - certdata = data->set.ssl.primary.cert_blob->data; - certsize = data->set.ssl.primary.cert_blob->len; - } - else { - cert_path = curlx_convert_UTF8_to_tchar( - data->set.ssl.primary.clientcert); - if(!cert_path) - return CURLE_OUT_OF_MEMORY; - - result = get_cert_location(cert_path, &cert_store_name, - &cert_store_path, &cert_thumbprint_str); - - if(result && (data->set.ssl.primary.clientcert[0]!='\0')) - fInCert = fopen(data->set.ssl.primary.clientcert, "rb"); - - if(result && !fInCert) { - failf(data, "schannel: Failed to get certificate location" - " or file for %s", - data->set.ssl.primary.clientcert); - curlx_unicodefree(cert_path); - return result; - } - } - - if((fInCert || blob) && (data->set.ssl.cert_type) && - (!strcasecompare(data->set.ssl.cert_type, "P12"))) { - failf(data, "schannel: certificate format compatibility error " - " for %s", - blob ? "(memory blob)" : data->set.ssl.primary.clientcert); - curlx_unicodefree(cert_path); - return CURLE_SSL_CERTPROBLEM; - } - - if(fInCert || blob) { - /* Reading a .P12 or .pfx file, like the example at bottom of - https://social.msdn.microsoft.com/Forums/windowsdesktop/ - en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5 - */ - CRYPT_DATA_BLOB datablob; - WCHAR* pszPassword; - size_t pwd_len = 0; - int str_w_len = 0; - const char *cert_showfilename_error = blob ? - "(memory blob)" : data->set.ssl.primary.clientcert; - curlx_unicodefree(cert_path); - if(fInCert) { - long cert_tell = 0; - bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0; - if(continue_reading) - cert_tell = ftell(fInCert); - if(cert_tell < 0) - continue_reading = FALSE; - else - certsize = (size_t)cert_tell; - if(continue_reading) - continue_reading = fseek(fInCert, 0, SEEK_SET) == 0; - if(continue_reading) - certdata = malloc(certsize + 1); - if((!certdata) || - ((int) fread(certdata, certsize, 1, fInCert) != 1)) - continue_reading = FALSE; - fclose(fInCert); - if(!continue_reading) { - failf(data, "schannel: Failed to read cert file %s", - data->set.ssl.primary.clientcert); - free(certdata); - return CURLE_SSL_CERTPROBLEM; - } - } - - /* Convert key-pair data to the in-memory certificate store */ - datablob.pbData = (BYTE*)certdata; - datablob.cbData = (DWORD)certsize; - - if(data->set.ssl.key_passwd) - pwd_len = strlen(data->set.ssl.key_passwd); - pszPassword = (WCHAR*)malloc(sizeof(WCHAR)*(pwd_len + 1)); - if(pszPassword) { - if(pwd_len > 0) - str_w_len = MultiByteToWideChar(CP_UTF8, - MB_ERR_INVALID_CHARS, - data->set.ssl.key_passwd, - (int)pwd_len, - pszPassword, (int)(pwd_len + 1)); - - if((str_w_len >= 0) && (str_w_len <= (int)pwd_len)) - pszPassword[str_w_len] = 0; - else - pszPassword[0] = 0; - - if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) - cert_store = PFXImportCertStore(&datablob, pszPassword, - PKCS12_NO_PERSIST_KEY); - else - cert_store = PFXImportCertStore(&datablob, pszPassword, 0); - - free(pszPassword); - } - if(!blob) - free(certdata); - if(!cert_store) { - DWORD errorcode = GetLastError(); - if(errorcode == ERROR_INVALID_PASSWORD) - failf(data, "schannel: Failed to import cert file %s, " - "password is bad", - cert_showfilename_error); - else - failf(data, "schannel: Failed to import cert file %s, " - "last error is 0x%x", - cert_showfilename_error, errorcode); - return CURLE_SSL_CERTPROBLEM; - } - - client_certs[0] = CertFindCertificateInStore( - cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, - CERT_FIND_ANY, NULL, NULL); - - if(!client_certs[0]) { - failf(data, "schannel: Failed to get certificate from file %s" - ", last error is 0x%x", - cert_showfilename_error, GetLastError()); - CertCloseStore(cert_store, 0); - return CURLE_SSL_CERTPROBLEM; - } - } - else { - cert_store = - CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0, - (HCRYPTPROV)NULL, - CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name, - cert_store_path); - if(!cert_store) { - failf(data, "schannel: Failed to open cert store %x %s, " - "last error is 0x%x", - cert_store_name, cert_store_path, GetLastError()); - free(cert_store_path); - curlx_unicodefree(cert_path); - return CURLE_SSL_CERTPROBLEM; - } - free(cert_store_path); - - cert_thumbprint.pbData = cert_thumbprint_data; - cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN; - - if(!CryptStringToBinary(cert_thumbprint_str, - CERT_THUMBPRINT_STR_LEN, - CRYPT_STRING_HEX, - cert_thumbprint_data, - &cert_thumbprint.cbData, - NULL, NULL)) { - curlx_unicodefree(cert_path); - CertCloseStore(cert_store, 0); - return CURLE_SSL_CERTPROBLEM; - } - - client_certs[0] = CertFindCertificateInStore( - cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, - CERT_FIND_HASH, &cert_thumbprint, NULL); - - curlx_unicodefree(cert_path); - - if(!client_certs[0]) { - /* CRYPT_E_NOT_FOUND / E_INVALIDARG */ - CertCloseStore(cert_store, 0); - return CURLE_SSL_CERTPROBLEM; - } - } - client_cert_store = cert_store; - } -#else - if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) { - failf(data, "schannel: client cert support not built in"); - return CURLE_NOT_BUILT_IN; - } -#endif - - /* allocate memory for the re-usable credential handle */ - backend->cred = (struct Curl_schannel_cred *) - calloc(1, sizeof(struct Curl_schannel_cred)); - if(!backend->cred) { - failf(data, "schannel: unable to allocate memory"); - -#ifdef HAS_CLIENT_CERT_PATH - if(client_certs[0]) - CertFreeCertificateContext(client_certs[0]); - if(client_cert_store) - CertCloseStore(client_cert_store, 0); -#endif - - return CURLE_OUT_OF_MEMORY; - } - backend->cred->refcount = 1; - -#ifdef HAS_CLIENT_CERT_PATH - /* Since we did not persist the key, we need to extend the store's - * lifetime until the end of the connection - */ - backend->cred->client_cert_store = client_cert_store; -#endif - - /* Windows 10, 1809 (a.k.a. Windows 10 build 17763) */ - if(curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) { - - char *ciphers13 = 0; - - bool disable_aes_gcm_sha384 = FALSE; - bool disable_aes_gcm_sha256 = FALSE; - bool disable_chacha_poly = FALSE; - bool disable_aes_ccm_8_sha256 = FALSE; - bool disable_aes_ccm_sha256 = FALSE; - - SCH_CREDENTIALS credentials = { 0 }; - TLS_PARAMETERS tls_parameters = { 0 }; - CRYPTO_SETTINGS crypto_settings[4] = { 0 }; - UNICODE_STRING blocked_ccm_modes[1] = { 0 }; - UNICODE_STRING blocked_gcm_modes[1] = { 0 }; - - int crypto_settings_idx = 0; - - - /* If TLS 1.3 ciphers are explicitly listed, then - * disable all the ciphers and re-enable which - * ciphers the user has provided. - */ - ciphers13 = SSL_CONN_CONFIG(cipher_list13); - if(ciphers13) { - const int remaining_ciphers = 5; - - /* detect which remaining ciphers to enable - and then disable everything else. - */ - - char *startCur = ciphers13; - int algCount = 0; - char tmp[LONGEST_ALG_ID] = { 0 }; - char *nameEnd; - size_t n; - - disable_aes_gcm_sha384 = TRUE; - disable_aes_gcm_sha256 = TRUE; - disable_chacha_poly = TRUE; - disable_aes_ccm_8_sha256 = TRUE; - disable_aes_ccm_sha256 = TRUE; - - while(startCur && (0 != *startCur) && (algCount < remaining_ciphers)) { - nameEnd = strchr(startCur, ':'); - n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur); - - /* reject too-long cipher names */ - if(n > (LONGEST_ALG_ID - 1)) { - failf(data, "Cipher name too long, not checked."); - return CURLE_SSL_CIPHER; - } - - strncpy(tmp, startCur, n); - tmp[n] = 0; - - if(disable_aes_gcm_sha384 - && !strcmp("TLS_AES_256_GCM_SHA384", tmp)) { - disable_aes_gcm_sha384 = FALSE; - } - else if(disable_aes_gcm_sha256 - && !strcmp("TLS_AES_128_GCM_SHA256", tmp)) { - disable_aes_gcm_sha256 = FALSE; - } - else if(disable_chacha_poly - && !strcmp("TLS_CHACHA20_POLY1305_SHA256", tmp)) { - disable_chacha_poly = FALSE; - } - else if(disable_aes_ccm_8_sha256 - && !strcmp("TLS_AES_128_CCM_8_SHA256", tmp)) { - disable_aes_ccm_8_sha256 = FALSE; - } - else if(disable_aes_ccm_sha256 - && !strcmp("TLS_AES_128_CCM_SHA256", tmp)) { - disable_aes_ccm_sha256 = FALSE; - } - else { - failf(data, "Passed in an unknown TLS 1.3 cipher."); - return CURLE_SSL_CIPHER; - } - - startCur = nameEnd; - if(startCur) - startCur++; - - algCount++; - } - } - - if(disable_aes_gcm_sha384 && disable_aes_gcm_sha256 - && disable_chacha_poly && disable_aes_ccm_8_sha256 - && disable_aes_ccm_sha256) { - failf(data, "All available TLS 1.3 ciphers were disabled."); - return CURLE_SSL_CIPHER; - } - - /* Disable TLS_AES_128_CCM_8_SHA256 and/or TLS_AES_128_CCM_SHA256 */ - if(disable_aes_ccm_8_sha256 || disable_aes_ccm_sha256) { - /* - Disallow AES_CCM algorithm. - */ - blocked_ccm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_CCM); - blocked_ccm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_CCM); - blocked_ccm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_CCM; - - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageCipher; - crypto_settings[crypto_settings_idx].rgstrChainingModes = - blocked_ccm_modes; - crypto_settings[crypto_settings_idx].cChainingModes = - ARRAYSIZE(blocked_ccm_modes); - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)BCRYPT_AES_ALGORITHM; - - /* only disabling one of the CCM modes */ - if(disable_aes_ccm_8_sha256 != disable_aes_ccm_sha256) { - if(disable_aes_ccm_8_sha256) - crypto_settings[crypto_settings_idx].dwMinBitLength = 128; - else /* disable_aes_ccm_sha256 */ - crypto_settings[crypto_settings_idx].dwMaxBitLength = 64; - } - - crypto_settings_idx++; - } - - /* Disable TLS_AES_256_GCM_SHA384 and/or TLS_AES_128_GCM_SHA256 */ - if(disable_aes_gcm_sha384 || disable_aes_gcm_sha256) { - - /* - Disallow AES_GCM algorithm - */ - blocked_gcm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_GCM); - blocked_gcm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_GCM); - blocked_gcm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_GCM; - - /* if only one is disabled, then explicitly disable the - digest cipher suite (sha384 or sha256) */ - if(disable_aes_gcm_sha384 != disable_aes_gcm_sha256) { - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageDigest; - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(disable_aes_gcm_sha384 ? - BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(disable_aes_gcm_sha384 ? - BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)(disable_aes_gcm_sha384 ? - BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM); - } - else { /* Disable both AES_GCM ciphers */ - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageCipher; - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(BCRYPT_AES_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)BCRYPT_AES_ALGORITHM; - } - - crypto_settings[crypto_settings_idx].rgstrChainingModes = - blocked_gcm_modes; - crypto_settings[crypto_settings_idx].cChainingModes = 1; - - crypto_settings_idx++; - } - - /* - Disable ChaCha20-Poly1305. - */ - if(disable_chacha_poly) { - crypto_settings[crypto_settings_idx].eAlgorithmUsage = - TlsParametersCngAlgUsageCipher; - crypto_settings[crypto_settings_idx].strCngAlgId.Length = - sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength = - sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM); - crypto_settings[crypto_settings_idx].strCngAlgId.Buffer = - (PWSTR)BCRYPT_CHACHA20_POLY1305_ALGORITHM; - crypto_settings_idx++; - } - - tls_parameters.pDisabledCrypto = crypto_settings; - - /* The number of blocked suites */ - tls_parameters.cDisabledCrypto = crypto_settings_idx; - credentials.pTlsParameters = &tls_parameters; - credentials.cTlsParameters = 1; - - credentials.dwVersion = SCH_CREDENTIALS_VERSION; - credentials.dwFlags = flags | SCH_USE_STRONG_CRYPTO; - - credentials.pTlsParameters->grbitDisabledProtocols = - (DWORD)~enabled_protocols; - -#ifdef HAS_CLIENT_CERT_PATH - if(client_certs[0]) { - credentials.cCreds = 1; - credentials.paCred = client_certs; - } -#endif - - sspi_status = - s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, - SECPKG_CRED_OUTBOUND, NULL, - &credentials, NULL, NULL, - &backend->cred->cred_handle, - &backend->cred->time_stamp); - } - else { - /* Pre-Windows 10 1809 */ - ALG_ID algIds[NUM_CIPHERS]; - char *ciphers = SSL_CONN_CONFIG(cipher_list); - SCHANNEL_CRED schannel_cred = { 0 }; - schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; - schannel_cred.dwFlags = flags; - schannel_cred.grbitEnabledProtocols = enabled_protocols; - - if(ciphers) { - result = set_ssl_ciphers(&schannel_cred, ciphers, algIds); - if(CURLE_OK != result) { - failf(data, "Unable to set ciphers to passed via SSL_CONN_CONFIG"); - return result; - } - } - else { - schannel_cred.dwFlags = flags | SCH_USE_STRONG_CRYPTO; - } - -#ifdef HAS_CLIENT_CERT_PATH - if(client_certs[0]) { - schannel_cred.cCreds = 1; - schannel_cred.paCred = client_certs; - } -#endif - - sspi_status = - s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME, - SECPKG_CRED_OUTBOUND, NULL, - &schannel_cred, NULL, NULL, - &backend->cred->cred_handle, - &backend->cred->time_stamp); - } - -#ifdef HAS_CLIENT_CERT_PATH - if(client_certs[0]) - CertFreeCertificateContext(client_certs[0]); -#endif - - if(sspi_status != SEC_E_OK) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: AcquireCredentialsHandle failed: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - Curl_safefree(backend->cred); - switch(sspi_status) { - case SEC_E_INSUFFICIENT_MEMORY: - return CURLE_OUT_OF_MEMORY; - case SEC_E_NO_CREDENTIALS: - case SEC_E_SECPKG_NOT_FOUND: - case SEC_E_NOT_OWNER: - case SEC_E_UNKNOWN_CREDENTIALS: - case SEC_E_INTERNAL_ERROR: - default: - return CURLE_SSL_CONNECT_ERROR; - } - } - - return CURLE_OK; -} - -static CURLcode -schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - ssize_t written = -1; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - SecBuffer outbuf; - SecBufferDesc outbuf_desc; - SecBuffer inbuf; - SecBufferDesc inbuf_desc; -#ifdef HAS_ALPN - unsigned char alpn_buffer[128]; -#endif - SECURITY_STATUS sspi_status = SEC_E_OK; - struct Curl_schannel_cred *old_cred = NULL; - struct in_addr addr; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; -#endif - CURLcode result; - char * const hostname = SSL_HOST_NAME(); - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - DEBUGF(infof(data, - "schannel: SSL/TLS connection with %s port %hu (step 1/3)", - hostname, conn->remote_port)); - - if(curlx_verify_windows_version(5, 1, 0, PLATFORM_WINNT, - VERSION_LESS_THAN_EQUAL)) { - /* Schannel in Windows XP (OS version 5.1) uses legacy handshakes and - algorithms that may not be supported by all servers. */ - infof(data, "schannel: Windows version is old and may not be able to " - "connect to some servers due to lack of SNI, algorithms, etc."); - } - -#ifdef HAS_ALPN - /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above. - Also it doesn't seem to be supported for Wine, see curl bug #983. */ - backend->use_alpn = conn->bits.tls_enable_alpn && - !GetProcAddress(GetModuleHandle(TEXT("ntdll")), - "wine_get_version") && - curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL); -#else - backend->use_alpn = false; -#endif - -#ifdef _WIN32_WCE -#ifdef HAS_MANUAL_VERIFY_API - /* certificate validation on CE doesn't seem to work right; we'll - * do it following a more manual process. */ - backend->use_manual_cred_validation = true; -#else -#error "compiler too old to support requisite manual cert verify for Win CE" -#endif -#else -#ifdef HAS_MANUAL_VERIFY_API - if(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) { - if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) { - backend->use_manual_cred_validation = true; - } - else { - failf(data, "schannel: this version of Windows is too old to support " - "certificate verification via CA bundle file."); - return CURLE_SSL_CACERT_BADFILE; - } - } - else - backend->use_manual_cred_validation = false; -#else - 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; - } -#endif -#endif - - backend->cred = NULL; - - /* check for an existing re-usable credential handle */ - if(SSL_SET_OPTION(primary.sessionid)) { - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - (void **)&old_cred, NULL, sockindex)) { - backend->cred = old_cred; - DEBUGF(infof(data, "schannel: re-using existing credential handle")); - - /* increment the reference counter of the credential/session handle */ - backend->cred->refcount++; - DEBUGF(infof(data, - "schannel: incremented credential handle refcount = %d", - backend->cred->refcount)); - } - Curl_ssl_sessionid_unlock(data); - } - - if(!backend->cred) { - char *snihost; - result = schannel_acquire_credential_handle(data, conn, sockindex); - if(result != CURLE_OK) { - return result; - } - /* A hostname associated with the credential is needed by - InitializeSecurityContext for SNI and other reasons. */ - snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost); - if(!backend->cred->sni_hostname) - return CURLE_OUT_OF_MEMORY; - } - - /* Warn if SNI is disabled due to use of an IP address */ - if(Curl_inet_pton(AF_INET, hostname, &addr) -#ifdef ENABLE_IPV6 - || Curl_inet_pton(AF_INET6, hostname, &addr6) -#endif - ) { - infof(data, "schannel: using IP address, SNI is not supported by OS."); - } - -#ifdef HAS_ALPN - if(backend->use_alpn) { - int cur = 0; - int list_start_index = 0; - unsigned int *extension_len = NULL; - unsigned short* list_len = NULL; - - /* The first four bytes will be an unsigned int indicating number - of bytes of data in the rest of the buffer. */ - extension_len = (unsigned int *)(&alpn_buffer[cur]); - cur += sizeof(unsigned int); - - /* The next four bytes are an indicator that this buffer will contain - ALPN data, as opposed to NPN, for example. */ - *(unsigned int *)&alpn_buffer[cur] = - SecApplicationProtocolNegotiationExt_ALPN; - cur += sizeof(unsigned int); - - /* The next two bytes will be an unsigned short indicating the number - of bytes used to list the preferred protocols. */ - list_len = (unsigned short*)(&alpn_buffer[cur]); - cur += sizeof(unsigned short); - - list_start_index = cur; - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) { - alpn_buffer[cur++] = ALPN_H2_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_H2, ALPN_H2_LENGTH); - cur += ALPN_H2_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH; - memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - cur += ALPN_HTTP_1_1_LENGTH; - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - *list_len = curlx_uitous(cur - list_start_index); - *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short); - - InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); - InitSecBufferDesc(&inbuf_desc, &inbuf, 1); - } - else { - InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&inbuf_desc, &inbuf, 1); - } -#else /* HAS_ALPN */ - InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&inbuf_desc, &inbuf, 1); -#endif - - /* setup output buffer */ - InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&outbuf_desc, &outbuf, 1); - - /* security request flags */ - backend->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | - ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY | - ISC_REQ_STREAM; - - if(!SSL_SET_OPTION(auto_client_cert)) { - backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; - } - - /* allocate memory for the security context handle */ - backend->ctxt = (struct Curl_schannel_ctxt *) - calloc(1, sizeof(struct Curl_schannel_ctxt)); - if(!backend->ctxt) { - failf(data, "schannel: unable to allocate memory"); - return CURLE_OUT_OF_MEMORY; - } - - /* Schannel InitializeSecurityContext: - https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx - - At the moment we don't pass inbuf unless we're using ALPN since we only - use it for that, and Wine (for which we currently disable ALPN) is giving - us problems with inbuf regardless. https://github.com/curl/curl/issues/983 - */ - sspi_status = s_pSecFn->InitializeSecurityContext( - &backend->cred->cred_handle, NULL, backend->cred->sni_hostname, - backend->req_flags, 0, 0, - (backend->use_alpn ? &inbuf_desc : NULL), - 0, &backend->ctxt->ctxt_handle, - &outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp); - - if(sspi_status != SEC_I_CONTINUE_NEEDED) { - char buffer[STRERROR_LEN]; - Curl_safefree(backend->ctxt); - switch(sspi_status) { - case SEC_E_INSUFFICIENT_MEMORY: - failf(data, "schannel: initial InitializeSecurityContext failed: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - return CURLE_OUT_OF_MEMORY; - case SEC_E_WRONG_PRINCIPAL: - failf(data, "schannel: SNI or certificate check failed: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - return CURLE_PEER_FAILED_VERIFICATION; - /* - case SEC_E_INVALID_HANDLE: - case SEC_E_INVALID_TOKEN: - case SEC_E_LOGON_DENIED: - case SEC_E_TARGET_UNKNOWN: - case SEC_E_NO_AUTHENTICATING_AUTHORITY: - case SEC_E_INTERNAL_ERROR: - case SEC_E_NO_CREDENTIALS: - case SEC_E_UNSUPPORTED_FUNCTION: - case SEC_E_APPLICATION_PROTOCOL_MISMATCH: - */ - default: - failf(data, "schannel: initial InitializeSecurityContext failed: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - return CURLE_SSL_CONNECT_ERROR; - } - } - - DEBUGF(infof(data, "schannel: sending initial handshake data: " - "sending %lu bytes.", outbuf.cbBuffer)); - - /* send initial handshake data which is now stored in output buffer */ - result = Curl_write_plain(data, conn->sock[sockindex], outbuf.pvBuffer, - outbuf.cbBuffer, &written); - s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); - if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { - failf(data, "schannel: failed to send initial handshake data: " - "sent %zd of %lu bytes", written, outbuf.cbBuffer); - return CURLE_SSL_CONNECT_ERROR; - } - - DEBUGF(infof(data, "schannel: sent initial handshake data: " - "sent %zd bytes", written)); - - backend->recv_unrecoverable_err = CURLE_OK; - backend->recv_sspi_close_notify = false; - backend->recv_connection_closed = false; - backend->recv_renegotiating = false; - backend->encdata_is_incomplete = false; - - /* continue to second handshake step */ - connssl->connecting_state = ssl_connect_2; - - return CURLE_OK; -} - -static CURLcode -schannel_connect_step2(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - int i; - ssize_t nread = -1, written = -1; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - unsigned char *reallocated_buffer; - SecBuffer outbuf[3]; - SecBufferDesc outbuf_desc; - SecBuffer inbuf[2]; - SecBufferDesc inbuf_desc; - SECURITY_STATUS sspi_status = SEC_E_OK; - CURLcode result; - bool doread; - const char *pubkey_ptr; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE; - - DEBUGF(infof(data, - "schannel: SSL/TLS connection with %s port %hu (step 2/3)", - SSL_HOST_NAME(), conn->remote_port)); - - if(!backend->cred || !backend->ctxt) - return CURLE_SSL_CONNECT_ERROR; - - /* buffer to store previously received and decrypted data */ - if(!backend->decdata_buffer) { - backend->decdata_offset = 0; - backend->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; - backend->decdata_buffer = malloc(backend->decdata_length); - if(!backend->decdata_buffer) { - failf(data, "schannel: unable to allocate memory"); - return CURLE_OUT_OF_MEMORY; - } - } - - /* buffer to store previously received and encrypted data */ - if(!backend->encdata_buffer) { - backend->encdata_is_incomplete = false; - backend->encdata_offset = 0; - backend->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; - backend->encdata_buffer = malloc(backend->encdata_length); - if(!backend->encdata_buffer) { - failf(data, "schannel: unable to allocate memory"); - return CURLE_OUT_OF_MEMORY; - } - } - - /* if we need a bigger buffer to read a full message, increase buffer now */ - if(backend->encdata_length - backend->encdata_offset < - CURL_SCHANNEL_BUFFER_FREE_SIZE) { - /* increase internal encrypted data buffer */ - size_t reallocated_length = backend->encdata_offset + - CURL_SCHANNEL_BUFFER_FREE_SIZE; - reallocated_buffer = realloc(backend->encdata_buffer, - reallocated_length); - - if(!reallocated_buffer) { - failf(data, "schannel: unable to re-allocate memory"); - return CURLE_OUT_OF_MEMORY; - } - else { - backend->encdata_buffer = reallocated_buffer; - backend->encdata_length = reallocated_length; - } - } - - for(;;) { - if(doread) { - /* read encrypted handshake data from socket */ - result = Curl_read_plain(conn->sock[sockindex], - (char *) (backend->encdata_buffer + - backend->encdata_offset), - backend->encdata_length - - backend->encdata_offset, - &nread); - if(result == CURLE_AGAIN) { - if(connssl->connecting_state != ssl_connect_2_writing) - connssl->connecting_state = ssl_connect_2_reading; - DEBUGF(infof(data, "schannel: failed to receive handshake, " - "need more data")); - return CURLE_OK; - } - else if((result != CURLE_OK) || (nread == 0)) { - failf(data, "schannel: failed to receive handshake, " - "SSL/TLS connection failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* increase encrypted data buffer offset */ - backend->encdata_offset += nread; - backend->encdata_is_incomplete = false; - DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); - } - - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); - - /* setup input buffers */ - InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(backend->encdata_offset), - curlx_uztoul(backend->encdata_offset)); - InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&inbuf_desc, inbuf, 2); - - /* setup output buffers */ - InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0); - InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0); - InitSecBuffer(&outbuf[2], SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&outbuf_desc, outbuf, 3); - - if(!inbuf[0].pvBuffer) { - failf(data, "schannel: unable to allocate memory"); - return CURLE_OUT_OF_MEMORY; - } - - /* copy received handshake data into input buffer */ - memcpy(inbuf[0].pvBuffer, backend->encdata_buffer, - backend->encdata_offset); - - sspi_status = s_pSecFn->InitializeSecurityContext( - &backend->cred->cred_handle, &backend->ctxt->ctxt_handle, - backend->cred->sni_hostname, backend->req_flags, - 0, 0, &inbuf_desc, 0, NULL, - &outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp); - - /* free buffer for received handshake data */ - Curl_safefree(inbuf[0].pvBuffer); - - /* check if the handshake was incomplete */ - if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { - backend->encdata_is_incomplete = true; - connssl->connecting_state = ssl_connect_2_reading; - DEBUGF(infof(data, - "schannel: received incomplete message, need more data")); - return CURLE_OK; - } - - /* If the server has requested a client certificate, attempt to continue - the handshake without one. This will allow connections to servers which - request a client certificate but do not require it. */ - if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS && - !(backend->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) { - backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS; - connssl->connecting_state = ssl_connect_2_writing; - DEBUGF(infof(data, - "schannel: a client certificate has been requested")); - return CURLE_OK; - } - - /* check if the handshake needs to be continued */ - if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) { - for(i = 0; i < 3; i++) { - /* search for handshake tokens that need to be send */ - if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) { - DEBUGF(infof(data, "schannel: sending next handshake data: " - "sending %lu bytes.", outbuf[i].cbBuffer)); - - /* send handshake token to server */ - result = Curl_write_plain(data, conn->sock[sockindex], - outbuf[i].pvBuffer, outbuf[i].cbBuffer, - &written); - if((result != CURLE_OK) || - (outbuf[i].cbBuffer != (size_t) written)) { - failf(data, "schannel: failed to send next handshake data: " - "sent %zd of %lu bytes", written, outbuf[i].cbBuffer); - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* free obsolete buffer */ - if(outbuf[i].pvBuffer) { - s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); - } - } - } - else { - char buffer[STRERROR_LEN]; - switch(sspi_status) { - case SEC_E_INSUFFICIENT_MEMORY: - failf(data, "schannel: next InitializeSecurityContext failed: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - return CURLE_OUT_OF_MEMORY; - case SEC_E_WRONG_PRINCIPAL: - failf(data, "schannel: SNI or certificate check failed: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - return CURLE_PEER_FAILED_VERIFICATION; - case SEC_E_UNTRUSTED_ROOT: - failf(data, "schannel: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - return CURLE_PEER_FAILED_VERIFICATION; - /* - case SEC_E_INVALID_HANDLE: - case SEC_E_INVALID_TOKEN: - case SEC_E_LOGON_DENIED: - case SEC_E_TARGET_UNKNOWN: - case SEC_E_NO_AUTHENTICATING_AUTHORITY: - case SEC_E_INTERNAL_ERROR: - case SEC_E_NO_CREDENTIALS: - case SEC_E_UNSUPPORTED_FUNCTION: - case SEC_E_APPLICATION_PROTOCOL_MISMATCH: - */ - default: - failf(data, "schannel: next InitializeSecurityContext failed: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - return CURLE_SSL_CONNECT_ERROR; - } - } - - /* check if there was additional remaining encrypted data */ - if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) { - DEBUGF(infof(data, "schannel: encrypted data length: %lu", - inbuf[1].cbBuffer)); - /* - There are two cases where we could be getting extra data here: - 1) If we're renegotiating a connection and the handshake is already - complete (from the server perspective), it can encrypted app data - (not handshake data) in an extra buffer at this point. - 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a - connection and this extra data is part of the handshake. - We should process the data immediately; waiting for the socket to - be ready may fail since the server is done sending handshake data. - */ - /* check if the remaining data is less than the total amount - and therefore begins after the already processed data */ - if(backend->encdata_offset > inbuf[1].cbBuffer) { - memmove(backend->encdata_buffer, - (backend->encdata_buffer + backend->encdata_offset) - - inbuf[1].cbBuffer, inbuf[1].cbBuffer); - backend->encdata_offset = inbuf[1].cbBuffer; - if(sspi_status == SEC_I_CONTINUE_NEEDED) { - doread = FALSE; - continue; - } - } - } - else { - backend->encdata_offset = 0; - } - break; - } - - /* check if the handshake needs to be continued */ - if(sspi_status == SEC_I_CONTINUE_NEEDED) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - - /* check if the handshake is complete */ - if(sspi_status == SEC_E_OK) { - connssl->connecting_state = ssl_connect_3; - DEBUGF(infof(data, "schannel: SSL/TLS handshake complete")); - } - - pubkey_ptr = SSL_PINNED_PUB_KEY(); - if(pubkey_ptr) { - result = pkp_pin_peer_pubkey(data, conn, sockindex, pubkey_ptr); - if(result) { - failf(data, "SSL: public key does not match pinned public key"); - return result; - } - } - -#ifdef HAS_MANUAL_VERIFY_API - if(conn->ssl_config.verifypeer && backend->use_manual_cred_validation) { - return Curl_verify_certificate(data, conn, sockindex); - } -#endif - - return CURLE_OK; -} - -static bool -valid_cert_encoding(const CERT_CONTEXT *cert_context) -{ - return (cert_context != NULL) && - ((cert_context->dwCertEncodingType & X509_ASN_ENCODING) != 0) && - (cert_context->pbCertEncoded != NULL) && - (cert_context->cbCertEncoded > 0); -} - -typedef bool(*Read_crt_func)(const CERT_CONTEXT *ccert_context, void *arg); - -static void -traverse_cert_store(const CERT_CONTEXT *context, Read_crt_func func, - void *arg) -{ - const CERT_CONTEXT *current_context = NULL; - bool should_continue = true; - while(should_continue && - (current_context = CertEnumCertificatesInStore( - context->hCertStore, - current_context)) != NULL) - should_continue = func(current_context, arg); - - if(current_context) - CertFreeCertificateContext(current_context); -} - -static bool -cert_counter_callback(const CERT_CONTEXT *ccert_context, void *certs_count) -{ - if(valid_cert_encoding(ccert_context)) - (*(int *)certs_count)++; - return true; -} - -struct Adder_args -{ - struct Curl_easy *data; - CURLcode result; - int idx; - int certs_count; -}; - -static bool -add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, void *raw_arg) -{ - struct Adder_args *args = (struct Adder_args*)raw_arg; - args->result = CURLE_OK; - if(valid_cert_encoding(ccert_context)) { - const char *beg = (const char *) ccert_context->pbCertEncoded; - const char *end = beg + ccert_context->cbCertEncoded; - int insert_index = (args->certs_count - 1) - args->idx; - args->result = Curl_extract_certinfo(args->data, insert_index, - beg, end); - args->idx++; - } - return args->result == CURLE_OK; -} - -static CURLcode -schannel_connect_step3(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - CURLcode result = CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - SECURITY_STATUS sspi_status = SEC_E_OK; - CERT_CONTEXT *ccert_context = NULL; - bool isproxy = SSL_IS_PROXY(); -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - const char * const hostname = SSL_HOST_NAME(); -#endif -#ifdef HAS_ALPN - SecPkgContext_ApplicationProtocol alpn_result; -#endif - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - DEBUGASSERT(backend); - - DEBUGF(infof(data, - "schannel: SSL/TLS connection with %s port %hu (step 3/3)", - hostname, conn->remote_port)); - - if(!backend->cred) - return CURLE_SSL_CONNECT_ERROR; - - /* check if the required context attributes are met */ - if(backend->ret_flags != backend->req_flags) { - if(!(backend->ret_flags & ISC_RET_SEQUENCE_DETECT)) - failf(data, "schannel: failed to setup sequence detection"); - if(!(backend->ret_flags & ISC_RET_REPLAY_DETECT)) - failf(data, "schannel: failed to setup replay detection"); - if(!(backend->ret_flags & ISC_RET_CONFIDENTIALITY)) - failf(data, "schannel: failed to setup confidentiality"); - if(!(backend->ret_flags & ISC_RET_ALLOCATED_MEMORY)) - failf(data, "schannel: failed to setup memory allocation"); - if(!(backend->ret_flags & ISC_RET_STREAM)) - failf(data, "schannel: failed to setup stream orientation"); - return CURLE_SSL_CONNECT_ERROR; - } - -#ifdef HAS_ALPN - if(backend->use_alpn) { - sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, - SECPKG_ATTR_APPLICATION_PROTOCOL, - &alpn_result); - - if(sspi_status != SEC_E_OK) { - failf(data, "schannel: failed to retrieve ALPN result"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(alpn_result.ProtoNegoStatus == - SecApplicationProtocolNegotiationStatus_Success) { - unsigned char alpn = 0; - - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, - alpn_result.ProtocolIdSize, alpn_result.ProtocolId); - -#ifdef USE_HTTP2 - if(alpn_result.ProtocolIdSize == ALPN_H2_LENGTH && - !memcmp(ALPN_H2, alpn_result.ProtocolId, ALPN_H2_LENGTH)) { - alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId, - ALPN_HTTP_1_1_LENGTH)) { - alpn = CURL_HTTP_VERSION_1_1; - } - if(backend->recv_renegotiating) { - if(alpn != conn->alpn) { - failf(data, "schannel: server selected an ALPN protocol too late"); - return CURLE_SSL_CONNECT_ERROR; - } - } - else - conn->alpn = alpn; - } - else { - if(!backend->recv_renegotiating) - infof(data, VTLS_INFOF_NO_ALPN); - } - - if(!backend->recv_renegotiating) { - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } - } -#endif - - /* save the current session data for possible re-use */ - if(SSL_SET_OPTION(primary.sessionid)) { - bool incache; - bool added = FALSE; - struct Curl_schannel_cred *old_cred = NULL; - - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(data, conn, isproxy, (void **)&old_cred, - NULL, sockindex)); - if(incache) { - if(old_cred != backend->cred) { - DEBUGF(infof(data, - "schannel: old credential handle is stale, removing")); - /* we're not taking old_cred ownership here, no refcount++ is needed */ - Curl_ssl_delsessionid(data, (void *)old_cred); - incache = FALSE; - } - } - if(!incache) { - result = Curl_ssl_addsessionid(data, conn, isproxy, backend->cred, - sizeof(struct Curl_schannel_cred), - sockindex, &added); - if(result) { - Curl_ssl_sessionid_unlock(data); - failf(data, "schannel: failed to store credential handle"); - return result; - } - else if(added) { - /* this cred session is now also referenced by sessionid cache */ - backend->cred->refcount++; - DEBUGF(infof(data, - "schannel: stored credential handle in session cache")); - } - } - Curl_ssl_sessionid_unlock(data); - } - - if(data->set.ssl.certinfo) { - int certs_count = 0; - sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &ccert_context); - - if((sspi_status != SEC_E_OK) || !ccert_context) { - failf(data, "schannel: failed to retrieve remote cert context"); - return CURLE_PEER_FAILED_VERIFICATION; - } - - traverse_cert_store(ccert_context, cert_counter_callback, &certs_count); - - result = Curl_ssl_init_certinfo(data, certs_count); - if(!result) { - struct Adder_args args; - args.data = data; - args.idx = 0; - args.certs_count = certs_count; - traverse_cert_store(ccert_context, add_cert_to_certinfo, &args); - result = args.result; - } - CertFreeCertificateContext(ccert_context); - if(result) - return result; - } - - connssl->connecting_state = ssl_connect_done; - - return CURLE_OK; -} - -static CURLcode -schannel_connect_common(struct Curl_easy *data, struct connectdata *conn, - int sockindex, bool nonblocking, bool *done) -{ - CURLcode result; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - timediff_t timeout_ms; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1 == connssl->connecting_state) { - /* check out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - result = schannel_connect_step1(data, conn, sockindex); - if(result) - return result; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check out how much more time we're allowed */ - timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state ? sockfd : CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking ? 0 : timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL/TLS connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ - result = schannel_connect_step2(data, conn, sockindex); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return result; - - } /* repeat step2 until all transactions are done. */ - - if(ssl_connect_3 == connssl->connecting_state) { - result = schannel_connect_step3(data, conn, sockindex); - if(result) - return result; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - if(!connssl->backend->recv_renegotiating) { - /* On renegotiation, we don't want to reset the existing recv/send - * function pointers. They will have been set after the initial TLS - * handshake was completed. If they were subsequently modified, as - * is the case with HTTP/2, we don't want to override that change. - */ - conn->recv[sockindex] = schannel_recv; - conn->send[sockindex] = schannel_send; - } - -#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS - /* When SSPI is used in combination with Schannel - * we need the Schannel context to create the Schannel - * binding to pass the IIS extended protection checks. - * Available on Windows 7 or later. - */ - { - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - conn->sslContext = &backend->ctxt->ctxt_handle; - } -#endif - - *done = TRUE; - } - else - *done = FALSE; - - /* reset our connection state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static ssize_t -schannel_send(struct Curl_easy *data, int sockindex, - const void *buf, size_t len, CURLcode *err) -{ - ssize_t written = -1; - size_t data_len = 0; - unsigned char *ptr = NULL; - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - SecBuffer outbuf[4]; - SecBufferDesc outbuf_desc; - SECURITY_STATUS sspi_status = SEC_E_OK; - CURLcode result; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - /* check if the maximum stream sizes were queried */ - if(backend->stream_sizes.cbMaximumMessage == 0) { - sspi_status = s_pSecFn->QueryContextAttributes( - &backend->ctxt->ctxt_handle, - SECPKG_ATTR_STREAM_SIZES, - &backend->stream_sizes); - if(sspi_status != SEC_E_OK) { - *err = CURLE_SEND_ERROR; - return -1; - } - } - - /* check if the buffer is longer than the maximum message length */ - if(len > backend->stream_sizes.cbMaximumMessage) { - len = backend->stream_sizes.cbMaximumMessage; - } - - /* calculate the complete message length and allocate a buffer for it */ - data_len = backend->stream_sizes.cbHeader + len + - backend->stream_sizes.cbTrailer; - ptr = (unsigned char *) malloc(data_len); - if(!ptr) { - *err = CURLE_OUT_OF_MEMORY; - return -1; - } - - /* setup output buffers (header, data, trailer, empty) */ - InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER, - ptr, backend->stream_sizes.cbHeader); - InitSecBuffer(&outbuf[1], SECBUFFER_DATA, - ptr + backend->stream_sizes.cbHeader, curlx_uztoul(len)); - InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER, - ptr + backend->stream_sizes.cbHeader + len, - backend->stream_sizes.cbTrailer); - InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&outbuf_desc, outbuf, 4); - - /* copy data into output buffer */ - memcpy(outbuf[1].pvBuffer, buf, len); - - /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ - sspi_status = s_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0, - &outbuf_desc, 0); - - /* check if the message was encrypted */ - if(sspi_status == SEC_E_OK) { - written = 0; - - /* send the encrypted message including header, data and trailer */ - len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; - - /* - It's important to send the full message which includes the header, - encrypted payload, and trailer. Until the client receives all the - data a coherent message has not been delivered and the client - can't read any of it. - - If we wanted to buffer the unwritten encrypted bytes, we would - tell the client that all data it has requested to be sent has been - sent. The unwritten encrypted bytes would be the first bytes to - send on the next invocation. - Here's the catch with this - if we tell the client that all the - bytes have been sent, will the client call this method again to - send the buffered data? Looking at who calls this function, it - seems the answer is NO. - */ - - /* send entire message or fail */ - while(len > (size_t)written) { - ssize_t this_write = 0; - int what; - timediff_t timeout_ms = Curl_timeleft(data, NULL, FALSE); - if(timeout_ms < 0) { - /* we already got the timeout */ - failf(data, "schannel: timed out sending data " - "(bytes sent: %zd)", written); - *err = CURLE_OPERATION_TIMEDOUT; - written = -1; - break; - } - else if(!timeout_ms) - timeout_ms = TIMEDIFF_T_MAX; - what = SOCKET_WRITABLE(conn->sock[sockindex], timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - *err = CURLE_SEND_ERROR; - written = -1; - break; - } - else if(0 == what) { - failf(data, "schannel: timed out sending data " - "(bytes sent: %zd)", written); - *err = CURLE_OPERATION_TIMEDOUT; - written = -1; - break; - } - /* socket is writable */ - - result = Curl_write_plain(data, conn->sock[sockindex], ptr + written, - len - written, &this_write); - if(result == CURLE_AGAIN) - continue; - else if(result != CURLE_OK) { - *err = result; - written = -1; - break; - } - - written += this_write; - } - } - else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) { - *err = CURLE_OUT_OF_MEMORY; - } - else{ - *err = CURLE_SEND_ERROR; - } - - Curl_safefree(ptr); - - if(len == (size_t)written) - /* Encrypted message including header, data and trailer entirely sent. - The return value is the number of unencrypted bytes that were sent. */ - written = outbuf[1].cbBuffer; - - return written; -} - -static ssize_t -schannel_recv(struct Curl_easy *data, int sockindex, - char *buf, size_t len, CURLcode *err) -{ - size_t size = 0; - ssize_t nread = -1; - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - unsigned char *reallocated_buffer; - size_t reallocated_length; - bool done = FALSE; - SecBuffer inbuf[4]; - SecBufferDesc inbuf_desc; - SECURITY_STATUS sspi_status = SEC_E_OK; - /* we want the length of the encrypted buffer to be at least large enough - that it can hold all the bytes requested and some TLS record overhead. */ - size_t min_encdata_length = len + CURL_SCHANNEL_BUFFER_FREE_SIZE; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - /**************************************************************************** - * Don't return or set backend->recv_unrecoverable_err unless in the cleanup. - * The pattern for return error is set *err, optional infof, goto cleanup. - * - * Our priority is to always return as much decrypted data to the caller as - * possible, even if an error occurs. The state of the decrypted buffer must - * always be valid. Transfer of decrypted data to the caller's buffer is - * handled in the cleanup. - */ - - DEBUGF(infof(data, "schannel: client wants to read %zu bytes", len)); - *err = CURLE_OK; - - if(len && len <= backend->decdata_offset) { - infof(data, "schannel: enough decrypted data is already available"); - goto cleanup; - } - else if(backend->recv_unrecoverable_err) { - *err = backend->recv_unrecoverable_err; - infof(data, "schannel: an unrecoverable error occurred in a prior call"); - goto cleanup; - } - else if(backend->recv_sspi_close_notify) { - /* once a server has indicated shutdown there is no more encrypted data */ - infof(data, "schannel: server indicated shutdown in a prior call"); - goto cleanup; - } - - /* It's debatable what to return when !len. Regardless we can't return - immediately because there may be data to decrypt (in the case we want to - decrypt all encrypted cached data) so handle !len later in cleanup. - */ - else if(len && !backend->recv_connection_closed) { - /* increase enc buffer in order to fit the requested amount of data */ - size = backend->encdata_length - backend->encdata_offset; - if(size < CURL_SCHANNEL_BUFFER_FREE_SIZE || - backend->encdata_length < min_encdata_length) { - reallocated_length = backend->encdata_offset + - CURL_SCHANNEL_BUFFER_FREE_SIZE; - if(reallocated_length < min_encdata_length) { - reallocated_length = min_encdata_length; - } - reallocated_buffer = realloc(backend->encdata_buffer, - reallocated_length); - if(!reallocated_buffer) { - *err = CURLE_OUT_OF_MEMORY; - failf(data, "schannel: unable to re-allocate memory"); - goto cleanup; - } - - backend->encdata_buffer = reallocated_buffer; - backend->encdata_length = reallocated_length; - size = backend->encdata_length - backend->encdata_offset; - DEBUGF(infof(data, "schannel: encdata_buffer resized %zu", - backend->encdata_length)); - } - - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); - - /* read encrypted data from socket */ - *err = Curl_read_plain(conn->sock[sockindex], - (char *)(backend->encdata_buffer + - backend->encdata_offset), - size, &nread); - if(*err) { - nread = -1; - if(*err == CURLE_AGAIN) - DEBUGF(infof(data, - "schannel: Curl_read_plain returned CURLE_AGAIN")); - else if(*err == CURLE_RECV_ERROR) - infof(data, "schannel: Curl_read_plain returned CURLE_RECV_ERROR"); - else - infof(data, "schannel: Curl_read_plain returned error %d", *err); - } - else if(nread == 0) { - backend->recv_connection_closed = true; - DEBUGF(infof(data, "schannel: server closed the connection")); - } - else if(nread > 0) { - backend->encdata_offset += (size_t)nread; - backend->encdata_is_incomplete = false; - DEBUGF(infof(data, "schannel: encrypted data got %zd", nread)); - } - } - - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); - - /* decrypt loop */ - while(backend->encdata_offset > 0 && sspi_status == SEC_E_OK && - (!len || backend->decdata_offset < len || - backend->recv_connection_closed)) { - /* prepare data buffer for DecryptMessage call */ - InitSecBuffer(&inbuf[0], SECBUFFER_DATA, backend->encdata_buffer, - curlx_uztoul(backend->encdata_offset)); - - /* we need 3 more empty input buffers for possible output */ - InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0); - InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0); - InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&inbuf_desc, inbuf, 4); - - /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx - */ - sspi_status = s_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle, - &inbuf_desc, 0, NULL); - - /* check if everything went fine (server may want to renegotiate - or shutdown the connection context) */ - if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE || - sspi_status == SEC_I_CONTEXT_EXPIRED) { - /* check for successfully decrypted data, even before actual - renegotiation or shutdown of the connection context */ - if(inbuf[1].BufferType == SECBUFFER_DATA) { - DEBUGF(infof(data, "schannel: decrypted data length: %lu", - inbuf[1].cbBuffer)); - - /* increase buffer in order to fit the received amount of data */ - size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ? - inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE; - if(backend->decdata_length - backend->decdata_offset < size || - backend->decdata_length < len) { - /* increase internal decrypted data buffer */ - reallocated_length = backend->decdata_offset + size; - /* make sure that the requested amount of data fits */ - if(reallocated_length < len) { - reallocated_length = len; - } - reallocated_buffer = realloc(backend->decdata_buffer, - reallocated_length); - if(!reallocated_buffer) { - *err = CURLE_OUT_OF_MEMORY; - failf(data, "schannel: unable to re-allocate memory"); - goto cleanup; - } - backend->decdata_buffer = reallocated_buffer; - backend->decdata_length = reallocated_length; - } - - /* copy decrypted data to internal buffer */ - size = inbuf[1].cbBuffer; - if(size) { - memcpy(backend->decdata_buffer + backend->decdata_offset, - inbuf[1].pvBuffer, size); - backend->decdata_offset += size; - } - - DEBUGF(infof(data, "schannel: decrypted data added: %zu", size)); - DEBUGF(infof(data, - "schannel: decrypted cached: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); - } - - /* check for remaining encrypted data */ - if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) { - DEBUGF(infof(data, "schannel: encrypted data length: %lu", - inbuf[3].cbBuffer)); - - /* check if the remaining data is less than the total amount - * and therefore begins after the already processed data - */ - if(backend->encdata_offset > inbuf[3].cbBuffer) { - /* move remaining encrypted data forward to the beginning of - buffer */ - memmove(backend->encdata_buffer, - (backend->encdata_buffer + backend->encdata_offset) - - inbuf[3].cbBuffer, inbuf[3].cbBuffer); - backend->encdata_offset = inbuf[3].cbBuffer; - } - - DEBUGF(infof(data, - "schannel: encrypted cached: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); - } - else { - /* reset encrypted buffer offset, because there is no data remaining */ - backend->encdata_offset = 0; - } - - /* check if server wants to renegotiate the connection context */ - if(sspi_status == SEC_I_RENEGOTIATE) { - infof(data, "schannel: remote party requests renegotiation"); - if(*err && *err != CURLE_AGAIN) { - infof(data, "schannel: can't renegotiate, an error is pending"); - goto cleanup; - } - - /* begin renegotiation */ - infof(data, "schannel: renegotiating SSL/TLS connection"); - connssl->state = ssl_connection_negotiating; - connssl->connecting_state = ssl_connect_2_writing; - backend->recv_renegotiating = true; - *err = schannel_connect_common(data, conn, sockindex, FALSE, &done); - backend->recv_renegotiating = false; - if(*err) { - infof(data, "schannel: renegotiation failed"); - goto cleanup; - } - /* now retry receiving data */ - sspi_status = SEC_E_OK; - infof(data, "schannel: SSL/TLS connection renegotiated"); - continue; - } - /* check if the server closed the connection */ - else if(sspi_status == SEC_I_CONTEXT_EXPIRED) { - /* In Windows 2000 SEC_I_CONTEXT_EXPIRED (close_notify) is not - returned so we have to work around that in cleanup. */ - backend->recv_sspi_close_notify = true; - if(!backend->recv_connection_closed) { - backend->recv_connection_closed = true; - infof(data, "schannel: server closed the connection"); - } - goto cleanup; - } - } - else if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { - backend->encdata_is_incomplete = true; - if(!*err) - *err = CURLE_AGAIN; - infof(data, "schannel: failed to decrypt data, need more data"); - goto cleanup; - } - else { -#ifndef CURL_DISABLE_VERBOSE_STRINGS - char buffer[STRERROR_LEN]; -#endif - *err = CURLE_RECV_ERROR; - infof(data, "schannel: failed to read data from server: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - goto cleanup; - } - } - - DEBUGF(infof(data, - "schannel: encrypted data buffer: offset %zu length %zu", - backend->encdata_offset, backend->encdata_length)); - - DEBUGF(infof(data, - "schannel: decrypted data buffer: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); - - cleanup: - /* Warning- there is no guarantee the encdata state is valid at this point */ - DEBUGF(infof(data, "schannel: schannel_recv cleanup")); - - /* Error if the connection has closed without a close_notify. - - The behavior here is a matter of debate. We don't want to be vulnerable - to a truncation attack however there's some browser precedent for - ignoring the close_notify for compatibility reasons. - - Additionally, Windows 2000 (v5.0) is a special case since it seems it - doesn't return close_notify. In that case if the connection was closed we - assume it was graceful (close_notify) since there doesn't seem to be a - way to tell. - */ - if(len && !backend->decdata_offset && backend->recv_connection_closed && - !backend->recv_sspi_close_notify) { - bool isWin2k = curlx_verify_windows_version(5, 0, 0, PLATFORM_WINNT, - VERSION_EQUAL); - - if(isWin2k && sspi_status == SEC_E_OK) - backend->recv_sspi_close_notify = true; - else { - *err = CURLE_RECV_ERROR; - infof(data, "schannel: server closed abruptly (missing close_notify)"); - } - } - - /* Any error other than CURLE_AGAIN is an unrecoverable error. */ - if(*err && *err != CURLE_AGAIN) - backend->recv_unrecoverable_err = *err; - - size = len < backend->decdata_offset ? len : backend->decdata_offset; - if(size) { - memcpy(buf, backend->decdata_buffer, size); - memmove(backend->decdata_buffer, backend->decdata_buffer + size, - backend->decdata_offset - size); - backend->decdata_offset -= size; - DEBUGF(infof(data, "schannel: decrypted data returned %zu", size)); - DEBUGF(infof(data, - "schannel: decrypted data buffer: offset %zu length %zu", - backend->decdata_offset, backend->decdata_length)); - *err = CURLE_OK; - return (ssize_t)size; - } - - if(!*err && !backend->recv_connection_closed) - *err = CURLE_AGAIN; - - /* It's debatable what to return when !len. We could return whatever error - we got from decryption but instead we override here so the return is - consistent. - */ - if(!len) - *err = CURLE_OK; - - return *err ? -1 : 0; -} - -static CURLcode schannel_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - return schannel_connect_common(data, conn, sockindex, TRUE, done); -} - -static CURLcode schannel_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = schannel_connect_common(data, conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static bool schannel_data_pending(const struct connectdata *conn, - int sockindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(backend); - - if(connssl->use) /* SSL/TLS is in use */ - return (backend->decdata_offset > 0 || - (backend->encdata_offset > 0 && !backend->encdata_is_incomplete)); - else - return FALSE; -} - -static void schannel_session_free(void *ptr) -{ - /* this is expected to be called under sessionid lock */ - struct Curl_schannel_cred *cred = ptr; - - if(cred) { - cred->refcount--; - if(cred->refcount == 0) { - s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); - curlx_unicodefree(cred->sni_hostname); -#ifdef HAS_CLIENT_CERT_PATH - if(cred->client_cert_store) { - CertCloseStore(cred->client_cert_store, 0); - cred->client_cert_store = NULL; - } -#endif - Curl_safefree(cred); - } - } -} - -/* shut down the SSL connection and clean up related memory. - this function can be called multiple times on the same connection including - if the SSL connection failed (eg connection made but failed handshake). */ -static int schannel_shutdown(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - /* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx - * Shutting Down an Schannel Connection - */ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - char * const hostname = SSL_HOST_NAME(); - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(data); - DEBUGASSERT(backend); - - if(connssl->use) { - infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu", - hostname, conn->remote_port); - } - - if(connssl->use && backend->cred && backend->ctxt) { - SecBufferDesc BuffDesc; - SecBuffer Buffer; - SECURITY_STATUS sspi_status; - SecBuffer outbuf; - SecBufferDesc outbuf_desc; - CURLcode result; - DWORD dwshut = SCHANNEL_SHUTDOWN; - - InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut)); - InitSecBufferDesc(&BuffDesc, &Buffer, 1); - - sspi_status = s_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle, - &BuffDesc); - - if(sspi_status != SEC_E_OK) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: ApplyControlToken failure: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - } - - /* setup output buffer */ - InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); - InitSecBufferDesc(&outbuf_desc, &outbuf, 1); - - sspi_status = s_pSecFn->InitializeSecurityContext( - &backend->cred->cred_handle, - &backend->ctxt->ctxt_handle, - backend->cred->sni_hostname, - backend->req_flags, - 0, - 0, - NULL, - 0, - &backend->ctxt->ctxt_handle, - &outbuf_desc, - &backend->ret_flags, - &backend->ctxt->time_stamp); - - if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) { - /* send close message which is in output buffer */ - ssize_t written; - result = Curl_write_plain(data, conn->sock[sockindex], outbuf.pvBuffer, - outbuf.cbBuffer, &written); - - s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); - if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) { - infof(data, "schannel: failed to send close msg: %s" - " (bytes written: %zd)", curl_easy_strerror(result), written); - } - } - } - - /* free SSPI Schannel API security context handle */ - if(backend->ctxt) { - DEBUGF(infof(data, "schannel: clear security context handle")); - s_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle); - Curl_safefree(backend->ctxt); - } - - /* free SSPI Schannel API credential handle */ - if(backend->cred) { - Curl_ssl_sessionid_lock(data); - schannel_session_free(backend->cred); - Curl_ssl_sessionid_unlock(data); - backend->cred = NULL; - } - - /* free internal buffer for received encrypted data */ - if(backend->encdata_buffer) { - Curl_safefree(backend->encdata_buffer); - backend->encdata_length = 0; - backend->encdata_offset = 0; - backend->encdata_is_incomplete = false; - } - - /* free internal buffer for received decrypted data */ - if(backend->decdata_buffer) { - Curl_safefree(backend->decdata_buffer); - backend->decdata_length = 0; - backend->decdata_offset = 0; - } - - return CURLE_OK; -} - -static void schannel_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - if(conn->ssl[sockindex].use) - /* Curl_ssl_shutdown resets the socket state and calls schannel_shutdown */ - Curl_ssl_shutdown(data, conn, sockindex); - else - schannel_shutdown(data, conn, sockindex); -} - -static int schannel_init(void) -{ - return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0); -} - -static void schannel_cleanup(void) -{ - Curl_sspi_global_cleanup(); -} - -static size_t schannel_version(char *buffer, size_t size) -{ - size = msnprintf(buffer, size, "Schannel"); - - return size; -} - -static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM, - unsigned char *entropy, size_t length) -{ - (void)data; - - return Curl_win32_random(entropy, length); -} - -static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data, - struct connectdata *conn, int sockindex, - const char *pinnedpubkey) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - CERT_CONTEXT *pCertContextServer = NULL; - - /* Result is returned to caller */ - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - - DEBUGASSERT(backend); - - /* if a path wasn't specified, don't pin */ - if(!pinnedpubkey) - return CURLE_OK; - - do { - SECURITY_STATUS sspi_status; - const char *x509_der; - DWORD x509_der_len; - struct Curl_X509certificate x509_parsed; - struct Curl_asn1Element *pubkey; - - sspi_status = - s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &pCertContextServer); - - if((sspi_status != SEC_E_OK) || !pCertContextServer) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: Failed to read remote certificate context: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - break; /* failed */ - } - - - if(!(((pCertContextServer->dwCertEncodingType & X509_ASN_ENCODING) != 0) && - (pCertContextServer->cbCertEncoded > 0))) - break; - - x509_der = (const char *)pCertContextServer->pbCertEncoded; - x509_der_len = pCertContextServer->cbCertEncoded; - memset(&x509_parsed, 0, sizeof(x509_parsed)); - if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len)) - break; - - pubkey = &x509_parsed.subjectPublicKeyInfo; - if(!pubkey->header || pubkey->end <= pubkey->header) { - failf(data, "SSL: failed retrieving public key from server certificate"); - break; - } - - result = Curl_pin_peer_pubkey(data, - pinnedpubkey, - (const unsigned char *)pubkey->header, - (size_t)(pubkey->end - pubkey->header)); - if(result) { - failf(data, "SSL: public key does not match pinned public key"); - } - } while(0); - - if(pCertContextServer) - CertFreeCertificateContext(pCertContextServer); - - return result; -} - -static void schannel_checksum(const unsigned char *input, - size_t inputlen, - unsigned char *checksum, - size_t checksumlen, - DWORD provType, - const unsigned int algId) -{ - HCRYPTPROV hProv = 0; - HCRYPTHASH hHash = 0; - DWORD cbHashSize = 0; - DWORD dwHashSizeLen = (DWORD)sizeof(cbHashSize); - DWORD dwChecksumLen = (DWORD)checksumlen; - - /* since this can fail in multiple ways, zero memory first so we never - * return old data - */ - memset(checksum, 0, checksumlen); - - if(!CryptAcquireContext(&hProv, NULL, NULL, provType, - CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) - return; /* failed */ - - do { - if(!CryptCreateHash(hProv, algId, 0, 0, &hHash)) - break; /* failed */ - - /* workaround for original MinGW, should be (const BYTE*) */ - if(!CryptHashData(hHash, (BYTE*)input, (DWORD)inputlen, 0)) - break; /* failed */ - - /* get hash size */ - if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize, - &dwHashSizeLen, 0)) - break; /* failed */ - - /* check hash size */ - if(checksumlen < cbHashSize) - break; /* failed */ - - if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0)) - break; /* failed */ - } while(0); - - if(hHash) - CryptDestroyHash(hHash); - - if(hProv) - CryptReleaseContext(hProv, 0); -} - -static CURLcode schannel_sha256sum(const unsigned char *input, - size_t inputlen, - unsigned char *sha256sum, - size_t sha256len) -{ - schannel_checksum(input, inputlen, sha256sum, sha256len, - PROV_RSA_AES, CALG_SHA_256); - return CURLE_OK; -} - -static void *schannel_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - (void)info; - DEBUGASSERT(backend); - return &backend->ctxt->ctxt_handle; -} - -const struct Curl_ssl Curl_ssl_schannel = { - { CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */ - - SSLSUPP_CERTINFO | -#ifdef HAS_MANUAL_VERIFY_API - SSLSUPP_CAINFO_BLOB | -#endif - SSLSUPP_PINNEDPUBKEY | - SSLSUPP_TLS13_CIPHERSUITES, - - sizeof(struct ssl_backend_data), - - schannel_init, /* init */ - schannel_cleanup, /* cleanup */ - schannel_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - schannel_shutdown, /* shutdown */ - schannel_data_pending, /* data_pending */ - schannel_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - schannel_connect, /* connect */ - schannel_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - schannel_get_internals, /* get_internals */ - schannel_close, /* close_one */ - Curl_none_close_all, /* close_all */ - schannel_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - schannel_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif /* USE_SCHANNEL */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
+ * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for all Schannel-specific code for the TLS/SSL layer. No code
+ * but vtls.c should ever call or use these functions.
+ */
+
+#include "curl_setup.h"
+
+#ifdef USE_SCHANNEL
+
+#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS
+
+#ifndef USE_WINDOWS_SSPI
+# error "Can't compile SCHANNEL support without SSPI."
+#endif
+
+#include "schannel.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "strcase.h"
+#include "sendf.h"
+#include "connect.h" /* for the connect timeout */
+#include "strerror.h"
+#include "select.h" /* for the socket readiness */
+#include "inet_pton.h" /* for IP addr SNI check */
+#include "curl_multibyte.h"
+#include "warnless.h"
+#include "x509asn1.h"
+#include "curl_printf.h"
+#include "multiif.h"
+#include "version_win32.h"
+#include "rand.h"
+
+/* The last #include file should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* ALPN requires version 8.1 of the Windows SDK, which was
+ shipped with Visual Studio 2013, aka _MSC_VER 1800:
+
+ https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx
+*/
+#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_)
+# define HAS_ALPN 1
+#endif
+
+#ifndef UNISP_NAME_A
+#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider"
+#endif
+
+#ifndef UNISP_NAME_W
+#define UNISP_NAME_W L"Microsoft Unified Security Protocol Provider"
+#endif
+
+#ifndef UNISP_NAME
+#ifdef UNICODE
+#define UNISP_NAME UNISP_NAME_W
+#else
+#define UNISP_NAME UNISP_NAME_A
+#endif
+#endif
+
+#ifndef BCRYPT_CHACHA20_POLY1305_ALGORITHM
+#define BCRYPT_CHACHA20_POLY1305_ALGORITHM L"CHACHA20_POLY1305"
+#endif
+
+#ifndef BCRYPT_CHAIN_MODE_CCM
+#define BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM"
+#endif
+
+#ifndef BCRYPT_CHAIN_MODE_GCM
+#define BCRYPT_CHAIN_MODE_GCM L"ChainingModeGCM"
+#endif
+
+#ifndef BCRYPT_AES_ALGORITHM
+#define BCRYPT_AES_ALGORITHM L"AES"
+#endif
+
+#ifndef BCRYPT_SHA256_ALGORITHM
+#define BCRYPT_SHA256_ALGORITHM L"SHA256"
+#endif
+
+#ifndef BCRYPT_SHA384_ALGORITHM
+#define BCRYPT_SHA384_ALGORITHM L"SHA384"
+#endif
+
+/* Workaround broken compilers like MinGW.
+ Return the number of elements in a statically sized array.
+*/
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+#ifdef HAS_CLIENT_CERT_PATH
+#ifdef UNICODE
+#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W
+#else
+#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_A
+#endif
+#endif
+
+#ifndef SP_PROT_SSL2_CLIENT
+#define SP_PROT_SSL2_CLIENT 0x00000008
+#endif
+
+#ifndef SP_PROT_SSL3_CLIENT
+#define SP_PROT_SSL3_CLIENT 0x00000008
+#endif
+
+#ifndef SP_PROT_TLS1_CLIENT
+#define SP_PROT_TLS1_CLIENT 0x00000080
+#endif
+
+#ifndef SP_PROT_TLS1_0_CLIENT
+#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT
+#endif
+
+#ifndef SP_PROT_TLS1_1_CLIENT
+#define SP_PROT_TLS1_1_CLIENT 0x00000200
+#endif
+
+#ifndef SP_PROT_TLS1_2_CLIENT
+#define SP_PROT_TLS1_2_CLIENT 0x00000800
+#endif
+
+#ifndef SP_PROT_TLS1_3_CLIENT
+#define SP_PROT_TLS1_3_CLIENT 0x00002000
+#endif
+
+#ifndef SCH_USE_STRONG_CRYPTO
+#define SCH_USE_STRONG_CRYPTO 0x00400000
+#endif
+
+#ifndef SECBUFFER_ALERT
+#define SECBUFFER_ALERT 17
+#endif
+
+/* Both schannel buffer sizes must be > 0 */
+#define CURL_SCHANNEL_BUFFER_INIT_SIZE 4096
+#define CURL_SCHANNEL_BUFFER_FREE_SIZE 1024
+
+#define CERT_THUMBPRINT_STR_LEN 40
+#define CERT_THUMBPRINT_DATA_LEN 20
+
+/* Uncomment to force verbose output
+ * #define infof(x, y, ...) printf(y, __VA_ARGS__)
+ * #define failf(x, y, ...) printf(y, __VA_ARGS__)
+ */
+
+#ifndef CALG_SHA_256
+# define CALG_SHA_256 0x0000800c
+#endif
+
+/* Work around typo in classic MinGW's w32api up to version 5.0,
+ see https://osdn.net/projects/mingw/ticket/38391 */
+#if !defined(ALG_CLASS_DHASH) && defined(ALG_CLASS_HASH)
+#define ALG_CLASS_DHASH ALG_CLASS_HASH
+#endif
+
+#ifndef PKCS12_NO_PERSIST_KEY
+#define PKCS12_NO_PERSIST_KEY 0x00008000
+#endif
+
+static CURLcode pkp_pin_peer_pubkey(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const char *pinnedpubkey);
+
+static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType,
+ void *BufDataPtr, unsigned long BufByteSize)
+{
+ buffer->cbBuffer = BufByteSize;
+ buffer->BufferType = BufType;
+ buffer->pvBuffer = BufDataPtr;
+}
+
+static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr,
+ unsigned long NumArrElem)
+{
+ desc->ulVersion = SECBUFFER_VERSION;
+ desc->pBuffers = BufArr;
+ desc->cBuffers = NumArrElem;
+}
+
+static CURLcode
+set_ssl_version_min_max(DWORD *enabled_protocols,
+ struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ long ssl_version = conn_config->version;
+ long ssl_version_max = conn_config->version_max;
+ long i = ssl_version;
+
+ switch(ssl_version_max) {
+ case CURL_SSLVERSION_MAX_NONE:
+ case CURL_SSLVERSION_MAX_DEFAULT:
+
+ /* Windows Server 2022 and newer (including Windows 11) support TLS 1.3
+ built-in. Previous builds of Windows 10 had broken TLS 1.3
+ implementations that could be enabled via registry.
+ */
+ if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL)) {
+ ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_3;
+ }
+ else /* Windows 10 and older */
+ ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2;
+
+ break;
+ }
+
+ for(; i <= (ssl_version_max >> 16); ++i) {
+ switch(i) {
+ case CURL_SSLVERSION_TLSv1_0:
+ (*enabled_protocols) |= SP_PROT_TLS1_0_CLIENT;
+ break;
+ case CURL_SSLVERSION_TLSv1_1:
+ (*enabled_protocols) |= SP_PROT_TLS1_1_CLIENT;
+ break;
+ case CURL_SSLVERSION_TLSv1_2:
+ (*enabled_protocols) |= SP_PROT_TLS1_2_CLIENT;
+ break;
+ case CURL_SSLVERSION_TLSv1_3:
+
+ /* Windows Server 2022 and newer */
+ if(curlx_verify_windows_version(10, 0, 20348, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL)) {
+ (*enabled_protocols) |= SP_PROT_TLS1_3_CLIENT;
+ break;
+ }
+ else { /* Windows 10 and older */
+ failf(data, "schannel: TLS 1.3 not supported on Windows prior to 11");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ }
+ return CURLE_OK;
+}
+
+/* longest is 26, buffer is slightly bigger */
+#define LONGEST_ALG_ID 32
+#define CIPHEROPTION(X) \
+ if(strcmp(#X, tmp) == 0) \
+ return X
+
+static int
+get_alg_id_by_name(char *name)
+{
+ char tmp[LONGEST_ALG_ID] = { 0 };
+ char *nameEnd = strchr(name, ':');
+ size_t n = nameEnd ? (size_t)(nameEnd - name) : strlen(name);
+
+ /* reject too-long alg names */
+ if(n > (LONGEST_ALG_ID - 1))
+ return 0;
+
+ strncpy(tmp, name, n);
+ tmp[n] = 0;
+ CIPHEROPTION(CALG_MD2);
+ CIPHEROPTION(CALG_MD4);
+ CIPHEROPTION(CALG_MD5);
+ CIPHEROPTION(CALG_SHA);
+ CIPHEROPTION(CALG_SHA1);
+ CIPHEROPTION(CALG_MAC);
+ CIPHEROPTION(CALG_RSA_SIGN);
+ CIPHEROPTION(CALG_DSS_SIGN);
+/* ifdefs for the options that are defined conditionally in wincrypt.h */
+#ifdef CALG_NO_SIGN
+ CIPHEROPTION(CALG_NO_SIGN);
+#endif
+ CIPHEROPTION(CALG_RSA_KEYX);
+ CIPHEROPTION(CALG_DES);
+#ifdef CALG_3DES_112
+ CIPHEROPTION(CALG_3DES_112);
+#endif
+ CIPHEROPTION(CALG_3DES);
+ CIPHEROPTION(CALG_DESX);
+ CIPHEROPTION(CALG_RC2);
+ CIPHEROPTION(CALG_RC4);
+ CIPHEROPTION(CALG_SEAL);
+#ifdef CALG_DH_SF
+ CIPHEROPTION(CALG_DH_SF);
+#endif
+ CIPHEROPTION(CALG_DH_EPHEM);
+#ifdef CALG_AGREEDKEY_ANY
+ CIPHEROPTION(CALG_AGREEDKEY_ANY);
+#endif
+#ifdef CALG_HUGHES_MD5
+ CIPHEROPTION(CALG_HUGHES_MD5);
+#endif
+ CIPHEROPTION(CALG_SKIPJACK);
+#ifdef CALG_TEK
+ CIPHEROPTION(CALG_TEK);
+#endif
+ CIPHEROPTION(CALG_CYLINK_MEK);
+ CIPHEROPTION(CALG_SSL3_SHAMD5);
+#ifdef CALG_SSL3_MASTER
+ CIPHEROPTION(CALG_SSL3_MASTER);
+#endif
+#ifdef CALG_SCHANNEL_MASTER_HASH
+ CIPHEROPTION(CALG_SCHANNEL_MASTER_HASH);
+#endif
+#ifdef CALG_SCHANNEL_MAC_KEY
+ CIPHEROPTION(CALG_SCHANNEL_MAC_KEY);
+#endif
+#ifdef CALG_SCHANNEL_ENC_KEY
+ CIPHEROPTION(CALG_SCHANNEL_ENC_KEY);
+#endif
+#ifdef CALG_PCT1_MASTER
+ CIPHEROPTION(CALG_PCT1_MASTER);
+#endif
+#ifdef CALG_SSL2_MASTER
+ CIPHEROPTION(CALG_SSL2_MASTER);
+#endif
+#ifdef CALG_TLS1_MASTER
+ CIPHEROPTION(CALG_TLS1_MASTER);
+#endif
+#ifdef CALG_RC5
+ CIPHEROPTION(CALG_RC5);
+#endif
+#ifdef CALG_HMAC
+ CIPHEROPTION(CALG_HMAC);
+#endif
+#ifdef CALG_TLS1PRF
+ CIPHEROPTION(CALG_TLS1PRF);
+#endif
+#ifdef CALG_HASH_REPLACE_OWF
+ CIPHEROPTION(CALG_HASH_REPLACE_OWF);
+#endif
+#ifdef CALG_AES_128
+ CIPHEROPTION(CALG_AES_128);
+#endif
+#ifdef CALG_AES_192
+ CIPHEROPTION(CALG_AES_192);
+#endif
+#ifdef CALG_AES_256
+ CIPHEROPTION(CALG_AES_256);
+#endif
+#ifdef CALG_AES
+ CIPHEROPTION(CALG_AES);
+#endif
+#ifdef CALG_SHA_256
+ CIPHEROPTION(CALG_SHA_256);
+#endif
+#ifdef CALG_SHA_384
+ CIPHEROPTION(CALG_SHA_384);
+#endif
+#ifdef CALG_SHA_512
+ CIPHEROPTION(CALG_SHA_512);
+#endif
+#ifdef CALG_ECDH
+ CIPHEROPTION(CALG_ECDH);
+#endif
+#ifdef CALG_ECMQV
+ CIPHEROPTION(CALG_ECMQV);
+#endif
+#ifdef CALG_ECDSA
+ CIPHEROPTION(CALG_ECDSA);
+#endif
+#ifdef CALG_ECDH_EPHEM
+ CIPHEROPTION(CALG_ECDH_EPHEM);
+#endif
+ return 0;
+}
+
+#define NUM_CIPHERS 47 /* There are 47 options listed above */
+
+static CURLcode
+set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers,
+ ALG_ID *algIds)
+{
+ char *startCur = ciphers;
+ int algCount = 0;
+ while(startCur && (0 != *startCur) && (algCount < NUM_CIPHERS)) {
+ long alg = strtol(startCur, 0, 0);
+ if(!alg)
+ alg = get_alg_id_by_name(startCur);
+ if(alg)
+ algIds[algCount++] = alg;
+ else if(!strncmp(startCur, "USE_STRONG_CRYPTO",
+ sizeof("USE_STRONG_CRYPTO") - 1) ||
+ !strncmp(startCur, "SCH_USE_STRONG_CRYPTO",
+ sizeof("SCH_USE_STRONG_CRYPTO") - 1))
+ schannel_cred->dwFlags |= SCH_USE_STRONG_CRYPTO;
+ else
+ return CURLE_SSL_CIPHER;
+ startCur = strchr(startCur, ':');
+ if(startCur)
+ startCur++;
+ }
+ schannel_cred->palgSupportedAlgs = algIds;
+ schannel_cred->cSupportedAlgs = algCount;
+ return CURLE_OK;
+}
+
+#ifdef HAS_CLIENT_CERT_PATH
+
+/* Function allocates memory for store_path only if CURLE_OK is returned */
+static CURLcode
+get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path,
+ TCHAR **thumbprint)
+{
+ TCHAR *sep;
+ TCHAR *store_path_start;
+ size_t store_name_len;
+
+ sep = _tcschr(path, TEXT('\\'));
+ if(!sep)
+ return CURLE_SSL_CERTPROBLEM;
+
+ store_name_len = sep - path;
+
+ if(_tcsncmp(path, TEXT("CurrentUser"), store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_CURRENT_USER;
+ else if(_tcsncmp(path, TEXT("LocalMachine"), store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE;
+ else if(_tcsncmp(path, TEXT("CurrentService"), store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_CURRENT_SERVICE;
+ else if(_tcsncmp(path, TEXT("Services"), store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_SERVICES;
+ else if(_tcsncmp(path, TEXT("Users"), store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_USERS;
+ else if(_tcsncmp(path, TEXT("CurrentUserGroupPolicy"),
+ store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY;
+ else if(_tcsncmp(path, TEXT("LocalMachineGroupPolicy"),
+ store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY;
+ else if(_tcsncmp(path, TEXT("LocalMachineEnterprise"),
+ store_name_len) == 0)
+ *store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE;
+ else
+ return CURLE_SSL_CERTPROBLEM;
+
+ store_path_start = sep + 1;
+
+ sep = _tcschr(store_path_start, TEXT('\\'));
+ if(!sep)
+ return CURLE_SSL_CERTPROBLEM;
+
+ *thumbprint = sep + 1;
+ if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN)
+ return CURLE_SSL_CERTPROBLEM;
+
+ *sep = TEXT('\0');
+ *store_path = _tcsdup(store_path_start);
+ *sep = TEXT('\\');
+ if(!*store_path)
+ return CURLE_OUT_OF_MEMORY;
+
+ return CURLE_OK;
+}
+#endif
+static CURLcode
+schannel_acquire_credential_handle(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+
+#ifdef HAS_CLIENT_CERT_PATH
+ PCCERT_CONTEXT client_certs[1] = { NULL };
+ HCERTSTORE client_cert_store = NULL;
+#endif
+ SECURITY_STATUS sspi_status = SEC_E_OK;
+ CURLcode result;
+
+ /* setup Schannel API options */
+ DWORD flags = 0;
+ DWORD enabled_protocols = 0;
+
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ if(conn_config->verifypeer) {
+#ifdef HAS_MANUAL_VERIFY_API
+ if(backend->use_manual_cred_validation)
+ flags = SCH_CRED_MANUAL_CRED_VALIDATION;
+ else
+#endif
+ flags = SCH_CRED_AUTO_CRED_VALIDATION;
+
+ if(ssl_config->no_revoke) {
+ flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
+ SCH_CRED_IGNORE_REVOCATION_OFFLINE;
+
+ DEBUGF(infof(data, "schannel: disabled server certificate revocation "
+ "checks"));
+ }
+ else if(ssl_config->revoke_best_effort) {
+ flags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
+ SCH_CRED_IGNORE_REVOCATION_OFFLINE | SCH_CRED_REVOCATION_CHECK_CHAIN;
+
+ DEBUGF(infof(data, "schannel: ignore revocation offline errors"));
+ }
+ else {
+ flags |= SCH_CRED_REVOCATION_CHECK_CHAIN;
+
+ DEBUGF(infof(data,
+ "schannel: checking server certificate revocation"));
+ }
+ }
+ else {
+ flags = SCH_CRED_MANUAL_CRED_VALIDATION |
+ SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
+ SCH_CRED_IGNORE_REVOCATION_OFFLINE;
+ DEBUGF(infof(data,
+ "schannel: disabled server cert revocation checks"));
+ }
+
+ if(!conn_config->verifyhost) {
+ flags |= SCH_CRED_NO_SERVERNAME_CHECK;
+ DEBUGF(infof(data, "schannel: verifyhost setting prevents Schannel from "
+ "comparing the supplied target name with the subject "
+ "names in server certificates."));
+ }
+
+ if(!ssl_config->auto_client_cert) {
+ flags &= ~SCH_CRED_USE_DEFAULT_CREDS;
+ flags |= SCH_CRED_NO_DEFAULT_CREDS;
+ infof(data, "schannel: disabled automatic use of client certificate");
+ }
+ else
+ infof(data, "schannel: enabled automatic use of client certificate");
+
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1_1:
+ case CURL_SSLVERSION_TLSv1_2:
+ case CURL_SSLVERSION_TLSv1_3:
+ {
+ result = set_ssl_version_min_max(&enabled_protocols, cf, data);
+ if(result != CURLE_OK)
+ return result;
+ break;
+ }
+ case CURL_SSLVERSION_SSLv3:
+ case CURL_SSLVERSION_SSLv2:
+ failf(data, "SSL versions not supported");
+ return CURLE_NOT_BUILT_IN;
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+#ifdef HAS_CLIENT_CERT_PATH
+ /* client certificate */
+ if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) {
+ DWORD cert_store_name = 0;
+ TCHAR *cert_store_path = NULL;
+ TCHAR *cert_thumbprint_str = NULL;
+ CRYPT_HASH_BLOB cert_thumbprint;
+ BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN];
+ HCERTSTORE cert_store = NULL;
+ FILE *fInCert = NULL;
+ void *certdata = NULL;
+ size_t certsize = 0;
+ bool blob = data->set.ssl.primary.cert_blob != NULL;
+ TCHAR *cert_path = NULL;
+ if(blob) {
+ certdata = data->set.ssl.primary.cert_blob->data;
+ certsize = data->set.ssl.primary.cert_blob->len;
+ }
+ else {
+ cert_path = curlx_convert_UTF8_to_tchar(
+ data->set.ssl.primary.clientcert);
+ if(!cert_path)
+ return CURLE_OUT_OF_MEMORY;
+
+ result = get_cert_location(cert_path, &cert_store_name,
+ &cert_store_path, &cert_thumbprint_str);
+
+ if(result && (data->set.ssl.primary.clientcert[0]!='\0'))
+ fInCert = fopen(data->set.ssl.primary.clientcert, "rb");
+
+ if(result && !fInCert) {
+ failf(data, "schannel: Failed to get certificate location"
+ " or file for %s",
+ data->set.ssl.primary.clientcert);
+ curlx_unicodefree(cert_path);
+ return result;
+ }
+ }
+
+ if((fInCert || blob) && (data->set.ssl.cert_type) &&
+ (!strcasecompare(data->set.ssl.cert_type, "P12"))) {
+ failf(data, "schannel: certificate format compatibility error "
+ " for %s",
+ blob ? "(memory blob)" : data->set.ssl.primary.clientcert);
+ curlx_unicodefree(cert_path);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+ if(fInCert || blob) {
+ /* Reading a .P12 or .pfx file, like the example at bottom of
+ https://social.msdn.microsoft.com/Forums/windowsdesktop/
+ en-US/3e7bc95f-b21a-4bcd-bd2c-7f996718cae5
+ */
+ CRYPT_DATA_BLOB datablob;
+ WCHAR* pszPassword;
+ size_t pwd_len = 0;
+ int str_w_len = 0;
+ const char *cert_showfilename_error = blob ?
+ "(memory blob)" : data->set.ssl.primary.clientcert;
+ curlx_unicodefree(cert_path);
+ if(fInCert) {
+ long cert_tell = 0;
+ bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0;
+ if(continue_reading)
+ cert_tell = ftell(fInCert);
+ if(cert_tell < 0)
+ continue_reading = FALSE;
+ else
+ certsize = (size_t)cert_tell;
+ if(continue_reading)
+ continue_reading = fseek(fInCert, 0, SEEK_SET) == 0;
+ if(continue_reading)
+ certdata = malloc(certsize + 1);
+ if((!certdata) ||
+ ((int) fread(certdata, certsize, 1, fInCert) != 1))
+ continue_reading = FALSE;
+ fclose(fInCert);
+ if(!continue_reading) {
+ failf(data, "schannel: Failed to read cert file %s",
+ data->set.ssl.primary.clientcert);
+ free(certdata);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
+
+ /* Convert key-pair data to the in-memory certificate store */
+ datablob.pbData = (BYTE*)certdata;
+ datablob.cbData = (DWORD)certsize;
+
+ if(data->set.ssl.key_passwd)
+ pwd_len = strlen(data->set.ssl.key_passwd);
+ pszPassword = (WCHAR*)malloc(sizeof(WCHAR)*(pwd_len + 1));
+ if(pszPassword) {
+ if(pwd_len > 0)
+ str_w_len = MultiByteToWideChar(CP_UTF8,
+ MB_ERR_INVALID_CHARS,
+ data->set.ssl.key_passwd,
+ (int)pwd_len,
+ pszPassword, (int)(pwd_len + 1));
+
+ if((str_w_len >= 0) && (str_w_len <= (int)pwd_len))
+ pszPassword[str_w_len] = 0;
+ else
+ pszPassword[0] = 0;
+
+ if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL))
+ cert_store = PFXImportCertStore(&datablob, pszPassword,
+ PKCS12_NO_PERSIST_KEY);
+ else
+ cert_store = PFXImportCertStore(&datablob, pszPassword, 0);
+
+ free(pszPassword);
+ }
+ if(!blob)
+ free(certdata);
+ if(!cert_store) {
+ DWORD errorcode = GetLastError();
+ if(errorcode == ERROR_INVALID_PASSWORD)
+ failf(data, "schannel: Failed to import cert file %s, "
+ "password is bad",
+ cert_showfilename_error);
+ else
+ failf(data, "schannel: Failed to import cert file %s, "
+ "last error is 0x%x",
+ cert_showfilename_error, errorcode);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+ client_certs[0] = CertFindCertificateInStore(
+ cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
+ CERT_FIND_ANY, NULL, NULL);
+
+ if(!client_certs[0]) {
+ failf(data, "schannel: Failed to get certificate from file %s"
+ ", last error is 0x%x",
+ cert_showfilename_error, GetLastError());
+ CertCloseStore(cert_store, 0);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
+ else {
+ cert_store =
+ CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0,
+ (HCRYPTPROV)NULL,
+ CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name,
+ cert_store_path);
+ if(!cert_store) {
+ failf(data, "schannel: Failed to open cert store %x %s, "
+ "last error is 0x%x",
+ cert_store_name, cert_store_path, GetLastError());
+ free(cert_store_path);
+ curlx_unicodefree(cert_path);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ free(cert_store_path);
+
+ cert_thumbprint.pbData = cert_thumbprint_data;
+ cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN;
+
+ if(!CryptStringToBinary(cert_thumbprint_str,
+ CERT_THUMBPRINT_STR_LEN,
+ CRYPT_STRING_HEX,
+ cert_thumbprint_data,
+ &cert_thumbprint.cbData,
+ NULL, NULL)) {
+ curlx_unicodefree(cert_path);
+ CertCloseStore(cert_store, 0);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+ client_certs[0] = CertFindCertificateInStore(
+ cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
+ CERT_FIND_HASH, &cert_thumbprint, NULL);
+
+ curlx_unicodefree(cert_path);
+
+ if(!client_certs[0]) {
+ /* CRYPT_E_NOT_FOUND / E_INVALIDARG */
+ CertCloseStore(cert_store, 0);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
+ client_cert_store = cert_store;
+ }
+#else
+ if(data->set.ssl.primary.clientcert || data->set.ssl.primary.cert_blob) {
+ failf(data, "schannel: client cert support not built in");
+ return CURLE_NOT_BUILT_IN;
+ }
+#endif
+
+ /* allocate memory for the re-usable credential handle */
+ backend->cred = (struct Curl_schannel_cred *)
+ calloc(1, sizeof(struct Curl_schannel_cred));
+ if(!backend->cred) {
+ failf(data, "schannel: unable to allocate memory");
+
+#ifdef HAS_CLIENT_CERT_PATH
+ if(client_certs[0])
+ CertFreeCertificateContext(client_certs[0]);
+ if(client_cert_store)
+ CertCloseStore(client_cert_store, 0);
+#endif
+
+ return CURLE_OUT_OF_MEMORY;
+ }
+ backend->cred->refcount = 1;
+
+#ifdef HAS_CLIENT_CERT_PATH
+ /* Since we did not persist the key, we need to extend the store's
+ * lifetime until the end of the connection
+ */
+ backend->cred->client_cert_store = client_cert_store;
+#endif
+
+ /* Windows 10, 1809 (a.k.a. Windows 10 build 17763) */
+ if(curlx_verify_windows_version(10, 0, 17763, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL)) {
+
+ char *ciphers13 = 0;
+
+ bool disable_aes_gcm_sha384 = FALSE;
+ bool disable_aes_gcm_sha256 = FALSE;
+ bool disable_chacha_poly = FALSE;
+ bool disable_aes_ccm_8_sha256 = FALSE;
+ bool disable_aes_ccm_sha256 = FALSE;
+
+ SCH_CREDENTIALS credentials = { 0 };
+ TLS_PARAMETERS tls_parameters = { 0 };
+ CRYPTO_SETTINGS crypto_settings[4] = { 0 };
+ UNICODE_STRING blocked_ccm_modes[1] = { 0 };
+ UNICODE_STRING blocked_gcm_modes[1] = { 0 };
+
+ int crypto_settings_idx = 0;
+
+
+ /* If TLS 1.3 ciphers are explicitly listed, then
+ * disable all the ciphers and re-enable which
+ * ciphers the user has provided.
+ */
+ ciphers13 = conn_config->cipher_list13;
+ if(ciphers13) {
+ const int remaining_ciphers = 5;
+
+ /* detect which remaining ciphers to enable
+ and then disable everything else.
+ */
+
+ char *startCur = ciphers13;
+ int algCount = 0;
+ char tmp[LONGEST_ALG_ID] = { 0 };
+ char *nameEnd;
+ size_t n;
+
+ disable_aes_gcm_sha384 = TRUE;
+ disable_aes_gcm_sha256 = TRUE;
+ disable_chacha_poly = TRUE;
+ disable_aes_ccm_8_sha256 = TRUE;
+ disable_aes_ccm_sha256 = TRUE;
+
+ while(startCur && (0 != *startCur) && (algCount < remaining_ciphers)) {
+ nameEnd = strchr(startCur, ':');
+ n = nameEnd ? (size_t)(nameEnd - startCur) : strlen(startCur);
+
+ /* reject too-long cipher names */
+ if(n > (LONGEST_ALG_ID - 1)) {
+ failf(data, "Cipher name too long, not checked.");
+ return CURLE_SSL_CIPHER;
+ }
+
+ strncpy(tmp, startCur, n);
+ tmp[n] = 0;
+
+ if(disable_aes_gcm_sha384
+ && !strcmp("TLS_AES_256_GCM_SHA384", tmp)) {
+ disable_aes_gcm_sha384 = FALSE;
+ }
+ else if(disable_aes_gcm_sha256
+ && !strcmp("TLS_AES_128_GCM_SHA256", tmp)) {
+ disable_aes_gcm_sha256 = FALSE;
+ }
+ else if(disable_chacha_poly
+ && !strcmp("TLS_CHACHA20_POLY1305_SHA256", tmp)) {
+ disable_chacha_poly = FALSE;
+ }
+ else if(disable_aes_ccm_8_sha256
+ && !strcmp("TLS_AES_128_CCM_8_SHA256", tmp)) {
+ disable_aes_ccm_8_sha256 = FALSE;
+ }
+ else if(disable_aes_ccm_sha256
+ && !strcmp("TLS_AES_128_CCM_SHA256", tmp)) {
+ disable_aes_ccm_sha256 = FALSE;
+ }
+ else {
+ failf(data, "Passed in an unknown TLS 1.3 cipher.");
+ return CURLE_SSL_CIPHER;
+ }
+
+ startCur = nameEnd;
+ if(startCur)
+ startCur++;
+
+ algCount++;
+ }
+ }
+
+ if(disable_aes_gcm_sha384 && disable_aes_gcm_sha256
+ && disable_chacha_poly && disable_aes_ccm_8_sha256
+ && disable_aes_ccm_sha256) {
+ failf(data, "All available TLS 1.3 ciphers were disabled.");
+ return CURLE_SSL_CIPHER;
+ }
+
+ /* Disable TLS_AES_128_CCM_8_SHA256 and/or TLS_AES_128_CCM_SHA256 */
+ if(disable_aes_ccm_8_sha256 || disable_aes_ccm_sha256) {
+ /*
+ Disallow AES_CCM algorithm.
+ */
+ blocked_ccm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_CCM);
+ blocked_ccm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_CCM);
+ blocked_ccm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_CCM;
+
+ crypto_settings[crypto_settings_idx].eAlgorithmUsage =
+ TlsParametersCngAlgUsageCipher;
+ crypto_settings[crypto_settings_idx].rgstrChainingModes =
+ blocked_ccm_modes;
+ crypto_settings[crypto_settings_idx].cChainingModes =
+ ARRAYSIZE(blocked_ccm_modes);
+ crypto_settings[crypto_settings_idx].strCngAlgId.Length =
+ sizeof(BCRYPT_AES_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength =
+ sizeof(BCRYPT_AES_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.Buffer =
+ (PWSTR)BCRYPT_AES_ALGORITHM;
+
+ /* only disabling one of the CCM modes */
+ if(disable_aes_ccm_8_sha256 != disable_aes_ccm_sha256) {
+ if(disable_aes_ccm_8_sha256)
+ crypto_settings[crypto_settings_idx].dwMinBitLength = 128;
+ else /* disable_aes_ccm_sha256 */
+ crypto_settings[crypto_settings_idx].dwMaxBitLength = 64;
+ }
+
+ crypto_settings_idx++;
+ }
+
+ /* Disable TLS_AES_256_GCM_SHA384 and/or TLS_AES_128_GCM_SHA256 */
+ if(disable_aes_gcm_sha384 || disable_aes_gcm_sha256) {
+
+ /*
+ Disallow AES_GCM algorithm
+ */
+ blocked_gcm_modes[0].Length = sizeof(BCRYPT_CHAIN_MODE_GCM);
+ blocked_gcm_modes[0].MaximumLength = sizeof(BCRYPT_CHAIN_MODE_GCM);
+ blocked_gcm_modes[0].Buffer = (PWSTR)BCRYPT_CHAIN_MODE_GCM;
+
+ /* if only one is disabled, then explicitly disable the
+ digest cipher suite (sha384 or sha256) */
+ if(disable_aes_gcm_sha384 != disable_aes_gcm_sha256) {
+ crypto_settings[crypto_settings_idx].eAlgorithmUsage =
+ TlsParametersCngAlgUsageDigest;
+ crypto_settings[crypto_settings_idx].strCngAlgId.Length =
+ sizeof(disable_aes_gcm_sha384 ?
+ BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength =
+ sizeof(disable_aes_gcm_sha384 ?
+ BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.Buffer =
+ (PWSTR)(disable_aes_gcm_sha384 ?
+ BCRYPT_SHA384_ALGORITHM : BCRYPT_SHA256_ALGORITHM);
+ }
+ else { /* Disable both AES_GCM ciphers */
+ crypto_settings[crypto_settings_idx].eAlgorithmUsage =
+ TlsParametersCngAlgUsageCipher;
+ crypto_settings[crypto_settings_idx].strCngAlgId.Length =
+ sizeof(BCRYPT_AES_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength =
+ sizeof(BCRYPT_AES_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.Buffer =
+ (PWSTR)BCRYPT_AES_ALGORITHM;
+ }
+
+ crypto_settings[crypto_settings_idx].rgstrChainingModes =
+ blocked_gcm_modes;
+ crypto_settings[crypto_settings_idx].cChainingModes = 1;
+
+ crypto_settings_idx++;
+ }
+
+ /*
+ Disable ChaCha20-Poly1305.
+ */
+ if(disable_chacha_poly) {
+ crypto_settings[crypto_settings_idx].eAlgorithmUsage =
+ TlsParametersCngAlgUsageCipher;
+ crypto_settings[crypto_settings_idx].strCngAlgId.Length =
+ sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.MaximumLength =
+ sizeof(BCRYPT_CHACHA20_POLY1305_ALGORITHM);
+ crypto_settings[crypto_settings_idx].strCngAlgId.Buffer =
+ (PWSTR)BCRYPT_CHACHA20_POLY1305_ALGORITHM;
+ crypto_settings_idx++;
+ }
+
+ tls_parameters.pDisabledCrypto = crypto_settings;
+
+ /* The number of blocked suites */
+ tls_parameters.cDisabledCrypto = crypto_settings_idx;
+ credentials.pTlsParameters = &tls_parameters;
+ credentials.cTlsParameters = 1;
+
+ credentials.dwVersion = SCH_CREDENTIALS_VERSION;
+ credentials.dwFlags = flags | SCH_USE_STRONG_CRYPTO;
+
+ credentials.pTlsParameters->grbitDisabledProtocols =
+ (DWORD)~enabled_protocols;
+
+#ifdef HAS_CLIENT_CERT_PATH
+ if(client_certs[0]) {
+ credentials.cCreds = 1;
+ credentials.paCred = client_certs;
+ }
+#endif
+
+ sspi_status =
+ s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME,
+ SECPKG_CRED_OUTBOUND, NULL,
+ &credentials, NULL, NULL,
+ &backend->cred->cred_handle,
+ &backend->cred->time_stamp);
+ }
+ else {
+ /* Pre-Windows 10 1809 */
+ ALG_ID algIds[NUM_CIPHERS];
+ char *ciphers = conn_config->cipher_list;
+ SCHANNEL_CRED schannel_cred = { 0 };
+ schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
+ schannel_cred.dwFlags = flags;
+ schannel_cred.grbitEnabledProtocols = enabled_protocols;
+
+ if(ciphers) {
+ result = set_ssl_ciphers(&schannel_cred, ciphers, algIds);
+ if(CURLE_OK != result) {
+ failf(data, "Unable to set ciphers to from connection ssl config");
+ return result;
+ }
+ }
+ else {
+ schannel_cred.dwFlags = flags | SCH_USE_STRONG_CRYPTO;
+ }
+
+#ifdef HAS_CLIENT_CERT_PATH
+ if(client_certs[0]) {
+ schannel_cred.cCreds = 1;
+ schannel_cred.paCred = client_certs;
+ }
+#endif
+
+ sspi_status =
+ s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR*)UNISP_NAME,
+ SECPKG_CRED_OUTBOUND, NULL,
+ &schannel_cred, NULL, NULL,
+ &backend->cred->cred_handle,
+ &backend->cred->time_stamp);
+ }
+
+#ifdef HAS_CLIENT_CERT_PATH
+ if(client_certs[0])
+ CertFreeCertificateContext(client_certs[0]);
+#endif
+
+ if(sspi_status != SEC_E_OK) {
+ char buffer[STRERROR_LEN];
+ failf(data, "schannel: AcquireCredentialsHandle failed: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ Curl_safefree(backend->cred);
+ switch(sspi_status) {
+ case SEC_E_INSUFFICIENT_MEMORY:
+ return CURLE_OUT_OF_MEMORY;
+ case SEC_E_NO_CREDENTIALS:
+ case SEC_E_SECPKG_NOT_FOUND:
+ case SEC_E_NOT_OWNER:
+ case SEC_E_UNKNOWN_CREDENTIALS:
+ case SEC_E_INTERNAL_ERROR:
+ default:
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ return CURLE_OK;
+}
+
+static CURLcode
+schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ ssize_t written = -1;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ SecBuffer outbuf;
+ SecBufferDesc outbuf_desc;
+ SecBuffer inbuf;
+ SecBufferDesc inbuf_desc;
+#ifdef HAS_ALPN
+ unsigned char alpn_buffer[128];
+#endif
+ SECURITY_STATUS sspi_status = SEC_E_OK;
+ struct Curl_schannel_cred *old_cred = NULL;
+ struct in_addr addr;
+#ifdef ENABLE_IPV6
+ struct in6_addr addr6;
+#endif
+ CURLcode result;
+ const char *hostname = connssl->hostname;
+
+ DEBUGASSERT(backend);
+ DEBUGF(infof(data,
+ "schannel: SSL/TLS connection with %s port %d (step 1/3)",
+ hostname, connssl->port));
+
+ if(curlx_verify_windows_version(5, 1, 0, PLATFORM_WINNT,
+ VERSION_LESS_THAN_EQUAL)) {
+ /* Schannel in Windows XP (OS version 5.1) uses legacy handshakes and
+ algorithms that may not be supported by all servers. */
+ infof(data, "schannel: Windows version is old and may not be able to "
+ "connect to some servers due to lack of SNI, algorithms, etc.");
+ }
+
+#ifdef HAS_ALPN
+ /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above.
+ Also it doesn't seem to be supported for Wine, see curl bug #983. */
+ backend->use_alpn = cf->conn->bits.tls_enable_alpn &&
+ !GetProcAddress(GetModuleHandle(TEXT("ntdll")),
+ "wine_get_version") &&
+ curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL);
+#else
+ backend->use_alpn = false;
+#endif
+
+#ifdef _WIN32_WCE
+#ifdef HAS_MANUAL_VERIFY_API
+ /* certificate validation on CE doesn't seem to work right; we'll
+ * do it following a more manual process. */
+ backend->use_manual_cred_validation = true;
+#else
+#error "compiler too old to support requisite manual cert verify for Win CE"
+#endif
+#else
+#ifdef HAS_MANUAL_VERIFY_API
+ if(conn_config->CAfile || conn_config->ca_info_blob) {
+ if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL)) {
+ backend->use_manual_cred_validation = true;
+ }
+ else {
+ failf(data, "schannel: this version of Windows is too old to support "
+ "certificate verification via CA bundle file.");
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+ else
+ backend->use_manual_cred_validation = false;
+#else
+ if(conn_config->CAfile || conn_config->ca_info_blob) {
+ failf(data, "schannel: CA cert support not built in");
+ return CURLE_NOT_BUILT_IN;
+ }
+#endif
+#endif
+
+ backend->cred = NULL;
+
+ /* check for an existing re-usable credential handle */
+ if(ssl_config->primary.sessionid) {
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL)) {
+ backend->cred = old_cred;
+ DEBUGF(infof(data, "schannel: re-using existing credential handle"));
+
+ /* increment the reference counter of the credential/session handle */
+ backend->cred->refcount++;
+ DEBUGF(infof(data,
+ "schannel: incremented credential handle refcount = %d",
+ backend->cred->refcount));
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+ if(!backend->cred) {
+ char *snihost;
+ result = schannel_acquire_credential_handle(cf, data);
+ if(result != CURLE_OK) {
+ return result;
+ }
+ /* A hostname associated with the credential is needed by
+ InitializeSecurityContext for SNI and other reasons. */
+ snihost = Curl_ssl_snihost(data, hostname, NULL);
+ if(!snihost) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ backend->cred->sni_hostname = curlx_convert_UTF8_to_tchar(snihost);
+ if(!backend->cred->sni_hostname)
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ /* Warn if SNI is disabled due to use of an IP address */
+ if(Curl_inet_pton(AF_INET, hostname, &addr)
+#ifdef ENABLE_IPV6
+ || Curl_inet_pton(AF_INET6, hostname, &addr6)
+#endif
+ ) {
+ infof(data, "schannel: using IP address, SNI is not supported by OS.");
+ }
+
+#ifdef HAS_ALPN
+ if(backend->use_alpn) {
+ int cur = 0;
+ int list_start_index = 0;
+ unsigned int *extension_len = NULL;
+ unsigned short* list_len = NULL;
+
+ /* The first four bytes will be an unsigned int indicating number
+ of bytes of data in the rest of the buffer. */
+ extension_len = (unsigned int *)(void *)(&alpn_buffer[cur]);
+ cur += sizeof(unsigned int);
+
+ /* The next four bytes are an indicator that this buffer will contain
+ ALPN data, as opposed to NPN, for example. */
+ *(unsigned int *)(void *)&alpn_buffer[cur] =
+ SecApplicationProtocolNegotiationExt_ALPN;
+ cur += sizeof(unsigned int);
+
+ /* The next two bytes will be an unsigned short indicating the number
+ of bytes used to list the preferred protocols. */
+ list_len = (unsigned short*)(void *)(&alpn_buffer[cur]);
+ cur += sizeof(unsigned short);
+
+ list_start_index = cur;
+
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2) {
+ alpn_buffer[cur++] = ALPN_H2_LENGTH;
+ memcpy(&alpn_buffer[cur], ALPN_H2, ALPN_H2_LENGTH);
+ cur += ALPN_H2_LENGTH;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+ }
+#endif
+
+ alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH;
+ memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
+ cur += ALPN_HTTP_1_1_LENGTH;
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+
+ *list_len = curlx_uitous(cur - list_start_index);
+ *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short);
+
+ InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur);
+ InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
+ }
+ else {
+ InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
+ }
+#else /* HAS_ALPN */
+ InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
+#endif
+
+ /* setup output buffer */
+ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
+
+ /* security request flags */
+ backend->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
+ ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_STREAM;
+
+ if(!ssl_config->auto_client_cert) {
+ backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS;
+ }
+
+ /* allocate memory for the security context handle */
+ backend->ctxt = (struct Curl_schannel_ctxt *)
+ calloc(1, sizeof(struct Curl_schannel_ctxt));
+ if(!backend->ctxt) {
+ failf(data, "schannel: unable to allocate memory");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ /* Schannel InitializeSecurityContext:
+ https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx
+
+ At the moment we don't pass inbuf unless we're using ALPN since we only
+ use it for that, and Wine (for which we currently disable ALPN) is giving
+ us problems with inbuf regardless. https://github.com/curl/curl/issues/983
+ */
+ sspi_status = s_pSecFn->InitializeSecurityContext(
+ &backend->cred->cred_handle, NULL, backend->cred->sni_hostname,
+ backend->req_flags, 0, 0,
+ (backend->use_alpn ? &inbuf_desc : NULL),
+ 0, &backend->ctxt->ctxt_handle,
+ &outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp);
+
+ if(sspi_status != SEC_I_CONTINUE_NEEDED) {
+ char buffer[STRERROR_LEN];
+ Curl_safefree(backend->ctxt);
+ switch(sspi_status) {
+ case SEC_E_INSUFFICIENT_MEMORY:
+ failf(data, "schannel: initial InitializeSecurityContext failed: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ return CURLE_OUT_OF_MEMORY;
+ case SEC_E_WRONG_PRINCIPAL:
+ failf(data, "schannel: SNI or certificate check failed: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ return CURLE_PEER_FAILED_VERIFICATION;
+ /*
+ case SEC_E_INVALID_HANDLE:
+ case SEC_E_INVALID_TOKEN:
+ case SEC_E_LOGON_DENIED:
+ case SEC_E_TARGET_UNKNOWN:
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ case SEC_E_INTERNAL_ERROR:
+ case SEC_E_NO_CREDENTIALS:
+ case SEC_E_UNSUPPORTED_FUNCTION:
+ case SEC_E_APPLICATION_PROTOCOL_MISMATCH:
+ */
+ default:
+ failf(data, "schannel: initial InitializeSecurityContext failed: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ DEBUGF(infof(data, "schannel: sending initial handshake data: "
+ "sending %lu bytes.", outbuf.cbBuffer));
+
+ /* send initial handshake data which is now stored in output buffer */
+ written = Curl_conn_cf_send(cf->next, data,
+ outbuf.pvBuffer, outbuf.cbBuffer,
+ &result);
+ s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
+ if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) {
+ failf(data, "schannel: failed to send initial handshake data: "
+ "sent %zd of %lu bytes", written, outbuf.cbBuffer);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ DEBUGF(infof(data, "schannel: sent initial handshake data: "
+ "sent %zd bytes", written));
+
+ backend->recv_unrecoverable_err = CURLE_OK;
+ backend->recv_sspi_close_notify = false;
+ backend->recv_connection_closed = false;
+ backend->recv_renegotiating = false;
+ backend->encdata_is_incomplete = false;
+
+ /* continue to second handshake step */
+ connssl->connecting_state = ssl_connect_2;
+
+ return CURLE_OK;
+}
+
+static CURLcode
+schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ int i;
+ ssize_t nread = -1, written = -1;
+ unsigned char *reallocated_buffer;
+ SecBuffer outbuf[3];
+ SecBufferDesc outbuf_desc;
+ SecBuffer inbuf[2];
+ SecBufferDesc inbuf_desc;
+ SECURITY_STATUS sspi_status = SEC_E_OK;
+ CURLcode result;
+ bool doread;
+ const char *pubkey_ptr;
+
+ DEBUGASSERT(backend);
+
+ doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
+
+ DEBUGF(infof(data,
+ "schannel: SSL/TLS connection with %s port %d (step 2/3)",
+ connssl->hostname, connssl->port));
+
+ if(!backend->cred || !backend->ctxt)
+ return CURLE_SSL_CONNECT_ERROR;
+
+ /* buffer to store previously received and decrypted data */
+ if(!backend->decdata_buffer) {
+ backend->decdata_offset = 0;
+ backend->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
+ backend->decdata_buffer = malloc(backend->decdata_length);
+ if(!backend->decdata_buffer) {
+ failf(data, "schannel: unable to allocate memory");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+
+ /* buffer to store previously received and encrypted data */
+ if(!backend->encdata_buffer) {
+ backend->encdata_is_incomplete = false;
+ backend->encdata_offset = 0;
+ backend->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
+ backend->encdata_buffer = malloc(backend->encdata_length);
+ if(!backend->encdata_buffer) {
+ failf(data, "schannel: unable to allocate memory");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+
+ /* if we need a bigger buffer to read a full message, increase buffer now */
+ if(backend->encdata_length - backend->encdata_offset <
+ CURL_SCHANNEL_BUFFER_FREE_SIZE) {
+ /* increase internal encrypted data buffer */
+ size_t reallocated_length = backend->encdata_offset +
+ CURL_SCHANNEL_BUFFER_FREE_SIZE;
+ reallocated_buffer = realloc(backend->encdata_buffer,
+ reallocated_length);
+
+ if(!reallocated_buffer) {
+ failf(data, "schannel: unable to re-allocate memory");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ else {
+ backend->encdata_buffer = reallocated_buffer;
+ backend->encdata_length = reallocated_length;
+ }
+ }
+
+ for(;;) {
+ if(doread) {
+ /* read encrypted handshake data from socket */
+ nread = Curl_conn_cf_recv(cf->next, data,
+ (char *) (backend->encdata_buffer +
+ backend->encdata_offset),
+ backend->encdata_length -
+ backend->encdata_offset,
+ &result);
+ if(result == CURLE_AGAIN) {
+ if(connssl->connecting_state != ssl_connect_2_writing)
+ connssl->connecting_state = ssl_connect_2_reading;
+ DEBUGF(infof(data, "schannel: failed to receive handshake, "
+ "need more data"));
+ return CURLE_OK;
+ }
+ else if((result != CURLE_OK) || (nread == 0)) {
+ failf(data, "schannel: failed to receive handshake, "
+ "SSL/TLS connection failed");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ /* increase encrypted data buffer offset */
+ backend->encdata_offset += nread;
+ backend->encdata_is_incomplete = false;
+ DEBUGF(infof(data, "schannel: encrypted data got %zd", nread));
+ }
+
+ DEBUGF(infof(data,
+ "schannel: encrypted data buffer: offset %zu length %zu",
+ backend->encdata_offset, backend->encdata_length));
+
+ /* setup input buffers */
+ InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(backend->encdata_offset),
+ curlx_uztoul(backend->encdata_offset));
+ InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&inbuf_desc, inbuf, 2);
+
+ /* setup output buffers */
+ InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0);
+ InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0);
+ InitSecBuffer(&outbuf[2], SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&outbuf_desc, outbuf, 3);
+
+ if(!inbuf[0].pvBuffer) {
+ failf(data, "schannel: unable to allocate memory");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ /* copy received handshake data into input buffer */
+ memcpy(inbuf[0].pvBuffer, backend->encdata_buffer,
+ backend->encdata_offset);
+
+ sspi_status = s_pSecFn->InitializeSecurityContext(
+ &backend->cred->cred_handle, &backend->ctxt->ctxt_handle,
+ backend->cred->sni_hostname, backend->req_flags,
+ 0, 0, &inbuf_desc, 0, NULL,
+ &outbuf_desc, &backend->ret_flags, &backend->ctxt->time_stamp);
+
+ /* free buffer for received handshake data */
+ Curl_safefree(inbuf[0].pvBuffer);
+
+ /* check if the handshake was incomplete */
+ if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
+ backend->encdata_is_incomplete = true;
+ connssl->connecting_state = ssl_connect_2_reading;
+ DEBUGF(infof(data,
+ "schannel: received incomplete message, need more data"));
+ return CURLE_OK;
+ }
+
+ /* If the server has requested a client certificate, attempt to continue
+ the handshake without one. This will allow connections to servers which
+ request a client certificate but do not require it. */
+ if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS &&
+ !(backend->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) {
+ backend->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS;
+ connssl->connecting_state = ssl_connect_2_writing;
+ DEBUGF(infof(data,
+ "schannel: a client certificate has been requested"));
+ return CURLE_OK;
+ }
+
+ /* check if the handshake needs to be continued */
+ if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) {
+ for(i = 0; i < 3; i++) {
+ /* search for handshake tokens that need to be send */
+ if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
+ DEBUGF(infof(data, "schannel: sending next handshake data: "
+ "sending %lu bytes.", outbuf[i].cbBuffer));
+
+ /* send handshake token to server */
+ written = Curl_conn_cf_send(cf->next, data,
+ outbuf[i].pvBuffer, outbuf[i].cbBuffer,
+ &result);
+ if((result != CURLE_OK) ||
+ (outbuf[i].cbBuffer != (size_t) written)) {
+ failf(data, "schannel: failed to send next handshake data: "
+ "sent %zd of %lu bytes", written, outbuf[i].cbBuffer);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ /* free obsolete buffer */
+ if(outbuf[i].pvBuffer) {
+ s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer);
+ }
+ }
+ }
+ else {
+ char buffer[STRERROR_LEN];
+ switch(sspi_status) {
+ case SEC_E_INSUFFICIENT_MEMORY:
+ failf(data, "schannel: next InitializeSecurityContext failed: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ return CURLE_OUT_OF_MEMORY;
+ case SEC_E_WRONG_PRINCIPAL:
+ failf(data, "schannel: SNI or certificate check failed: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case SEC_E_UNTRUSTED_ROOT:
+ failf(data, "schannel: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ return CURLE_PEER_FAILED_VERIFICATION;
+ /*
+ case SEC_E_INVALID_HANDLE:
+ case SEC_E_INVALID_TOKEN:
+ case SEC_E_LOGON_DENIED:
+ case SEC_E_TARGET_UNKNOWN:
+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
+ case SEC_E_INTERNAL_ERROR:
+ case SEC_E_NO_CREDENTIALS:
+ case SEC_E_UNSUPPORTED_FUNCTION:
+ case SEC_E_APPLICATION_PROTOCOL_MISMATCH:
+ */
+ default:
+ failf(data, "schannel: next InitializeSecurityContext failed: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ /* check if there was additional remaining encrypted data */
+ if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
+ DEBUGF(infof(data, "schannel: encrypted data length: %lu",
+ inbuf[1].cbBuffer));
+ /*
+ There are two cases where we could be getting extra data here:
+ 1) If we're renegotiating a connection and the handshake is already
+ complete (from the server perspective), it can encrypted app data
+ (not handshake data) in an extra buffer at this point.
+ 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a
+ connection and this extra data is part of the handshake.
+ We should process the data immediately; waiting for the socket to
+ be ready may fail since the server is done sending handshake data.
+ */
+ /* check if the remaining data is less than the total amount
+ and therefore begins after the already processed data */
+ if(backend->encdata_offset > inbuf[1].cbBuffer) {
+ memmove(backend->encdata_buffer,
+ (backend->encdata_buffer + backend->encdata_offset) -
+ inbuf[1].cbBuffer, inbuf[1].cbBuffer);
+ backend->encdata_offset = inbuf[1].cbBuffer;
+ if(sspi_status == SEC_I_CONTINUE_NEEDED) {
+ doread = FALSE;
+ continue;
+ }
+ }
+ }
+ else {
+ backend->encdata_offset = 0;
+ }
+ break;
+ }
+
+ /* check if the handshake needs to be continued */
+ if(sspi_status == SEC_I_CONTINUE_NEEDED) {
+ connssl->connecting_state = ssl_connect_2_reading;
+ return CURLE_OK;
+ }
+
+ /* check if the handshake is complete */
+ if(sspi_status == SEC_E_OK) {
+ connssl->connecting_state = ssl_connect_3;
+ DEBUGF(infof(data, "schannel: SSL/TLS handshake complete"));
+ }
+
+ pubkey_ptr = Curl_ssl_cf_is_proxy(cf)?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]:
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+ if(pubkey_ptr) {
+ result = pkp_pin_peer_pubkey(cf, data, pubkey_ptr);
+ if(result) {
+ failf(data, "SSL: public key does not match pinned public key");
+ return result;
+ }
+ }
+
+#ifdef HAS_MANUAL_VERIFY_API
+ if(conn_config->verifypeer && backend->use_manual_cred_validation) {
+ return Curl_verify_certificate(cf, data);
+ }
+#endif
+
+ return CURLE_OK;
+}
+
+static bool
+valid_cert_encoding(const CERT_CONTEXT *cert_context)
+{
+ return (cert_context != NULL) &&
+ ((cert_context->dwCertEncodingType & X509_ASN_ENCODING) != 0) &&
+ (cert_context->pbCertEncoded != NULL) &&
+ (cert_context->cbCertEncoded > 0);
+}
+
+typedef bool(*Read_crt_func)(const CERT_CONTEXT *ccert_context, void *arg);
+
+static void
+traverse_cert_store(const CERT_CONTEXT *context, Read_crt_func func,
+ void *arg)
+{
+ const CERT_CONTEXT *current_context = NULL;
+ bool should_continue = true;
+ while(should_continue &&
+ (current_context = CertEnumCertificatesInStore(
+ context->hCertStore,
+ current_context)) != NULL)
+ should_continue = func(current_context, arg);
+
+ if(current_context)
+ CertFreeCertificateContext(current_context);
+}
+
+static bool
+cert_counter_callback(const CERT_CONTEXT *ccert_context, void *certs_count)
+{
+ if(valid_cert_encoding(ccert_context))
+ (*(int *)certs_count)++;
+ return true;
+}
+
+struct Adder_args
+{
+ struct Curl_easy *data;
+ CURLcode result;
+ int idx;
+ int certs_count;
+};
+
+static bool
+add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, void *raw_arg)
+{
+ struct Adder_args *args = (struct Adder_args*)raw_arg;
+ args->result = CURLE_OK;
+ if(valid_cert_encoding(ccert_context)) {
+ const char *beg = (const char *) ccert_context->pbCertEncoded;
+ const char *end = beg + ccert_context->cbCertEncoded;
+ int insert_index = (args->certs_count - 1) - args->idx;
+ args->result = Curl_extract_certinfo(args->data, insert_index,
+ beg, end);
+ args->idx++;
+ }
+ return args->result == CURLE_OK;
+}
+
+static CURLcode
+schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ CURLcode result = CURLE_OK;
+ SECURITY_STATUS sspi_status = SEC_E_OK;
+ CERT_CONTEXT *ccert_context = NULL;
+#ifdef HAS_ALPN
+ SecPkgContext_ApplicationProtocol alpn_result;
+#endif
+
+ DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
+ DEBUGASSERT(backend);
+
+ DEBUGF(infof(data,
+ "schannel: SSL/TLS connection with %s port %d (step 3/3)",
+ connssl->hostname, connssl->port));
+
+ if(!backend->cred)
+ return CURLE_SSL_CONNECT_ERROR;
+
+ /* check if the required context attributes are met */
+ if(backend->ret_flags != backend->req_flags) {
+ if(!(backend->ret_flags & ISC_RET_SEQUENCE_DETECT))
+ failf(data, "schannel: failed to setup sequence detection");
+ if(!(backend->ret_flags & ISC_RET_REPLAY_DETECT))
+ failf(data, "schannel: failed to setup replay detection");
+ if(!(backend->ret_flags & ISC_RET_CONFIDENTIALITY))
+ failf(data, "schannel: failed to setup confidentiality");
+ if(!(backend->ret_flags & ISC_RET_ALLOCATED_MEMORY))
+ failf(data, "schannel: failed to setup memory allocation");
+ if(!(backend->ret_flags & ISC_RET_STREAM))
+ failf(data, "schannel: failed to setup stream orientation");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+#ifdef HAS_ALPN
+ if(backend->use_alpn) {
+ sspi_status =
+ s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle,
+ SECPKG_ATTR_APPLICATION_PROTOCOL,
+ &alpn_result);
+
+ if(sspi_status != SEC_E_OK) {
+ failf(data, "schannel: failed to retrieve ALPN result");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if(alpn_result.ProtoNegoStatus ==
+ SecApplicationProtocolNegotiationStatus_Success) {
+ unsigned char alpn = 0;
+
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR,
+ alpn_result.ProtocolIdSize, alpn_result.ProtocolId);
+
+#ifdef USE_HTTP2
+ if(alpn_result.ProtocolIdSize == ALPN_H2_LENGTH &&
+ !memcmp(ALPN_H2, alpn_result.ProtocolId, ALPN_H2_LENGTH)) {
+ alpn = CURL_HTTP_VERSION_2;
+ }
+ else
+#endif
+ if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH &&
+ !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId,
+ ALPN_HTTP_1_1_LENGTH)) {
+ alpn = CURL_HTTP_VERSION_1_1;
+ }
+ if(backend->recv_renegotiating) {
+ if(alpn != cf->conn->alpn) {
+ failf(data, "schannel: server selected an ALPN protocol too late");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ else
+ cf->conn->alpn = alpn;
+ }
+ else {
+ if(!backend->recv_renegotiating)
+ infof(data, VTLS_INFOF_NO_ALPN);
+ }
+
+ if(!backend->recv_renegotiating) {
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+ }
+ }
+#endif
+
+ /* save the current session data for possible re-use */
+ if(ssl_config->primary.sessionid) {
+ bool incache;
+ bool added = FALSE;
+ struct Curl_schannel_cred *old_cred = NULL;
+
+ Curl_ssl_sessionid_lock(data);
+ incache = !(Curl_ssl_getsessionid(cf, data, (void **)&old_cred, NULL));
+ if(incache) {
+ if(old_cred != backend->cred) {
+ DEBUGF(infof(data,
+ "schannel: old credential handle is stale, removing"));
+ /* we're not taking old_cred ownership here, no refcount++ is needed */
+ Curl_ssl_delsessionid(data, (void *)old_cred);
+ incache = FALSE;
+ }
+ }
+ if(!incache) {
+ result = Curl_ssl_addsessionid(cf, data, backend->cred,
+ sizeof(struct Curl_schannel_cred),
+ &added);
+ if(result) {
+ Curl_ssl_sessionid_unlock(data);
+ failf(data, "schannel: failed to store credential handle");
+ return result;
+ }
+ else if(added) {
+ /* this cred session is now also referenced by sessionid cache */
+ backend->cred->refcount++;
+ DEBUGF(infof(data,
+ "schannel: stored credential handle in session cache"));
+ }
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+ if(data->set.ssl.certinfo) {
+ int certs_count = 0;
+ sspi_status =
+ s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle,
+ SECPKG_ATTR_REMOTE_CERT_CONTEXT,
+ &ccert_context);
+
+ if((sspi_status != SEC_E_OK) || !ccert_context) {
+ failf(data, "schannel: failed to retrieve remote cert context");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ traverse_cert_store(ccert_context, cert_counter_callback, &certs_count);
+
+ result = Curl_ssl_init_certinfo(data, certs_count);
+ if(!result) {
+ struct Adder_args args;
+ args.data = data;
+ args.idx = 0;
+ args.certs_count = certs_count;
+ traverse_cert_store(ccert_context, add_cert_to_certinfo, &args);
+ result = args.result;
+ }
+ CertFreeCertificateContext(ccert_context);
+ if(result)
+ return result;
+ }
+
+ connssl->connecting_state = ssl_connect_done;
+
+ return CURLE_OK;
+}
+
+static CURLcode
+schannel_connect_common(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool nonblocking, bool *done)
+{
+ CURLcode result;
+ struct ssl_connect_data *connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ timediff_t timeout_ms;
+ int what;
+
+ /* check if the connection has already been established */
+ if(ssl_connection_complete == connssl->state) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ if(ssl_connect_1 == connssl->connecting_state) {
+ /* check out how much more time we're allowed */
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL/TLS connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ result = schannel_connect_step1(cf, data);
+ if(result)
+ return result;
+ }
+
+ while(ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state) {
+
+ /* check out how much more time we're allowed */
+ timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL/TLS connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ /* if ssl is expecting something, check if it's available. */
+ if(connssl->connecting_state == ssl_connect_2_reading
+ || connssl->connecting_state == ssl_connect_2_writing) {
+
+ curl_socket_t writefd = ssl_connect_2_writing ==
+ connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading ==
+ connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ nonblocking ? 0 : timeout_ms);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else if(0 == what) {
+ if(nonblocking) {
+ *done = FALSE;
+ return CURLE_OK;
+ }
+ else {
+ /* timeout */
+ failf(data, "SSL/TLS connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+ /* socket is readable or writable */
+ }
+
+ /* Run transaction, and return to the caller if it failed or if
+ * this connection is part of a multi handle and this loop would
+ * execute again. This permits the owner of a multi handle to
+ * abort a connection attempt before step2 has completed while
+ * ensuring that a client using select() or epoll() will always
+ * have a valid fdset to wait on.
+ */
+ result = schannel_connect_step2(cf, data);
+ if(result || (nonblocking &&
+ (ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state)))
+ return result;
+
+ } /* repeat step2 until all transactions are done. */
+
+ if(ssl_connect_3 == connssl->connecting_state) {
+ result = schannel_connect_step3(cf, data);
+ if(result)
+ return result;
+ }
+
+ if(ssl_connect_done == connssl->connecting_state) {
+ connssl->state = ssl_connection_complete;
+
+#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS
+ /* When SSPI is used in combination with Schannel
+ * we need the Schannel context to create the Schannel
+ * binding to pass the IIS extended protection checks.
+ * Available on Windows 7 or later.
+ */
+ {
+ struct ssl_backend_data *backend = connssl->backend;
+ DEBUGASSERT(backend);
+ cf->conn->sslContext = &backend->ctxt->ctxt_handle;
+ }
+#endif
+
+ *done = TRUE;
+ }
+ else
+ *done = FALSE;
+
+ /* reset our connection state machine */
+ connssl->connecting_state = ssl_connect_1;
+
+ return CURLE_OK;
+}
+
+static ssize_t
+schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *buf, size_t len, CURLcode *err)
+{
+ ssize_t written = -1;
+ size_t data_len = 0;
+ unsigned char *ptr = NULL;
+ struct ssl_connect_data *connssl = cf->ctx;
+ SecBuffer outbuf[4];
+ SecBufferDesc outbuf_desc;
+ SECURITY_STATUS sspi_status = SEC_E_OK;
+ CURLcode result;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ /* check if the maximum stream sizes were queried */
+ if(backend->stream_sizes.cbMaximumMessage == 0) {
+ sspi_status = s_pSecFn->QueryContextAttributes(
+ &backend->ctxt->ctxt_handle,
+ SECPKG_ATTR_STREAM_SIZES,
+ &backend->stream_sizes);
+ if(sspi_status != SEC_E_OK) {
+ *err = CURLE_SEND_ERROR;
+ return -1;
+ }
+ }
+
+ /* check if the buffer is longer than the maximum message length */
+ if(len > backend->stream_sizes.cbMaximumMessage) {
+ len = backend->stream_sizes.cbMaximumMessage;
+ }
+
+ /* calculate the complete message length and allocate a buffer for it */
+ data_len = backend->stream_sizes.cbHeader + len +
+ backend->stream_sizes.cbTrailer;
+ ptr = (unsigned char *) malloc(data_len);
+ if(!ptr) {
+ *err = CURLE_OUT_OF_MEMORY;
+ return -1;
+ }
+
+ /* setup output buffers (header, data, trailer, empty) */
+ InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER,
+ ptr, backend->stream_sizes.cbHeader);
+ InitSecBuffer(&outbuf[1], SECBUFFER_DATA,
+ ptr + backend->stream_sizes.cbHeader, curlx_uztoul(len));
+ InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER,
+ ptr + backend->stream_sizes.cbHeader + len,
+ backend->stream_sizes.cbTrailer);
+ InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&outbuf_desc, outbuf, 4);
+
+ /* copy data into output buffer */
+ memcpy(outbuf[1].pvBuffer, buf, len);
+
+ /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */
+ sspi_status = s_pSecFn->EncryptMessage(&backend->ctxt->ctxt_handle, 0,
+ &outbuf_desc, 0);
+
+ /* check if the message was encrypted */
+ if(sspi_status == SEC_E_OK) {
+ written = 0;
+
+ /* send the encrypted message including header, data and trailer */
+ len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
+
+ /*
+ It's important to send the full message which includes the header,
+ encrypted payload, and trailer. Until the client receives all the
+ data a coherent message has not been delivered and the client
+ can't read any of it.
+
+ If we wanted to buffer the unwritten encrypted bytes, we would
+ tell the client that all data it has requested to be sent has been
+ sent. The unwritten encrypted bytes would be the first bytes to
+ send on the next invocation.
+ Here's the catch with this - if we tell the client that all the
+ bytes have been sent, will the client call this method again to
+ send the buffered data? Looking at who calls this function, it
+ seems the answer is NO.
+ */
+
+ /* send entire message or fail */
+ while(len > (size_t)written) {
+ ssize_t this_write = 0;
+ int what;
+ timediff_t timeout_ms = Curl_timeleft(data, NULL, FALSE);
+ if(timeout_ms < 0) {
+ /* we already got the timeout */
+ failf(data, "schannel: timed out sending data "
+ "(bytes sent: %zd)", written);
+ *err = CURLE_OPERATION_TIMEDOUT;
+ written = -1;
+ break;
+ }
+ else if(!timeout_ms)
+ timeout_ms = TIMEDIFF_T_MAX;
+ what = SOCKET_WRITABLE(cf->conn->sock[cf->sockindex], timeout_ms);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ *err = CURLE_SEND_ERROR;
+ written = -1;
+ break;
+ }
+ else if(0 == what) {
+ failf(data, "schannel: timed out sending data "
+ "(bytes sent: %zd)", written);
+ *err = CURLE_OPERATION_TIMEDOUT;
+ written = -1;
+ break;
+ }
+ /* socket is writable */
+
+ this_write = Curl_conn_cf_send(cf->next, data,
+ ptr + written, len - written,
+ &result);
+ if(result == CURLE_AGAIN)
+ continue;
+ else if(result != CURLE_OK) {
+ *err = result;
+ written = -1;
+ break;
+ }
+
+ written += this_write;
+ }
+ }
+ else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) {
+ *err = CURLE_OUT_OF_MEMORY;
+ }
+ else{
+ *err = CURLE_SEND_ERROR;
+ }
+
+ Curl_safefree(ptr);
+
+ if(len == (size_t)written)
+ /* Encrypted message including header, data and trailer entirely sent.
+ The return value is the number of unencrypted bytes that were sent. */
+ written = outbuf[1].cbBuffer;
+
+ return written;
+}
+
+static ssize_t
+schannel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *err)
+{
+ size_t size = 0;
+ ssize_t nread = -1;
+ struct ssl_connect_data *connssl = cf->ctx;
+ unsigned char *reallocated_buffer;
+ size_t reallocated_length;
+ bool done = FALSE;
+ SecBuffer inbuf[4];
+ SecBufferDesc inbuf_desc;
+ SECURITY_STATUS sspi_status = SEC_E_OK;
+ /* we want the length of the encrypted buffer to be at least large enough
+ that it can hold all the bytes requested and some TLS record overhead. */
+ size_t min_encdata_length = len + CURL_SCHANNEL_BUFFER_FREE_SIZE;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(backend);
+
+ /****************************************************************************
+ * Don't return or set backend->recv_unrecoverable_err unless in the cleanup.
+ * The pattern for return error is set *err, optional infof, goto cleanup.
+ *
+ * Our priority is to always return as much decrypted data to the caller as
+ * possible, even if an error occurs. The state of the decrypted buffer must
+ * always be valid. Transfer of decrypted data to the caller's buffer is
+ * handled in the cleanup.
+ */
+
+ DEBUGF(infof(data, "schannel: client wants to read %zu bytes", len));
+ *err = CURLE_OK;
+
+ if(len && len <= backend->decdata_offset) {
+ infof(data, "schannel: enough decrypted data is already available");
+ goto cleanup;
+ }
+ else if(backend->recv_unrecoverable_err) {
+ *err = backend->recv_unrecoverable_err;
+ infof(data, "schannel: an unrecoverable error occurred in a prior call");
+ goto cleanup;
+ }
+ else if(backend->recv_sspi_close_notify) {
+ /* once a server has indicated shutdown there is no more encrypted data */
+ infof(data, "schannel: server indicated shutdown in a prior call");
+ goto cleanup;
+ }
+
+ /* It's debatable what to return when !len. Regardless we can't return
+ immediately because there may be data to decrypt (in the case we want to
+ decrypt all encrypted cached data) so handle !len later in cleanup.
+ */
+ else if(len && !backend->recv_connection_closed) {
+ /* increase enc buffer in order to fit the requested amount of data */
+ size = backend->encdata_length - backend->encdata_offset;
+ if(size < CURL_SCHANNEL_BUFFER_FREE_SIZE ||
+ backend->encdata_length < min_encdata_length) {
+ reallocated_length = backend->encdata_offset +
+ CURL_SCHANNEL_BUFFER_FREE_SIZE;
+ if(reallocated_length < min_encdata_length) {
+ reallocated_length = min_encdata_length;
+ }
+ reallocated_buffer = realloc(backend->encdata_buffer,
+ reallocated_length);
+ if(!reallocated_buffer) {
+ *err = CURLE_OUT_OF_MEMORY;
+ failf(data, "schannel: unable to re-allocate memory");
+ goto cleanup;
+ }
+
+ backend->encdata_buffer = reallocated_buffer;
+ backend->encdata_length = reallocated_length;
+ size = backend->encdata_length - backend->encdata_offset;
+ DEBUGF(infof(data, "schannel: encdata_buffer resized %zu",
+ backend->encdata_length));
+ }
+
+ DEBUGF(infof(data,
+ "schannel: encrypted data buffer: offset %zu length %zu",
+ backend->encdata_offset, backend->encdata_length));
+
+ /* read encrypted data from socket */
+ nread = Curl_conn_cf_recv(cf->next, data,
+ (char *)(backend->encdata_buffer +
+ backend->encdata_offset),
+ size, err);
+ if(*err) {
+ nread = -1;
+ if(*err == CURLE_AGAIN)
+ DEBUGF(infof(data,
+ "schannel: recv returned CURLE_AGAIN"));
+ else if(*err == CURLE_RECV_ERROR)
+ infof(data, "schannel: recv returned CURLE_RECV_ERROR");
+ else
+ infof(data, "schannel: recv returned error %d", *err);
+ }
+ else if(nread == 0) {
+ backend->recv_connection_closed = true;
+ DEBUGF(infof(data, "schannel: server closed the connection"));
+ }
+ else if(nread > 0) {
+ backend->encdata_offset += (size_t)nread;
+ backend->encdata_is_incomplete = false;
+ DEBUGF(infof(data, "schannel: encrypted data got %zd", nread));
+ }
+ }
+
+ DEBUGF(infof(data,
+ "schannel: encrypted data buffer: offset %zu length %zu",
+ backend->encdata_offset, backend->encdata_length));
+
+ /* decrypt loop */
+ while(backend->encdata_offset > 0 && sspi_status == SEC_E_OK &&
+ (!len || backend->decdata_offset < len ||
+ backend->recv_connection_closed)) {
+ /* prepare data buffer for DecryptMessage call */
+ InitSecBuffer(&inbuf[0], SECBUFFER_DATA, backend->encdata_buffer,
+ curlx_uztoul(backend->encdata_offset));
+
+ /* we need 3 more empty input buffers for possible output */
+ InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
+ InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0);
+ InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&inbuf_desc, inbuf, 4);
+
+ /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx
+ */
+ sspi_status = s_pSecFn->DecryptMessage(&backend->ctxt->ctxt_handle,
+ &inbuf_desc, 0, NULL);
+
+ /* check if everything went fine (server may want to renegotiate
+ or shutdown the connection context) */
+ if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE ||
+ sspi_status == SEC_I_CONTEXT_EXPIRED) {
+ /* check for successfully decrypted data, even before actual
+ renegotiation or shutdown of the connection context */
+ if(inbuf[1].BufferType == SECBUFFER_DATA) {
+ DEBUGF(infof(data, "schannel: decrypted data length: %lu",
+ inbuf[1].cbBuffer));
+
+ /* increase buffer in order to fit the received amount of data */
+ size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ?
+ inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE;
+ if(backend->decdata_length - backend->decdata_offset < size ||
+ backend->decdata_length < len) {
+ /* increase internal decrypted data buffer */
+ reallocated_length = backend->decdata_offset + size;
+ /* make sure that the requested amount of data fits */
+ if(reallocated_length < len) {
+ reallocated_length = len;
+ }
+ reallocated_buffer = realloc(backend->decdata_buffer,
+ reallocated_length);
+ if(!reallocated_buffer) {
+ *err = CURLE_OUT_OF_MEMORY;
+ failf(data, "schannel: unable to re-allocate memory");
+ goto cleanup;
+ }
+ backend->decdata_buffer = reallocated_buffer;
+ backend->decdata_length = reallocated_length;
+ }
+
+ /* copy decrypted data to internal buffer */
+ size = inbuf[1].cbBuffer;
+ if(size) {
+ memcpy(backend->decdata_buffer + backend->decdata_offset,
+ inbuf[1].pvBuffer, size);
+ backend->decdata_offset += size;
+ }
+
+ DEBUGF(infof(data, "schannel: decrypted data added: %zu", size));
+ DEBUGF(infof(data,
+ "schannel: decrypted cached: offset %zu length %zu",
+ backend->decdata_offset, backend->decdata_length));
+ }
+
+ /* check for remaining encrypted data */
+ if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) {
+ DEBUGF(infof(data, "schannel: encrypted data length: %lu",
+ inbuf[3].cbBuffer));
+
+ /* check if the remaining data is less than the total amount
+ * and therefore begins after the already processed data
+ */
+ if(backend->encdata_offset > inbuf[3].cbBuffer) {
+ /* move remaining encrypted data forward to the beginning of
+ buffer */
+ memmove(backend->encdata_buffer,
+ (backend->encdata_buffer + backend->encdata_offset) -
+ inbuf[3].cbBuffer, inbuf[3].cbBuffer);
+ backend->encdata_offset = inbuf[3].cbBuffer;
+ }
+
+ DEBUGF(infof(data,
+ "schannel: encrypted cached: offset %zu length %zu",
+ backend->encdata_offset, backend->encdata_length));
+ }
+ else {
+ /* reset encrypted buffer offset, because there is no data remaining */
+ backend->encdata_offset = 0;
+ }
+
+ /* check if server wants to renegotiate the connection context */
+ if(sspi_status == SEC_I_RENEGOTIATE) {
+ infof(data, "schannel: remote party requests renegotiation");
+ if(*err && *err != CURLE_AGAIN) {
+ infof(data, "schannel: can't renegotiate, an error is pending");
+ goto cleanup;
+ }
+
+ /* begin renegotiation */
+ infof(data, "schannel: renegotiating SSL/TLS connection");
+ connssl->state = ssl_connection_negotiating;
+ connssl->connecting_state = ssl_connect_2_writing;
+ backend->recv_renegotiating = true;
+ *err = schannel_connect_common(cf, data, FALSE, &done);
+ backend->recv_renegotiating = false;
+ if(*err) {
+ infof(data, "schannel: renegotiation failed");
+ goto cleanup;
+ }
+ /* now retry receiving data */
+ sspi_status = SEC_E_OK;
+ infof(data, "schannel: SSL/TLS connection renegotiated");
+ continue;
+ }
+ /* check if the server closed the connection */
+ else if(sspi_status == SEC_I_CONTEXT_EXPIRED) {
+ /* In Windows 2000 SEC_I_CONTEXT_EXPIRED (close_notify) is not
+ returned so we have to work around that in cleanup. */
+ backend->recv_sspi_close_notify = true;
+ if(!backend->recv_connection_closed) {
+ backend->recv_connection_closed = true;
+ infof(data, "schannel: server closed the connection");
+ }
+ goto cleanup;
+ }
+ }
+ else if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
+ backend->encdata_is_incomplete = true;
+ if(!*err)
+ *err = CURLE_AGAIN;
+ infof(data, "schannel: failed to decrypt data, need more data");
+ goto cleanup;
+ }
+ else {
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ char buffer[STRERROR_LEN];
+#endif
+ *err = CURLE_RECV_ERROR;
+ infof(data, "schannel: failed to read data from server: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ goto cleanup;
+ }
+ }
+
+ DEBUGF(infof(data,
+ "schannel: encrypted data buffer: offset %zu length %zu",
+ backend->encdata_offset, backend->encdata_length));
+
+ DEBUGF(infof(data,
+ "schannel: decrypted data buffer: offset %zu length %zu",
+ backend->decdata_offset, backend->decdata_length));
+
+ cleanup:
+ /* Warning- there is no guarantee the encdata state is valid at this point */
+ DEBUGF(infof(data, "schannel: schannel_recv cleanup"));
+
+ /* Error if the connection has closed without a close_notify.
+
+ The behavior here is a matter of debate. We don't want to be vulnerable
+ to a truncation attack however there's some browser precedent for
+ ignoring the close_notify for compatibility reasons.
+
+ Additionally, Windows 2000 (v5.0) is a special case since it seems it
+ doesn't return close_notify. In that case if the connection was closed we
+ assume it was graceful (close_notify) since there doesn't seem to be a
+ way to tell.
+ */
+ if(len && !backend->decdata_offset && backend->recv_connection_closed &&
+ !backend->recv_sspi_close_notify) {
+ bool isWin2k = curlx_verify_windows_version(5, 0, 0, PLATFORM_WINNT,
+ VERSION_EQUAL);
+
+ if(isWin2k && sspi_status == SEC_E_OK)
+ backend->recv_sspi_close_notify = true;
+ else {
+ *err = CURLE_RECV_ERROR;
+ infof(data, "schannel: server closed abruptly (missing close_notify)");
+ }
+ }
+
+ /* Any error other than CURLE_AGAIN is an unrecoverable error. */
+ if(*err && *err != CURLE_AGAIN)
+ backend->recv_unrecoverable_err = *err;
+
+ size = len < backend->decdata_offset ? len : backend->decdata_offset;
+ if(size) {
+ memcpy(buf, backend->decdata_buffer, size);
+ memmove(backend->decdata_buffer, backend->decdata_buffer + size,
+ backend->decdata_offset - size);
+ backend->decdata_offset -= size;
+ DEBUGF(infof(data, "schannel: decrypted data returned %zu", size));
+ DEBUGF(infof(data,
+ "schannel: decrypted data buffer: offset %zu length %zu",
+ backend->decdata_offset, backend->decdata_length));
+ *err = CURLE_OK;
+ return (ssize_t)size;
+ }
+
+ if(!*err && !backend->recv_connection_closed)
+ *err = CURLE_AGAIN;
+
+ /* It's debatable what to return when !len. We could return whatever error
+ we got from decryption but instead we override here so the return is
+ consistent.
+ */
+ if(!len)
+ *err = CURLE_OK;
+
+ return *err ? -1 : 0;
+}
+
+static CURLcode schannel_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return schannel_connect_common(cf, data, TRUE, done);
+}
+
+static CURLcode schannel_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode result;
+ bool done = FALSE;
+
+ result = schannel_connect_common(cf, data, FALSE, &done);
+ if(result)
+ return result;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+static bool schannel_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ const struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ if(connssl->backend->ctxt) /* SSL/TLS is in use */
+ return (backend->decdata_offset > 0 ||
+ (backend->encdata_offset > 0 && !backend->encdata_is_incomplete));
+ else
+ return FALSE;
+}
+
+static void schannel_session_free(void *ptr)
+{
+ /* this is expected to be called under sessionid lock */
+ struct Curl_schannel_cred *cred = ptr;
+
+ if(cred) {
+ cred->refcount--;
+ if(cred->refcount == 0) {
+ s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
+ curlx_unicodefree(cred->sni_hostname);
+#ifdef HAS_CLIENT_CERT_PATH
+ if(cred->client_cert_store) {
+ CertCloseStore(cred->client_cert_store, 0);
+ cred->client_cert_store = NULL;
+ }
+#endif
+ Curl_safefree(cred);
+ }
+ }
+}
+
+/* shut down the SSL connection and clean up related memory.
+ this function can be called multiple times on the same connection including
+ if the SSL connection failed (eg connection made but failed handshake). */
+static int schannel_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ /* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
+ * Shutting Down an Schannel Connection
+ */
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ DEBUGASSERT(data);
+ DEBUGASSERT(backend);
+
+ if(connssl->backend->ctxt) {
+ infof(data, "schannel: shutting down SSL/TLS connection with %s port %d",
+ connssl->hostname, connssl->port);
+ }
+
+ if(backend->cred && backend->ctxt) {
+ SecBufferDesc BuffDesc;
+ SecBuffer Buffer;
+ SECURITY_STATUS sspi_status;
+ SecBuffer outbuf;
+ SecBufferDesc outbuf_desc;
+ CURLcode result;
+ DWORD dwshut = SCHANNEL_SHUTDOWN;
+
+ InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
+ InitSecBufferDesc(&BuffDesc, &Buffer, 1);
+
+ sspi_status = s_pSecFn->ApplyControlToken(&backend->ctxt->ctxt_handle,
+ &BuffDesc);
+
+ if(sspi_status != SEC_E_OK) {
+ char buffer[STRERROR_LEN];
+ failf(data, "schannel: ApplyControlToken failure: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ }
+
+ /* setup output buffer */
+ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
+ InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
+
+ sspi_status = s_pSecFn->InitializeSecurityContext(
+ &backend->cred->cred_handle,
+ &backend->ctxt->ctxt_handle,
+ backend->cred->sni_hostname,
+ backend->req_flags,
+ 0,
+ 0,
+ NULL,
+ 0,
+ &backend->ctxt->ctxt_handle,
+ &outbuf_desc,
+ &backend->ret_flags,
+ &backend->ctxt->time_stamp);
+
+ if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
+ /* send close message which is in output buffer */
+ ssize_t written = Curl_conn_cf_send(cf->next, data,
+ outbuf.pvBuffer, outbuf.cbBuffer,
+ &result);
+ s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
+ if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) {
+ infof(data, "schannel: failed to send close msg: %s"
+ " (bytes written: %zd)", curl_easy_strerror(result), written);
+ }
+ }
+ }
+
+ /* free SSPI Schannel API security context handle */
+ if(backend->ctxt) {
+ DEBUGF(infof(data, "schannel: clear security context handle"));
+ s_pSecFn->DeleteSecurityContext(&backend->ctxt->ctxt_handle);
+ Curl_safefree(backend->ctxt);
+ }
+
+ /* free SSPI Schannel API credential handle */
+ if(backend->cred) {
+ Curl_ssl_sessionid_lock(data);
+ schannel_session_free(backend->cred);
+ Curl_ssl_sessionid_unlock(data);
+ backend->cred = NULL;
+ }
+
+ /* free internal buffer for received encrypted data */
+ if(backend->encdata_buffer) {
+ Curl_safefree(backend->encdata_buffer);
+ backend->encdata_length = 0;
+ backend->encdata_offset = 0;
+ backend->encdata_is_incomplete = false;
+ }
+
+ /* free internal buffer for received decrypted data */
+ if(backend->decdata_buffer) {
+ Curl_safefree(backend->decdata_buffer);
+ backend->decdata_length = 0;
+ backend->decdata_offset = 0;
+ }
+
+ return CURLE_OK;
+}
+
+static void schannel_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ schannel_shutdown(cf, data);
+}
+
+static int schannel_init(void)
+{
+ return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0);
+}
+
+static void schannel_cleanup(void)
+{
+ Curl_sspi_global_cleanup();
+}
+
+static size_t schannel_version(char *buffer, size_t size)
+{
+ size = msnprintf(buffer, size, "Schannel");
+
+ return size;
+}
+
+static CURLcode schannel_random(struct Curl_easy *data UNUSED_PARAM,
+ unsigned char *entropy, size_t length)
+{
+ (void)data;
+
+ return Curl_win32_random(entropy, length);
+}
+
+static CURLcode pkp_pin_peer_pubkey(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const char *pinnedpubkey)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ CERT_CONTEXT *pCertContextServer = NULL;
+
+ /* Result is returned to caller */
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+ DEBUGASSERT(backend);
+
+ /* if a path wasn't specified, don't pin */
+ if(!pinnedpubkey)
+ return CURLE_OK;
+
+ do {
+ SECURITY_STATUS sspi_status;
+ const char *x509_der;
+ DWORD x509_der_len;
+ struct Curl_X509certificate x509_parsed;
+ struct Curl_asn1Element *pubkey;
+
+ sspi_status =
+ s_pSecFn->QueryContextAttributes(&backend->ctxt->ctxt_handle,
+ SECPKG_ATTR_REMOTE_CERT_CONTEXT,
+ &pCertContextServer);
+
+ if((sspi_status != SEC_E_OK) || !pCertContextServer) {
+ char buffer[STRERROR_LEN];
+ failf(data, "schannel: Failed to read remote certificate context: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ break; /* failed */
+ }
+
+
+ if(!(((pCertContextServer->dwCertEncodingType & X509_ASN_ENCODING) != 0) &&
+ (pCertContextServer->cbCertEncoded > 0)))
+ break;
+
+ x509_der = (const char *)pCertContextServer->pbCertEncoded;
+ x509_der_len = pCertContextServer->cbCertEncoded;
+ memset(&x509_parsed, 0, sizeof(x509_parsed));
+ if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len))
+ break;
+
+ pubkey = &x509_parsed.subjectPublicKeyInfo;
+ if(!pubkey->header || pubkey->end <= pubkey->header) {
+ failf(data, "SSL: failed retrieving public key from server certificate");
+ break;
+ }
+
+ result = Curl_pin_peer_pubkey(data,
+ pinnedpubkey,
+ (const unsigned char *)pubkey->header,
+ (size_t)(pubkey->end - pubkey->header));
+ if(result) {
+ failf(data, "SSL: public key does not match pinned public key");
+ }
+ } while(0);
+
+ if(pCertContextServer)
+ CertFreeCertificateContext(pCertContextServer);
+
+ return result;
+}
+
+static void schannel_checksum(const unsigned char *input,
+ size_t inputlen,
+ unsigned char *checksum,
+ size_t checksumlen,
+ DWORD provType,
+ const unsigned int algId)
+{
+ HCRYPTPROV hProv = 0;
+ HCRYPTHASH hHash = 0;
+ DWORD cbHashSize = 0;
+ DWORD dwHashSizeLen = (DWORD)sizeof(cbHashSize);
+ DWORD dwChecksumLen = (DWORD)checksumlen;
+
+ /* since this can fail in multiple ways, zero memory first so we never
+ * return old data
+ */
+ memset(checksum, 0, checksumlen);
+
+ if(!CryptAcquireContext(&hProv, NULL, NULL, provType,
+ CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
+ return; /* failed */
+
+ do {
+ if(!CryptCreateHash(hProv, algId, 0, 0, &hHash))
+ break; /* failed */
+
+ /* workaround for original MinGW, should be (const BYTE*) */
+ if(!CryptHashData(hHash, (BYTE*)input, (DWORD)inputlen, 0))
+ break; /* failed */
+
+ /* get hash size */
+ if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize,
+ &dwHashSizeLen, 0))
+ break; /* failed */
+
+ /* check hash size */
+ if(checksumlen < cbHashSize)
+ break; /* failed */
+
+ if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0))
+ break; /* failed */
+ } while(0);
+
+ if(hHash)
+ CryptDestroyHash(hHash);
+
+ if(hProv)
+ CryptReleaseContext(hProv, 0);
+}
+
+static CURLcode schannel_sha256sum(const unsigned char *input,
+ size_t inputlen,
+ unsigned char *sha256sum,
+ size_t sha256len)
+{
+ schannel_checksum(input, inputlen, sha256sum, sha256len,
+ PROV_RSA_AES, CALG_SHA_256);
+ return CURLE_OK;
+}
+
+static void *schannel_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ (void)info;
+ DEBUGASSERT(backend);
+ return &backend->ctxt->ctxt_handle;
+}
+
+const struct Curl_ssl Curl_ssl_schannel = {
+ { CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */
+
+ SSLSUPP_CERTINFO |
+#ifdef HAS_MANUAL_VERIFY_API
+ SSLSUPP_CAINFO_BLOB |
+#endif
+ SSLSUPP_PINNEDPUBKEY |
+ SSLSUPP_TLS13_CIPHERSUITES |
+ SSLSUPP_HTTPS_PROXY,
+
+ sizeof(struct ssl_backend_data),
+
+ schannel_init, /* init */
+ schannel_cleanup, /* cleanup */
+ schannel_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ schannel_shutdown, /* shutdown */
+ schannel_data_pending, /* data_pending */
+ schannel_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ schannel_connect, /* connect */
+ schannel_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ schannel_get_internals, /* get_internals */
+ schannel_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ schannel_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ schannel_sha256sum, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ schannel_recv, /* recv decrypted data */
+ schannel_send, /* send data to encrypt */
+};
+
+#endif /* USE_SCHANNEL */
diff --git a/libs/libcurl/src/vtls/schannel.h b/libs/libcurl/src/vtls/schannel.h index 24d7eff25b..234315272b 100644 --- a/libs/libcurl/src/vtls/schannel.h +++ b/libs/libcurl/src/vtls/schannel.h @@ -1,200 +1,199 @@ -#ifndef HEADER_CURL_SCHANNEL_H -#define HEADER_CURL_SCHANNEL_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al. - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_SCHANNEL - -#define SCHANNEL_USE_BLACKLISTS 1 - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4201) -#endif -#include <subauth.h> -#ifdef _MSC_VER -#pragma warning(pop) -#endif -/* Wincrypt must be included before anything that could include OpenSSL. */ -#if defined(USE_WIN32_CRYPTO) -#include <wincrypt.h> -/* Undefine wincrypt conflicting symbols for BoringSSL. */ -#undef X509_NAME -#undef X509_EXTENSIONS -#undef PKCS7_ISSUER_AND_SERIAL -#undef PKCS7_SIGNER_INFO -#undef OCSP_REQUEST -#undef OCSP_RESPONSE -#endif - -#include <schnlsp.h> -#include <schannel.h> -#include "curl_sspi.h" - -#include "urldata.h" - -/* <wincrypt.h> has been included via the above <schnlsp.h>. - * Or in case of ldap.c, it was included via <winldap.h>. - * And since <wincrypt.h> has this: - * #define X509_NAME ((LPCSTR) 7) - * - * And in BoringSSL's <openssl/base.h> there is: - * typedef struct X509_name_st X509_NAME; - * etc. - * - * this will cause all kinds of C-preprocessing paste errors in - * BoringSSL's <openssl/x509.h>: So just undefine those defines here - * (and only here). - */ -#if defined(HAVE_BORINGSSL) || defined(OPENSSL_IS_BORINGSSL) -# undef X509_NAME -# undef X509_CERT_PAIR -# undef X509_EXTENSIONS -#endif - -extern const struct Curl_ssl Curl_ssl_schannel; - -CURLcode Curl_verify_certificate(struct Curl_easy *data, - struct connectdata *conn, int sockindex); - -/* structs to expose only in schannel.c and schannel_verify.c */ -#ifdef EXPOSE_SCHANNEL_INTERNAL_STRUCTS - -#include <wincrypt.h> - -#ifdef __MINGW32__ -#ifdef __MINGW64_VERSION_MAJOR -#define HAS_MANUAL_VERIFY_API -#endif -#else -#ifdef CERT_CHAIN_REVOCATION_CHECK_CHAIN -#define HAS_MANUAL_VERIFY_API -#endif -#endif - -#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX) \ - && !defined(DISABLE_SCHANNEL_CLIENT_CERT) -#define HAS_CLIENT_CERT_PATH -#endif - -#ifndef SCH_CREDENTIALS_VERSION - -#define SCH_CREDENTIALS_VERSION 0x00000005 - -typedef enum _eTlsAlgorithmUsage -{ - TlsParametersCngAlgUsageKeyExchange, - TlsParametersCngAlgUsageSignature, - TlsParametersCngAlgUsageCipher, - TlsParametersCngAlgUsageDigest, - TlsParametersCngAlgUsageCertSig -} eTlsAlgorithmUsage; - -typedef struct _CRYPTO_SETTINGS -{ - eTlsAlgorithmUsage eAlgorithmUsage; - UNICODE_STRING strCngAlgId; - DWORD cChainingModes; - PUNICODE_STRING rgstrChainingModes; - DWORD dwMinBitLength; - DWORD dwMaxBitLength; -} CRYPTO_SETTINGS, * PCRYPTO_SETTINGS; - -typedef struct _TLS_PARAMETERS -{ - DWORD cAlpnIds; - PUNICODE_STRING rgstrAlpnIds; - DWORD grbitDisabledProtocols; - DWORD cDisabledCrypto; - PCRYPTO_SETTINGS pDisabledCrypto; - DWORD dwFlags; -} TLS_PARAMETERS, * PTLS_PARAMETERS; - -typedef struct _SCH_CREDENTIALS -{ - DWORD dwVersion; - DWORD dwCredFormat; - DWORD cCreds; - PCCERT_CONTEXT* paCred; - HCERTSTORE hRootStore; - - DWORD cMappers; - struct _HMAPPER **aphMappers; - - DWORD dwSessionLifespan; - DWORD dwFlags; - DWORD cTlsParameters; - PTLS_PARAMETERS pTlsParameters; -} SCH_CREDENTIALS, * PSCH_CREDENTIALS; - -#define SCH_CRED_MAX_SUPPORTED_PARAMETERS 16 -#define SCH_CRED_MAX_SUPPORTED_ALPN_IDS 16 -#define SCH_CRED_MAX_SUPPORTED_CRYPTO_SETTINGS 16 -#define SCH_CRED_MAX_SUPPORTED_CHAINING_MODES 16 - -#endif - -struct Curl_schannel_cred { - CredHandle cred_handle; - TimeStamp time_stamp; - TCHAR *sni_hostname; -#ifdef HAS_CLIENT_CERT_PATH - HCERTSTORE client_cert_store; -#endif - int refcount; -}; - -struct Curl_schannel_ctxt { - CtxtHandle ctxt_handle; - TimeStamp time_stamp; -}; - -struct ssl_backend_data { - struct Curl_schannel_cred *cred; - struct Curl_schannel_ctxt *ctxt; - SecPkgContext_StreamSizes stream_sizes; - size_t encdata_length, decdata_length; - size_t encdata_offset, decdata_offset; - unsigned char *encdata_buffer, *decdata_buffer; - /* encdata_is_incomplete: if encdata contains only a partial record that - can't be decrypted without another Curl_read_plain (that is, status is - SEC_E_INCOMPLETE_MESSAGE) then set this true. after Curl_read_plain writes - more bytes into encdata then set this back to false. */ - bool encdata_is_incomplete; - unsigned long req_flags, ret_flags; - CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */ - bool recv_sspi_close_notify; /* true if connection closed by close_notify */ - bool recv_connection_closed; /* true if connection closed, regardless how */ - bool recv_renegotiating; /* true if recv is doing renegotiation */ - bool use_alpn; /* true if ALPN is used for this connection */ -#ifdef HAS_MANUAL_VERIFY_API - bool use_manual_cred_validation; /* true if manual cred validation is used */ -#endif -}; -#endif /* EXPOSE_SCHANNEL_INTERNAL_STRUCTS */ - -#endif /* USE_SCHANNEL */ -#endif /* HEADER_CURL_SCHANNEL_H */ +#ifndef HEADER_CURL_SCHANNEL_H
+#define HEADER_CURL_SCHANNEL_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al.
+ * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_SCHANNEL
+
+#define SCHANNEL_USE_BLACKLISTS 1
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4201)
+#endif
+#include <subauth.h>
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+/* Wincrypt must be included before anything that could include OpenSSL. */
+#if defined(USE_WIN32_CRYPTO)
+#include <wincrypt.h>
+/* Undefine wincrypt conflicting symbols for BoringSSL. */
+#undef X509_NAME
+#undef X509_EXTENSIONS
+#undef PKCS7_ISSUER_AND_SERIAL
+#undef PKCS7_SIGNER_INFO
+#undef OCSP_REQUEST
+#undef OCSP_RESPONSE
+#endif
+
+#include <schnlsp.h>
+#include <schannel.h>
+#include "curl_sspi.h"
+
+#include "cfilters.h"
+#include "urldata.h"
+
+/* <wincrypt.h> has been included via the above <schnlsp.h>.
+ * Or in case of ldap.c, it was included via <winldap.h>.
+ * And since <wincrypt.h> has this:
+ * #define X509_NAME ((LPCSTR) 7)
+ *
+ * And in BoringSSL's <openssl/base.h> there is:
+ * typedef struct X509_name_st X509_NAME;
+ * etc.
+ *
+ * this will cause all kinds of C-preprocessing paste errors in
+ * BoringSSL's <openssl/x509.h>: So just undefine those defines here
+ * (and only here).
+ */
+#if defined(HAVE_BORINGSSL) || defined(OPENSSL_IS_BORINGSSL)
+# undef X509_NAME
+# undef X509_CERT_PAIR
+# undef X509_EXTENSIONS
+#endif
+
+extern const struct Curl_ssl Curl_ssl_schannel;
+
+CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+
+/* structs to expose only in schannel.c and schannel_verify.c */
+#ifdef EXPOSE_SCHANNEL_INTERNAL_STRUCTS
+
+#ifdef __MINGW32__
+#ifdef __MINGW64_VERSION_MAJOR
+#define HAS_MANUAL_VERIFY_API
+#endif
+#else
+#ifdef CERT_CHAIN_REVOCATION_CHECK_CHAIN
+#define HAS_MANUAL_VERIFY_API
+#endif
+#endif
+
+#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX) \
+ && !defined(DISABLE_SCHANNEL_CLIENT_CERT)
+#define HAS_CLIENT_CERT_PATH
+#endif
+
+#ifndef SCH_CREDENTIALS_VERSION
+
+#define SCH_CREDENTIALS_VERSION 0x00000005
+
+typedef enum _eTlsAlgorithmUsage
+{
+ TlsParametersCngAlgUsageKeyExchange,
+ TlsParametersCngAlgUsageSignature,
+ TlsParametersCngAlgUsageCipher,
+ TlsParametersCngAlgUsageDigest,
+ TlsParametersCngAlgUsageCertSig
+} eTlsAlgorithmUsage;
+
+typedef struct _CRYPTO_SETTINGS
+{
+ eTlsAlgorithmUsage eAlgorithmUsage;
+ UNICODE_STRING strCngAlgId;
+ DWORD cChainingModes;
+ PUNICODE_STRING rgstrChainingModes;
+ DWORD dwMinBitLength;
+ DWORD dwMaxBitLength;
+} CRYPTO_SETTINGS, * PCRYPTO_SETTINGS;
+
+typedef struct _TLS_PARAMETERS
+{
+ DWORD cAlpnIds;
+ PUNICODE_STRING rgstrAlpnIds;
+ DWORD grbitDisabledProtocols;
+ DWORD cDisabledCrypto;
+ PCRYPTO_SETTINGS pDisabledCrypto;
+ DWORD dwFlags;
+} TLS_PARAMETERS, * PTLS_PARAMETERS;
+
+typedef struct _SCH_CREDENTIALS
+{
+ DWORD dwVersion;
+ DWORD dwCredFormat;
+ DWORD cCreds;
+ PCCERT_CONTEXT* paCred;
+ HCERTSTORE hRootStore;
+
+ DWORD cMappers;
+ struct _HMAPPER **aphMappers;
+
+ DWORD dwSessionLifespan;
+ DWORD dwFlags;
+ DWORD cTlsParameters;
+ PTLS_PARAMETERS pTlsParameters;
+} SCH_CREDENTIALS, * PSCH_CREDENTIALS;
+
+#define SCH_CRED_MAX_SUPPORTED_PARAMETERS 16
+#define SCH_CRED_MAX_SUPPORTED_ALPN_IDS 16
+#define SCH_CRED_MAX_SUPPORTED_CRYPTO_SETTINGS 16
+#define SCH_CRED_MAX_SUPPORTED_CHAINING_MODES 16
+
+#endif
+
+struct Curl_schannel_cred {
+ CredHandle cred_handle;
+ TimeStamp time_stamp;
+ TCHAR *sni_hostname;
+#ifdef HAS_CLIENT_CERT_PATH
+ HCERTSTORE client_cert_store;
+#endif
+ int refcount;
+};
+
+struct Curl_schannel_ctxt {
+ CtxtHandle ctxt_handle;
+ TimeStamp time_stamp;
+};
+
+struct ssl_backend_data {
+ struct Curl_schannel_cred *cred;
+ struct Curl_schannel_ctxt *ctxt;
+ SecPkgContext_StreamSizes stream_sizes;
+ size_t encdata_length, decdata_length;
+ size_t encdata_offset, decdata_offset;
+ unsigned char *encdata_buffer, *decdata_buffer;
+ /* encdata_is_incomplete: if encdata contains only a partial record that
+ can't be decrypted without another Curl_read_plain (that is, status is
+ SEC_E_INCOMPLETE_MESSAGE) then set this true. after Curl_read_plain writes
+ more bytes into encdata then set this back to false. */
+ bool encdata_is_incomplete;
+ unsigned long req_flags, ret_flags;
+ CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */
+ bool recv_sspi_close_notify; /* true if connection closed by close_notify */
+ bool recv_connection_closed; /* true if connection closed, regardless how */
+ bool recv_renegotiating; /* true if recv is doing renegotiation */
+ bool use_alpn; /* true if ALPN is used for this connection */
+#ifdef HAS_MANUAL_VERIFY_API
+ bool use_manual_cred_validation; /* true if manual cred validation is used */
+#endif
+};
+#endif /* EXPOSE_SCHANNEL_INTERNAL_STRUCTS */
+
+#endif /* USE_SCHANNEL */
+#endif /* HEADER_CURL_SCHANNEL_H */
diff --git a/libs/libcurl/src/vtls/schannel_verify.c b/libs/libcurl/src/vtls/schannel_verify.c index 1ac1d3eaf5..3ec3503871 100644 --- a/libs/libcurl/src/vtls/schannel_verify.c +++ b/libs/libcurl/src/vtls/schannel_verify.c @@ -1,744 +1,746 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de> - * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com> - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for Schannel-specific certificate verification. This code should - * only be invoked by code in schannel.c. - */ - -#include "curl_setup.h" - -#ifdef USE_SCHANNEL -#ifndef USE_WINDOWS_SSPI -# error "Can't compile SCHANNEL support without SSPI." -#endif - -#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS -#include "schannel.h" - -#ifdef HAS_MANUAL_VERIFY_API - -#include "vtls.h" -#include "sendf.h" -#include "strerror.h" -#include "curl_multibyte.h" -#include "curl_printf.h" -#include "hostcheck.h" -#include "version_win32.h" - -/* The last #include file should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -#define BACKEND connssl->backend - -#define MAX_CAFILE_SIZE 1048576 /* 1 MiB */ -#define BEGIN_CERT "-----BEGIN CERTIFICATE-----" -#define END_CERT "\n-----END CERTIFICATE-----" - -struct cert_chain_engine_config_win7 { - DWORD cbSize; - HCERTSTORE hRestrictedRoot; - HCERTSTORE hRestrictedTrust; - HCERTSTORE hRestrictedOther; - DWORD cAdditionalStore; - HCERTSTORE *rghAdditionalStore; - DWORD dwFlags; - DWORD dwUrlRetrievalTimeout; - DWORD MaximumCachedCertificates; - DWORD CycleDetectionModulus; - HCERTSTORE hExclusiveRoot; - HCERTSTORE hExclusiveTrustedPeople; -}; - -static int is_cr_or_lf(char c) -{ - return c == '\r' || c == '\n'; -} - -/* 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 occurrence 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'", - ca_file_text); - } - else { - infof(data, - "schannel: added %d certificate(s) from CA file '%s'", - 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; - TCHAR *ca_file_tstr = NULL; - size_t ca_file_bufsize = 0; - DWORD total_bytes_read = 0; - - ca_file_tstr = curlx_convert_UTF8_to_tchar((char *)ca_file); - if(!ca_file_tstr) { - char buffer[STRERROR_LEN]; - failf(data, - "schannel: invalid path name for CA file '%s': %s", - ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; - goto cleanup; - } - - /* - * Read the CA file completely into memory before parsing it. This - * optimizes for the common case where the CA file will be relatively - * small ( < 1 MiB ). - */ - ca_file_handle = CreateFile(ca_file_tstr, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - if(ca_file_handle == INVALID_HANDLE_VALUE) { - char buffer[STRERROR_LEN]; - failf(data, - "schannel: failed to open CA file '%s': %s", - ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; - goto cleanup; - } - - if(!GetFileSizeEx(ca_file_handle, &file_size)) { - char buffer[STRERROR_LEN]; - failf(data, - "schannel: failed to determine size of CA file '%s': %s", - ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; - goto cleanup; - } - - if(file_size.QuadPart > MAX_CAFILE_SIZE) { - failf(data, - "schannel: CA file exceeds max size of %u bytes", - MAX_CAFILE_SIZE); - result = CURLE_SSL_CACERT_BADFILE; - goto cleanup; - } - - ca_file_bufsize = (size_t)file_size.QuadPart; - ca_file_buffer = (char *)malloc(ca_file_bufsize + 1); - if(!ca_file_buffer) { - result = CURLE_OUT_OF_MEMORY; - goto cleanup; - } - - while(total_bytes_read < ca_file_bufsize) { - DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read); - DWORD bytes_read = 0; - - if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read, - bytes_to_read, &bytes_read, NULL)) { - char buffer[STRERROR_LEN]; - failf(data, - "schannel: failed to read from CA file '%s': %s", - ca_file, - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; - goto cleanup; - } - if(bytes_read == 0) { - /* Premature EOF -- adjust the bufsize to the new value */ - ca_file_bufsize = total_bytes_read; - } - else { - total_bytes_read += bytes_read; - } - } - - /* Null terminate the buffer */ - ca_file_buffer[ca_file_bufsize] = '\0'; - - 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) { - CloseHandle(ca_file_handle); - } - Curl_safefree(ca_file_buffer); - curlx_unicodefree(ca_file_tstr); - - return result; -} - -/* - * Returns the number of characters necessary to populate all the host_names. - * If host_names is not NULL, populate it with all the host names. Each string - * in the host_names is null-terminated and the last string is double - * null-terminated. If no DNS names are found, a single null-terminated empty - * string is returned. - */ -static DWORD cert_get_name_string(struct Curl_easy *data, - CERT_CONTEXT *cert_context, - LPTSTR host_names, - DWORD length) -{ - DWORD actual_length = 0; - BOOL compute_content = FALSE; - CERT_INFO *cert_info = NULL; - CERT_EXTENSION *extension = NULL; - CRYPT_DECODE_PARA decode_para = {0, 0, 0}; - CERT_ALT_NAME_INFO *alt_name_info = NULL; - DWORD alt_name_info_size = 0; - BOOL ret_val = FALSE; - LPTSTR current_pos = NULL; - DWORD i; - - /* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */ - if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT, - VERSION_GREATER_THAN_EQUAL)) { -#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG - /* CertGetNameString will provide the 8-bit character string without - * any decoding */ - DWORD name_flags = - CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG; - actual_length = CertGetNameString(cert_context, - CERT_NAME_DNS_TYPE, - name_flags, - NULL, - host_names, - length); - return actual_length; -#endif - } - - compute_content = host_names != NULL && length != 0; - - /* Initialize default return values. */ - actual_length = 1; - if(compute_content) { - *host_names = '\0'; - } - - if(!cert_context) { - failf(data, "schannel: Null certificate context."); - return actual_length; - } - - cert_info = cert_context->pCertInfo; - if(!cert_info) { - failf(data, "schannel: Null certificate info."); - return actual_length; - } - - extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2, - cert_info->cExtension, - cert_info->rgExtension); - if(!extension) { - failf(data, "schannel: CertFindExtension() returned no extension."); - return actual_length; - } - - decode_para.cbSize = sizeof(CRYPT_DECODE_PARA); - - ret_val = - CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - szOID_SUBJECT_ALT_NAME2, - extension->Value.pbData, - extension->Value.cbData, - CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG, - &decode_para, - &alt_name_info, - &alt_name_info_size); - if(!ret_val) { - failf(data, - "schannel: CryptDecodeObjectEx() returned no alternate name " - "information."); - return actual_length; - } - - current_pos = host_names; - - /* Iterate over the alternate names and populate host_names. */ - for(i = 0; i < alt_name_info->cAltEntry; i++) { - const CERT_ALT_NAME_ENTRY *entry = &alt_name_info->rgAltEntry[i]; - wchar_t *dns_w = NULL; - size_t current_length = 0; - - if(entry->dwAltNameChoice != CERT_ALT_NAME_DNS_NAME) { - continue; - } - if(!entry->pwszDNSName) { - infof(data, "schannel: Empty DNS name."); - continue; - } - current_length = wcslen(entry->pwszDNSName) + 1; - if(!compute_content) { - actual_length += (DWORD)current_length; - continue; - } - /* Sanity check to prevent buffer overrun. */ - if((actual_length + current_length) > length) { - failf(data, "schannel: Not enough memory to list all host names."); - break; - } - dns_w = entry->pwszDNSName; - /* pwszDNSName is in ia5 string format and hence doesn't contain any - * non-ascii characters. */ - while(*dns_w != '\0') { - *current_pos++ = (char)(*dns_w++); - } - *current_pos++ = '\0'; - actual_length += (DWORD)current_length; - } - if(compute_content) { - /* Last string has double null-terminator. */ - *current_pos = '\0'; - } - return actual_length; -} - -static CURLcode verify_host(struct Curl_easy *data, - CERT_CONTEXT *pCertContextServer, - const char * const conn_hostname) -{ - CURLcode result = CURLE_PEER_FAILED_VERIFICATION; - TCHAR *cert_hostname_buff = NULL; - size_t cert_hostname_buff_index = 0; - size_t hostlen = strlen(conn_hostname); - DWORD len = 0; - DWORD actual_len = 0; - - /* Determine the size of the string needed for the cert hostname */ - len = cert_get_name_string(data, pCertContextServer, NULL, 0); - if(len == 0) { - failf(data, - "schannel: CertGetNameString() returned no " - "certificate name information"); - result = CURLE_PEER_FAILED_VERIFICATION; - goto cleanup; - } - - /* CertGetNameString guarantees that the returned name will not contain - * embedded null bytes. This appears to be undocumented behavior. - */ - cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR)); - if(!cert_hostname_buff) { - result = CURLE_OUT_OF_MEMORY; - goto cleanup; - } - actual_len = cert_get_name_string( - data, pCertContextServer, (LPTSTR)cert_hostname_buff, len); - - /* Sanity check */ - if(actual_len != len) { - failf(data, - "schannel: CertGetNameString() returned certificate " - "name information of unexpected size"); - result = CURLE_PEER_FAILED_VERIFICATION; - goto cleanup; - } - - /* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output - * will contain all DNS names, where each name is null-terminated - * and the last DNS name is double null-terminated. Due to this - * encoding, use the length of the buffer to iterate over all names. - */ - result = CURLE_PEER_FAILED_VERIFICATION; - while(cert_hostname_buff_index < len && - cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') && - result == CURLE_PEER_FAILED_VERIFICATION) { - - char *cert_hostname; - - /* Comparing the cert name and the connection hostname encoded as UTF-8 - * is acceptable since both values are assumed to use ASCII - * (or some equivalent) encoding - */ - cert_hostname = curlx_convert_tchar_to_UTF8( - &cert_hostname_buff[cert_hostname_buff_index]); - if(!cert_hostname) { - result = CURLE_OUT_OF_MEMORY; - } - else { - if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname), - conn_hostname, hostlen)) { - infof(data, - "schannel: connection hostname (%s) validated " - "against certificate name (%s)", - conn_hostname, cert_hostname); - result = CURLE_OK; - } - else { - size_t cert_hostname_len; - - infof(data, - "schannel: connection hostname (%s) did not match " - "against certificate name (%s)", - conn_hostname, cert_hostname); - - cert_hostname_len = - _tcslen(&cert_hostname_buff[cert_hostname_buff_index]); - - /* Move on to next cert name */ - cert_hostname_buff_index += cert_hostname_len + 1; - - result = CURLE_PEER_FAILED_VERIFICATION; - } - curlx_unicodefree(cert_hostname); - } - } - - if(result == CURLE_PEER_FAILED_VERIFICATION) { - failf(data, - "schannel: CertGetNameString() failed to match " - "connection hostname (%s) against server certificate names", - conn_hostname); - } - else if(result != CURLE_OK) - failf(data, "schannel: server certificate name verification failed"); - -cleanup: - Curl_safefree(cert_hostname_buff); - - return result; -} - -CURLcode Curl_verify_certificate(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - SECURITY_STATUS sspi_status; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - CURLcode result = CURLE_OK; - CERT_CONTEXT *pCertContextServer = NULL; - const CERT_CHAIN_CONTEXT *pChainContext = NULL; - HCERTCHAINENGINE cert_chain_engine = NULL; - HCERTSTORE trust_store = NULL; - const char * const conn_hostname = SSL_HOST_NAME(); - - DEBUGASSERT(BACKEND); - - sspi_status = - s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle, - SECPKG_ATTR_REMOTE_CERT_CONTEXT, - &pCertContextServer); - - if((sspi_status != SEC_E_OK) || !pCertContextServer) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: Failed to read remote certificate context: %s", - Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer))); - result = CURLE_PEER_FAILED_VERIFICATION; - } - - 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 - * trusted certificates. This is only supported on Windows 7+. - */ - - if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT, - VERSION_LESS_THAN)) { - failf(data, "schannel: this version of Windows is too old to support " - "certificate verification via CA bundle file."); - result = CURLE_SSL_CACERT_BADFILE; - } - else { - /* Open the certificate store */ - trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY, - 0, - (HCRYPTPROV)NULL, - CERT_STORE_CREATE_NEW_FLAG, - NULL); - if(!trust_store) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: failed to create certificate store: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; - } - else { - 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); - } - } - } - - if(result == CURLE_OK) { - struct cert_chain_engine_config_win7 engine_config; - BOOL create_engine_result; - - memset(&engine_config, 0, sizeof(engine_config)); - engine_config.cbSize = sizeof(engine_config); - engine_config.hExclusiveRoot = trust_store; - - /* CertCreateCertificateChainEngine will check the expected size of the - * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size - * does not match the expected size. When this occurs, it indicates that - * CAINFO is not supported on the version of Windows in use. - */ - create_engine_result = - CertCreateCertificateChainEngine( - (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine); - if(!create_engine_result) { - char buffer[STRERROR_LEN]; - failf(data, - "schannel: failed to create certificate chain engine: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - result = CURLE_SSL_CACERT_BADFILE; - } - } - } - - if(result == CURLE_OK) { - CERT_CHAIN_PARA ChainPara; - - memset(&ChainPara, 0, sizeof(ChainPara)); - ChainPara.cbSize = sizeof(ChainPara); - - if(!CertGetCertificateChain(cert_chain_engine, - pCertContextServer, - NULL, - pCertContextServer->hCertStore, - &ChainPara, - (SSL_SET_OPTION(no_revoke) ? 0 : - CERT_CHAIN_REVOCATION_CHECK_CHAIN), - NULL, - &pChainContext)) { - char buffer[STRERROR_LEN]; - failf(data, "schannel: CertGetCertificateChain failed: %s", - Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer))); - pChainContext = NULL; - result = CURLE_PEER_FAILED_VERIFICATION; - } - - if(result == CURLE_OK) { - CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0]; - DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED); - dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus; - - if(data->set.ssl.revoke_best_effort) { - /* Ignore errors when root certificates are missing the revocation - * list URL, or when the list could not be downloaded because the - * server is currently unreachable. */ - dwTrustErrorMask &= ~(DWORD)(CERT_TRUST_REVOCATION_STATUS_UNKNOWN | - CERT_TRUST_IS_OFFLINE_REVOCATION); - } - - if(dwTrustErrorMask) { - if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_REVOKED"); - else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_PARTIAL_CHAIN"); - else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_UNTRUSTED_ROOT"); - else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_IS_NOT_TIME_VALID"); - else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN) - failf(data, "schannel: CertGetCertificateChain trust error" - " CERT_TRUST_REVOCATION_STATUS_UNKNOWN"); - else - failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x", - dwTrustErrorMask); - result = CURLE_PEER_FAILED_VERIFICATION; - } - } - } - - if(result == CURLE_OK) { - if(SSL_CONN_CONFIG(verifyhost)) { - result = verify_host(data, pCertContextServer, conn_hostname); - } - } - - if(cert_chain_engine) { - CertFreeCertificateChainEngine(cert_chain_engine); - } - - if(trust_store) { - CertCloseStore(trust_store, 0); - } - - if(pChainContext) - CertFreeCertificateChain(pChainContext); - - if(pCertContextServer) - CertFreeCertificateContext(pCertContextServer); - - return result; -} - -#endif /* HAS_MANUAL_VERIFY_API */ -#endif /* USE_SCHANNEL */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
+ * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
+ * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for Schannel-specific certificate verification. This code should
+ * only be invoked by code in schannel.c.
+ */
+
+#include "curl_setup.h"
+
+#ifdef USE_SCHANNEL
+#ifndef USE_WINDOWS_SSPI
+# error "Can't compile SCHANNEL support without SSPI."
+#endif
+
+#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS
+#include "schannel.h"
+
+#ifdef HAS_MANUAL_VERIFY_API
+
+#include "vtls.h"
+#include "vtls_int.h"
+#include "sendf.h"
+#include "strerror.h"
+#include "curl_multibyte.h"
+#include "curl_printf.h"
+#include "hostcheck.h"
+#include "version_win32.h"
+
+/* The last #include file should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define BACKEND connssl->backend
+
+#define MAX_CAFILE_SIZE 1048576 /* 1 MiB */
+#define BEGIN_CERT "-----BEGIN CERTIFICATE-----"
+#define END_CERT "\n-----END CERTIFICATE-----"
+
+struct cert_chain_engine_config_win7 {
+ DWORD cbSize;
+ HCERTSTORE hRestrictedRoot;
+ HCERTSTORE hRestrictedTrust;
+ HCERTSTORE hRestrictedOther;
+ DWORD cAdditionalStore;
+ HCERTSTORE *rghAdditionalStore;
+ DWORD dwFlags;
+ DWORD dwUrlRetrievalTimeout;
+ DWORD MaximumCachedCertificates;
+ DWORD CycleDetectionModulus;
+ HCERTSTORE hExclusiveRoot;
+ HCERTSTORE hExclusiveTrustedPeople;
+};
+
+static int is_cr_or_lf(char c)
+{
+ return c == '\r' || c == '\n';
+}
+
+/* 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 occurrence 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'",
+ ca_file_text);
+ }
+ else {
+ infof(data,
+ "schannel: added %d certificate(s) from CA file '%s'",
+ 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;
+ TCHAR *ca_file_tstr = NULL;
+ size_t ca_file_bufsize = 0;
+ DWORD total_bytes_read = 0;
+
+ ca_file_tstr = curlx_convert_UTF8_to_tchar((char *)ca_file);
+ if(!ca_file_tstr) {
+ char buffer[STRERROR_LEN];
+ failf(data,
+ "schannel: invalid path name for CA file '%s': %s",
+ ca_file,
+ Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+ result = CURLE_SSL_CACERT_BADFILE;
+ goto cleanup;
+ }
+
+ /*
+ * Read the CA file completely into memory before parsing it. This
+ * optimizes for the common case where the CA file will be relatively
+ * small ( < 1 MiB ).
+ */
+ ca_file_handle = CreateFile(ca_file_tstr,
+ GENERIC_READ,
+ FILE_SHARE_READ,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if(ca_file_handle == INVALID_HANDLE_VALUE) {
+ char buffer[STRERROR_LEN];
+ failf(data,
+ "schannel: failed to open CA file '%s': %s",
+ ca_file,
+ Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+ result = CURLE_SSL_CACERT_BADFILE;
+ goto cleanup;
+ }
+
+ if(!GetFileSizeEx(ca_file_handle, &file_size)) {
+ char buffer[STRERROR_LEN];
+ failf(data,
+ "schannel: failed to determine size of CA file '%s': %s",
+ ca_file,
+ Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+ result = CURLE_SSL_CACERT_BADFILE;
+ goto cleanup;
+ }
+
+ if(file_size.QuadPart > MAX_CAFILE_SIZE) {
+ failf(data,
+ "schannel: CA file exceeds max size of %u bytes",
+ MAX_CAFILE_SIZE);
+ result = CURLE_SSL_CACERT_BADFILE;
+ goto cleanup;
+ }
+
+ ca_file_bufsize = (size_t)file_size.QuadPart;
+ ca_file_buffer = (char *)malloc(ca_file_bufsize + 1);
+ if(!ca_file_buffer) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ while(total_bytes_read < ca_file_bufsize) {
+ DWORD bytes_to_read = (DWORD)(ca_file_bufsize - total_bytes_read);
+ DWORD bytes_read = 0;
+
+ if(!ReadFile(ca_file_handle, ca_file_buffer + total_bytes_read,
+ bytes_to_read, &bytes_read, NULL)) {
+ char buffer[STRERROR_LEN];
+ failf(data,
+ "schannel: failed to read from CA file '%s': %s",
+ ca_file,
+ Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+ result = CURLE_SSL_CACERT_BADFILE;
+ goto cleanup;
+ }
+ if(bytes_read == 0) {
+ /* Premature EOF -- adjust the bufsize to the new value */
+ ca_file_bufsize = total_bytes_read;
+ }
+ else {
+ total_bytes_read += bytes_read;
+ }
+ }
+
+ /* Null terminate the buffer */
+ ca_file_buffer[ca_file_bufsize] = '\0';
+
+ 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) {
+ CloseHandle(ca_file_handle);
+ }
+ Curl_safefree(ca_file_buffer);
+ curlx_unicodefree(ca_file_tstr);
+
+ return result;
+}
+
+/*
+ * Returns the number of characters necessary to populate all the host_names.
+ * If host_names is not NULL, populate it with all the host names. Each string
+ * in the host_names is null-terminated and the last string is double
+ * null-terminated. If no DNS names are found, a single null-terminated empty
+ * string is returned.
+ */
+static DWORD cert_get_name_string(struct Curl_easy *data,
+ CERT_CONTEXT *cert_context,
+ LPTSTR host_names,
+ DWORD length)
+{
+ DWORD actual_length = 0;
+ BOOL compute_content = FALSE;
+ CERT_INFO *cert_info = NULL;
+ CERT_EXTENSION *extension = NULL;
+ CRYPT_DECODE_PARA decode_para = {0, 0, 0};
+ CERT_ALT_NAME_INFO *alt_name_info = NULL;
+ DWORD alt_name_info_size = 0;
+ BOOL ret_val = FALSE;
+ LPTSTR current_pos = NULL;
+ DWORD i;
+
+ /* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */
+ if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
+ VERSION_GREATER_THAN_EQUAL)) {
+#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
+ /* CertGetNameString will provide the 8-bit character string without
+ * any decoding */
+ DWORD name_flags =
+ CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG;
+ actual_length = CertGetNameString(cert_context,
+ CERT_NAME_DNS_TYPE,
+ name_flags,
+ NULL,
+ host_names,
+ length);
+ return actual_length;
+#endif
+ }
+
+ compute_content = host_names != NULL && length != 0;
+
+ /* Initialize default return values. */
+ actual_length = 1;
+ if(compute_content) {
+ *host_names = '\0';
+ }
+
+ if(!cert_context) {
+ failf(data, "schannel: Null certificate context.");
+ return actual_length;
+ }
+
+ cert_info = cert_context->pCertInfo;
+ if(!cert_info) {
+ failf(data, "schannel: Null certificate info.");
+ return actual_length;
+ }
+
+ extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
+ cert_info->cExtension,
+ cert_info->rgExtension);
+ if(!extension) {
+ failf(data, "schannel: CertFindExtension() returned no extension.");
+ return actual_length;
+ }
+
+ decode_para.cbSize = sizeof(CRYPT_DECODE_PARA);
+
+ ret_val =
+ CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ szOID_SUBJECT_ALT_NAME2,
+ extension->Value.pbData,
+ extension->Value.cbData,
+ CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
+ &decode_para,
+ &alt_name_info,
+ &alt_name_info_size);
+ if(!ret_val) {
+ failf(data,
+ "schannel: CryptDecodeObjectEx() returned no alternate name "
+ "information.");
+ return actual_length;
+ }
+
+ current_pos = host_names;
+
+ /* Iterate over the alternate names and populate host_names. */
+ for(i = 0; i < alt_name_info->cAltEntry; i++) {
+ const CERT_ALT_NAME_ENTRY *entry = &alt_name_info->rgAltEntry[i];
+ wchar_t *dns_w = NULL;
+ size_t current_length = 0;
+
+ if(entry->dwAltNameChoice != CERT_ALT_NAME_DNS_NAME) {
+ continue;
+ }
+ if(!entry->pwszDNSName) {
+ infof(data, "schannel: Empty DNS name.");
+ continue;
+ }
+ current_length = wcslen(entry->pwszDNSName) + 1;
+ if(!compute_content) {
+ actual_length += (DWORD)current_length;
+ continue;
+ }
+ /* Sanity check to prevent buffer overrun. */
+ if((actual_length + current_length) > length) {
+ failf(data, "schannel: Not enough memory to list all host names.");
+ break;
+ }
+ dns_w = entry->pwszDNSName;
+ /* pwszDNSName is in ia5 string format and hence doesn't contain any
+ * non-ascii characters. */
+ while(*dns_w != '\0') {
+ *current_pos++ = (char)(*dns_w++);
+ }
+ *current_pos++ = '\0';
+ actual_length += (DWORD)current_length;
+ }
+ if(compute_content) {
+ /* Last string has double null-terminator. */
+ *current_pos = '\0';
+ }
+ return actual_length;
+}
+
+static CURLcode verify_host(struct Curl_easy *data,
+ CERT_CONTEXT *pCertContextServer,
+ const char *conn_hostname)
+{
+ CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
+ TCHAR *cert_hostname_buff = NULL;
+ size_t cert_hostname_buff_index = 0;
+ size_t hostlen = strlen(conn_hostname);
+ DWORD len = 0;
+ DWORD actual_len = 0;
+
+ /* Determine the size of the string needed for the cert hostname */
+ len = cert_get_name_string(data, pCertContextServer, NULL, 0);
+ if(len == 0) {
+ failf(data,
+ "schannel: CertGetNameString() returned no "
+ "certificate name information");
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ goto cleanup;
+ }
+
+ /* CertGetNameString guarantees that the returned name will not contain
+ * embedded null bytes. This appears to be undocumented behavior.
+ */
+ cert_hostname_buff = (LPTSTR)malloc(len * sizeof(TCHAR));
+ if(!cert_hostname_buff) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+ actual_len = cert_get_name_string(
+ data, pCertContextServer, (LPTSTR)cert_hostname_buff, len);
+
+ /* Sanity check */
+ if(actual_len != len) {
+ failf(data,
+ "schannel: CertGetNameString() returned certificate "
+ "name information of unexpected size");
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ goto cleanup;
+ }
+
+ /* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output
+ * will contain all DNS names, where each name is null-terminated
+ * and the last DNS name is double null-terminated. Due to this
+ * encoding, use the length of the buffer to iterate over all names.
+ */
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ while(cert_hostname_buff_index < len &&
+ cert_hostname_buff[cert_hostname_buff_index] != TEXT('\0') &&
+ result == CURLE_PEER_FAILED_VERIFICATION) {
+
+ char *cert_hostname;
+
+ /* Comparing the cert name and the connection hostname encoded as UTF-8
+ * is acceptable since both values are assumed to use ASCII
+ * (or some equivalent) encoding
+ */
+ cert_hostname = curlx_convert_tchar_to_UTF8(
+ &cert_hostname_buff[cert_hostname_buff_index]);
+ if(!cert_hostname) {
+ result = CURLE_OUT_OF_MEMORY;
+ }
+ else {
+ if(Curl_cert_hostcheck(cert_hostname, strlen(cert_hostname),
+ conn_hostname, hostlen)) {
+ infof(data,
+ "schannel: connection hostname (%s) validated "
+ "against certificate name (%s)",
+ conn_hostname, cert_hostname);
+ result = CURLE_OK;
+ }
+ else {
+ size_t cert_hostname_len;
+
+ infof(data,
+ "schannel: connection hostname (%s) did not match "
+ "against certificate name (%s)",
+ conn_hostname, cert_hostname);
+
+ cert_hostname_len =
+ _tcslen(&cert_hostname_buff[cert_hostname_buff_index]);
+
+ /* Move on to next cert name */
+ cert_hostname_buff_index += cert_hostname_len + 1;
+
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ curlx_unicodefree(cert_hostname);
+ }
+ }
+
+ if(result == CURLE_PEER_FAILED_VERIFICATION) {
+ failf(data,
+ "schannel: CertGetNameString() failed to match "
+ "connection hostname (%s) against server certificate names",
+ conn_hostname);
+ }
+ else if(result != CURLE_OK)
+ failf(data, "schannel: server certificate name verification failed");
+
+cleanup:
+ Curl_safefree(cert_hostname_buff);
+
+ return result;
+}
+
+CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ SECURITY_STATUS sspi_status;
+ CURLcode result = CURLE_OK;
+ CERT_CONTEXT *pCertContextServer = NULL;
+ const CERT_CHAIN_CONTEXT *pChainContext = NULL;
+ HCERTCHAINENGINE cert_chain_engine = NULL;
+ HCERTSTORE trust_store = NULL;
+
+ DEBUGASSERT(BACKEND);
+
+ sspi_status =
+ s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
+ SECPKG_ATTR_REMOTE_CERT_CONTEXT,
+ &pCertContextServer);
+
+ if((sspi_status != SEC_E_OK) || !pCertContextServer) {
+ char buffer[STRERROR_LEN];
+ failf(data, "schannel: Failed to read remote certificate context: %s",
+ Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ if(result == CURLE_OK &&
+ (conn_config->CAfile || conn_config->ca_info_blob) &&
+ BACKEND->use_manual_cred_validation) {
+ /*
+ * Create a chain engine that uses the certificates in the CA file as
+ * trusted certificates. This is only supported on Windows 7+.
+ */
+
+ if(curlx_verify_windows_version(6, 1, 0, PLATFORM_WINNT,
+ VERSION_LESS_THAN)) {
+ failf(data, "schannel: this version of Windows is too old to support "
+ "certificate verification via CA bundle file.");
+ result = CURLE_SSL_CACERT_BADFILE;
+ }
+ else {
+ /* Open the certificate store */
+ trust_store = CertOpenStore(CERT_STORE_PROV_MEMORY,
+ 0,
+ (HCRYPTPROV)NULL,
+ CERT_STORE_CREATE_NEW_FLAG,
+ NULL);
+ if(!trust_store) {
+ char buffer[STRERROR_LEN];
+ failf(data, "schannel: failed to create certificate store: %s",
+ Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+ result = CURLE_SSL_CACERT_BADFILE;
+ }
+ else {
+ const struct curl_blob *ca_info_blob = 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,
+ conn_config->CAfile,
+ data);
+ }
+ }
+ }
+
+ if(result == CURLE_OK) {
+ struct cert_chain_engine_config_win7 engine_config;
+ BOOL create_engine_result;
+
+ memset(&engine_config, 0, sizeof(engine_config));
+ engine_config.cbSize = sizeof(engine_config);
+ engine_config.hExclusiveRoot = trust_store;
+
+ /* CertCreateCertificateChainEngine will check the expected size of the
+ * CERT_CHAIN_ENGINE_CONFIG structure and fail if the specified size
+ * does not match the expected size. When this occurs, it indicates that
+ * CAINFO is not supported on the version of Windows in use.
+ */
+ create_engine_result =
+ CertCreateCertificateChainEngine(
+ (CERT_CHAIN_ENGINE_CONFIG *)&engine_config, &cert_chain_engine);
+ if(!create_engine_result) {
+ char buffer[STRERROR_LEN];
+ failf(data,
+ "schannel: failed to create certificate chain engine: %s",
+ Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+ result = CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+ }
+
+ if(result == CURLE_OK) {
+ CERT_CHAIN_PARA ChainPara;
+
+ memset(&ChainPara, 0, sizeof(ChainPara));
+ ChainPara.cbSize = sizeof(ChainPara);
+
+ if(!CertGetCertificateChain(cert_chain_engine,
+ pCertContextServer,
+ NULL,
+ pCertContextServer->hCertStore,
+ &ChainPara,
+ (ssl_config->no_revoke ? 0 :
+ CERT_CHAIN_REVOCATION_CHECK_CHAIN),
+ NULL,
+ &pChainContext)) {
+ char buffer[STRERROR_LEN];
+ failf(data, "schannel: CertGetCertificateChain failed: %s",
+ Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+ pChainContext = NULL;
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ if(result == CURLE_OK) {
+ CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
+ DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED);
+ dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
+
+ if(data->set.ssl.revoke_best_effort) {
+ /* Ignore errors when root certificates are missing the revocation
+ * list URL, or when the list could not be downloaded because the
+ * server is currently unreachable. */
+ dwTrustErrorMask &= ~(DWORD)(CERT_TRUST_REVOCATION_STATUS_UNKNOWN |
+ CERT_TRUST_IS_OFFLINE_REVOCATION);
+ }
+
+ if(dwTrustErrorMask) {
+ if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED)
+ failf(data, "schannel: CertGetCertificateChain trust error"
+ " CERT_TRUST_IS_REVOKED");
+ else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
+ failf(data, "schannel: CertGetCertificateChain trust error"
+ " CERT_TRUST_IS_PARTIAL_CHAIN");
+ else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
+ failf(data, "schannel: CertGetCertificateChain trust error"
+ " CERT_TRUST_IS_UNTRUSTED_ROOT");
+ else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
+ failf(data, "schannel: CertGetCertificateChain trust error"
+ " CERT_TRUST_IS_NOT_TIME_VALID");
+ else if(dwTrustErrorMask & CERT_TRUST_REVOCATION_STATUS_UNKNOWN)
+ failf(data, "schannel: CertGetCertificateChain trust error"
+ " CERT_TRUST_REVOCATION_STATUS_UNKNOWN");
+ else
+ failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
+ dwTrustErrorMask);
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ }
+ }
+
+ if(result == CURLE_OK) {
+ if(conn_config->verifyhost) {
+ result = verify_host(data, pCertContextServer, connssl->hostname);
+ }
+ }
+
+ if(cert_chain_engine) {
+ CertFreeCertificateChainEngine(cert_chain_engine);
+ }
+
+ if(trust_store) {
+ CertCloseStore(trust_store, 0);
+ }
+
+ if(pChainContext)
+ CertFreeCertificateChain(pChainContext);
+
+ if(pCertContextServer)
+ CertFreeCertificateContext(pCertContextServer);
+
+ return result;
+}
+
+#endif /* HAS_MANUAL_VERIFY_API */
+#endif /* USE_SCHANNEL */
diff --git a/libs/libcurl/src/vtls/sectransp.c b/libs/libcurl/src/vtls/sectransp.c index c764e3631b..c5707a4df1 100644 --- a/libs/libcurl/src/vtls/sectransp.c +++ b/libs/libcurl/src/vtls/sectransp.c @@ -1,3542 +1,3474 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * Copyright (C) 2012 - 2017, Nick Zitzmann, <nickzman@gmail.com>. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for all iOS and macOS SecureTransport-specific code for the - * TLS/SSL layer. No code but vtls.c should ever call or use these functions. - */ - -#include "curl_setup.h" - -#include "urldata.h" /* for the Curl_easy definition */ -#include "curl_base64.h" -#include "strtok.h" -#include "multiif.h" -#include "strcase.h" -#include "x509asn1.h" -#include "strerror.h" - -#ifdef USE_SECTRANSP - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-pointer-compare" -#endif /* __clang__ */ - -#include <limits.h> - -#include <Security/Security.h> -/* For some reason, when building for iOS, the omnibus header above does - * not include SecureTransport.h as of iOS SDK 5.1. */ -#include <Security/SecureTransport.h> -#include <CoreFoundation/CoreFoundation.h> -#include <CommonCrypto/CommonDigest.h> - -/* The Security framework has changed greatly between iOS and different macOS - versions, and we will try to support as many of them as we can (back to - Leopard and iOS 5) by using macros and weak-linking. - - In general, you want to build this using the most recent OS SDK, since some - features require curl to be built against the latest SDK. TLS 1.1 and 1.2 - support, for instance, require the macOS 10.8 SDK or later. TLS 1.3 - requires the macOS 10.13 or iOS 11 SDK or later. */ -#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) - -#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050 -#error "The Secure Transport back-end requires Leopard or later." -#endif /* MAC_OS_X_VERSION_MAX_ALLOWED < 1050 */ - -#define CURL_BUILD_IOS 0 -#define CURL_BUILD_IOS_7 0 -#define CURL_BUILD_IOS_9 0 -#define CURL_BUILD_IOS_11 0 -#define CURL_BUILD_IOS_13 0 -#define CURL_BUILD_MAC 1 -/* This is the maximum API level we are allowed to use when building: */ -#define CURL_BUILD_MAC_10_5 MAC_OS_X_VERSION_MAX_ALLOWED >= 1050 -#define CURL_BUILD_MAC_10_6 MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 -#define CURL_BUILD_MAC_10_7 MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 -#define CURL_BUILD_MAC_10_8 MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 -#define CURL_BUILD_MAC_10_9 MAC_OS_X_VERSION_MAX_ALLOWED >= 1090 -#define CURL_BUILD_MAC_10_11 MAC_OS_X_VERSION_MAX_ALLOWED >= 101100 -#define CURL_BUILD_MAC_10_13 MAC_OS_X_VERSION_MAX_ALLOWED >= 101300 -#define CURL_BUILD_MAC_10_15 MAC_OS_X_VERSION_MAX_ALLOWED >= 101500 -/* These macros mean "the following code is present to allow runtime backward - compatibility with at least this cat or earlier": - (You set this at build-time using the compiler command line option - "-mmacosx-version-min.") */ -#define CURL_SUPPORT_MAC_10_5 MAC_OS_X_VERSION_MIN_REQUIRED <= 1050 -#define CURL_SUPPORT_MAC_10_6 MAC_OS_X_VERSION_MIN_REQUIRED <= 1060 -#define CURL_SUPPORT_MAC_10_7 MAC_OS_X_VERSION_MIN_REQUIRED <= 1070 -#define CURL_SUPPORT_MAC_10_8 MAC_OS_X_VERSION_MIN_REQUIRED <= 1080 -#define CURL_SUPPORT_MAC_10_9 MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 - -#elif TARGET_OS_EMBEDDED || TARGET_OS_IPHONE -#define CURL_BUILD_IOS 1 -#define CURL_BUILD_IOS_7 __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 -#define CURL_BUILD_IOS_9 __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000 -#define CURL_BUILD_IOS_11 __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 -#define CURL_BUILD_IOS_13 __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 -#define CURL_BUILD_MAC 0 -#define CURL_BUILD_MAC_10_5 0 -#define CURL_BUILD_MAC_10_6 0 -#define CURL_BUILD_MAC_10_7 0 -#define CURL_BUILD_MAC_10_8 0 -#define CURL_BUILD_MAC_10_9 0 -#define CURL_BUILD_MAC_10_11 0 -#define CURL_BUILD_MAC_10_13 0 -#define CURL_BUILD_MAC_10_15 0 -#define CURL_SUPPORT_MAC_10_5 0 -#define CURL_SUPPORT_MAC_10_6 0 -#define CURL_SUPPORT_MAC_10_7 0 -#define CURL_SUPPORT_MAC_10_8 0 -#define CURL_SUPPORT_MAC_10_9 0 - -#else -#error "The Secure Transport back-end requires iOS or macOS." -#endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */ - -#if CURL_BUILD_MAC -#include <sys/sysctl.h> -#endif /* CURL_BUILD_MAC */ - -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "connect.h" -#include "select.h" -#include "vtls.h" -#include "sectransp.h" -#include "curl_printf.h" -#include "strdup.h" - -#include "curl_memory.h" -/* The last #include file should be: */ -#include "memdebug.h" - -/* From MacTypes.h (which we can't include because it isn't present in iOS: */ -#define ioErr -36 -#define paramErr -50 - -struct ssl_backend_data { - SSLContextRef ssl_ctx; - curl_socket_t ssl_sockfd; - bool ssl_direction; /* true if writing, false if reading */ - size_t ssl_write_buffered_length; -}; - -struct st_cipher { - const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */ - const char *alias_name; /* Alias name is the same as OpenSSL cipher name */ - SSLCipherSuite num; /* Cipher suite code/number defined in IANA registry */ - bool weak; /* Flag to mark cipher as weak based on previous implementation - of Secure Transport back-end by CURL */ -}; - -/* Macro to initialize st_cipher data structure: stringify id to name, cipher - number/id, 'weak' suite flag - */ -#define CIPHER_DEF(num, alias, weak) \ - { #num, alias, num, weak } - -/* - Macro to initialize st_cipher data structure with name, code (IANA cipher - number/id value), and 'weak' suite flag. The first 28 cipher suite numbers - have the same IANA code for both SSL and TLS standards: numbers 0x0000 to - 0x001B. They have different names though. The first 4 letters of the cipher - suite name are the protocol name: "SSL_" or "TLS_", rest of the IANA name is - the same for both SSL and TLS cipher suite name. - The second part of the problem is that macOS/iOS SDKs don't define all TLS - codes but only 12 of them. The SDK defines all SSL codes though, i.e. SSL_NUM - constant is always defined for those 28 ciphers while TLS_NUM is defined only - for 12 of the first 28 ciphers. Those 12 TLS cipher codes match to - corresponding SSL enum value and represent the same cipher suite. Therefore - we'll use the SSL enum value for those cipher suites because it is defined - for all 28 of them. - We make internal data consistent and based on TLS names, i.e. all st_cipher - item names start with the "TLS_" prefix. - Summarizing all the above, those 28 first ciphers are presented in our table - with both TLS and SSL names. Their cipher numbers are assigned based on the - SDK enum value for the SSL cipher, which matches to IANA TLS number. - */ -#define CIPHER_DEF_SSLTLS(num_wo_prefix, alias, weak) \ - { "TLS_" #num_wo_prefix, alias, SSL_##num_wo_prefix, weak } - -/* - Cipher suites were marked as weak based on the following: - RC4 encryption - rfc7465, the document contains a list of deprecated ciphers. - Marked in the code below as weak. - RC2 encryption - many mentions, was found vulnerable to a relatively easy - attack https://link.springer.com/chapter/10.1007%2F3-540-69710-1_14 - Marked in the code below as weak. - DES and IDEA encryption - rfc5469, has a list of deprecated ciphers. - Marked in the code below as weak. - Anonymous Diffie-Hellman authentication and anonymous elliptic curve - Diffie-Hellman - vulnerable to a man-in-the-middle attack. Deprecated by - RFC 4346 aka TLS 1.1 (section A.5, page 60) - Null bulk encryption suites - not encrypted communication - Export ciphers, i.e. ciphers with restrictions to be used outside the US for - software exported to some countries, they were excluded from TLS 1.1 - version. More precisely, they were noted as ciphers which MUST NOT be - negotiated in RFC 4346 aka TLS 1.1 (section A.5, pages 60 and 61). - All of those filters were considered weak because they contain a weak - algorithm like DES, RC2 or RC4, and already considered weak by other - criteria. - 3DES - NIST deprecated it and is going to retire it by 2023 - https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA - OpenSSL https://www.openssl.org/blog/blog/2016/08/24/sweet32/ also - deprecated those ciphers. Some other libraries also consider it - vulnerable or at least not strong enough. - - CBC ciphers are vulnerable with SSL3.0 and TLS1.0: - https://www.cisco.com/c/en/us/support/docs/security/email-security-appliance - /118518-technote-esa-00.html - We don't take care of this issue because it is resolved by later TLS - versions and for us, it requires more complicated checks, we need to - check a protocol version also. Vulnerability doesn't look very critical - and we do not filter out those cipher suites. - */ - -#define CIPHER_WEAK_NOT_ENCRYPTED TRUE -#define CIPHER_WEAK_RC_ENCRYPTION TRUE -#define CIPHER_WEAK_DES_ENCRYPTION TRUE -#define CIPHER_WEAK_IDEA_ENCRYPTION TRUE -#define CIPHER_WEAK_ANON_AUTH TRUE -#define CIPHER_WEAK_3DES_ENCRYPTION TRUE -#define CIPHER_STRONG_ENOUGH FALSE - -/* Please do not change the order of the first ciphers available for SSL. - Do not insert and do not delete any of them. Code below - depends on their order and continuity. - If you add a new cipher, please maintain order by number, i.e. - insert in between existing items to appropriate place based on - cipher suite IANA number -*/ -const static struct st_cipher ciphertable[] = { - /* SSL version 3.0 and initial TLS 1.0 cipher suites. - Defined since SDK 10.2.8 */ - CIPHER_DEF_SSLTLS(NULL_WITH_NULL_NULL, /* 0x0000 */ - NULL, - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_WITH_NULL_MD5, /* 0x0001 */ - "NULL-MD5", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_WITH_NULL_SHA, /* 0x0002 */ - "NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC4_40_MD5, /* 0x0003 */ - "EXP-RC4-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_MD5, /* 0x0004 */ - "RC4-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_SHA, /* 0x0005 */ - "RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* 0x0006 */ - "EXP-RC2-CBC-MD5", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_IDEA_CBC_SHA, /* 0x0007 */ - "IDEA-CBC-SHA", - CIPHER_WEAK_IDEA_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0008 */ - "EXP-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_DES_CBC_SHA, /* 0x0009 */ - "DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */ - "DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x000B */ - "EXP-DH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_WITH_DES_CBC_SHA, /* 0x000C */ - "DH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x000D */ - "DH-DSS-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x000E */ - "EXP-DH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_WITH_DES_CBC_SHA, /* 0x000F */ - "DH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0010 */ - "DH-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x0011 */ - "EXP-EDH-DSS-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_WITH_DES_CBC_SHA, /* 0x0012 */ - "EDH-DSS-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x0013 */ - "DHE-DSS-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0014 */ - "EXP-EDH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_WITH_DES_CBC_SHA, /* 0x0015 */ - "EDH-RSA-DES-CBC-SHA", - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0016 */ - "DHE-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_RC4_40_MD5, /* 0x0017 */ - "EXP-ADH-RC4-MD5", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_RC4_128_MD5, /* 0x0018 */ - "ADH-RC4-MD5", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_DES40_CBC_SHA, /* 0x0019 */ - "EXP-ADH-DES-CBC-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_DES_CBC_SHA, /* 0x001A */ - "ADH-DES-CBC-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF_SSLTLS(DH_anon_WITH_3DES_EDE_CBC_SHA, /* 0x001B */ - "ADH-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_NULL_SHA, /* 0x001C */ - NULL, - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, /* 0x001D */ - NULL, - CIPHER_STRONG_ENOUGH), - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */ - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA, /* 0x002C */ - "PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA, /* 0x002D */ - "DHE-PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA, /* 0x002E */ - "RSA-PSK-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - - /* TLS addenda using AES, per RFC 3268. Defined since SDK 10.4u */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */ - "AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA, /* 0x0030 */ - "DH-DSS-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA, /* 0x0031 */ - "DH-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* 0x0032 */ - "DHE-DSS-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* 0x0033 */ - "DHE-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA, /* 0x0034 */ - "ADH-AES128-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */ - "AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA, /* 0x0036 */ - "DH-DSS-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA, /* 0x0037 */ - "DH-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* 0x0038 */ - "DHE-DSS-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* 0x0039 */ - "DHE-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA, /* 0x003A */ - "ADH-AES256-SHA", - CIPHER_WEAK_ANON_AUTH), - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* TLS 1.2 addenda, RFC 5246 */ - /* Server provided RSA certificate for key exchange. */ - CIPHER_DEF(TLS_RSA_WITH_NULL_SHA256, /* 0x003B */ - "NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */ - "AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */ - "AES256-SHA256", - CIPHER_STRONG_ENOUGH), - /* Server-authenticated (and optionally client-authenticated) - Diffie-Hellman. */ - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA256, /* 0x003E */ - "DH-DSS-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA256, /* 0x003F */ - "DH-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, /* 0x0040 */ - "DHE-DSS-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - - /* TLS 1.2 addenda, RFC 5246 */ - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */ - "DHE-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA256, /* 0x0068 */ - "DH-DSS-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA256, /* 0x0069 */ - "DH-RSA-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, /* 0x006A */ - "DHE-DSS-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */ - "DHE-RSA-AES256-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA256, /* 0x006C */ - "ADH-AES128-SHA256", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA256, /* 0x006D */ - "ADH-AES256-SHA256", - CIPHER_WEAK_ANON_AUTH), -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* Addendum from RFC 4279, TLS PSK */ - CIPHER_DEF(TLS_PSK_WITH_RC4_128_SHA, /* 0x008A */ - "PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008B */ - "PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA, /* 0x008C */ - "PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA, /* 0x008D */ - "PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_RC4_128_SHA, /* 0x008E */ - "DHE-PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008F */ - "DHE-PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA, /* 0x0090 */ - "DHE-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA, /* 0x0091 */ - "DHE-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_RC4_128_SHA, /* 0x0092 */ - "RSA-PSK-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x0093 */ - "RSA-PSK-3DES-EDE-CBC-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA, /* 0x0094 */ - "RSA-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA, /* 0x0095 */ - "RSA-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites - for TLS. */ - CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */ - "AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */ - "AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */ - "DHE-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */ - "DHE-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_GCM_SHA256, /* 0x00A0 */ - "DH-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_GCM_SHA384, /* 0x00A1 */ - "DH-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A2 */ - "DHE-DSS-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A3 */ - "DHE-DSS-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A4 */ - "DH-DSS-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A5 */ - "DH-DSS-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_128_GCM_SHA256, /* 0x00A6 */ - "ADH-AES128-GCM-SHA256", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_DH_anon_WITH_AES_256_GCM_SHA384, /* 0x00A7 */ - "ADH-AES256-GCM-SHA384", - CIPHER_WEAK_ANON_AUTH), -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* RFC 5487 - PSK with SHA-256/384 and AES GCM */ - CIPHER_DEF(TLS_PSK_WITH_AES_128_GCM_SHA256, /* 0x00A8 */ - "PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_GCM_SHA384, /* 0x00A9 */ - "PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AA */ - "DHE-PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AB */ - "DHE-PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AC */ - "RSA-PSK-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AD */ - "RSA-PSK-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA256, /* 0x00AE */ - "PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA384, /* 0x00AF */ - "PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA256, /* 0x00B0 */ - "PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_PSK_WITH_NULL_SHA384, /* 0x00B1 */ - "PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B2 */ - "DHE-PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B3 */ - "DHE-PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA256, /* 0x00B4 */ - "DHE-PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA384, /* 0x00B5 */ - "DHE-PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B6 */ - "RSA-PSK-AES128-CBC-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B7 */ - "RSA-PSK-AES256-CBC-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA256, /* 0x00B8 */ - "RSA-PSK-NULL-SHA256", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA384, /* 0x00B9 */ - "RSA-PSK-NULL-SHA384", - CIPHER_WEAK_NOT_ENCRYPTED), -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - - /* RFC 5746 - Secure Renegotiation. This is not a real suite, - it is a response to initiate negotiation again */ - CIPHER_DEF(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, /* 0x00FF */ - NULL, - CIPHER_STRONG_ENOUGH), - -#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 - /* TLS 1.3 standard cipher suites for ChaCha20+Poly1305. - Note: TLS 1.3 ciphersuites do not specify the key exchange - algorithm -- they only specify the symmetric ciphers. - Cipher alias name matches to OpenSSL cipher name, and for - TLS 1.3 ciphers */ - CIPHER_DEF(TLS_AES_128_GCM_SHA256, /* 0x1301 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_256_GCM_SHA384, /* 0x1302 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_128_CCM_SHA256, /* 0x1304 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_AES_128_CCM_8_SHA256, /* 0x1305 */ - NULL, /* The OpenSSL cipher name matches to the IANA name */ - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ - -#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS - /* ECDSA addenda, RFC 4492 */ - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_NULL_SHA, /* 0xC001 */ - "ECDH-ECDSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_RC4_128_SHA, /* 0xC002 */ - "ECDH-ECDSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */ - "ECDH-ECDSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */ - "ECDH-ECDSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */ - "ECDH-ECDSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_NULL_SHA, /* 0xC006 */ - "ECDHE-ECDSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, /* 0xC007 */ - "ECDHE-ECDSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */ - "ECDHE-ECDSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */ - "ECDHE-ECDSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */ - "ECDHE-ECDSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_NULL_SHA, /* 0xC00B */ - "ECDH-RSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDH_RSA_WITH_RC4_128_SHA, /* 0xC00C */ - "ECDH-RSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */ - "ECDH-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */ - "ECDH-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */ - "ECDH-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_NULL_SHA, /* 0xC010 */ - "ECDHE-RSA-NULL-SHA", - CIPHER_WEAK_NOT_ENCRYPTED), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_RC4_128_SHA, /* 0xC011 */ - "ECDHE-RSA-RC4-SHA", - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */ - "ECDHE-RSA-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */ - "ECDHE-RSA-AES128-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */ - "ECDHE-RSA-AES256-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_anon_WITH_NULL_SHA, /* 0xC015 */ - "AECDH-NULL-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_RC4_128_SHA, /* 0xC016 */ - "AECDH-RC4-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, /* 0xC017 */ - "AECDH-DES-CBC3-SHA", - CIPHER_WEAK_3DES_ENCRYPTION), - CIPHER_DEF(TLS_ECDH_anon_WITH_AES_128_CBC_SHA, /* 0xC018 */ - "AECDH-AES128-SHA", - CIPHER_WEAK_ANON_AUTH), - CIPHER_DEF(TLS_ECDH_anon_WITH_AES_256_CBC_SHA, /* 0xC019 */ - "AECDH-AES256-SHA", - CIPHER_WEAK_ANON_AUTH), -#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with - HMAC SHA-256/384. */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */ - "ECDHE-ECDSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */ - "ECDHE-ECDSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */ - "ECDH-ECDSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */ - "ECDH-ECDSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */ - "ECDHE-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */ - "ECDHE-RSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */ - "ECDH-RSA-AES128-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */ - "ECDH-RSA-AES256-SHA384", - CIPHER_STRONG_ENOUGH), - /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with - SHA-256/384 and AES Galois Counter Mode (GCM) */ - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */ - "ECDHE-ECDSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */ - "ECDHE-ECDSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */ - "ECDH-ECDSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */ - "ECDH-ECDSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */ - "ECDHE-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */ - "ECDHE-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */ - "ECDH-RSA-AES128-GCM-SHA256", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */ - "ECDH-RSA-AES256-GCM-SHA384", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 - /* ECDHE_PSK Cipher Suites for Transport Layer Security (TLS), RFC 5489 */ - CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, /* 0xC035 */ - "ECDHE-PSK-AES128-CBC-SHA", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, /* 0xC036 */ - "ECDHE-PSK-AES256-CBC-SHA", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ - -#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 - /* Addenda from rfc 7905 ChaCha20-Poly1305 Cipher Suites for - Transport Layer Security (TLS). */ - CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */ - "ECDHE-RSA-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), - CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */ - "ECDHE-ECDSA-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ - -#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 - /* ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS), - RFC 7905 */ - CIPHER_DEF(TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCAB */ - "PSK-CHACHA20-POLY1305", - CIPHER_STRONG_ENOUGH), -#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */ - - /* Tags for SSL 2 cipher kinds which are not specified for SSL 3. - Defined since SDK 10.2.8 */ - CIPHER_DEF(SSL_RSA_WITH_RC2_CBC_MD5, /* 0xFF80 */ - NULL, - CIPHER_WEAK_RC_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_IDEA_CBC_MD5, /* 0xFF81 */ - NULL, - CIPHER_WEAK_IDEA_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_DES_CBC_MD5, /* 0xFF82 */ - NULL, - CIPHER_WEAK_DES_ENCRYPTION), - CIPHER_DEF(SSL_RSA_WITH_3DES_EDE_CBC_MD5, /* 0xFF83 */ - NULL, - CIPHER_WEAK_3DES_ENCRYPTION), -}; - -#define NUM_OF_CIPHERS sizeof(ciphertable)/sizeof(ciphertable[0]) - - -/* pinned public key support tests */ - -/* version 1 supports macOS 10.12+ and iOS 10+ */ -#if ((TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000) || \ - (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)) -#define SECTRANSP_PINNEDPUBKEY_V1 1 -#endif - -/* version 2 supports MacOSX 10.7+ */ -#if (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070) -#define SECTRANSP_PINNEDPUBKEY_V2 1 -#endif - -#if defined(SECTRANSP_PINNEDPUBKEY_V1) || defined(SECTRANSP_PINNEDPUBKEY_V2) -/* this backend supports CURLOPT_PINNEDPUBLICKEY */ -#define SECTRANSP_PINNEDPUBKEY 1 -#endif /* SECTRANSP_PINNEDPUBKEY */ - -#ifdef SECTRANSP_PINNEDPUBKEY -/* both new and old APIs return rsa keys missing the spki header (not DER) */ -static const unsigned char rsa4096SpkiHeader[] = { - 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00}; - -static const unsigned char rsa2048SpkiHeader[] = { - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, - 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, - 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00}; -#ifdef SECTRANSP_PINNEDPUBKEY_V1 -/* the *new* version doesn't return DER encoded ecdsa certs like the old... */ -static const unsigned char ecDsaSecp256r1SpkiHeader[] = { - 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, - 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, - 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, - 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, - 0x42, 0x00}; - -static const unsigned char ecDsaSecp384r1SpkiHeader[] = { - 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, - 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, - 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, - 0x00, 0x22, 0x03, 0x62, 0x00}; -#endif /* SECTRANSP_PINNEDPUBKEY_V1 */ -#endif /* SECTRANSP_PINNEDPUBKEY */ - -/* The following two functions were ripped from Apple sample code, - * with some modifications: */ -static OSStatus SocketRead(SSLConnectionRef connection, - void *data, /* owned by - * caller, data - * RETURNED */ - size_t *dataLength) /* IN/OUT */ -{ - size_t bytesToGo = *dataLength; - size_t initLen = bytesToGo; - UInt8 *currData = (UInt8 *)data; - /*int sock = *(int *)connection;*/ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)connection; - struct ssl_backend_data *backend = connssl->backend; - int sock; - OSStatus rtn = noErr; - size_t bytesRead; - ssize_t rrtn; - int theErr; - - DEBUGASSERT(backend); - sock = backend->ssl_sockfd; - *dataLength = 0; - - for(;;) { - bytesRead = 0; - rrtn = read(sock, currData, bytesToGo); - if(rrtn <= 0) { - /* this is guesswork... */ - theErr = errno; - if(rrtn == 0) { /* EOF = server hung up */ - /* the framework will turn this into errSSLClosedNoNotify */ - rtn = errSSLClosedGraceful; - } - else /* do the switch */ - switch(theErr) { - case ENOENT: - /* connection closed */ - rtn = errSSLClosedGraceful; - break; - case ECONNRESET: - rtn = errSSLClosedAbort; - break; - case EAGAIN: - rtn = errSSLWouldBlock; - backend->ssl_direction = false; - break; - default: - rtn = ioErr; - break; - } - break; - } - else { - bytesRead = rrtn; - } - bytesToGo -= bytesRead; - currData += bytesRead; - - if(bytesToGo == 0) { - /* filled buffer with incoming data, done */ - break; - } - } - *dataLength = initLen - bytesToGo; - - return rtn; -} - -static OSStatus SocketWrite(SSLConnectionRef connection, - const void *data, - size_t *dataLength) /* IN/OUT */ -{ - size_t bytesSent = 0; - /*int sock = *(int *)connection;*/ - struct ssl_connect_data *connssl = (struct ssl_connect_data *)connection; - struct ssl_backend_data *backend = connssl->backend; - int sock; - ssize_t length; - size_t dataLen = *dataLength; - const UInt8 *dataPtr = (UInt8 *)data; - OSStatus ortn; - int theErr; - - DEBUGASSERT(backend); - sock = backend->ssl_sockfd; - *dataLength = 0; - - do { - length = write(sock, - (char *)dataPtr + bytesSent, - dataLen - bytesSent); - } while((length > 0) && - ( (bytesSent += length) < dataLen) ); - - if(length <= 0) { - theErr = errno; - if(theErr == EAGAIN) { - ortn = errSSLWouldBlock; - backend->ssl_direction = true; - } - else { - ortn = ioErr; - } - } - else { - ortn = noErr; - } - *dataLength = bytesSent; - return ortn; -} - -#ifndef CURL_DISABLE_VERBOSE_STRINGS -CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher) -{ - /* The first ciphers in the ciphertable are continuous. Here we do small - optimization and instead of loop directly get SSL name by cipher number. - */ - if(cipher <= SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) { - return ciphertable[cipher].name; - } - /* Iterate through the rest of the ciphers */ - for(size_t i = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA + 1; - i < NUM_OF_CIPHERS; - ++i) { - if(ciphertable[i].num == cipher) { - return ciphertable[i].name; - } - } - return ciphertable[SSL_NULL_WITH_NULL_NULL].name; -} -#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ - -#if CURL_BUILD_MAC -CF_INLINE void GetDarwinVersionNumber(int *major, int *minor) -{ - int mib[2]; - char *os_version; - size_t os_version_len; - char *os_version_major, *os_version_minor; - char *tok_buf; - - /* Get the Darwin kernel version from the kernel using sysctl(): */ - mib[0] = CTL_KERN; - mib[1] = KERN_OSRELEASE; - if(sysctl(mib, 2, NULL, &os_version_len, NULL, 0) == -1) - return; - os_version = malloc(os_version_len*sizeof(char)); - if(!os_version) - return; - if(sysctl(mib, 2, os_version, &os_version_len, NULL, 0) == -1) { - free(os_version); - return; - } - - /* Parse the version: */ - os_version_major = strtok_r(os_version, ".", &tok_buf); - os_version_minor = strtok_r(NULL, ".", &tok_buf); - *major = atoi(os_version_major); - *minor = atoi(os_version_minor); - free(os_version); -} -#endif /* CURL_BUILD_MAC */ - -/* Apple provides a myriad of ways of getting information about a certificate - into a string. Some aren't available under iOS or newer cats. So here's - a unified function for getting a string describing the certificate that - ought to work in all cats starting with Leopard. */ -CF_INLINE CFStringRef getsubject(SecCertificateRef cert) -{ - CFStringRef server_cert_summary = CFSTR("(null)"); - -#if CURL_BUILD_IOS - /* iOS: There's only one way to do this. */ - server_cert_summary = SecCertificateCopySubjectSummary(cert); -#else -#if CURL_BUILD_MAC_10_7 - /* Lion & later: Get the long description if we can. */ - if(SecCertificateCopyLongDescription) - server_cert_summary = - SecCertificateCopyLongDescription(NULL, cert, NULL); - else -#endif /* CURL_BUILD_MAC_10_7 */ -#if CURL_BUILD_MAC_10_6 - /* Snow Leopard: Get the certificate summary. */ - if(SecCertificateCopySubjectSummary) - server_cert_summary = SecCertificateCopySubjectSummary(cert); - else -#endif /* CURL_BUILD_MAC_10_6 */ - /* Leopard is as far back as we go... */ - (void)SecCertificateCopyCommonName(cert, &server_cert_summary); -#endif /* CURL_BUILD_IOS */ - return server_cert_summary; -} - -static CURLcode CopyCertSubject(struct Curl_easy *data, - SecCertificateRef cert, char **certp) -{ - CFStringRef c = getsubject(cert); - CURLcode result = CURLE_OK; - const char *direct; - char *cbuf = NULL; - *certp = NULL; - - if(!c) { - failf(data, "SSL: invalid CA certificate subject"); - return CURLE_PEER_FAILED_VERIFICATION; - } - - /* If the subject is already available as UTF-8 encoded (ie 'direct') then - use that, else convert it. */ - direct = CFStringGetCStringPtr(c, kCFStringEncodingUTF8); - if(direct) { - *certp = strdup(direct); - if(!*certp) { - failf(data, "SSL: out of memory"); - result = CURLE_OUT_OF_MEMORY; - } - } - else { - size_t cbuf_size = ((size_t)CFStringGetLength(c) * 4) + 1; - cbuf = calloc(cbuf_size, 1); - if(cbuf) { - if(!CFStringGetCString(c, cbuf, cbuf_size, - kCFStringEncodingUTF8)) { - failf(data, "SSL: invalid CA certificate subject"); - result = CURLE_PEER_FAILED_VERIFICATION; - } - else - /* pass back the buffer */ - *certp = cbuf; - } - else { - failf(data, "SSL: couldn't allocate %zu bytes of memory", cbuf_size); - result = CURLE_OUT_OF_MEMORY; - } - } - if(result) - free(cbuf); - CFRelease(c); - return result; -} - -#if CURL_SUPPORT_MAC_10_6 -/* The SecKeychainSearch API was deprecated in Lion, and using it will raise - deprecation warnings, so let's not compile this unless it's necessary: */ -static OSStatus CopyIdentityWithLabelOldSchool(char *label, - SecIdentityRef *out_c_a_k) -{ - OSStatus status = errSecItemNotFound; - SecKeychainAttributeList attr_list; - SecKeychainAttribute attr; - SecKeychainSearchRef search = NULL; - SecCertificateRef cert = NULL; - - /* Set up the attribute list: */ - attr_list.count = 1L; - attr_list.attr = &attr; - - /* Set up our lone search criterion: */ - attr.tag = kSecLabelItemAttr; - attr.data = label; - attr.length = (UInt32)strlen(label); - - /* Start searching: */ - status = SecKeychainSearchCreateFromAttributes(NULL, - kSecCertificateItemClass, - &attr_list, - &search); - if(status == noErr) { - status = SecKeychainSearchCopyNext(search, - (SecKeychainItemRef *)&cert); - if(status == noErr && cert) { - /* If we found a certificate, does it have a private key? */ - status = SecIdentityCreateWithCertificate(NULL, cert, out_c_a_k); - CFRelease(cert); - } - } - - if(search) - CFRelease(search); - return status; -} -#endif /* CURL_SUPPORT_MAC_10_6 */ - -static OSStatus CopyIdentityWithLabel(char *label, - SecIdentityRef *out_cert_and_key) -{ - OSStatus status = errSecItemNotFound; - -#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS - CFArrayRef keys_list; - CFIndex keys_list_count; - CFIndex i; - CFStringRef common_name; - - /* SecItemCopyMatching() was introduced in iOS and Snow Leopard. - kSecClassIdentity was introduced in Lion. If both exist, let's use them - to find the certificate. */ - if(SecItemCopyMatching && kSecClassIdentity) { - CFTypeRef keys[5]; - CFTypeRef values[5]; - CFDictionaryRef query_dict; - CFStringRef label_cf = CFStringCreateWithCString(NULL, label, - kCFStringEncodingUTF8); - - /* Set up our search criteria and expected results: */ - values[0] = kSecClassIdentity; /* we want a certificate and a key */ - keys[0] = kSecClass; - values[1] = kCFBooleanTrue; /* we want a reference */ - keys[1] = kSecReturnRef; - values[2] = kSecMatchLimitAll; /* kSecMatchLimitOne would be better if the - * label matching below worked correctly */ - keys[2] = kSecMatchLimit; - /* identity searches need a SecPolicyRef in order to work */ - values[3] = SecPolicyCreateSSL(false, NULL); - keys[3] = kSecMatchPolicy; - /* match the name of the certificate (doesn't work in macOS 10.12.1) */ - values[4] = label_cf; - keys[4] = kSecAttrLabel; - query_dict = CFDictionaryCreate(NULL, (const void **)keys, - (const void **)values, 5L, - &kCFCopyStringDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - CFRelease(values[3]); - - /* Do we have a match? */ - status = SecItemCopyMatching(query_dict, (CFTypeRef *) &keys_list); - - /* Because kSecAttrLabel matching doesn't work with kSecClassIdentity, - * we need to find the correct identity ourselves */ - if(status == noErr) { - keys_list_count = CFArrayGetCount(keys_list); - *out_cert_and_key = NULL; - status = 1; - for(i = 0; i<keys_list_count; i++) { - OSStatus err = noErr; - SecCertificateRef cert = NULL; - SecIdentityRef identity = - (SecIdentityRef) CFArrayGetValueAtIndex(keys_list, i); - err = SecIdentityCopyCertificate(identity, &cert); - if(err == noErr) { - OSStatus copy_status = noErr; -#if CURL_BUILD_IOS - common_name = SecCertificateCopySubjectSummary(cert); -#elif CURL_BUILD_MAC_10_7 - copy_status = SecCertificateCopyCommonName(cert, &common_name); -#endif - if(copy_status == noErr && - CFStringCompare(common_name, label_cf, 0) == kCFCompareEqualTo) { - CFRelease(cert); - CFRelease(common_name); - CFRetain(identity); - *out_cert_and_key = identity; - status = noErr; - break; - } - CFRelease(common_name); - } - CFRelease(cert); - } - } - - if(keys_list) - CFRelease(keys_list); - CFRelease(query_dict); - CFRelease(label_cf); - } - else { -#if CURL_SUPPORT_MAC_10_6 - /* On Leopard and Snow Leopard, fall back to SecKeychainSearch. */ - status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key); -#endif /* CURL_SUPPORT_MAC_10_6 */ - } -#elif CURL_SUPPORT_MAC_10_6 - /* For developers building on older cats, we have no choice but to fall back - to SecKeychainSearch. */ - status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key); -#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */ - return status; -} - -static OSStatus CopyIdentityFromPKCS12File(const char *cPath, - const struct curl_blob *blob, - const char *cPassword, - SecIdentityRef *out_cert_and_key) -{ - OSStatus status = errSecItemNotFound; - CFURLRef pkcs_url = NULL; - CFStringRef password = cPassword ? CFStringCreateWithCString(NULL, - cPassword, kCFStringEncodingUTF8) : NULL; - CFDataRef pkcs_data = NULL; - - /* We can import P12 files on iOS or OS X 10.7 or later: */ - /* These constants are documented as having first appeared in 10.6 but they - raise linker errors when used on that cat for some reason. */ -#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS - bool resource_imported; - - if(blob) { - pkcs_data = CFDataCreate(kCFAllocatorDefault, - (const unsigned char *)blob->data, blob->len); - status = (pkcs_data != NULL) ? errSecSuccess : errSecAllocate; - resource_imported = (pkcs_data != NULL); - } - else { - pkcs_url = - CFURLCreateFromFileSystemRepresentation(NULL, - (const UInt8 *)cPath, - strlen(cPath), false); - resource_imported = - CFURLCreateDataAndPropertiesFromResource(NULL, - pkcs_url, &pkcs_data, - NULL, NULL, &status); - } - - if(resource_imported) { - CFArrayRef items = NULL; - - /* On iOS SecPKCS12Import will never add the client certificate to the - * Keychain. - * - * It gives us back a SecIdentityRef that we can use directly. */ -#if CURL_BUILD_IOS - const void *cKeys[] = {kSecImportExportPassphrase}; - const void *cValues[] = {password}; - CFDictionaryRef options = CFDictionaryCreate(NULL, cKeys, cValues, - password ? 1L : 0L, NULL, NULL); - - if(options) { - status = SecPKCS12Import(pkcs_data, options, &items); - CFRelease(options); - } - - - /* On macOS SecPKCS12Import will always add the client certificate to - * the Keychain. - * - * As this doesn't match iOS, and apps may not want to see their client - * certificate saved in the user's keychain, we use SecItemImport - * with a NULL keychain to avoid importing it. - * - * This returns a SecCertificateRef from which we can construct a - * SecIdentityRef. - */ -#elif CURL_BUILD_MAC_10_7 - SecItemImportExportKeyParameters keyParams; - SecExternalFormat inputFormat = kSecFormatPKCS12; - SecExternalItemType inputType = kSecItemTypeCertificate; - - memset(&keyParams, 0x00, sizeof(keyParams)); - keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; - keyParams.passphrase = password; - - status = SecItemImport(pkcs_data, NULL, &inputFormat, &inputType, - 0, &keyParams, NULL, &items); -#endif - - - /* Extract the SecIdentityRef */ - if(status == errSecSuccess && items && CFArrayGetCount(items)) { - CFIndex i, count; - count = CFArrayGetCount(items); - - for(i = 0; i < count; i++) { - CFTypeRef item = (CFTypeRef) CFArrayGetValueAtIndex(items, i); - CFTypeID itemID = CFGetTypeID(item); - - if(itemID == CFDictionaryGetTypeID()) { - CFTypeRef identity = (CFTypeRef) CFDictionaryGetValue( - (CFDictionaryRef) item, - kSecImportItemIdentity); - CFRetain(identity); - *out_cert_and_key = (SecIdentityRef) identity; - break; - } -#if CURL_BUILD_MAC_10_7 - else if(itemID == SecCertificateGetTypeID()) { - status = SecIdentityCreateWithCertificate(NULL, - (SecCertificateRef) item, - out_cert_and_key); - break; - } -#endif - } - } - - if(items) - CFRelease(items); - CFRelease(pkcs_data); - } -#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */ - if(password) - CFRelease(password); - if(pkcs_url) - CFRelease(pkcs_url); - return status; -} - -/* This code was borrowed from nss.c, with some modifications: - * Determine whether the nickname passed in is a filename that needs to - * be loaded as a PEM or a regular NSS nickname. - * - * returns 1 for a file - * returns 0 for not a file - */ -CF_INLINE bool is_file(const char *filename) -{ - struct_stat st; - - if(!filename) - return false; - - if(stat(filename, &st) == 0) - return S_ISREG(st.st_mode); - return false; -} - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS -static CURLcode sectransp_version_from_curl(SSLProtocol *darwinver, - long ssl_version) -{ - switch(ssl_version) { - case CURL_SSLVERSION_TLSv1_0: - *darwinver = kTLSProtocol1; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_1: - *darwinver = kTLSProtocol11; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_2: - *darwinver = kTLSProtocol12; - return CURLE_OK; - case CURL_SSLVERSION_TLSv1_3: - /* TLS 1.3 support first appeared in iOS 11 and macOS 10.13 */ -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - *darwinver = kTLSProtocol13; - return CURLE_OK; - } -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ - break; - } - return CURLE_SSL_CONNECT_ERROR; -} -#endif - -static CURLcode -set_ssl_version_min_max(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - long ssl_version = SSL_CONN_CONFIG(version); - long ssl_version_max = SSL_CONN_CONFIG(version_max); - long max_supported_version_by_os; - - DEBUGASSERT(backend); - - /* macOS 10.5-10.7 supported TLS 1.0 only. - macOS 10.8 and later, and iOS 5 and later, added TLS 1.1 and 1.2. - macOS 10.13 and later, and iOS 11 and later, added TLS 1.3. */ -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_3; - } - else { - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; - } -#else - max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2; -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ - - switch(ssl_version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - ssl_version = CURL_SSLVERSION_TLSv1_0; - break; - } - - switch(ssl_version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - ssl_version_max = max_supported_version_by_os; - break; - } - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLSetProtocolVersionMax) { - SSLProtocol darwin_ver_min = kTLSProtocol1; - SSLProtocol darwin_ver_max = kTLSProtocol1; - CURLcode result = sectransp_version_from_curl(&darwin_ver_min, - ssl_version); - if(result) { - failf(data, "unsupported min version passed via CURLOPT_SSLVERSION"); - return result; - } - result = sectransp_version_from_curl(&darwin_ver_max, - ssl_version_max >> 16); - if(result) { - failf(data, "unsupported max version passed via CURLOPT_SSLVERSION"); - return result; - } - - (void)SSLSetProtocolVersionMin(backend->ssl_ctx, darwin_ver_min); - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, darwin_ver_max); - return result; - } - else { -#if CURL_SUPPORT_MAC_10_8 - long i = ssl_version; - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kSSLProtocolAll, - false); - for(; i <= (ssl_version_max >> 16); i++) { - switch(i) { - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - break; - case CURL_SSLVERSION_TLSv1_1: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol11, - true); - break; - case CURL_SSLVERSION_TLSv1_2: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "Your version of the OS does not support TLSv1.3"); - return CURLE_SSL_CONNECT_ERROR; - } - } - return CURLE_OK; -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - failf(data, "Secure Transport: cannot set SSL protocol"); - return CURLE_SSL_CONNECT_ERROR; -} - -static bool is_cipher_suite_strong(SSLCipherSuite suite_num) -{ - for(size_t i = 0; i < NUM_OF_CIPHERS; ++i) { - if(ciphertable[i].num == suite_num) { - return !ciphertable[i].weak; - } - } - /* If the cipher is not in our list, assume it is a new one - and therefore strong. Previous implementation was the same, - if cipher suite is not in the list, it was considered strong enough */ - return true; -} - -static bool is_separator(char c) -{ - /* Return whether character is a cipher list separator. */ - switch(c) { - case ' ': - case '\t': - case ':': - case ',': - case ';': - return true; - } - return false; -} - -static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data, - SSLContextRef ssl_ctx) -{ - size_t all_ciphers_count = 0UL, allowed_ciphers_count = 0UL, i; - SSLCipherSuite *all_ciphers = NULL, *allowed_ciphers = NULL; - OSStatus err = noErr; - -#if CURL_BUILD_MAC - int darwinver_maj = 0, darwinver_min = 0; - - GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); -#endif /* CURL_BUILD_MAC */ - - /* Disable cipher suites that ST supports but are not safe. These ciphers - are unlikely to be used in any case since ST gives other ciphers a much - higher priority, but it's probably better that we not connect at all than - to give the user a false sense of security if the server only supports - insecure ciphers. (Note: We don't care about SSLv2-only ciphers.) */ - err = SSLGetNumberSupportedCiphers(ssl_ctx, &all_ciphers_count); - if(err != noErr) { - failf(data, "SSL: SSLGetNumberSupportedCiphers() failed: OSStatus %d", - err); - return CURLE_SSL_CIPHER; - } - all_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - if(!all_ciphers) { - failf(data, "SSL: Failed to allocate memory for all ciphers"); - return CURLE_OUT_OF_MEMORY; - } - allowed_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite)); - if(!allowed_ciphers) { - Curl_safefree(all_ciphers); - failf(data, "SSL: Failed to allocate memory for allowed ciphers"); - return CURLE_OUT_OF_MEMORY; - } - err = SSLGetSupportedCiphers(ssl_ctx, all_ciphers, - &all_ciphers_count); - if(err != noErr) { - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); - return CURLE_SSL_CIPHER; - } - for(i = 0UL ; i < all_ciphers_count ; i++) { -#if CURL_BUILD_MAC - /* There's a known bug in early versions of Mountain Lion where ST's ECC - ciphers (cipher suite 0xC001 through 0xC032) simply do not work. - Work around the problem here by disabling those ciphers if we are - running in an affected version of OS X. */ - if(darwinver_maj == 12 && darwinver_min <= 3 && - all_ciphers[i] >= 0xC001 && all_ciphers[i] <= 0xC032) { - continue; - } -#endif /* CURL_BUILD_MAC */ - if(is_cipher_suite_strong(all_ciphers[i])) { - allowed_ciphers[allowed_ciphers_count++] = all_ciphers[i]; - } - } - err = SSLSetEnabledCiphers(ssl_ctx, allowed_ciphers, - allowed_ciphers_count); - Curl_safefree(all_ciphers); - Curl_safefree(allowed_ciphers); - if(err != noErr) { - failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); - return CURLE_SSL_CIPHER; - } - return CURLE_OK; -} - -static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data, - SSLContextRef ssl_ctx, - const char *ciphers) -{ - size_t ciphers_count = 0; - const char *cipher_start = ciphers; - OSStatus err = noErr; - SSLCipherSuite selected_ciphers[NUM_OF_CIPHERS]; - - if(!ciphers) - return CURLE_OK; - - while(is_separator(*ciphers)) /* Skip initial separators. */ - ciphers++; - if(!*ciphers) - return CURLE_OK; - - cipher_start = ciphers; - while(*cipher_start && ciphers_count < NUM_OF_CIPHERS) { - bool cipher_found = FALSE; - size_t cipher_len = 0; - const char *cipher_end = NULL; - bool tls_name = FALSE; - - /* Skip separators */ - while(is_separator(*cipher_start)) - cipher_start++; - if(*cipher_start == '\0') { - break; - } - /* Find last position of a cipher in the ciphers string */ - cipher_end = cipher_start; - while (*cipher_end != '\0' && !is_separator(*cipher_end)) { - ++cipher_end; - } - - /* IANA cipher names start with the TLS_ or SSL_ prefix. - If the 4th symbol of the cipher is '_' we look for a cipher in the - table by its (TLS) name. - Otherwise, we try to match cipher by an alias. */ - if(cipher_start[3] == '_') { - tls_name = TRUE; - } - /* Iterate through the cipher table and look for the cipher, starting - the cipher number 0x01 because the 0x00 is not the real cipher */ - cipher_len = cipher_end - cipher_start; - for(size_t i = 1; i < NUM_OF_CIPHERS; ++i) { - const char *table_cipher_name = NULL; - if(tls_name) { - table_cipher_name = ciphertable[i].name; - } - else if(ciphertable[i].alias_name) { - table_cipher_name = ciphertable[i].alias_name; - } - else { - continue; - } - /* Compare a part of the string between separators with a cipher name - in the table and make sure we matched the whole cipher name */ - if(strncmp(cipher_start, table_cipher_name, cipher_len) == 0 - && table_cipher_name[cipher_len] == '\0') { - selected_ciphers[ciphers_count] = ciphertable[i].num; - ++ciphers_count; - cipher_found = TRUE; - break; - } - } - if(!cipher_found) { - /* It would be more human-readable if we print the wrong cipher name - but we don't want to allocate any additional memory and copy the name - into it, then add it into logs. - Also, we do not modify an original cipher list string. We just point - to positions where cipher starts and ends in the cipher list string. - The message is a bit cryptic and longer than necessary but can be - understood by humans. */ - failf(data, "SSL: cipher string \"%s\" contains unsupported cipher name" - " starting position %d and ending position %d", - ciphers, - cipher_start - ciphers, - cipher_end - ciphers); - return CURLE_SSL_CIPHER; - } - if(*cipher_end) { - cipher_start = cipher_end + 1; - } - else { - break; - } - } - /* All cipher suites in the list are found. Report to logs as-is */ - infof(data, "SSL: Setting cipher suites list \"%s\"", ciphers); - - err = SSLSetEnabledCiphers(ssl_ctx, selected_ciphers, ciphers_count); - if(err != noErr) { - failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err); - return CURLE_SSL_CIPHER; - } - return CURLE_OK; -} - -static CURLcode sectransp_connect_step1(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) -{ - curl_socket_t sockfd = conn->sock[sockindex]; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - 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); - bool isproxy = SSL_IS_PROXY(); - const char * const hostname = SSL_HOST_NAME(); - const long int port = SSL_HOST_PORT(); -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif /* ENABLE_IPV6 */ - char *ciphers; - OSStatus err = noErr; -#if CURL_BUILD_MAC - int darwinver_maj = 0, darwinver_min = 0; - - DEBUGASSERT(backend); - - GetDarwinVersionNumber(&darwinver_maj, &darwinver_min); -#endif /* CURL_BUILD_MAC */ - -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext) { /* use the newer API if available */ - if(backend->ssl_ctx) - CFRelease(backend->ssl_ctx); - backend->ssl_ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType); - if(!backend->ssl_ctx) { - failf(data, "SSL: couldn't create a context"); - return CURLE_OUT_OF_MEMORY; - } - } - else { - /* The old ST API does not exist under iOS, so don't compile it: */ -#if CURL_SUPPORT_MAC_10_8 - if(backend->ssl_ctx) - (void)SSLDisposeContext(backend->ssl_ctx); - err = SSLNewContext(false, &(backend->ssl_ctx)); - if(err != noErr) { - failf(data, "SSL: couldn't create a context: OSStatus %d", err); - return CURLE_OUT_OF_MEMORY; - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#else - if(backend->ssl_ctx) - (void)SSLDisposeContext(backend->ssl_ctx); - err = SSLNewContext(false, &(backend->ssl_ctx)); - if(err != noErr) { - failf(data, "SSL: couldn't create a context: OSStatus %d", err); - return CURLE_OUT_OF_MEMORY; - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - backend->ssl_write_buffered_length = 0UL; /* reset buffered write length */ - - /* check to see if we've been told to use an explicit SSL/TLS version */ -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLSetProtocolVersionMax) { - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionMin(backend->ssl_ctx, kTLSProtocol1); -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(__builtin_available(macOS 10.13, iOS 11.0, *)) { - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol13); - } - else { - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); - } -#else - (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12); -#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && - HAVE_BUILTIN_AVAILABLE == 1 */ - break; - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(data, conn, sockindex); - if(result != CURLE_OK) - return result; - break; - } - case CURL_SSLVERSION_SSLv3: - case CURL_SSLVERSION_SSLv2: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { -#if CURL_SUPPORT_MAC_10_8 - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kSSLProtocolAll, - false); - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol11, - true); - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol12, - true); - break; - case CURL_SSLVERSION_TLSv1_0: - case CURL_SSLVERSION_TLSv1_1: - case CURL_SSLVERSION_TLSv1_2: - case CURL_SSLVERSION_TLSv1_3: - { - CURLcode result = set_ssl_version_min_max(data, conn, sockindex); - if(result != CURLE_OK) - return result; - break; - } - case CURL_SSLVERSION_SSLv3: - case CURL_SSLVERSION_SSLv2: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#else - if(conn->ssl_config.version_max != CURL_SSLVERSION_MAX_NONE) { - failf(data, "Your version of the OS does not support to set maximum" - " SSL/TLS version"); - return CURLE_SSL_CONNECT_ERROR; - } - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, false); - switch(conn->ssl_config.version) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: - case CURL_SSLVERSION_TLSv1_0: - (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, - kTLSProtocol1, - true); - break; - case CURL_SSLVERSION_TLSv1_1: - failf(data, "Your version of the OS does not support TLSv1.1"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_2: - failf(data, "Your version of the OS does not support TLSv1.2"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_TLSv1_3: - failf(data, "Your version of the OS does not support TLSv1.3"); - return CURLE_SSL_CONNECT_ERROR; - case CURL_SSLVERSION_SSLv2: - case CURL_SSLVERSION_SSLv3: - failf(data, "SSL versions not supported"); - return CURLE_NOT_BUILT_IN; - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - -#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(conn->bits.tls_enable_alpn) { - if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { - CFMutableArrayRef alpnArr = CFArrayCreateMutable(NULL, 0, - &kCFTypeArrayCallBacks); - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2 -#ifndef CURL_DISABLE_PROXY - && (!isproxy || !conn->bits.tunnel_proxy) -#endif - ) { - CFArrayAppendValue(alpnArr, CFSTR(ALPN_H2)); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_1)); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - /* expects length prefixed preference ordered list of protocols in wire - * format - */ - err = SSLSetALPNProtocols(backend->ssl_ctx, alpnArr); - if(err != noErr) - infof(data, "WARNING: failed to set ALPN protocols; OSStatus %d", - err); - CFRelease(alpnArr); - } - } -#endif - - if(SSL_SET_OPTION(key)) { - infof(data, "WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure " - "Transport. The private key must be in the Keychain."); - } - - if(ssl_cert || ssl_cert_blob) { - bool is_cert_data = ssl_cert_blob != NULL; - bool is_cert_file = (!is_cert_data) && is_file(ssl_cert); - SecIdentityRef cert_and_key = NULL; - - /* User wants to authenticate with a client cert. Look for it. Assume that - the user wants to use an identity loaded from the Keychain. If not, try - it as a file on disk */ - - if(!is_cert_data) - err = CopyIdentityWithLabel(ssl_cert, &cert_and_key); - else - err = !noErr; - if((err != noErr) && (is_cert_file || is_cert_data)) { - if(!SSL_SET_OPTION(cert_type)) - infof(data, "SSL: Certificate type not set, assuming " - "PKCS#12 format."); - else if(!strcasecompare(SSL_SET_OPTION(cert_type), "P12")) { - failf(data, "SSL: The Security framework only supports " - "loading identities that are in PKCS#12 format."); - return CURLE_SSL_CERTPROBLEM; - } - - err = CopyIdentityFromPKCS12File(ssl_cert, ssl_cert_blob, - SSL_SET_OPTION(key_passwd), - &cert_and_key); - } - - if(err == noErr && cert_and_key) { - SecCertificateRef cert = NULL; - CFTypeRef certs_c[1]; - CFArrayRef certs; - - /* If we found one, print it out: */ - err = SecIdentityCopyCertificate(cert_and_key, &cert); - if(err == noErr) { - char *certp; - CURLcode result = CopyCertSubject(data, cert, &certp); - if(!result) { - infof(data, "Client certificate: %s", certp); - free(certp); - } - - CFRelease(cert); - if(result == CURLE_PEER_FAILED_VERIFICATION) - return CURLE_SSL_CERTPROBLEM; - if(result) - return result; - } - certs_c[0] = cert_and_key; - certs = CFArrayCreate(NULL, (const void **)certs_c, 1L, - &kCFTypeArrayCallBacks); - err = SSLSetCertificate(backend->ssl_ctx, certs); - if(certs) - CFRelease(certs); - if(err != noErr) { - failf(data, "SSL: SSLSetCertificate() failed: OSStatus %d", err); - return CURLE_SSL_CERTPROBLEM; - } - CFRelease(cert_and_key); - } - else { - const char *cert_showfilename_error = - is_cert_data ? "(memory blob)" : ssl_cert; - - switch(err) { - case errSecAuthFailed: case -25264: /* errSecPkcs12VerifyFailure */ - failf(data, "SSL: Incorrect password for the certificate \"%s\" " - "and its private key.", cert_showfilename_error); - break; - case -26275: /* errSecDecode */ case -25257: /* errSecUnknownFormat */ - failf(data, "SSL: Couldn't make sense of the data in the " - "certificate \"%s\" and its private key.", - cert_showfilename_error); - break; - case -25260: /* errSecPassphraseRequired */ - failf(data, "SSL The certificate \"%s\" requires a password.", - cert_showfilename_error); - break; - case errSecItemNotFound: - failf(data, "SSL: Can't find the certificate \"%s\" and its private " - "key in the Keychain.", cert_showfilename_error); - break; - default: - failf(data, "SSL: Can't load the certificate \"%s\" and its private " - "key: OSStatus %d", cert_showfilename_error, err); - break; - } - return CURLE_SSL_CERTPROBLEM; - } - } - - /* SSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ -#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS - /* Snow Leopard introduced the SSLSetSessionOption() function, but due to - a library bug with the way the kSSLSessionOptionBreakOnServerAuth flag - works, it doesn't work as expected under Snow Leopard, Lion or - Mountain Lion. - So we need to call SSLSetEnableCertVerify() on those older cats in order - to disable certificate validation if the user turned that off. - (SecureTransport will always validate the certificate chain by - default.) - Note: - Darwin 11.x.x is Lion (10.7) - Darwin 12.x.x is Mountain Lion (10.8) - Darwin 13.x.x is Mavericks (10.9) - Darwin 14.x.x is Yosemite (10.10) - Darwin 15.x.x is El Capitan (10.11) - */ -#if CURL_BUILD_MAC - if(SSLSetSessionOption && darwinver_maj >= 13) { -#else - if(SSLSetSessionOption) { -#endif /* CURL_BUILD_MAC */ - bool break_on_auth = !conn->ssl_config.verifypeer || - ssl_cafile || ssl_cablob; - err = SSLSetSessionOption(backend->ssl_ctx, - kSSLSessionOptionBreakOnServerAuth, - break_on_auth); - if(err != noErr) { - failf(data, "SSL: SSLSetSessionOption() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - } - else { -#if CURL_SUPPORT_MAC_10_8 - err = SSLSetEnableCertVerify(backend->ssl_ctx, - conn->ssl_config.verifypeer?true:false); - if(err != noErr) { - failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#else - err = SSLSetEnableCertVerify(backend->ssl_ctx, - conn->ssl_config.verifypeer?true:false); - if(err != noErr) { - failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */ - - if((ssl_cafile || ssl_cablob) && verifypeer) { - bool is_cert_data = ssl_cablob != NULL; - 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 ? ssl_cafile : "(blob memory)"); - return CURLE_SSL_CACERT_BADFILE; - } - } - - /* Configure hostname check. SNI is used if available. - * Both hostname check and SNI require SSLSetPeerDomainName(). - * Also: the verifyhost setting influences SNI usage */ - if(conn->ssl_config.verifyhost) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, hostname, &snilen); - if(!snihost) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - err = SSLSetPeerDomainName(backend->ssl_ctx, snihost, snilen); - - if(err != noErr) { - failf(data, "SSL: SSLSetPeerDomainName() failed: OSStatus %d", - err); - return CURLE_SSL_CONNECT_ERROR; - } - - if((Curl_inet_pton(AF_INET, hostname, &addr)) - #ifdef ENABLE_IPV6 - || (Curl_inet_pton(AF_INET6, hostname, &addr)) - #endif - ) { - infof(data, "WARNING: using IP address, SNI is being disabled by " - "the OS."); - } - } - else { - infof(data, "WARNING: disabling hostname validation also disables SNI."); - } - - ciphers = SSL_CONN_CONFIG(cipher_list); - if(ciphers) { - err = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers); - } - else { - err = sectransp_set_default_ciphers(data, backend->ssl_ctx); - } - if(err != noErr) { - failf(data, "SSL: Unable to set ciphers for SSL/TLS handshake. " - "Error code: %d", err); - return CURLE_SSL_CIPHER; - } - -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - /* We want to enable 1/n-1 when using a CBC cipher unless the user - specifically doesn't want us doing that: */ - if(SSLSetSessionOption) { - SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionSendOneByteRecord, - !SSL_SET_OPTION(enable_beast)); - SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionFalseStart, - data->set.ssl.falsestart); /* false start support */ - } -#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */ - - /* Check if there's a cached ID we can/should use here! */ - if(SSL_SET_OPTION(primary.sessionid)) { - char *ssl_sessionid; - size_t ssl_sessionid_len; - - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, isproxy, (void **)&ssl_sessionid, - &ssl_sessionid_len, sockindex)) { - /* we got a session id, use it! */ - err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); - Curl_ssl_sessionid_unlock(data); - if(err != noErr) { - failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - /* Informational message */ - infof(data, "SSL re-using session ID"); - } - /* If there isn't one, then let's make one up! This has to be done prior - to starting the handshake. */ - else { - CURLcode result; - ssl_sessionid = - 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); - - err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len); - if(err != noErr) { - Curl_ssl_sessionid_unlock(data); - failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - - result = Curl_ssl_addsessionid(data, conn, isproxy, ssl_sessionid, - ssl_sessionid_len, sockindex, NULL); - Curl_ssl_sessionid_unlock(data); - if(result) { - failf(data, "failed to store ssl session"); - return result; - } - } - } - - err = SSLSetIOFuncs(backend->ssl_ctx, SocketRead, SocketWrite); - if(err != noErr) { - failf(data, "SSL: SSLSetIOFuncs() failed: OSStatus %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - - /* pass the raw socket into the SSL layers */ - /* We need to store the FD in a constant memory address, because - * SSLSetConnection() will not copy that address. I've found that - * conn->sock[sockindex] may change on its own. */ - backend->ssl_sockfd = sockfd; - err = SSLSetConnection(backend->ssl_ctx, connssl); - if(err != noErr) { - failf(data, "SSL: SSLSetConnection() failed: %d", err); - return CURLE_SSL_CONNECT_ERROR; - } - - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; -} - -static long pem_to_der(const char *in, unsigned char **out, size_t *outlen) -{ - char *sep_start, *sep_end, *cert_start, *cert_end; - size_t i, j, err; - size_t len; - unsigned char *b64; - - /* Jump through the separators at the beginning of the certificate. */ - sep_start = strstr(in, "-----"); - if(!sep_start) - return 0; - cert_start = strstr(sep_start + 1, "-----"); - if(!cert_start) - return -1; - - cert_start += 5; - - /* Find separator after the end of the certificate. */ - cert_end = strstr(cert_start, "-----"); - if(!cert_end) - return -1; - - sep_end = strstr(cert_end + 1, "-----"); - if(!sep_end) - return -1; - sep_end += 5; - - len = cert_end - cert_start; - b64 = malloc(len + 1); - if(!b64) - return -1; - - /* Create base64 string without linefeeds. */ - for(i = 0, j = 0; i < len; i++) { - if(cert_start[i] != '\r' && cert_start[i] != '\n') - b64[j++] = cert_start[i]; - } - b64[j] = '\0'; - - err = Curl_base64_decode((const char *)b64, out, outlen); - free(b64); - if(err) { - free(*out); - return -1; - } - - return sep_end - in; -} - -static int read_cert(const char *file, unsigned char **out, size_t *outlen) -{ - int fd; - ssize_t n, len = 0, cap = 512; - unsigned char buf[512], *data; - - 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 = Curl_saferealloc(data, cap); - if(!data) { - close(fd); - return -1; - } - } - - memcpy(data + len, buf, n); - len += n; - } - data[len] = '\0'; - - *out = data; - *outlen = len; - - return 0; -} - -static int append_cert_to_array(struct Curl_easy *data, - const unsigned char *buf, size_t buflen, - CFMutableArrayRef array) -{ - CFDataRef certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen); - char *certp; - CURLcode result; - 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_BADFILE; - } - - /* Check if cacert is valid. */ - result = CopyCertSubject(data, cacert, &certp); - switch(result) { - case CURLE_OK: - break; - case CURLE_PEER_FAILED_VERIFICATION: - return CURLE_SSL_CACERT_BADFILE; - case CURLE_OUT_OF_MEMORY: - default: - return result; - } - free(certp); - - CFArrayAppendValue(array, cacert); - CFRelease(cacert); - - return CURLE_OK; -} - -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 *der; - size_t derlen, offset = 0; - - /* - * Certbuf now contains the contents of the certificate file, which can be - * - a single DER certificate, - * - a single PEM certificate or - * - a bunch of PEM certificates (certificate bundle). - * - * Go through certbuf, and convert any PEM certificate in it into DER - * format. - */ - CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeArrayCallBacks); - if(!array) { - failf(data, "SSL: out of memory creating CA certificate array"); - return CURLE_OUT_OF_MEMORY; - } - - while(offset < buflen) { - n++; - - /* - * Check if the certificate is in PEM format, and convert it to DER. If - * this fails, we assume the certificate is in DER format. - */ - res = pem_to_der((const char *)certbuf + offset, &der, &derlen); - if(res < 0) { - CFRelease(array); - failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle", - n, offset); - return CURLE_SSL_CACERT_BADFILE; - } - offset += res; - - 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); - if(rc != CURLE_OK) { - CFRelease(array); - return rc; - } - break; - } - else if(res == 0) { - /* No more certificates in the bundle. */ - break; - } - - rc = append_cert_to_array(data, der, derlen, array); - free(der); - if(rc != CURLE_OK) { - CFRelease(array); - return rc; - } - } - - SecTrustRef trust; - OSStatus ret = SSLCopyPeerTrust(ctx, &trust); - if(!trust) { - failf(data, "SSL: error getting certificate chain"); - CFRelease(array); - return CURLE_PEER_FAILED_VERIFICATION; - } - else if(ret != noErr) { - CFRelease(array); - failf(data, "SSLCopyPeerTrust() returned error %d", ret); - return CURLE_PEER_FAILED_VERIFICATION; - } - - ret = SecTrustSetAnchorCertificates(trust, array); - if(ret != noErr) { - CFRelease(array); - CFRelease(trust); - failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret); - return CURLE_PEER_FAILED_VERIFICATION; - } - ret = SecTrustSetAnchorCertificatesOnly(trust, true); - if(ret != noErr) { - CFRelease(array); - CFRelease(trust); - failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret); - return CURLE_PEER_FAILED_VERIFICATION; - } - - SecTrustResultType trust_eval = 0; - ret = SecTrustEvaluate(trust, &trust_eval); - CFRelease(array); - CFRelease(trust); - if(ret != noErr) { - failf(data, "SecTrustEvaluate() returned error %d", ret); - return CURLE_PEER_FAILED_VERIFICATION; - } - - switch(trust_eval) { - case kSecTrustResultUnspecified: - case kSecTrustResultProceed: - 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 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, - const char *pinnedpubkey) -{ /* Scratch */ - size_t pubkeylen, realpubkeylen, spkiHeaderLength = 24; - unsigned char *pubkey = NULL, *realpubkey = NULL; - const unsigned char *spkiHeader = NULL; - CFDataRef publicKeyBits = NULL; - - /* Result is returned to caller */ - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - - /* if a path wasn't specified, don't pin */ - if(!pinnedpubkey) - return CURLE_OK; - - - if(!ctx) - return result; - - do { - SecTrustRef trust; - OSStatus ret = SSLCopyPeerTrust(ctx, &trust); - if(ret != noErr || !trust) - break; - - SecKeyRef keyRef = SecTrustCopyPublicKey(trust); - CFRelease(trust); - if(!keyRef) - break; - -#ifdef SECTRANSP_PINNEDPUBKEY_V1 - - publicKeyBits = SecKeyCopyExternalRepresentation(keyRef, NULL); - CFRelease(keyRef); - if(!publicKeyBits) - break; - -#elif SECTRANSP_PINNEDPUBKEY_V2 - - OSStatus success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, - &publicKeyBits); - CFRelease(keyRef); - if(success != errSecSuccess || !publicKeyBits) - break; - -#endif /* SECTRANSP_PINNEDPUBKEY_V2 */ - - pubkeylen = CFDataGetLength(publicKeyBits); - pubkey = (unsigned char *)CFDataGetBytePtr(publicKeyBits); - - switch(pubkeylen) { - case 526: - /* 4096 bit RSA pubkeylen == 526 */ - spkiHeader = rsa4096SpkiHeader; - break; - case 270: - /* 2048 bit RSA pubkeylen == 270 */ - spkiHeader = rsa2048SpkiHeader; - break; -#ifdef SECTRANSP_PINNEDPUBKEY_V1 - case 65: - /* ecDSA secp256r1 pubkeylen == 65 */ - spkiHeader = ecDsaSecp256r1SpkiHeader; - spkiHeaderLength = 26; - break; - case 97: - /* ecDSA secp384r1 pubkeylen == 97 */ - spkiHeader = ecDsaSecp384r1SpkiHeader; - spkiHeaderLength = 23; - break; - default: - infof(data, "SSL: unhandled public key length: %d", pubkeylen); -#elif SECTRANSP_PINNEDPUBKEY_V2 - default: - /* ecDSA secp256r1 pubkeylen == 91 header already included? - * ecDSA secp384r1 header already included too - * we assume rest of algorithms do same, so do nothing - */ - result = Curl_pin_peer_pubkey(data, pinnedpubkey, pubkey, - pubkeylen); -#endif /* SECTRANSP_PINNEDPUBKEY_V2 */ - continue; /* break from loop */ - } - - realpubkeylen = pubkeylen + spkiHeaderLength; - realpubkey = malloc(realpubkeylen); - if(!realpubkey) - break; - - memcpy(realpubkey, spkiHeader, spkiHeaderLength); - memcpy(realpubkey + spkiHeaderLength, pubkey, pubkeylen); - - result = Curl_pin_peer_pubkey(data, pinnedpubkey, realpubkey, - realpubkeylen); - - } while(0); - - Curl_safefree(realpubkey); - if(publicKeyBits) - CFRelease(publicKeyBits); - - return result; -} -#endif /* SECTRANSP_PINNEDPUBKEY */ - -static CURLcode -sectransp_connect_step2(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - OSStatus err; - SSLCipherSuite cipher; - SSLProtocol protocol = 0; - const char * const hostname = SSL_HOST_NAME(); - - DEBUGASSERT(ssl_connect_2 == connssl->connecting_state - || ssl_connect_2_reading == connssl->connecting_state - || ssl_connect_2_writing == connssl->connecting_state); - DEBUGASSERT(backend); - - /* Here goes nothing: */ - err = SSLHandshake(backend->ssl_ctx); - - if(err != noErr) { - switch(err) { - case errSSLWouldBlock: /* they're not done with us yet */ - connssl->connecting_state = backend->ssl_direction ? - ssl_connect_2_writing : ssl_connect_2_reading; - return CURLE_OK; - - /* The below is errSSLServerAuthCompleted; it's not defined in - Leopard's headers */ - case -9841: - 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; - } - /* the documentation says we need to call SSLHandshake() again */ - return sectransp_connect_step2(data, conn, sockindex); - - /* Problem with encrypt / decrypt */ - case errSSLPeerDecodeError: - failf(data, "Decode failed"); - break; - case errSSLDecryptionFail: - case errSSLPeerDecryptionFail: - failf(data, "Decryption failed"); - break; - case errSSLPeerDecryptError: - failf(data, "A decryption error occurred"); - break; - case errSSLBadCipherSuite: - failf(data, "A bad SSL cipher suite was encountered"); - break; - case errSSLCrypto: - failf(data, "An underlying cryptographic error was encountered"); - break; -#if CURL_BUILD_MAC_10_11 || CURL_BUILD_IOS_9 - case errSSLWeakPeerEphemeralDHKey: - failf(data, "Indicates a weak ephemeral Diffie-Hellman key"); - break; -#endif - - /* Problem with the message record validation */ - case errSSLBadRecordMac: - case errSSLPeerBadRecordMac: - failf(data, "A record with a bad message authentication code (MAC) " - "was encountered"); - break; - case errSSLRecordOverflow: - case errSSLPeerRecordOverflow: - failf(data, "A record overflow occurred"); - break; - - /* Problem with zlib decompression */ - case errSSLPeerDecompressFail: - failf(data, "Decompression failed"); - break; - - /* Problem with access */ - case errSSLPeerAccessDenied: - failf(data, "Access was denied"); - break; - case errSSLPeerInsufficientSecurity: - failf(data, "There is insufficient security for this operation"); - break; - - /* These are all certificate problems with the server: */ - case errSSLXCertChainInvalid: - failf(data, "SSL certificate problem: Invalid certificate chain"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLUnknownRootCert: - failf(data, "SSL certificate problem: Untrusted root certificate"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLNoRootCert: - failf(data, "SSL certificate problem: No root certificate"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLCertNotYetValid: - failf(data, "SSL certificate problem: The certificate chain had a " - "certificate that is not yet valid"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLCertExpired: - case errSSLPeerCertExpired: - failf(data, "SSL certificate problem: Certificate chain had an " - "expired certificate"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLBadCert: - case errSSLPeerBadCert: - failf(data, "SSL certificate problem: Couldn't understand the server " - "certificate format"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLPeerUnsupportedCert: - failf(data, "SSL certificate problem: An unsupported certificate " - "format was encountered"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLPeerCertRevoked: - failf(data, "SSL certificate problem: The certificate was revoked"); - return CURLE_PEER_FAILED_VERIFICATION; - case errSSLPeerCertUnknown: - failf(data, "SSL certificate problem: The certificate is unknown"); - return CURLE_PEER_FAILED_VERIFICATION; - - /* These are all certificate problems with the client: */ - case errSecAuthFailed: - failf(data, "SSL authentication failed"); - break; - case errSSLPeerHandshakeFail: - failf(data, "SSL peer handshake failed, the server most likely " - "requires a client certificate to connect"); - break; - case errSSLPeerUnknownCA: - failf(data, "SSL server rejected the client certificate due to " - "the certificate being signed by an unknown certificate " - "authority"); - break; - - /* This error is raised if the server's cert didn't match the server's - host name: */ - case errSSLHostNameMismatch: - failf(data, "SSL certificate peer verification failed, the " - "certificate did not match \"%s\"\n", conn->host.dispname); - return CURLE_PEER_FAILED_VERIFICATION; - - /* Problem with SSL / TLS negotiation */ - case errSSLNegotiation: - failf(data, "Could not negotiate an SSL cipher suite with the server"); - break; - case errSSLBadConfiguration: - failf(data, "A configuration error occurred"); - break; - case errSSLProtocol: - failf(data, "SSL protocol error"); - break; - case errSSLPeerProtocolVersion: - failf(data, "A bad protocol version was encountered"); - break; - case errSSLPeerNoRenegotiation: - failf(data, "No renegotiation is allowed"); - break; - - /* Generic handshake errors: */ - case errSSLConnectionRefused: - failf(data, "Server dropped the connection during the SSL handshake"); - break; - case errSSLClosedAbort: - failf(data, "Server aborted the SSL handshake"); - break; - case errSSLClosedGraceful: - failf(data, "The connection closed gracefully"); - break; - case errSSLClosedNoNotify: - failf(data, "The server closed the session with no notification"); - break; - /* Sometimes paramErr happens with buggy ciphers: */ - case paramErr: - case errSSLInternal: - case errSSLPeerInternalError: - failf(data, "Internal SSL engine error encountered during the " - "SSL handshake"); - break; - case errSSLFatalAlert: - failf(data, "Fatal SSL engine error encountered during the SSL " - "handshake"); - break; - /* Unclassified error */ - case errSSLBufferOverflow: - failf(data, "An insufficient buffer was provided"); - break; - case errSSLIllegalParam: - failf(data, "An illegal parameter was encountered"); - break; - case errSSLModuleAttach: - failf(data, "Module attach failure"); - break; - case errSSLSessionNotFound: - failf(data, "An attempt to restore an unknown session failed"); - break; - case errSSLPeerExportRestriction: - failf(data, "An export restriction occurred"); - break; - case errSSLPeerUserCancelled: - failf(data, "The user canceled the operation"); - break; - case errSSLPeerUnexpectedMsg: - failf(data, "Peer rejected unexpected message"); - break; -#if CURL_BUILD_MAC_10_11 || CURL_BUILD_IOS_9 - /* Treaing non-fatal error as fatal like before */ - case errSSLClientHelloReceived: - failf(data, "A non-fatal result for providing a server name " - "indication"); - break; -#endif - - /* Error codes defined in the enum but should never be returned. - We list them here just in case. */ -#if CURL_BUILD_MAC_10_6 - /* Only returned when kSSLSessionOptionBreakOnCertRequested is set */ - case errSSLClientCertRequested: - failf(data, "Server requested a client certificate during the " - "handshake"); - return CURLE_SSL_CLIENTCERT; -#endif -#if CURL_BUILD_MAC_10_9 - /* Alias for errSSLLast, end of error range */ - case errSSLUnexpectedRecord: - failf(data, "Unexpected (skipped) record in DTLS"); - break; -#endif - default: - /* May also return codes listed in Security Framework Result Codes */ - failf(data, "Unknown SSL protocol error in connection to %s:%d", - hostname, err); - break; - } - return CURLE_SSL_CONNECT_ERROR; - } - else { - /* we have been connected fine, we're not waiting for anything else. */ - connssl->connecting_state = ssl_connect_3; - -#ifdef SECTRANSP_PINNEDPUBKEY - if(data->set.str[STRING_SSL_PINNEDPUBLICKEY]) { - CURLcode result = - pkp_pin_peer_pubkey(data, backend->ssl_ctx, - data->set.str[STRING_SSL_PINNEDPUBLICKEY]); - if(result) { - failf(data, "SSL: public key does not match pinned public key"); - return result; - } - } -#endif /* SECTRANSP_PINNEDPUBKEY */ - - /* Informational message */ - (void)SSLGetNegotiatedCipher(backend->ssl_ctx, &cipher); - (void)SSLGetNegotiatedProtocolVersion(backend->ssl_ctx, &protocol); - switch(protocol) { - case kSSLProtocol2: - infof(data, "SSL 2.0 connection using %s", - TLSCipherNameForNumber(cipher)); - break; - case kSSLProtocol3: - infof(data, "SSL 3.0 connection using %s", - TLSCipherNameForNumber(cipher)); - break; - case kTLSProtocol1: - infof(data, "TLS 1.0 connection using %s", - TLSCipherNameForNumber(cipher)); - break; -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - case kTLSProtocol11: - infof(data, "TLS 1.1 connection using %s", - TLSCipherNameForNumber(cipher)); - break; - case kTLSProtocol12: - infof(data, "TLS 1.2 connection using %s", - TLSCipherNameForNumber(cipher)); - break; -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ -#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 - case kTLSProtocol13: - infof(data, "TLS 1.3 connection using %s", - TLSCipherNameForNumber(cipher)); - break; -#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */ - default: - infof(data, "Unknown protocol connection"); - break; - } - -#if(CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1 - if(conn->bits.tls_enable_alpn) { - if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) { - CFArrayRef alpnArr = NULL; - CFStringRef chosenProtocol = NULL; - err = SSLCopyALPNProtocols(backend->ssl_ctx, &alpnArr); - - if(err == noErr && alpnArr && CFArrayGetCount(alpnArr) >= 1) - chosenProtocol = CFArrayGetValueAtIndex(alpnArr, 0); - -#ifdef USE_HTTP2 - if(chosenProtocol && - !CFStringCompare(chosenProtocol, CFSTR(ALPN_H2), 0)) { - conn->alpn = CURL_HTTP_VERSION_2; - } - else -#endif - if(chosenProtocol && - !CFStringCompare(chosenProtocol, CFSTR(ALPN_HTTP_1_1), 0)) { - conn->alpn = CURL_HTTP_VERSION_1_1; - } - else - infof(data, VTLS_INFOF_NO_ALPN); - - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - - /* chosenProtocol is a reference to the string within alpnArr - and doesn't need to be freed separately */ - if(alpnArr) - CFRelease(alpnArr); - } - } -#endif - - return CURLE_OK; - } -} - -static CURLcode -add_cert_to_certinfo(struct Curl_easy *data, - SecCertificateRef server_cert, - int idx) -{ - CURLcode result = CURLE_OK; - const char *beg; - const char *end; - CFDataRef cert_data = SecCertificateCopyData(server_cert); - - if(!cert_data) - return CURLE_PEER_FAILED_VERIFICATION; - - beg = (const char *)CFDataGetBytePtr(cert_data); - end = beg + CFDataGetLength(cert_data); - result = Curl_extract_certinfo(data, idx, beg, end); - CFRelease(cert_data); - return result; -} - -static CURLcode -collect_server_cert_single(struct Curl_easy *data, - SecCertificateRef server_cert, - CFIndex idx) -{ - CURLcode result = CURLE_OK; -#ifndef CURL_DISABLE_VERBOSE_STRINGS - if(data->set.verbose) { - char *certp; - result = CopyCertSubject(data, server_cert, &certp); - if(!result) { - infof(data, "Server certificate: %s", certp); - free(certp); - } - } -#endif - if(data->set.ssl.certinfo) - result = add_cert_to_certinfo(data, server_cert, (int)idx); - return result; -} - -/* This should be called during step3 of the connection at the earliest */ -static CURLcode -collect_server_cert(struct Curl_easy *data, - struct connectdata *conn, - int sockindex) -{ -#ifndef CURL_DISABLE_VERBOSE_STRINGS - const bool show_verbose_server_cert = data->set.verbose; -#else - const bool show_verbose_server_cert = false; -#endif - CURLcode result = data->set.ssl.certinfo ? - CURLE_PEER_FAILED_VERIFICATION : CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - CFArrayRef server_certs = NULL; - SecCertificateRef server_cert; - OSStatus err; - CFIndex i, count; - SecTrustRef trust = NULL; - - DEBUGASSERT(backend); - - if(!show_verbose_server_cert && !data->set.ssl.certinfo) - return CURLE_OK; - - if(!backend->ssl_ctx) - return result; - -#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS -#if CURL_BUILD_IOS -#pragma unused(server_certs) - err = SSLCopyPeerTrust(backend->ssl_ctx, &trust); - /* For some reason, SSLCopyPeerTrust() can return noErr and yet return - a null trust, so be on guard for that: */ - if(err == noErr && trust) { - count = SecTrustGetCertificateCount(trust); - if(data->set.ssl.certinfo) - result = Curl_ssl_init_certinfo(data, (int)count); - for(i = 0L ; !result && (i < count) ; i++) { - server_cert = SecTrustGetCertificateAtIndex(trust, i); - result = collect_server_cert_single(data, server_cert, i); - } - CFRelease(trust); - } -#else - /* SSLCopyPeerCertificates() is deprecated as of Mountain Lion. - The function SecTrustGetCertificateAtIndex() is officially present - in Lion, but it is unfortunately also present in Snow Leopard as - private API and doesn't work as expected. So we have to look for - a different symbol to make sure this code is only executed under - Lion or later. */ - if(SecTrustCopyPublicKey) { -#pragma unused(server_certs) - err = SSLCopyPeerTrust(backend->ssl_ctx, &trust); - /* For some reason, SSLCopyPeerTrust() can return noErr and yet return - a null trust, so be on guard for that: */ - if(err == noErr && trust) { - count = SecTrustGetCertificateCount(trust); - if(data->set.ssl.certinfo) - result = Curl_ssl_init_certinfo(data, (int)count); - for(i = 0L ; !result && (i < count) ; i++) { - server_cert = SecTrustGetCertificateAtIndex(trust, i); - result = collect_server_cert_single(data, server_cert, i); - } - CFRelease(trust); - } - } - else { -#if CURL_SUPPORT_MAC_10_8 - err = SSLCopyPeerCertificates(backend->ssl_ctx, &server_certs); - /* Just in case SSLCopyPeerCertificates() returns null too... */ - if(err == noErr && server_certs) { - count = CFArrayGetCount(server_certs); - if(data->set.ssl.certinfo) - result = Curl_ssl_init_certinfo(data, (int)count); - for(i = 0L ; !result && (i < count) ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, - i); - result = collect_server_cert_single(data, server_cert, i); - } - CFRelease(server_certs); - } -#endif /* CURL_SUPPORT_MAC_10_8 */ - } -#endif /* CURL_BUILD_IOS */ -#else -#pragma unused(trust) - err = SSLCopyPeerCertificates(backend->ssl_ctx, &server_certs); - if(err == noErr) { - count = CFArrayGetCount(server_certs); - if(data->set.ssl.certinfo) - result = Curl_ssl_init_certinfo(data, (int)count); - for(i = 0L ; !result && (i < count) ; i++) { - server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, i); - result = collect_server_cert_single(data, server_cert, i); - } - CFRelease(server_certs); - } -#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */ - return result; -} - -static CURLcode -sectransp_connect_step3(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - - /* There is no step 3! - * Well, okay, let's collect server certificates, and if verbose mode is on, - * let's print the details of the server certificates. */ - const CURLcode result = collect_server_cert(data, conn, sockindex); - if(result) - return result; - - connssl->connecting_state = ssl_connect_done; - return CURLE_OK; -} - -static Curl_recv sectransp_recv; -static Curl_send sectransp_send; - -static CURLcode -sectransp_connect_common(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode result; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - result = sectransp_connect_step1(data, conn, sockindex); - if(result) - return result; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading || - connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking ? 0 : timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if this - * connection is done nonblocking and this loop would execute again. This - * permits the owner of a multi handle to abort a connection attempt - * before step2 has completed while ensuring that a client using select() - * or epoll() will always have a valid fdset to wait on. - */ - result = sectransp_connect_step2(data, conn, sockindex); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return result; - - } /* repeat step2 until all transactions are done. */ - - - if(ssl_connect_3 == connssl->connecting_state) { - result = sectransp_connect_step3(data, conn, sockindex); - if(result) - return result; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = sectransp_recv; - conn->send[sockindex] = sectransp_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - -static CURLcode sectransp_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - return sectransp_connect_common(data, conn, sockindex, TRUE, done); -} - -static CURLcode sectransp_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = sectransp_connect_common(data, conn, sockindex, FALSE, &done); - - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static void sectransp_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - - (void) data; - - DEBUGASSERT(backend); - - if(backend->ssl_ctx) { - (void)SSLClose(backend->ssl_ctx); -#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS - if(SSLCreateContext) - CFRelease(backend->ssl_ctx); -#if CURL_SUPPORT_MAC_10_8 - else - (void)SSLDisposeContext(backend->ssl_ctx); -#endif /* CURL_SUPPORT_MAC_10_8 */ -#else - (void)SSLDisposeContext(backend->ssl_ctx); -#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */ - backend->ssl_ctx = NULL; - } - backend->ssl_sockfd = 0; -} - -static int sectransp_shutdown(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - ssize_t nread; - int what; - int rc; - char buf[120]; - int loop = 10; /* avoid getting stuck */ - - DEBUGASSERT(backend); - - if(!backend->ssl_ctx) - return 0; - -#ifndef CURL_DISABLE_FTP - if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE) - return 0; -#endif - - sectransp_close(data, conn, sockindex); - - rc = 0; - - what = SOCKET_READABLE(conn->sock[sockindex], SSL_SHUTDOWN_TIMEOUT); - - while(loop--) { - if(what < 0) { - /* anything that gets here is fatally bad */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - rc = -1; - break; - } - - if(!what) { /* timeout */ - failf(data, "SSL shutdown timeout"); - break; - } - - /* Something to read, let's do it and hope that it is the close - notify alert from the server. No way to SSL_Read now, so use read(). */ - - nread = read(conn->sock[sockindex], buf, sizeof(buf)); - - if(nread < 0) { - char buffer[STRERROR_LEN]; - failf(data, "read: %s", - Curl_strerror(errno, buffer, sizeof(buffer))); - rc = -1; - } - - if(nread <= 0) - break; - - what = SOCKET_READABLE(conn->sock[sockindex], 0); - } - - return rc; -} - -static void sectransp_session_free(void *ptr) -{ - /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a - cached session ID inside the Security framework. There is a private - function that does this, but I don't want to have to explain to you why I - got your application rejected from the App Store due to the use of a - private API, so the best we can do is free up our own char array that we - created way back in sectransp_connect_step1... */ - Curl_safefree(ptr); -} - -static size_t sectransp_version(char *buffer, size_t size) -{ - return msnprintf(buffer, size, "SecureTransport"); -} - -/* - * This function uses SSLGetSessionState to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -static int sectransp_check_cxn(struct connectdata *conn) -{ - struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; - struct ssl_backend_data *backend = connssl->backend; - OSStatus err; - SSLSessionState state; - - DEBUGASSERT(backend); - - if(backend->ssl_ctx) { - err = SSLGetSessionState(backend->ssl_ctx, &state); - if(err == noErr) - return state == kSSLConnected || state == kSSLHandshake; - return -1; - } - return 0; -} - -static bool sectransp_data_pending(const struct connectdata *conn, - int connindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[connindex]; - struct ssl_backend_data *backend = connssl->backend; - OSStatus err; - size_t buffer; - - DEBUGASSERT(backend); - - if(backend->ssl_ctx) { /* SSL is in use */ - err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer); - if(err == noErr) - return buffer > 0UL; - return false; - } - else - return false; -} - -static CURLcode sectransp_random(struct Curl_easy *data UNUSED_PARAM, - unsigned char *entropy, size_t length) -{ - /* arc4random_buf() isn't available on cats older than Lion, so let's - do this manually for the benefit of the older cats. */ - size_t i; - u_int32_t random_number = 0; - - (void)data; - - for(i = 0 ; i < length ; i++) { - if(i % sizeof(u_int32_t) == 0) - random_number = arc4random(); - entropy[i] = random_number & 0xFF; - random_number >>= 8; - } - i = random_number = 0; - return CURLE_OK; -} - -static CURLcode sectransp_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum, /* output */ - size_t sha256len) -{ - assert(sha256len >= CURL_SHA256_DIGEST_LENGTH); - (void)CC_SHA256(tmp, (CC_LONG)tmplen, sha256sum); - return CURLE_OK; -} - -static bool sectransp_false_start(void) -{ -#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 - if(SSLSetSessionOption) - return TRUE; -#endif - return FALSE; -} - -static ssize_t sectransp_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - size_t processed = 0UL; - OSStatus err; - - DEBUGASSERT(backend); - - /* The SSLWrite() function works a little differently than expected. The - fourth argument (processed) is currently documented in Apple's - documentation as: "On return, the length, in bytes, of the data actually - written." - - Now, one could interpret that as "written to the socket," but actually, - it returns the amount of data that was written to a buffer internal to - the SSLContextRef instead. So it's possible for SSLWrite() to return - errSSLWouldBlock and a number of bytes "written" because those bytes were - encrypted and written to a buffer, not to the socket. - - So if this happens, then we need to keep calling SSLWrite() over and - over again with no new data until it quits returning errSSLWouldBlock. */ - - /* Do we have buffered data to write from the last time we were called? */ - if(backend->ssl_write_buffered_length) { - /* Write the buffered data: */ - err = SSLWrite(backend->ssl_ctx, NULL, 0UL, &processed); - switch(err) { - case noErr: - /* processed is always going to be 0 because we didn't write to - the buffer, so return how much was written to the socket */ - processed = backend->ssl_write_buffered_length; - backend->ssl_write_buffered_length = 0UL; - break; - case errSSLWouldBlock: /* argh, try again */ - *curlcode = CURLE_AGAIN; - return -1L; - default: - failf(data, "SSLWrite() returned error %d", err); - *curlcode = CURLE_SEND_ERROR; - return -1L; - } - } - else { - /* We've got new data to write: */ - err = SSLWrite(backend->ssl_ctx, mem, len, &processed); - if(err != noErr) { - switch(err) { - case errSSLWouldBlock: - /* Data was buffered but not sent, we have to tell the caller - to try sending again, and remember how much was buffered */ - backend->ssl_write_buffered_length = len; - *curlcode = CURLE_AGAIN; - return -1L; - default: - failf(data, "SSLWrite() returned error %d", err); - *curlcode = CURLE_SEND_ERROR; - return -1L; - } - } - } - return (ssize_t)processed; -} - -static ssize_t sectransp_recv(struct Curl_easy *data, - int num, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[num]; - struct ssl_backend_data *backend = connssl->backend; - size_t processed = 0UL; - OSStatus err; - - DEBUGASSERT(backend); - - again: - err = SSLRead(backend->ssl_ctx, buf, buffersize, &processed); - - if(err != noErr) { - switch(err) { - case errSSLWouldBlock: /* return how much we read (if anything) */ - if(processed) - return (ssize_t)processed; - *curlcode = CURLE_AGAIN; - return -1L; - break; - - /* errSSLClosedGraceful - server gracefully shut down the SSL session - errSSLClosedNoNotify - server hung up on us instead of sending a - closure alert notice, read() is returning 0 - Either way, inform the caller that the server disconnected. */ - case errSSLClosedGraceful: - case errSSLClosedNoNotify: - *curlcode = CURLE_OK; - return -1L; - break; - - /* The below is errSSLPeerAuthCompleted; it's not defined in - Leopard's headers */ - case -9841: - 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; - } - goto again; - default: - failf(data, "SSLRead() return error %d", err); - *curlcode = CURLE_RECV_ERROR; - return -1L; - break; - } - } - return (ssize_t)processed; -} - -static void *sectransp_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - (void)info; - DEBUGASSERT(backend); - return backend->ssl_ctx; -} - -const struct Curl_ssl Curl_ssl_sectransp = { - { CURLSSLBACKEND_SECURETRANSPORT, "secure-transport" }, /* info */ - - SSLSUPP_CAINFO_BLOB | - SSLSUPP_CERTINFO | -#ifdef SECTRANSP_PINNEDPUBKEY - SSLSUPP_PINNEDPUBKEY, -#else - 0, -#endif /* SECTRANSP_PINNEDPUBKEY */ - - sizeof(struct ssl_backend_data), - - Curl_none_init, /* init */ - Curl_none_cleanup, /* cleanup */ - sectransp_version, /* version */ - sectransp_check_cxn, /* check_cxn */ - sectransp_shutdown, /* shutdown */ - sectransp_data_pending, /* data_pending */ - sectransp_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - sectransp_connect, /* connect */ - sectransp_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - sectransp_get_internals, /* get_internals */ - sectransp_close, /* close_one */ - Curl_none_close_all, /* close_all */ - sectransp_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - sectransp_false_start, /* false_start */ - sectransp_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -#endif /* USE_SECTRANSP */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2012 - 2017, Nick Zitzmann, <nickzman@gmail.com>.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for all iOS and macOS SecureTransport-specific code for the
+ * TLS/SSL layer. No code but vtls.c should ever call or use these functions.
+ */
+
+#include "curl_setup.h"
+
+#include "urldata.h" /* for the Curl_easy definition */
+#include "curl_base64.h"
+#include "strtok.h"
+#include "multiif.h"
+#include "strcase.h"
+#include "x509asn1.h"
+#include "strerror.h"
+
+#ifdef USE_SECTRANSP
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
+#endif /* __clang__ */
+
+#include <limits.h>
+
+#include <Security/Security.h>
+/* For some reason, when building for iOS, the omnibus header above does
+ * not include SecureTransport.h as of iOS SDK 5.1. */
+#include <Security/SecureTransport.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CommonCrypto/CommonDigest.h>
+
+/* The Security framework has changed greatly between iOS and different macOS
+ versions, and we will try to support as many of them as we can (back to
+ Leopard and iOS 5) by using macros and weak-linking.
+
+ In general, you want to build this using the most recent OS SDK, since some
+ features require curl to be built against the latest SDK. TLS 1.1 and 1.2
+ support, for instance, require the macOS 10.8 SDK or later. TLS 1.3
+ requires the macOS 10.13 or iOS 11 SDK or later. */
+#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+
+#if MAC_OS_X_VERSION_MAX_ALLOWED < 1050
+#error "The Secure Transport back-end requires Leopard or later."
+#endif /* MAC_OS_X_VERSION_MAX_ALLOWED < 1050 */
+
+#define CURL_BUILD_IOS 0
+#define CURL_BUILD_IOS_7 0
+#define CURL_BUILD_IOS_9 0
+#define CURL_BUILD_IOS_11 0
+#define CURL_BUILD_IOS_13 0
+#define CURL_BUILD_MAC 1
+/* This is the maximum API level we are allowed to use when building: */
+#define CURL_BUILD_MAC_10_5 MAC_OS_X_VERSION_MAX_ALLOWED >= 1050
+#define CURL_BUILD_MAC_10_6 MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
+#define CURL_BUILD_MAC_10_7 MAC_OS_X_VERSION_MAX_ALLOWED >= 1070
+#define CURL_BUILD_MAC_10_8 MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
+#define CURL_BUILD_MAC_10_9 MAC_OS_X_VERSION_MAX_ALLOWED >= 1090
+#define CURL_BUILD_MAC_10_11 MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
+#define CURL_BUILD_MAC_10_13 MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
+#define CURL_BUILD_MAC_10_15 MAC_OS_X_VERSION_MAX_ALLOWED >= 101500
+/* These macros mean "the following code is present to allow runtime backward
+ compatibility with at least this cat or earlier":
+ (You set this at build-time using the compiler command line option
+ "-mmacosx-version-min.") */
+#define CURL_SUPPORT_MAC_10_5 MAC_OS_X_VERSION_MIN_REQUIRED <= 1050
+#define CURL_SUPPORT_MAC_10_6 MAC_OS_X_VERSION_MIN_REQUIRED <= 1060
+#define CURL_SUPPORT_MAC_10_7 MAC_OS_X_VERSION_MIN_REQUIRED <= 1070
+#define CURL_SUPPORT_MAC_10_8 MAC_OS_X_VERSION_MIN_REQUIRED <= 1080
+#define CURL_SUPPORT_MAC_10_9 MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
+
+#elif TARGET_OS_EMBEDDED || TARGET_OS_IPHONE
+#define CURL_BUILD_IOS 1
+#define CURL_BUILD_IOS_7 __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
+#define CURL_BUILD_IOS_9 __IPHONE_OS_VERSION_MAX_ALLOWED >= 90000
+#define CURL_BUILD_IOS_11 __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
+#define CURL_BUILD_IOS_13 __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
+#define CURL_BUILD_MAC 0
+#define CURL_BUILD_MAC_10_5 0
+#define CURL_BUILD_MAC_10_6 0
+#define CURL_BUILD_MAC_10_7 0
+#define CURL_BUILD_MAC_10_8 0
+#define CURL_BUILD_MAC_10_9 0
+#define CURL_BUILD_MAC_10_11 0
+#define CURL_BUILD_MAC_10_13 0
+#define CURL_BUILD_MAC_10_15 0
+#define CURL_SUPPORT_MAC_10_5 0
+#define CURL_SUPPORT_MAC_10_6 0
+#define CURL_SUPPORT_MAC_10_7 0
+#define CURL_SUPPORT_MAC_10_8 0
+#define CURL_SUPPORT_MAC_10_9 0
+
+#else
+#error "The Secure Transport back-end requires iOS or macOS."
+#endif /* (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) */
+
+#if CURL_BUILD_MAC
+#include <sys/sysctl.h>
+#endif /* CURL_BUILD_MAC */
+
+#include "sendf.h"
+#include "inet_pton.h"
+#include "connect.h"
+#include "select.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "sectransp.h"
+#include "curl_printf.h"
+#include "strdup.h"
+
+#include "curl_memory.h"
+/* The last #include file should be: */
+#include "memdebug.h"
+
+/* From MacTypes.h (which we can't include because it isn't present in iOS: */
+#define ioErr -36
+#define paramErr -50
+
+struct ssl_backend_data {
+ SSLContextRef ssl_ctx;
+ bool ssl_direction; /* true if writing, false if reading */
+ size_t ssl_write_buffered_length;
+};
+
+struct st_cipher {
+ const char *name; /* Cipher suite IANA name. It starts with "TLS_" prefix */
+ const char *alias_name; /* Alias name is the same as OpenSSL cipher name */
+ SSLCipherSuite num; /* Cipher suite code/number defined in IANA registry */
+ bool weak; /* Flag to mark cipher as weak based on previous implementation
+ of Secure Transport back-end by CURL */
+};
+
+/* Macro to initialize st_cipher data structure: stringify id to name, cipher
+ number/id, 'weak' suite flag
+ */
+#define CIPHER_DEF(num, alias, weak) \
+ { #num, alias, num, weak }
+
+/*
+ Macro to initialize st_cipher data structure with name, code (IANA cipher
+ number/id value), and 'weak' suite flag. The first 28 cipher suite numbers
+ have the same IANA code for both SSL and TLS standards: numbers 0x0000 to
+ 0x001B. They have different names though. The first 4 letters of the cipher
+ suite name are the protocol name: "SSL_" or "TLS_", rest of the IANA name is
+ the same for both SSL and TLS cipher suite name.
+ The second part of the problem is that macOS/iOS SDKs don't define all TLS
+ codes but only 12 of them. The SDK defines all SSL codes though, i.e. SSL_NUM
+ constant is always defined for those 28 ciphers while TLS_NUM is defined only
+ for 12 of the first 28 ciphers. Those 12 TLS cipher codes match to
+ corresponding SSL enum value and represent the same cipher suite. Therefore
+ we'll use the SSL enum value for those cipher suites because it is defined
+ for all 28 of them.
+ We make internal data consistent and based on TLS names, i.e. all st_cipher
+ item names start with the "TLS_" prefix.
+ Summarizing all the above, those 28 first ciphers are presented in our table
+ with both TLS and SSL names. Their cipher numbers are assigned based on the
+ SDK enum value for the SSL cipher, which matches to IANA TLS number.
+ */
+#define CIPHER_DEF_SSLTLS(num_wo_prefix, alias, weak) \
+ { "TLS_" #num_wo_prefix, alias, SSL_##num_wo_prefix, weak }
+
+/*
+ Cipher suites were marked as weak based on the following:
+ RC4 encryption - rfc7465, the document contains a list of deprecated ciphers.
+ Marked in the code below as weak.
+ RC2 encryption - many mentions, was found vulnerable to a relatively easy
+ attack https://link.springer.com/chapter/10.1007%2F3-540-69710-1_14
+ Marked in the code below as weak.
+ DES and IDEA encryption - rfc5469, has a list of deprecated ciphers.
+ Marked in the code below as weak.
+ Anonymous Diffie-Hellman authentication and anonymous elliptic curve
+ Diffie-Hellman - vulnerable to a man-in-the-middle attack. Deprecated by
+ RFC 4346 aka TLS 1.1 (section A.5, page 60)
+ Null bulk encryption suites - not encrypted communication
+ Export ciphers, i.e. ciphers with restrictions to be used outside the US for
+ software exported to some countries, they were excluded from TLS 1.1
+ version. More precisely, they were noted as ciphers which MUST NOT be
+ negotiated in RFC 4346 aka TLS 1.1 (section A.5, pages 60 and 61).
+ All of those filters were considered weak because they contain a weak
+ algorithm like DES, RC2 or RC4, and already considered weak by other
+ criteria.
+ 3DES - NIST deprecated it and is going to retire it by 2023
+ https://csrc.nist.gov/News/2017/Update-to-Current-Use-and-Deprecation-of-TDEA
+ OpenSSL https://www.openssl.org/blog/blog/2016/08/24/sweet32/ also
+ deprecated those ciphers. Some other libraries also consider it
+ vulnerable or at least not strong enough.
+
+ CBC ciphers are vulnerable with SSL3.0 and TLS1.0:
+ https://www.cisco.com/c/en/us/support/docs/security/email-security-appliance
+ /118518-technote-esa-00.html
+ We don't take care of this issue because it is resolved by later TLS
+ versions and for us, it requires more complicated checks, we need to
+ check a protocol version also. Vulnerability doesn't look very critical
+ and we do not filter out those cipher suites.
+ */
+
+#define CIPHER_WEAK_NOT_ENCRYPTED TRUE
+#define CIPHER_WEAK_RC_ENCRYPTION TRUE
+#define CIPHER_WEAK_DES_ENCRYPTION TRUE
+#define CIPHER_WEAK_IDEA_ENCRYPTION TRUE
+#define CIPHER_WEAK_ANON_AUTH TRUE
+#define CIPHER_WEAK_3DES_ENCRYPTION TRUE
+#define CIPHER_STRONG_ENOUGH FALSE
+
+/* Please do not change the order of the first ciphers available for SSL.
+ Do not insert and do not delete any of them. Code below
+ depends on their order and continuity.
+ If you add a new cipher, please maintain order by number, i.e.
+ insert in between existing items to appropriate place based on
+ cipher suite IANA number
+*/
+const static struct st_cipher ciphertable[] = {
+ /* SSL version 3.0 and initial TLS 1.0 cipher suites.
+ Defined since SDK 10.2.8 */
+ CIPHER_DEF_SSLTLS(NULL_WITH_NULL_NULL, /* 0x0000 */
+ NULL,
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF_SSLTLS(RSA_WITH_NULL_MD5, /* 0x0001 */
+ "NULL-MD5",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF_SSLTLS(RSA_WITH_NULL_SHA, /* 0x0002 */
+ "NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC4_40_MD5, /* 0x0003 */
+ "EXP-RC4-MD5",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_MD5, /* 0x0004 */
+ "RC4-MD5",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(RSA_WITH_RC4_128_SHA, /* 0x0005 */
+ "RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_RC2_CBC_40_MD5, /* 0x0006 */
+ "EXP-RC2-CBC-MD5",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(RSA_WITH_IDEA_CBC_SHA, /* 0x0007 */
+ "IDEA-CBC-SHA",
+ CIPHER_WEAK_IDEA_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0008 */
+ "EXP-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(RSA_WITH_DES_CBC_SHA, /* 0x0009 */
+ "DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(RSA_WITH_3DES_EDE_CBC_SHA, /* 0x000A */
+ "DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DH_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x000B */
+ "EXP-DH-DSS-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DH_DSS_WITH_DES_CBC_SHA, /* 0x000C */
+ "DH-DSS-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DH_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x000D */
+ "DH-DSS-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DH_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x000E */
+ "EXP-DH-RSA-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DH_RSA_WITH_DES_CBC_SHA, /* 0x000F */
+ "DH-RSA-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0010 */
+ "DH-RSA-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, /* 0x0011 */
+ "EXP-EDH-DSS-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DHE_DSS_WITH_DES_CBC_SHA, /* 0x0012 */
+ "EDH-DSS-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DHE_DSS_WITH_3DES_EDE_CBC_SHA, /* 0x0013 */
+ "DHE-DSS-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, /* 0x0014 */
+ "EXP-EDH-RSA-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DHE_RSA_WITH_DES_CBC_SHA, /* 0x0015 */
+ "EDH-RSA-DES-CBC-SHA",
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0x0016 */
+ "DHE-RSA-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_RC4_40_MD5, /* 0x0017 */
+ "EXP-ADH-RC4-MD5",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF_SSLTLS(DH_anon_WITH_RC4_128_MD5, /* 0x0018 */
+ "ADH-RC4-MD5",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF_SSLTLS(DH_anon_EXPORT_WITH_DES40_CBC_SHA, /* 0x0019 */
+ "EXP-ADH-DES-CBC-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF_SSLTLS(DH_anon_WITH_DES_CBC_SHA, /* 0x001A */
+ "ADH-DES-CBC-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF_SSLTLS(DH_anon_WITH_3DES_EDE_CBC_SHA, /* 0x001B */
+ "ADH-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_NULL_SHA, /* 0x001C */
+ NULL,
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA, /* 0x001D */
+ NULL,
+ CIPHER_STRONG_ENOUGH),
+
+#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7
+ /* RFC 4785 - Pre-Shared Key (PSK) Ciphersuites with NULL Encryption */
+ CIPHER_DEF(TLS_PSK_WITH_NULL_SHA, /* 0x002C */
+ "PSK-NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA, /* 0x002D */
+ "DHE-PSK-NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA, /* 0x002E */
+ "RSA-PSK-NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */
+
+ /* TLS addenda using AES, per RFC 3268. Defined since SDK 10.4u */
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA, /* 0x002F */
+ "AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA, /* 0x0030 */
+ "DH-DSS-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA, /* 0x0031 */
+ "DH-RSA-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA, /* 0x0032 */
+ "DHE-DSS-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA, /* 0x0033 */
+ "DHE-RSA-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA, /* 0x0034 */
+ "ADH-AES128-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA, /* 0x0035 */
+ "AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA, /* 0x0036 */
+ "DH-DSS-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA, /* 0x0037 */
+ "DH-RSA-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA, /* 0x0038 */
+ "DHE-DSS-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA, /* 0x0039 */
+ "DHE-RSA-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA, /* 0x003A */
+ "ADH-AES256-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ /* TLS 1.2 addenda, RFC 5246 */
+ /* Server provided RSA certificate for key exchange. */
+ CIPHER_DEF(TLS_RSA_WITH_NULL_SHA256, /* 0x003B */
+ "NULL-SHA256",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_CBC_SHA256, /* 0x003C */
+ "AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_CBC_SHA256, /* 0x003D */
+ "AES256-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ /* Server-authenticated (and optionally client-authenticated)
+ Diffie-Hellman. */
+ CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_CBC_SHA256, /* 0x003E */
+ "DH-DSS-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_CBC_SHA256, /* 0x003F */
+ "DH-RSA-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, /* 0x0040 */
+ "DHE-DSS-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+
+ /* TLS 1.2 addenda, RFC 5246 */
+ CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, /* 0x0067 */
+ "DHE-RSA-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_CBC_SHA256, /* 0x0068 */
+ "DH-DSS-AES256-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_CBC_SHA256, /* 0x0069 */
+ "DH-RSA-AES256-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, /* 0x006A */
+ "DHE-DSS-AES256-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, /* 0x006B */
+ "DHE-RSA-AES256-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_anon_WITH_AES_128_CBC_SHA256, /* 0x006C */
+ "ADH-AES128-SHA256",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF(TLS_DH_anon_WITH_AES_256_CBC_SHA256, /* 0x006D */
+ "ADH-AES256-SHA256",
+ CIPHER_WEAK_ANON_AUTH),
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+
+#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7
+ /* Addendum from RFC 4279, TLS PSK */
+ CIPHER_DEF(TLS_PSK_WITH_RC4_128_SHA, /* 0x008A */
+ "PSK-RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(TLS_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008B */
+ "PSK-3DES-EDE-CBC-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA, /* 0x008C */
+ "PSK-AES128-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA, /* 0x008D */
+ "PSK-AES256-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_RC4_128_SHA, /* 0x008E */
+ "DHE-PSK-RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x008F */
+ "DHE-PSK-3DES-EDE-CBC-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA, /* 0x0090 */
+ "DHE-PSK-AES128-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA, /* 0x0091 */
+ "DHE-PSK-AES256-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_RC4_128_SHA, /* 0x0092 */
+ "RSA-PSK-RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, /* 0x0093 */
+ "RSA-PSK-3DES-EDE-CBC-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA, /* 0x0094 */
+ "RSA-PSK-AES128-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA, /* 0x0095 */
+ "RSA-PSK-AES256-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */
+
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ /* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites
+ for TLS. */
+ CIPHER_DEF(TLS_RSA_WITH_AES_128_GCM_SHA256, /* 0x009C */
+ "AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_WITH_AES_256_GCM_SHA384, /* 0x009D */
+ "AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, /* 0x009E */
+ "DHE-RSA-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, /* 0x009F */
+ "DHE-RSA-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_RSA_WITH_AES_128_GCM_SHA256, /* 0x00A0 */
+ "DH-RSA-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_RSA_WITH_AES_256_GCM_SHA384, /* 0x00A1 */
+ "DH-RSA-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A2 */
+ "DHE-DSS-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A3 */
+ "DHE-DSS-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_DSS_WITH_AES_128_GCM_SHA256, /* 0x00A4 */
+ "DH-DSS-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_DSS_WITH_AES_256_GCM_SHA384, /* 0x00A5 */
+ "DH-DSS-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DH_anon_WITH_AES_128_GCM_SHA256, /* 0x00A6 */
+ "ADH-AES128-GCM-SHA256",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF(TLS_DH_anon_WITH_AES_256_GCM_SHA384, /* 0x00A7 */
+ "ADH-AES256-GCM-SHA384",
+ CIPHER_WEAK_ANON_AUTH),
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+
+#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7
+ /* RFC 5487 - PSK with SHA-256/384 and AES GCM */
+ CIPHER_DEF(TLS_PSK_WITH_AES_128_GCM_SHA256, /* 0x00A8 */
+ "PSK-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_PSK_WITH_AES_256_GCM_SHA384, /* 0x00A9 */
+ "PSK-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AA */
+ "DHE-PSK-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AB */
+ "DHE-PSK-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, /* 0x00AC */
+ "RSA-PSK-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, /* 0x00AD */
+ "RSA-PSK-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_PSK_WITH_AES_128_CBC_SHA256, /* 0x00AE */
+ "PSK-AES128-CBC-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_PSK_WITH_AES_256_CBC_SHA384, /* 0x00AF */
+ "PSK-AES256-CBC-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_PSK_WITH_NULL_SHA256, /* 0x00B0 */
+ "PSK-NULL-SHA256",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_PSK_WITH_NULL_SHA384, /* 0x00B1 */
+ "PSK-NULL-SHA384",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B2 */
+ "DHE-PSK-AES128-CBC-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B3 */
+ "DHE-PSK-AES256-CBC-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA256, /* 0x00B4 */
+ "DHE-PSK-NULL-SHA256",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_DHE_PSK_WITH_NULL_SHA384, /* 0x00B5 */
+ "DHE-PSK-NULL-SHA384",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, /* 0x00B6 */
+ "RSA-PSK-AES128-CBC-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, /* 0x00B7 */
+ "RSA-PSK-AES256-CBC-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA256, /* 0x00B8 */
+ "RSA-PSK-NULL-SHA256",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_RSA_PSK_WITH_NULL_SHA384, /* 0x00B9 */
+ "RSA-PSK-NULL-SHA384",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */
+
+ /* RFC 5746 - Secure Renegotiation. This is not a real suite,
+ it is a response to initiate negotiation again */
+ CIPHER_DEF(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, /* 0x00FF */
+ NULL,
+ CIPHER_STRONG_ENOUGH),
+
+#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11
+ /* TLS 1.3 standard cipher suites for ChaCha20+Poly1305.
+ Note: TLS 1.3 ciphersuites do not specify the key exchange
+ algorithm -- they only specify the symmetric ciphers.
+ Cipher alias name matches to OpenSSL cipher name, and for
+ TLS 1.3 ciphers */
+ CIPHER_DEF(TLS_AES_128_GCM_SHA256, /* 0x1301 */
+ NULL, /* The OpenSSL cipher name matches to the IANA name */
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_AES_256_GCM_SHA384, /* 0x1302 */
+ NULL, /* The OpenSSL cipher name matches to the IANA name */
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_CHACHA20_POLY1305_SHA256, /* 0x1303 */
+ NULL, /* The OpenSSL cipher name matches to the IANA name */
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_AES_128_CCM_SHA256, /* 0x1304 */
+ NULL, /* The OpenSSL cipher name matches to the IANA name */
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_AES_128_CCM_8_SHA256, /* 0x1305 */
+ NULL, /* The OpenSSL cipher name matches to the IANA name */
+ CIPHER_STRONG_ENOUGH),
+#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */
+
+#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS
+ /* ECDSA addenda, RFC 4492 */
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_NULL_SHA, /* 0xC001 */
+ "ECDH-ECDSA-NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_RC4_128_SHA, /* 0xC002 */
+ "ECDH-ECDSA-RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC003 */
+ "ECDH-ECDSA-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC004 */
+ "ECDH-ECDSA-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC005 */
+ "ECDH-ECDSA-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_NULL_SHA, /* 0xC006 */
+ "ECDHE-ECDSA-NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, /* 0xC007 */
+ "ECDHE-ECDSA-RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, /* 0xC008 */
+ "ECDHE-ECDSA-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, /* 0xC009 */
+ "ECDHE-ECDSA-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, /* 0xC00A */
+ "ECDHE-ECDSA-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_NULL_SHA, /* 0xC00B */
+ "ECDH-RSA-NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_RC4_128_SHA, /* 0xC00C */
+ "ECDH-RSA-RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC00D */
+ "ECDH-RSA-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, /* 0xC00E */
+ "ECDH-RSA-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, /* 0xC00F */
+ "ECDH-RSA-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_NULL_SHA, /* 0xC010 */
+ "ECDHE-RSA-NULL-SHA",
+ CIPHER_WEAK_NOT_ENCRYPTED),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_RC4_128_SHA, /* 0xC011 */
+ "ECDHE-RSA-RC4-SHA",
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, /* 0xC012 */
+ "ECDHE-RSA-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, /* 0xC013 */
+ "ECDHE-RSA-AES128-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, /* 0xC014 */
+ "ECDHE-RSA-AES256-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_anon_WITH_NULL_SHA, /* 0xC015 */
+ "AECDH-NULL-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF(TLS_ECDH_anon_WITH_RC4_128_SHA, /* 0xC016 */
+ "AECDH-RC4-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF(TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA, /* 0xC017 */
+ "AECDH-DES-CBC3-SHA",
+ CIPHER_WEAK_3DES_ENCRYPTION),
+ CIPHER_DEF(TLS_ECDH_anon_WITH_AES_128_CBC_SHA, /* 0xC018 */
+ "AECDH-AES128-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+ CIPHER_DEF(TLS_ECDH_anon_WITH_AES_256_CBC_SHA, /* 0xC019 */
+ "AECDH-AES256-SHA",
+ CIPHER_WEAK_ANON_AUTH),
+#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */
+
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with
+ HMAC SHA-256/384. */
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC023 */
+ "ECDHE-ECDSA-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC024 */
+ "ECDHE-ECDSA-AES256-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, /* 0xC025 */
+ "ECDH-ECDSA-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, /* 0xC026 */
+ "ECDH-ECDSA-AES256-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, /* 0xC027 */
+ "ECDHE-RSA-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, /* 0xC028 */
+ "ECDHE-RSA-AES256-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, /* 0xC029 */
+ "ECDH-RSA-AES128-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, /* 0xC02A */
+ "ECDH-RSA-AES256-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with
+ SHA-256/384 and AES Galois Counter Mode (GCM) */
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02B */
+ "ECDHE-ECDSA-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02C */
+ "ECDHE-ECDSA-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, /* 0xC02D */
+ "ECDH-ECDSA-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, /* 0xC02E */
+ "ECDH-ECDSA-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, /* 0xC02F */
+ "ECDHE-RSA-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, /* 0xC030 */
+ "ECDHE-RSA-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, /* 0xC031 */
+ "ECDH-RSA-AES128-GCM-SHA256",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, /* 0xC032 */
+ "ECDH-RSA-AES256-GCM-SHA384",
+ CIPHER_STRONG_ENOUGH),
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+
+#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13
+ /* ECDHE_PSK Cipher Suites for Transport Layer Security (TLS), RFC 5489 */
+ CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, /* 0xC035 */
+ "ECDHE-PSK-AES128-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA, /* 0xC036 */
+ "ECDHE-PSK-AES256-CBC-SHA",
+ CIPHER_STRONG_ENOUGH),
+#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */
+
+#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11
+ /* Addenda from rfc 7905 ChaCha20-Poly1305 Cipher Suites for
+ Transport Layer Security (TLS). */
+ CIPHER_DEF(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA8 */
+ "ECDHE-RSA-CHACHA20-POLY1305",
+ CIPHER_STRONG_ENOUGH),
+ CIPHER_DEF(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCA9 */
+ "ECDHE-ECDSA-CHACHA20-POLY1305",
+ CIPHER_STRONG_ENOUGH),
+#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */
+
+#if CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13
+ /* ChaCha20-Poly1305 Cipher Suites for Transport Layer Security (TLS),
+ RFC 7905 */
+ CIPHER_DEF(TLS_PSK_WITH_CHACHA20_POLY1305_SHA256, /* 0xCCAB */
+ "PSK-CHACHA20-POLY1305",
+ CIPHER_STRONG_ENOUGH),
+#endif /* CURL_BUILD_MAC_10_15 || CURL_BUILD_IOS_13 */
+
+ /* Tags for SSL 2 cipher kinds which are not specified for SSL 3.
+ Defined since SDK 10.2.8 */
+ CIPHER_DEF(SSL_RSA_WITH_RC2_CBC_MD5, /* 0xFF80 */
+ NULL,
+ CIPHER_WEAK_RC_ENCRYPTION),
+ CIPHER_DEF(SSL_RSA_WITH_IDEA_CBC_MD5, /* 0xFF81 */
+ NULL,
+ CIPHER_WEAK_IDEA_ENCRYPTION),
+ CIPHER_DEF(SSL_RSA_WITH_DES_CBC_MD5, /* 0xFF82 */
+ NULL,
+ CIPHER_WEAK_DES_ENCRYPTION),
+ CIPHER_DEF(SSL_RSA_WITH_3DES_EDE_CBC_MD5, /* 0xFF83 */
+ NULL,
+ CIPHER_WEAK_3DES_ENCRYPTION),
+};
+
+#define NUM_OF_CIPHERS sizeof(ciphertable)/sizeof(ciphertable[0])
+
+
+/* pinned public key support tests */
+
+/* version 1 supports macOS 10.12+ and iOS 10+ */
+#if ((TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000) || \
+ (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200))
+#define SECTRANSP_PINNEDPUBKEY_V1 1
+#endif
+
+/* version 2 supports MacOSX 10.7+ */
+#if (!TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070)
+#define SECTRANSP_PINNEDPUBKEY_V2 1
+#endif
+
+#if defined(SECTRANSP_PINNEDPUBKEY_V1) || defined(SECTRANSP_PINNEDPUBKEY_V2)
+/* this backend supports CURLOPT_PINNEDPUBLICKEY */
+#define SECTRANSP_PINNEDPUBKEY 1
+#endif /* SECTRANSP_PINNEDPUBKEY */
+
+#ifdef SECTRANSP_PINNEDPUBKEY
+/* both new and old APIs return rsa keys missing the spki header (not DER) */
+static const unsigned char rsa4096SpkiHeader[] = {
+ 0x30, 0x82, 0x02, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+ 0x00, 0x03, 0x82, 0x02, 0x0f, 0x00};
+
+static const unsigned char rsa2048SpkiHeader[] = {
+ 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d,
+ 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+ 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05,
+ 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00};
+#ifdef SECTRANSP_PINNEDPUBKEY_V1
+/* the *new* version doesn't return DER encoded ecdsa certs like the old... */
+static const unsigned char ecDsaSecp256r1SpkiHeader[] = {
+ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
+ 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48,
+ 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+ 0x42, 0x00};
+
+static const unsigned char ecDsaSecp384r1SpkiHeader[] = {
+ 0x30, 0x76, 0x30, 0x10, 0x06, 0x07,
+ 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
+ 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04,
+ 0x00, 0x22, 0x03, 0x62, 0x00};
+#endif /* SECTRANSP_PINNEDPUBKEY_V1 */
+#endif /* SECTRANSP_PINNEDPUBKEY */
+
+static OSStatus bio_cf_in_read(SSLConnectionRef connection,
+ void *buf,
+ size_t *dataLength) /* IN/OUT */
+{
+ struct Curl_cfilter *cf = (struct Curl_cfilter *)connection;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nread;
+ CURLcode result;
+ OSStatus rtn = noErr;
+
+ DEBUGASSERT(data);
+ nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result);
+ if(nread < 0) {
+ switch(result) {
+ case CURLE_OK:
+ case CURLE_AGAIN:
+ rtn = errSSLWouldBlock;
+ backend->ssl_direction = false;
+ break;
+ default:
+ rtn = ioErr;
+ break;
+ }
+ nread = 0;
+ }
+ *dataLength = nread;
+ return rtn;
+}
+
+static OSStatus bio_cf_out_write(SSLConnectionRef connection,
+ const void *buf,
+ size_t *dataLength) /* IN/OUT */
+{
+ struct Curl_cfilter *cf = (struct Curl_cfilter *)connection;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nwritten;
+ CURLcode result;
+ OSStatus ortn = noErr;
+
+ DEBUGASSERT(data);
+ nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result);
+ if(nwritten <= 0) {
+ if(result == CURLE_AGAIN) {
+ ortn = errSSLWouldBlock;
+ backend->ssl_direction = true;
+ }
+ else {
+ ortn = ioErr;
+ }
+ nwritten = 0;
+ }
+ *dataLength = nwritten;
+ return ortn;
+}
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+CF_INLINE const char *TLSCipherNameForNumber(SSLCipherSuite cipher)
+{
+ /* The first ciphers in the ciphertable are continuous. Here we do small
+ optimization and instead of loop directly get SSL name by cipher number.
+ */
+ if(cipher <= SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA) {
+ return ciphertable[cipher].name;
+ }
+ /* Iterate through the rest of the ciphers */
+ for(size_t i = SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA + 1;
+ i < NUM_OF_CIPHERS;
+ ++i) {
+ if(ciphertable[i].num == cipher) {
+ return ciphertable[i].name;
+ }
+ }
+ return ciphertable[SSL_NULL_WITH_NULL_NULL].name;
+}
+#endif /* !CURL_DISABLE_VERBOSE_STRINGS */
+
+#if CURL_BUILD_MAC
+CF_INLINE void GetDarwinVersionNumber(int *major, int *minor)
+{
+ int mib[2];
+ char *os_version;
+ size_t os_version_len;
+ char *os_version_major, *os_version_minor;
+ char *tok_buf;
+
+ /* Get the Darwin kernel version from the kernel using sysctl(): */
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_OSRELEASE;
+ if(sysctl(mib, 2, NULL, &os_version_len, NULL, 0) == -1)
+ return;
+ os_version = malloc(os_version_len*sizeof(char));
+ if(!os_version)
+ return;
+ if(sysctl(mib, 2, os_version, &os_version_len, NULL, 0) == -1) {
+ free(os_version);
+ return;
+ }
+
+ /* Parse the version: */
+ os_version_major = strtok_r(os_version, ".", &tok_buf);
+ os_version_minor = strtok_r(NULL, ".", &tok_buf);
+ *major = atoi(os_version_major);
+ *minor = atoi(os_version_minor);
+ free(os_version);
+}
+#endif /* CURL_BUILD_MAC */
+
+/* Apple provides a myriad of ways of getting information about a certificate
+ into a string. Some aren't available under iOS or newer cats. So here's
+ a unified function for getting a string describing the certificate that
+ ought to work in all cats starting with Leopard. */
+CF_INLINE CFStringRef getsubject(SecCertificateRef cert)
+{
+ CFStringRef server_cert_summary = CFSTR("(null)");
+
+#if CURL_BUILD_IOS
+ /* iOS: There's only one way to do this. */
+ server_cert_summary = SecCertificateCopySubjectSummary(cert);
+#else
+#if CURL_BUILD_MAC_10_7
+ /* Lion & later: Get the long description if we can. */
+ if(SecCertificateCopyLongDescription)
+ server_cert_summary =
+ SecCertificateCopyLongDescription(NULL, cert, NULL);
+ else
+#endif /* CURL_BUILD_MAC_10_7 */
+#if CURL_BUILD_MAC_10_6
+ /* Snow Leopard: Get the certificate summary. */
+ if(SecCertificateCopySubjectSummary)
+ server_cert_summary = SecCertificateCopySubjectSummary(cert);
+ else
+#endif /* CURL_BUILD_MAC_10_6 */
+ /* Leopard is as far back as we go... */
+ (void)SecCertificateCopyCommonName(cert, &server_cert_summary);
+#endif /* CURL_BUILD_IOS */
+ return server_cert_summary;
+}
+
+static CURLcode CopyCertSubject(struct Curl_easy *data,
+ SecCertificateRef cert, char **certp)
+{
+ CFStringRef c = getsubject(cert);
+ CURLcode result = CURLE_OK;
+ const char *direct;
+ char *cbuf = NULL;
+ *certp = NULL;
+
+ if(!c) {
+ failf(data, "SSL: invalid CA certificate subject");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ /* If the subject is already available as UTF-8 encoded (ie 'direct') then
+ use that, else convert it. */
+ direct = CFStringGetCStringPtr(c, kCFStringEncodingUTF8);
+ if(direct) {
+ *certp = strdup(direct);
+ if(!*certp) {
+ failf(data, "SSL: out of memory");
+ result = CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ size_t cbuf_size = ((size_t)CFStringGetLength(c) * 4) + 1;
+ cbuf = calloc(cbuf_size, 1);
+ if(cbuf) {
+ if(!CFStringGetCString(c, cbuf, cbuf_size,
+ kCFStringEncodingUTF8)) {
+ failf(data, "SSL: invalid CA certificate subject");
+ result = CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else
+ /* pass back the buffer */
+ *certp = cbuf;
+ }
+ else {
+ failf(data, "SSL: couldn't allocate %zu bytes of memory", cbuf_size);
+ result = CURLE_OUT_OF_MEMORY;
+ }
+ }
+ if(result)
+ free(cbuf);
+ CFRelease(c);
+ return result;
+}
+
+#if CURL_SUPPORT_MAC_10_6
+/* The SecKeychainSearch API was deprecated in Lion, and using it will raise
+ deprecation warnings, so let's not compile this unless it's necessary: */
+static OSStatus CopyIdentityWithLabelOldSchool(char *label,
+ SecIdentityRef *out_c_a_k)
+{
+ OSStatus status = errSecItemNotFound;
+ SecKeychainAttributeList attr_list;
+ SecKeychainAttribute attr;
+ SecKeychainSearchRef search = NULL;
+ SecCertificateRef cert = NULL;
+
+ /* Set up the attribute list: */
+ attr_list.count = 1L;
+ attr_list.attr = &attr;
+
+ /* Set up our lone search criterion: */
+ attr.tag = kSecLabelItemAttr;
+ attr.data = label;
+ attr.length = (UInt32)strlen(label);
+
+ /* Start searching: */
+ status = SecKeychainSearchCreateFromAttributes(NULL,
+ kSecCertificateItemClass,
+ &attr_list,
+ &search);
+ if(status == noErr) {
+ status = SecKeychainSearchCopyNext(search,
+ (SecKeychainItemRef *)&cert);
+ if(status == noErr && cert) {
+ /* If we found a certificate, does it have a private key? */
+ status = SecIdentityCreateWithCertificate(NULL, cert, out_c_a_k);
+ CFRelease(cert);
+ }
+ }
+
+ if(search)
+ CFRelease(search);
+ return status;
+}
+#endif /* CURL_SUPPORT_MAC_10_6 */
+
+static OSStatus CopyIdentityWithLabel(char *label,
+ SecIdentityRef *out_cert_and_key)
+{
+ OSStatus status = errSecItemNotFound;
+
+#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS
+ CFArrayRef keys_list;
+ CFIndex keys_list_count;
+ CFIndex i;
+ CFStringRef common_name;
+
+ /* SecItemCopyMatching() was introduced in iOS and Snow Leopard.
+ kSecClassIdentity was introduced in Lion. If both exist, let's use them
+ to find the certificate. */
+ if(SecItemCopyMatching && kSecClassIdentity) {
+ CFTypeRef keys[5];
+ CFTypeRef values[5];
+ CFDictionaryRef query_dict;
+ CFStringRef label_cf = CFStringCreateWithCString(NULL, label,
+ kCFStringEncodingUTF8);
+
+ /* Set up our search criteria and expected results: */
+ values[0] = kSecClassIdentity; /* we want a certificate and a key */
+ keys[0] = kSecClass;
+ values[1] = kCFBooleanTrue; /* we want a reference */
+ keys[1] = kSecReturnRef;
+ values[2] = kSecMatchLimitAll; /* kSecMatchLimitOne would be better if the
+ * label matching below worked correctly */
+ keys[2] = kSecMatchLimit;
+ /* identity searches need a SecPolicyRef in order to work */
+ values[3] = SecPolicyCreateSSL(false, NULL);
+ keys[3] = kSecMatchPolicy;
+ /* match the name of the certificate (doesn't work in macOS 10.12.1) */
+ values[4] = label_cf;
+ keys[4] = kSecAttrLabel;
+ query_dict = CFDictionaryCreate(NULL, (const void **)keys,
+ (const void **)values, 5L,
+ &kCFCopyStringDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+ CFRelease(values[3]);
+
+ /* Do we have a match? */
+ status = SecItemCopyMatching(query_dict, (CFTypeRef *) &keys_list);
+
+ /* Because kSecAttrLabel matching doesn't work with kSecClassIdentity,
+ * we need to find the correct identity ourselves */
+ if(status == noErr) {
+ keys_list_count = CFArrayGetCount(keys_list);
+ *out_cert_and_key = NULL;
+ status = 1;
+ for(i = 0; i<keys_list_count; i++) {
+ OSStatus err = noErr;
+ SecCertificateRef cert = NULL;
+ SecIdentityRef identity =
+ (SecIdentityRef) CFArrayGetValueAtIndex(keys_list, i);
+ err = SecIdentityCopyCertificate(identity, &cert);
+ if(err == noErr) {
+ OSStatus copy_status = noErr;
+#if CURL_BUILD_IOS
+ common_name = SecCertificateCopySubjectSummary(cert);
+#elif CURL_BUILD_MAC_10_7
+ copy_status = SecCertificateCopyCommonName(cert, &common_name);
+#endif
+ if(copy_status == noErr &&
+ CFStringCompare(common_name, label_cf, 0) == kCFCompareEqualTo) {
+ CFRelease(cert);
+ CFRelease(common_name);
+ CFRetain(identity);
+ *out_cert_and_key = identity;
+ status = noErr;
+ break;
+ }
+ CFRelease(common_name);
+ }
+ CFRelease(cert);
+ }
+ }
+
+ if(keys_list)
+ CFRelease(keys_list);
+ CFRelease(query_dict);
+ CFRelease(label_cf);
+ }
+ else {
+#if CURL_SUPPORT_MAC_10_6
+ /* On Leopard and Snow Leopard, fall back to SecKeychainSearch. */
+ status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key);
+#endif /* CURL_SUPPORT_MAC_10_6 */
+ }
+#elif CURL_SUPPORT_MAC_10_6
+ /* For developers building on older cats, we have no choice but to fall back
+ to SecKeychainSearch. */
+ status = CopyIdentityWithLabelOldSchool(label, out_cert_and_key);
+#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */
+ return status;
+}
+
+static OSStatus CopyIdentityFromPKCS12File(const char *cPath,
+ const struct curl_blob *blob,
+ const char *cPassword,
+ SecIdentityRef *out_cert_and_key)
+{
+ OSStatus status = errSecItemNotFound;
+ CFURLRef pkcs_url = NULL;
+ CFStringRef password = cPassword ? CFStringCreateWithCString(NULL,
+ cPassword, kCFStringEncodingUTF8) : NULL;
+ CFDataRef pkcs_data = NULL;
+
+ /* We can import P12 files on iOS or OS X 10.7 or later: */
+ /* These constants are documented as having first appeared in 10.6 but they
+ raise linker errors when used on that cat for some reason. */
+#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS
+ bool resource_imported;
+
+ if(blob) {
+ pkcs_data = CFDataCreate(kCFAllocatorDefault,
+ (const unsigned char *)blob->data, blob->len);
+ status = (pkcs_data != NULL) ? errSecSuccess : errSecAllocate;
+ resource_imported = (pkcs_data != NULL);
+ }
+ else {
+ pkcs_url =
+ CFURLCreateFromFileSystemRepresentation(NULL,
+ (const UInt8 *)cPath,
+ strlen(cPath), false);
+ resource_imported =
+ CFURLCreateDataAndPropertiesFromResource(NULL,
+ pkcs_url, &pkcs_data,
+ NULL, NULL, &status);
+ }
+
+ if(resource_imported) {
+ CFArrayRef items = NULL;
+
+ /* On iOS SecPKCS12Import will never add the client certificate to the
+ * Keychain.
+ *
+ * It gives us back a SecIdentityRef that we can use directly. */
+#if CURL_BUILD_IOS
+ const void *cKeys[] = {kSecImportExportPassphrase};
+ const void *cValues[] = {password};
+ CFDictionaryRef options = CFDictionaryCreate(NULL, cKeys, cValues,
+ password ? 1L : 0L, NULL, NULL);
+
+ if(options) {
+ status = SecPKCS12Import(pkcs_data, options, &items);
+ CFRelease(options);
+ }
+
+
+ /* On macOS SecPKCS12Import will always add the client certificate to
+ * the Keychain.
+ *
+ * As this doesn't match iOS, and apps may not want to see their client
+ * certificate saved in the user's keychain, we use SecItemImport
+ * with a NULL keychain to avoid importing it.
+ *
+ * This returns a SecCertificateRef from which we can construct a
+ * SecIdentityRef.
+ */
+#elif CURL_BUILD_MAC_10_7
+ SecItemImportExportKeyParameters keyParams;
+ SecExternalFormat inputFormat = kSecFormatPKCS12;
+ SecExternalItemType inputType = kSecItemTypeCertificate;
+
+ memset(&keyParams, 0x00, sizeof(keyParams));
+ keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
+ keyParams.passphrase = password;
+
+ status = SecItemImport(pkcs_data, NULL, &inputFormat, &inputType,
+ 0, &keyParams, NULL, &items);
+#endif
+
+
+ /* Extract the SecIdentityRef */
+ if(status == errSecSuccess && items && CFArrayGetCount(items)) {
+ CFIndex i, count;
+ count = CFArrayGetCount(items);
+
+ for(i = 0; i < count; i++) {
+ CFTypeRef item = (CFTypeRef) CFArrayGetValueAtIndex(items, i);
+ CFTypeID itemID = CFGetTypeID(item);
+
+ if(itemID == CFDictionaryGetTypeID()) {
+ CFTypeRef identity = (CFTypeRef) CFDictionaryGetValue(
+ (CFDictionaryRef) item,
+ kSecImportItemIdentity);
+ CFRetain(identity);
+ *out_cert_and_key = (SecIdentityRef) identity;
+ break;
+ }
+#if CURL_BUILD_MAC_10_7
+ else if(itemID == SecCertificateGetTypeID()) {
+ status = SecIdentityCreateWithCertificate(NULL,
+ (SecCertificateRef) item,
+ out_cert_and_key);
+ break;
+ }
+#endif
+ }
+ }
+
+ if(items)
+ CFRelease(items);
+ CFRelease(pkcs_data);
+ }
+#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */
+ if(password)
+ CFRelease(password);
+ if(pkcs_url)
+ CFRelease(pkcs_url);
+ return status;
+}
+
+/* This code was borrowed from nss.c, with some modifications:
+ * Determine whether the nickname passed in is a filename that needs to
+ * be loaded as a PEM or a regular NSS nickname.
+ *
+ * returns 1 for a file
+ * returns 0 for not a file
+ */
+CF_INLINE bool is_file(const char *filename)
+{
+ struct_stat st;
+
+ if(!filename)
+ return false;
+
+ if(stat(filename, &st) == 0)
+ return S_ISREG(st.st_mode);
+ return false;
+}
+
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+static CURLcode sectransp_version_from_curl(SSLProtocol *darwinver,
+ long ssl_version)
+{
+ switch(ssl_version) {
+ case CURL_SSLVERSION_TLSv1_0:
+ *darwinver = kTLSProtocol1;
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_1:
+ *darwinver = kTLSProtocol11;
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_2:
+ *darwinver = kTLSProtocol12;
+ return CURLE_OK;
+ case CURL_SSLVERSION_TLSv1_3:
+ /* TLS 1.3 support first appeared in iOS 11 and macOS 10.13 */
+#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1
+ if(__builtin_available(macOS 10.13, iOS 11.0, *)) {
+ *darwinver = kTLSProtocol13;
+ return CURLE_OK;
+ }
+#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) &&
+ HAVE_BUILTIN_AVAILABLE == 1 */
+ break;
+ }
+ return CURLE_SSL_CONNECT_ERROR;
+}
+#endif
+
+static CURLcode set_ssl_version_min_max(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ long ssl_version = conn_config->version;
+ long ssl_version_max = conn_config->version_max;
+ long max_supported_version_by_os;
+
+ DEBUGASSERT(backend);
+
+ /* macOS 10.5-10.7 supported TLS 1.0 only.
+ macOS 10.8 and later, and iOS 5 and later, added TLS 1.1 and 1.2.
+ macOS 10.13 and later, and iOS 11 and later, added TLS 1.3. */
+#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1
+ if(__builtin_available(macOS 10.13, iOS 11.0, *)) {
+ max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_3;
+ }
+ else {
+ max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2;
+ }
+#else
+ max_supported_version_by_os = CURL_SSLVERSION_MAX_TLSv1_2;
+#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) &&
+ HAVE_BUILTIN_AVAILABLE == 1 */
+
+ switch(ssl_version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ ssl_version = CURL_SSLVERSION_TLSv1_0;
+ break;
+ }
+
+ switch(ssl_version_max) {
+ case CURL_SSLVERSION_MAX_NONE:
+ case CURL_SSLVERSION_MAX_DEFAULT:
+ ssl_version_max = max_supported_version_by_os;
+ break;
+ }
+
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ if(SSLSetProtocolVersionMax) {
+ SSLProtocol darwin_ver_min = kTLSProtocol1;
+ SSLProtocol darwin_ver_max = kTLSProtocol1;
+ CURLcode result = sectransp_version_from_curl(&darwin_ver_min,
+ ssl_version);
+ if(result) {
+ failf(data, "unsupported min version passed via CURLOPT_SSLVERSION");
+ return result;
+ }
+ result = sectransp_version_from_curl(&darwin_ver_max,
+ ssl_version_max >> 16);
+ if(result) {
+ failf(data, "unsupported max version passed via CURLOPT_SSLVERSION");
+ return result;
+ }
+
+ (void)SSLSetProtocolVersionMin(backend->ssl_ctx, darwin_ver_min);
+ (void)SSLSetProtocolVersionMax(backend->ssl_ctx, darwin_ver_max);
+ return result;
+ }
+ else {
+#if CURL_SUPPORT_MAC_10_8
+ long i = ssl_version;
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kSSLProtocolAll,
+ false);
+ for(; i <= (ssl_version_max >> 16); i++) {
+ switch(i) {
+ case CURL_SSLVERSION_TLSv1_0:
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kTLSProtocol1,
+ true);
+ break;
+ case CURL_SSLVERSION_TLSv1_1:
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kTLSProtocol11,
+ true);
+ break;
+ case CURL_SSLVERSION_TLSv1_2:
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kTLSProtocol12,
+ true);
+ break;
+ case CURL_SSLVERSION_TLSv1_3:
+ failf(data, "Your version of the OS does not support TLSv1.3");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ return CURLE_OK;
+#endif /* CURL_SUPPORT_MAC_10_8 */
+ }
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+ failf(data, "Secure Transport: cannot set SSL protocol");
+ return CURLE_SSL_CONNECT_ERROR;
+}
+
+static bool is_cipher_suite_strong(SSLCipherSuite suite_num)
+{
+ for(size_t i = 0; i < NUM_OF_CIPHERS; ++i) {
+ if(ciphertable[i].num == suite_num) {
+ return !ciphertable[i].weak;
+ }
+ }
+ /* If the cipher is not in our list, assume it is a new one
+ and therefore strong. Previous implementation was the same,
+ if cipher suite is not in the list, it was considered strong enough */
+ return true;
+}
+
+static bool is_separator(char c)
+{
+ /* Return whether character is a cipher list separator. */
+ switch(c) {
+ case ' ':
+ case '\t':
+ case ':':
+ case ',':
+ case ';':
+ return true;
+ }
+ return false;
+}
+
+static CURLcode sectransp_set_default_ciphers(struct Curl_easy *data,
+ SSLContextRef ssl_ctx)
+{
+ size_t all_ciphers_count = 0UL, allowed_ciphers_count = 0UL, i;
+ SSLCipherSuite *all_ciphers = NULL, *allowed_ciphers = NULL;
+ OSStatus err = noErr;
+
+#if CURL_BUILD_MAC
+ int darwinver_maj = 0, darwinver_min = 0;
+
+ GetDarwinVersionNumber(&darwinver_maj, &darwinver_min);
+#endif /* CURL_BUILD_MAC */
+
+ /* Disable cipher suites that ST supports but are not safe. These ciphers
+ are unlikely to be used in any case since ST gives other ciphers a much
+ higher priority, but it's probably better that we not connect at all than
+ to give the user a false sense of security if the server only supports
+ insecure ciphers. (Note: We don't care about SSLv2-only ciphers.) */
+ err = SSLGetNumberSupportedCiphers(ssl_ctx, &all_ciphers_count);
+ if(err != noErr) {
+ failf(data, "SSL: SSLGetNumberSupportedCiphers() failed: OSStatus %d",
+ err);
+ return CURLE_SSL_CIPHER;
+ }
+ all_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite));
+ if(!all_ciphers) {
+ failf(data, "SSL: Failed to allocate memory for all ciphers");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ allowed_ciphers = malloc(all_ciphers_count*sizeof(SSLCipherSuite));
+ if(!allowed_ciphers) {
+ Curl_safefree(all_ciphers);
+ failf(data, "SSL: Failed to allocate memory for allowed ciphers");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ err = SSLGetSupportedCiphers(ssl_ctx, all_ciphers,
+ &all_ciphers_count);
+ if(err != noErr) {
+ Curl_safefree(all_ciphers);
+ Curl_safefree(allowed_ciphers);
+ return CURLE_SSL_CIPHER;
+ }
+ for(i = 0UL ; i < all_ciphers_count ; i++) {
+#if CURL_BUILD_MAC
+ /* There's a known bug in early versions of Mountain Lion where ST's ECC
+ ciphers (cipher suite 0xC001 through 0xC032) simply do not work.
+ Work around the problem here by disabling those ciphers if we are
+ running in an affected version of OS X. */
+ if(darwinver_maj == 12 && darwinver_min <= 3 &&
+ all_ciphers[i] >= 0xC001 && all_ciphers[i] <= 0xC032) {
+ continue;
+ }
+#endif /* CURL_BUILD_MAC */
+ if(is_cipher_suite_strong(all_ciphers[i])) {
+ allowed_ciphers[allowed_ciphers_count++] = all_ciphers[i];
+ }
+ }
+ err = SSLSetEnabledCiphers(ssl_ctx, allowed_ciphers,
+ allowed_ciphers_count);
+ Curl_safefree(all_ciphers);
+ Curl_safefree(allowed_ciphers);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err);
+ return CURLE_SSL_CIPHER;
+ }
+ return CURLE_OK;
+}
+
+static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data,
+ SSLContextRef ssl_ctx,
+ const char *ciphers)
+{
+ size_t ciphers_count = 0;
+ const char *cipher_start = ciphers;
+ OSStatus err = noErr;
+ SSLCipherSuite selected_ciphers[NUM_OF_CIPHERS];
+
+ if(!ciphers)
+ return CURLE_OK;
+
+ while(is_separator(*ciphers)) /* Skip initial separators. */
+ ciphers++;
+ if(!*ciphers)
+ return CURLE_OK;
+
+ cipher_start = ciphers;
+ while(*cipher_start && ciphers_count < NUM_OF_CIPHERS) {
+ bool cipher_found = FALSE;
+ size_t cipher_len = 0;
+ const char *cipher_end = NULL;
+ bool tls_name = FALSE;
+
+ /* Skip separators */
+ while(is_separator(*cipher_start))
+ cipher_start++;
+ if(*cipher_start == '\0') {
+ break;
+ }
+ /* Find last position of a cipher in the ciphers string */
+ cipher_end = cipher_start;
+ while (*cipher_end != '\0' && !is_separator(*cipher_end)) {
+ ++cipher_end;
+ }
+
+ /* IANA cipher names start with the TLS_ or SSL_ prefix.
+ If the 4th symbol of the cipher is '_' we look for a cipher in the
+ table by its (TLS) name.
+ Otherwise, we try to match cipher by an alias. */
+ if(cipher_start[3] == '_') {
+ tls_name = TRUE;
+ }
+ /* Iterate through the cipher table and look for the cipher, starting
+ the cipher number 0x01 because the 0x00 is not the real cipher */
+ cipher_len = cipher_end - cipher_start;
+ for(size_t i = 1; i < NUM_OF_CIPHERS; ++i) {
+ const char *table_cipher_name = NULL;
+ if(tls_name) {
+ table_cipher_name = ciphertable[i].name;
+ }
+ else if(ciphertable[i].alias_name) {
+ table_cipher_name = ciphertable[i].alias_name;
+ }
+ else {
+ continue;
+ }
+ /* Compare a part of the string between separators with a cipher name
+ in the table and make sure we matched the whole cipher name */
+ if(strncmp(cipher_start, table_cipher_name, cipher_len) == 0
+ && table_cipher_name[cipher_len] == '\0') {
+ selected_ciphers[ciphers_count] = ciphertable[i].num;
+ ++ciphers_count;
+ cipher_found = TRUE;
+ break;
+ }
+ }
+ if(!cipher_found) {
+ /* It would be more human-readable if we print the wrong cipher name
+ but we don't want to allocate any additional memory and copy the name
+ into it, then add it into logs.
+ Also, we do not modify an original cipher list string. We just point
+ to positions where cipher starts and ends in the cipher list string.
+ The message is a bit cryptic and longer than necessary but can be
+ understood by humans. */
+ failf(data, "SSL: cipher string \"%s\" contains unsupported cipher name"
+ " starting position %d and ending position %d",
+ ciphers,
+ cipher_start - ciphers,
+ cipher_end - ciphers);
+ return CURLE_SSL_CIPHER;
+ }
+ if(*cipher_end) {
+ cipher_start = cipher_end + 1;
+ }
+ else {
+ break;
+ }
+ }
+ /* All cipher suites in the list are found. Report to logs as-is */
+ infof(data, "SSL: Setting cipher suites list \"%s\"", ciphers);
+
+ err = SSLSetEnabledCiphers(ssl_ctx, selected_ciphers, ciphers_count);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetEnabledCiphers() failed: OSStatus %d", err);
+ return CURLE_SSL_CIPHER;
+ }
+ return CURLE_OK;
+}
+
+static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ const struct curl_blob *ssl_cablob = conn_config->ca_info_blob;
+ const char * const ssl_cafile =
+ /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+ (ssl_cablob ? NULL : conn_config->CAfile);
+ const bool verifypeer = conn_config->verifypeer;
+ char * const ssl_cert = ssl_config->primary.clientcert;
+ const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
+ bool isproxy = Curl_ssl_cf_is_proxy(cf);
+#ifdef ENABLE_IPV6
+ struct in6_addr addr;
+#else
+ struct in_addr addr;
+#endif /* ENABLE_IPV6 */
+ char *ciphers;
+ OSStatus err = noErr;
+#if CURL_BUILD_MAC
+ int darwinver_maj = 0, darwinver_min = 0;
+
+ DEBUGASSERT(backend);
+
+ GetDarwinVersionNumber(&darwinver_maj, &darwinver_min);
+#endif /* CURL_BUILD_MAC */
+
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ if(SSLCreateContext) { /* use the newer API if available */
+ if(backend->ssl_ctx)
+ CFRelease(backend->ssl_ctx);
+ backend->ssl_ctx = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
+ if(!backend->ssl_ctx) {
+ failf(data, "SSL: couldn't create a context");
+ return CURLE_OUT_OF_MEMORY;
+ }
+ }
+ else {
+ /* The old ST API does not exist under iOS, so don't compile it: */
+#if CURL_SUPPORT_MAC_10_8
+ if(backend->ssl_ctx)
+ (void)SSLDisposeContext(backend->ssl_ctx);
+ err = SSLNewContext(false, &(backend->ssl_ctx));
+ if(err != noErr) {
+ failf(data, "SSL: couldn't create a context: OSStatus %d", err);
+ return CURLE_OUT_OF_MEMORY;
+ }
+#endif /* CURL_SUPPORT_MAC_10_8 */
+ }
+#else
+ if(backend->ssl_ctx)
+ (void)SSLDisposeContext(backend->ssl_ctx);
+ err = SSLNewContext(false, &(backend->ssl_ctx));
+ if(err != noErr) {
+ failf(data, "SSL: couldn't create a context: OSStatus %d", err);
+ return CURLE_OUT_OF_MEMORY;
+ }
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+ backend->ssl_write_buffered_length = 0UL; /* reset buffered write length */
+
+ /* check to see if we've been told to use an explicit SSL/TLS version */
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ if(SSLSetProtocolVersionMax) {
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_TLSv1:
+ (void)SSLSetProtocolVersionMin(backend->ssl_ctx, kTLSProtocol1);
+#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1
+ if(__builtin_available(macOS 10.13, iOS 11.0, *)) {
+ (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol13);
+ }
+ else {
+ (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12);
+ }
+#else
+ (void)SSLSetProtocolVersionMax(backend->ssl_ctx, kTLSProtocol12);
+#endif /* (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) &&
+ HAVE_BUILTIN_AVAILABLE == 1 */
+ break;
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1_1:
+ case CURL_SSLVERSION_TLSv1_2:
+ case CURL_SSLVERSION_TLSv1_3:
+ {
+ CURLcode result = set_ssl_version_min_max(cf, data);
+ if(result != CURLE_OK)
+ return result;
+ break;
+ }
+ case CURL_SSLVERSION_SSLv3:
+ case CURL_SSLVERSION_SSLv2:
+ failf(data, "SSL versions not supported");
+ return CURLE_NOT_BUILT_IN;
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ else {
+#if CURL_SUPPORT_MAC_10_8
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kSSLProtocolAll,
+ false);
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kTLSProtocol1,
+ true);
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kTLSProtocol11,
+ true);
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kTLSProtocol12,
+ true);
+ break;
+ case CURL_SSLVERSION_TLSv1_0:
+ case CURL_SSLVERSION_TLSv1_1:
+ case CURL_SSLVERSION_TLSv1_2:
+ case CURL_SSLVERSION_TLSv1_3:
+ {
+ CURLcode result = set_ssl_version_min_max(cf, data);
+ if(result != CURLE_OK)
+ return result;
+ break;
+ }
+ case CURL_SSLVERSION_SSLv3:
+ case CURL_SSLVERSION_SSLv2:
+ failf(data, "SSL versions not supported");
+ return CURLE_NOT_BUILT_IN;
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif /* CURL_SUPPORT_MAC_10_8 */
+ }
+#else
+ if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) {
+ failf(data, "Your version of the OS does not support to set maximum"
+ " SSL/TLS version");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx, kSSLProtocolAll, false);
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+ case CURL_SSLVERSION_TLSv1_0:
+ (void)SSLSetProtocolVersionEnabled(backend->ssl_ctx,
+ kTLSProtocol1,
+ true);
+ break;
+ case CURL_SSLVERSION_TLSv1_1:
+ failf(data, "Your version of the OS does not support TLSv1.1");
+ return CURLE_SSL_CONNECT_ERROR;
+ case CURL_SSLVERSION_TLSv1_2:
+ failf(data, "Your version of the OS does not support TLSv1.2");
+ return CURLE_SSL_CONNECT_ERROR;
+ case CURL_SSLVERSION_TLSv1_3:
+ failf(data, "Your version of the OS does not support TLSv1.3");
+ return CURLE_SSL_CONNECT_ERROR;
+ case CURL_SSLVERSION_SSLv2:
+ case CURL_SSLVERSION_SSLv3:
+ failf(data, "SSL versions not supported");
+ return CURLE_NOT_BUILT_IN;
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+
+#if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1
+ if(cf->conn->bits.tls_enable_alpn) {
+ if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) {
+ CFMutableArrayRef alpnArr = CFArrayCreateMutable(NULL, 0,
+ &kCFTypeArrayCallBacks);
+
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2
+#ifndef CURL_DISABLE_PROXY
+ && (!isproxy || !cf->conn->bits.tunnel_proxy)
+#endif
+ ) {
+ CFArrayAppendValue(alpnArr, CFSTR(ALPN_H2));
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+ }
+#endif
+
+ CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_1));
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+
+ /* expects length prefixed preference ordered list of protocols in wire
+ * format
+ */
+ err = SSLSetALPNProtocols(backend->ssl_ctx, alpnArr);
+ if(err != noErr)
+ infof(data, "WARNING: failed to set ALPN protocols; OSStatus %d",
+ err);
+ CFRelease(alpnArr);
+ }
+ }
+#endif
+
+ if(ssl_config->key) {
+ infof(data, "WARNING: SSL: CURLOPT_SSLKEY is ignored by Secure "
+ "Transport. The private key must be in the Keychain.");
+ }
+
+ if(ssl_cert || ssl_cert_blob) {
+ bool is_cert_data = ssl_cert_blob != NULL;
+ bool is_cert_file = (!is_cert_data) && is_file(ssl_cert);
+ SecIdentityRef cert_and_key = NULL;
+
+ /* User wants to authenticate with a client cert. Look for it. Assume that
+ the user wants to use an identity loaded from the Keychain. If not, try
+ it as a file on disk */
+
+ if(!is_cert_data)
+ err = CopyIdentityWithLabel(ssl_cert, &cert_and_key);
+ else
+ err = !noErr;
+ if((err != noErr) && (is_cert_file || is_cert_data)) {
+ if(!ssl_config->cert_type)
+ infof(data, "SSL: Certificate type not set, assuming "
+ "PKCS#12 format.");
+ else if(!strcasecompare(ssl_config->cert_type, "P12")) {
+ failf(data, "SSL: The Security framework only supports "
+ "loading identities that are in PKCS#12 format.");
+ return CURLE_SSL_CERTPROBLEM;
+ }
+
+ err = CopyIdentityFromPKCS12File(ssl_cert, ssl_cert_blob,
+ ssl_config->key_passwd,
+ &cert_and_key);
+ }
+
+ if(err == noErr && cert_and_key) {
+ SecCertificateRef cert = NULL;
+ CFTypeRef certs_c[1];
+ CFArrayRef certs;
+
+ /* If we found one, print it out: */
+ err = SecIdentityCopyCertificate(cert_and_key, &cert);
+ if(err == noErr) {
+ char *certp;
+ CURLcode result = CopyCertSubject(data, cert, &certp);
+ if(!result) {
+ infof(data, "Client certificate: %s", certp);
+ free(certp);
+ }
+
+ CFRelease(cert);
+ if(result == CURLE_PEER_FAILED_VERIFICATION)
+ return CURLE_SSL_CERTPROBLEM;
+ if(result)
+ return result;
+ }
+ certs_c[0] = cert_and_key;
+ certs = CFArrayCreate(NULL, (const void **)certs_c, 1L,
+ &kCFTypeArrayCallBacks);
+ err = SSLSetCertificate(backend->ssl_ctx, certs);
+ if(certs)
+ CFRelease(certs);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetCertificate() failed: OSStatus %d", err);
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ CFRelease(cert_and_key);
+ }
+ else {
+ const char *cert_showfilename_error =
+ is_cert_data ? "(memory blob)" : ssl_cert;
+
+ switch(err) {
+ case errSecAuthFailed: case -25264: /* errSecPkcs12VerifyFailure */
+ failf(data, "SSL: Incorrect password for the certificate \"%s\" "
+ "and its private key.", cert_showfilename_error);
+ break;
+ case -26275: /* errSecDecode */ case -25257: /* errSecUnknownFormat */
+ failf(data, "SSL: Couldn't make sense of the data in the "
+ "certificate \"%s\" and its private key.",
+ cert_showfilename_error);
+ break;
+ case -25260: /* errSecPassphraseRequired */
+ failf(data, "SSL The certificate \"%s\" requires a password.",
+ cert_showfilename_error);
+ break;
+ case errSecItemNotFound:
+ failf(data, "SSL: Can't find the certificate \"%s\" and its private "
+ "key in the Keychain.", cert_showfilename_error);
+ break;
+ default:
+ failf(data, "SSL: Can't load the certificate \"%s\" and its private "
+ "key: OSStatus %d", cert_showfilename_error, err);
+ break;
+ }
+ return CURLE_SSL_CERTPROBLEM;
+ }
+ }
+
+ /* SSL always tries to verify the peer, this only says whether it should
+ * fail to connect if the verification fails, or if it should continue
+ * anyway. In the latter case the result of the verification is checked with
+ * SSL_get_verify_result() below. */
+#if CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS
+ /* Snow Leopard introduced the SSLSetSessionOption() function, but due to
+ a library bug with the way the kSSLSessionOptionBreakOnServerAuth flag
+ works, it doesn't work as expected under Snow Leopard, Lion or
+ Mountain Lion.
+ So we need to call SSLSetEnableCertVerify() on those older cats in order
+ to disable certificate validation if the user turned that off.
+ (SecureTransport will always validate the certificate chain by
+ default.)
+ Note:
+ Darwin 11.x.x is Lion (10.7)
+ Darwin 12.x.x is Mountain Lion (10.8)
+ Darwin 13.x.x is Mavericks (10.9)
+ Darwin 14.x.x is Yosemite (10.10)
+ Darwin 15.x.x is El Capitan (10.11)
+ */
+#if CURL_BUILD_MAC
+ if(SSLSetSessionOption && darwinver_maj >= 13) {
+#else
+ if(SSLSetSessionOption) {
+#endif /* CURL_BUILD_MAC */
+ bool break_on_auth = !conn_config->verifypeer ||
+ ssl_cafile || ssl_cablob;
+ err = SSLSetSessionOption(backend->ssl_ctx,
+ kSSLSessionOptionBreakOnServerAuth,
+ break_on_auth);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetSessionOption() failed: OSStatus %d", err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ else {
+#if CURL_SUPPORT_MAC_10_8
+ err = SSLSetEnableCertVerify(backend->ssl_ctx,
+ conn_config->verifypeer?true:false);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif /* CURL_SUPPORT_MAC_10_8 */
+ }
+#else
+ err = SSLSetEnableCertVerify(backend->ssl_ctx,
+ conn_config->verifypeer?true:false);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetEnableCertVerify() failed: OSStatus %d", err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif /* CURL_BUILD_MAC_10_6 || CURL_BUILD_IOS */
+
+ if((ssl_cafile || ssl_cablob) && verifypeer) {
+ bool is_cert_data = ssl_cablob != NULL;
+ 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 ? ssl_cafile : "(blob memory)");
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ }
+
+ /* Configure hostname check. SNI is used if available.
+ * Both hostname check and SNI require SSLSetPeerDomainName().
+ * Also: the verifyhost setting influences SNI usage */
+ if(conn_config->verifyhost) {
+ size_t snilen;
+ char *snihost = Curl_ssl_snihost(data, connssl->hostname, &snilen);
+ if(!snihost) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ err = SSLSetPeerDomainName(backend->ssl_ctx, snihost, snilen);
+
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetPeerDomainName() failed: OSStatus %d",
+ err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if((Curl_inet_pton(AF_INET, connssl->hostname, &addr))
+ #ifdef ENABLE_IPV6
+ || (Curl_inet_pton(AF_INET6, connssl->hostname, &addr))
+ #endif
+ ) {
+ infof(data, "WARNING: using IP address, SNI is being disabled by "
+ "the OS.");
+ }
+ }
+ else {
+ infof(data, "WARNING: disabling hostname validation also disables SNI.");
+ }
+
+ ciphers = conn_config->cipher_list;
+ if(ciphers) {
+ err = sectransp_set_selected_ciphers(data, backend->ssl_ctx, ciphers);
+ }
+ else {
+ err = sectransp_set_default_ciphers(data, backend->ssl_ctx);
+ }
+ if(err != noErr) {
+ failf(data, "SSL: Unable to set ciphers for SSL/TLS handshake. "
+ "Error code: %d", err);
+ return CURLE_SSL_CIPHER;
+ }
+
+#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7
+ /* We want to enable 1/n-1 when using a CBC cipher unless the user
+ specifically doesn't want us doing that: */
+ if(SSLSetSessionOption) {
+ SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionSendOneByteRecord,
+ !ssl_config->enable_beast);
+ SSLSetSessionOption(backend->ssl_ctx, kSSLSessionOptionFalseStart,
+ ssl_config->falsestart); /* false start support */
+ }
+#endif /* CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7 */
+
+ /* Check if there's a cached ID we can/should use here! */
+ if(ssl_config->primary.sessionid) {
+ char *ssl_sessionid;
+ size_t ssl_sessionid_len;
+
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, (void **)&ssl_sessionid,
+ &ssl_sessionid_len)) {
+ /* we got a session id, use it! */
+ err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len);
+ Curl_ssl_sessionid_unlock(data);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ /* Informational message */
+ infof(data, "SSL re-using session ID");
+ }
+ /* If there isn't one, then let's make one up! This has to be done prior
+ to starting the handshake. */
+ else {
+ CURLcode result;
+ ssl_sessionid =
+ aprintf("%s:%d:%d:%s:%d",
+ ssl_cafile ? ssl_cafile : "(blob memory)",
+ verifypeer, conn_config->verifyhost, connssl->hostname,
+ connssl->port);
+ ssl_sessionid_len = strlen(ssl_sessionid);
+
+ err = SSLSetPeerID(backend->ssl_ctx, ssl_sessionid, ssl_sessionid_len);
+ if(err != noErr) {
+ Curl_ssl_sessionid_unlock(data);
+ failf(data, "SSL: SSLSetPeerID() failed: OSStatus %d", err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ result = Curl_ssl_addsessionid(cf, data, ssl_sessionid,
+ ssl_sessionid_len, NULL);
+ Curl_ssl_sessionid_unlock(data);
+ if(result) {
+ failf(data, "failed to store ssl session");
+ return result;
+ }
+ }
+ }
+
+ err = SSLSetIOFuncs(backend->ssl_ctx, bio_cf_in_read, bio_cf_out_write);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetIOFuncs() failed: OSStatus %d", err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ err = SSLSetConnection(backend->ssl_ctx, cf);
+ if(err != noErr) {
+ failf(data, "SSL: SSLSetConnection() failed: %d", err);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ connssl->connecting_state = ssl_connect_2;
+ return CURLE_OK;
+}
+
+static long pem_to_der(const char *in, unsigned char **out, size_t *outlen)
+{
+ char *sep_start, *sep_end, *cert_start, *cert_end;
+ size_t i, j, err;
+ size_t len;
+ unsigned char *b64;
+
+ /* Jump through the separators at the beginning of the certificate. */
+ sep_start = strstr(in, "-----");
+ if(!sep_start)
+ return 0;
+ cert_start = strstr(sep_start + 1, "-----");
+ if(!cert_start)
+ return -1;
+
+ cert_start += 5;
+
+ /* Find separator after the end of the certificate. */
+ cert_end = strstr(cert_start, "-----");
+ if(!cert_end)
+ return -1;
+
+ sep_end = strstr(cert_end + 1, "-----");
+ if(!sep_end)
+ return -1;
+ sep_end += 5;
+
+ len = cert_end - cert_start;
+ b64 = malloc(len + 1);
+ if(!b64)
+ return -1;
+
+ /* Create base64 string without linefeeds. */
+ for(i = 0, j = 0; i < len; i++) {
+ if(cert_start[i] != '\r' && cert_start[i] != '\n')
+ b64[j++] = cert_start[i];
+ }
+ b64[j] = '\0';
+
+ err = Curl_base64_decode((const char *)b64, out, outlen);
+ free(b64);
+ if(err) {
+ free(*out);
+ return -1;
+ }
+
+ return sep_end - in;
+}
+
+static int read_cert(const char *file, unsigned char **out, size_t *outlen)
+{
+ int fd;
+ ssize_t n, len = 0, cap = 512;
+ unsigned char buf[512], *data;
+
+ 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 = Curl_saferealloc(data, cap);
+ if(!data) {
+ close(fd);
+ return -1;
+ }
+ }
+
+ memcpy(data + len, buf, n);
+ len += n;
+ }
+ data[len] = '\0';
+
+ *out = data;
+ *outlen = len;
+
+ return 0;
+}
+
+static int append_cert_to_array(struct Curl_easy *data,
+ const unsigned char *buf, size_t buflen,
+ CFMutableArrayRef array)
+{
+ CFDataRef certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen);
+ char *certp;
+ CURLcode result;
+ 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_BADFILE;
+ }
+
+ /* Check if cacert is valid. */
+ result = CopyCertSubject(data, cacert, &certp);
+ switch(result) {
+ case CURLE_OK:
+ break;
+ case CURLE_PEER_FAILED_VERIFICATION:
+ return CURLE_SSL_CACERT_BADFILE;
+ case CURLE_OUT_OF_MEMORY:
+ default:
+ return result;
+ }
+ free(certp);
+
+ CFArrayAppendValue(array, cacert);
+ CFRelease(cacert);
+
+ return CURLE_OK;
+}
+
+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 *der;
+ size_t derlen, offset = 0;
+
+ /*
+ * Certbuf now contains the contents of the certificate file, which can be
+ * - a single DER certificate,
+ * - a single PEM certificate or
+ * - a bunch of PEM certificates (certificate bundle).
+ *
+ * Go through certbuf, and convert any PEM certificate in it into DER
+ * format.
+ */
+ CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeArrayCallBacks);
+ if(!array) {
+ failf(data, "SSL: out of memory creating CA certificate array");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ while(offset < buflen) {
+ n++;
+
+ /*
+ * Check if the certificate is in PEM format, and convert it to DER. If
+ * this fails, we assume the certificate is in DER format.
+ */
+ res = pem_to_der((const char *)certbuf + offset, &der, &derlen);
+ if(res < 0) {
+ CFRelease(array);
+ failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle",
+ n, offset);
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ offset += res;
+
+ 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);
+ if(rc != CURLE_OK) {
+ CFRelease(array);
+ return rc;
+ }
+ break;
+ }
+ else if(res == 0) {
+ /* No more certificates in the bundle. */
+ break;
+ }
+
+ rc = append_cert_to_array(data, der, derlen, array);
+ free(der);
+ if(rc != CURLE_OK) {
+ CFRelease(array);
+ return rc;
+ }
+ }
+
+ SecTrustRef trust;
+ OSStatus ret = SSLCopyPeerTrust(ctx, &trust);
+ if(!trust) {
+ failf(data, "SSL: error getting certificate chain");
+ CFRelease(array);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else if(ret != noErr) {
+ CFRelease(array);
+ failf(data, "SSLCopyPeerTrust() returned error %d", ret);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ ret = SecTrustSetAnchorCertificates(trust, array);
+ if(ret != noErr) {
+ CFRelease(array);
+ CFRelease(trust);
+ failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ ret = SecTrustSetAnchorCertificatesOnly(trust, true);
+ if(ret != noErr) {
+ CFRelease(array);
+ CFRelease(trust);
+ failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ SecTrustResultType trust_eval = 0;
+ ret = SecTrustEvaluate(trust, &trust_eval);
+ CFRelease(array);
+ CFRelease(trust);
+ if(ret != noErr) {
+ failf(data, "SecTrustEvaluate() returned error %d", ret);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ switch(trust_eval) {
+ case kSecTrustResultUnspecified:
+ case kSecTrustResultProceed:
+ 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 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,
+ const char *pinnedpubkey)
+{ /* Scratch */
+ size_t pubkeylen, realpubkeylen, spkiHeaderLength = 24;
+ unsigned char *pubkey = NULL, *realpubkey = NULL;
+ const unsigned char *spkiHeader = NULL;
+ CFDataRef publicKeyBits = NULL;
+
+ /* Result is returned to caller */
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+ /* if a path wasn't specified, don't pin */
+ if(!pinnedpubkey)
+ return CURLE_OK;
+
+
+ if(!ctx)
+ return result;
+
+ do {
+ SecTrustRef trust;
+ OSStatus ret = SSLCopyPeerTrust(ctx, &trust);
+ if(ret != noErr || !trust)
+ break;
+
+ SecKeyRef keyRef = SecTrustCopyPublicKey(trust);
+ CFRelease(trust);
+ if(!keyRef)
+ break;
+
+#ifdef SECTRANSP_PINNEDPUBKEY_V1
+
+ publicKeyBits = SecKeyCopyExternalRepresentation(keyRef, NULL);
+ CFRelease(keyRef);
+ if(!publicKeyBits)
+ break;
+
+#elif SECTRANSP_PINNEDPUBKEY_V2
+
+ OSStatus success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL,
+ &publicKeyBits);
+ CFRelease(keyRef);
+ if(success != errSecSuccess || !publicKeyBits)
+ break;
+
+#endif /* SECTRANSP_PINNEDPUBKEY_V2 */
+
+ pubkeylen = CFDataGetLength(publicKeyBits);
+ pubkey = (unsigned char *)CFDataGetBytePtr(publicKeyBits);
+
+ switch(pubkeylen) {
+ case 526:
+ /* 4096 bit RSA pubkeylen == 526 */
+ spkiHeader = rsa4096SpkiHeader;
+ break;
+ case 270:
+ /* 2048 bit RSA pubkeylen == 270 */
+ spkiHeader = rsa2048SpkiHeader;
+ break;
+#ifdef SECTRANSP_PINNEDPUBKEY_V1
+ case 65:
+ /* ecDSA secp256r1 pubkeylen == 65 */
+ spkiHeader = ecDsaSecp256r1SpkiHeader;
+ spkiHeaderLength = 26;
+ break;
+ case 97:
+ /* ecDSA secp384r1 pubkeylen == 97 */
+ spkiHeader = ecDsaSecp384r1SpkiHeader;
+ spkiHeaderLength = 23;
+ break;
+ default:
+ infof(data, "SSL: unhandled public key length: %d", pubkeylen);
+#elif SECTRANSP_PINNEDPUBKEY_V2
+ default:
+ /* ecDSA secp256r1 pubkeylen == 91 header already included?
+ * ecDSA secp384r1 header already included too
+ * we assume rest of algorithms do same, so do nothing
+ */
+ result = Curl_pin_peer_pubkey(data, pinnedpubkey, pubkey,
+ pubkeylen);
+#endif /* SECTRANSP_PINNEDPUBKEY_V2 */
+ continue; /* break from loop */
+ }
+
+ realpubkeylen = pubkeylen + spkiHeaderLength;
+ realpubkey = malloc(realpubkeylen);
+ if(!realpubkey)
+ break;
+
+ memcpy(realpubkey, spkiHeader, spkiHeaderLength);
+ memcpy(realpubkey + spkiHeaderLength, pubkey, pubkeylen);
+
+ result = Curl_pin_peer_pubkey(data, pinnedpubkey, realpubkey,
+ realpubkeylen);
+
+ } while(0);
+
+ Curl_safefree(realpubkey);
+ if(publicKeyBits)
+ CFRelease(publicKeyBits);
+
+ return result;
+}
+#endif /* SECTRANSP_PINNEDPUBKEY */
+
+static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ OSStatus err;
+ SSLCipherSuite cipher;
+ SSLProtocol protocol = 0;
+
+ DEBUGASSERT(ssl_connect_2 == connssl->connecting_state
+ || ssl_connect_2_reading == connssl->connecting_state
+ || ssl_connect_2_writing == connssl->connecting_state);
+ DEBUGASSERT(backend);
+
+ /* Here goes nothing: */
+ err = SSLHandshake(backend->ssl_ctx);
+
+ if(err != noErr) {
+ switch(err) {
+ case errSSLWouldBlock: /* they're not done with us yet */
+ connssl->connecting_state = backend->ssl_direction ?
+ ssl_connect_2_writing : ssl_connect_2_reading;
+ return CURLE_OK;
+
+ /* The below is errSSLServerAuthCompleted; it's not defined in
+ Leopard's headers */
+ case -9841:
+ if((conn_config->CAfile || conn_config->ca_info_blob) &&
+ conn_config->verifypeer) {
+ CURLcode result = verify_cert(data, conn_config->CAfile,
+ conn_config->ca_info_blob,
+ backend->ssl_ctx);
+ if(result)
+ return result;
+ }
+ /* the documentation says we need to call SSLHandshake() again */
+ return sectransp_connect_step2(cf, data);
+
+ /* Problem with encrypt / decrypt */
+ case errSSLPeerDecodeError:
+ failf(data, "Decode failed");
+ break;
+ case errSSLDecryptionFail:
+ case errSSLPeerDecryptionFail:
+ failf(data, "Decryption failed");
+ break;
+ case errSSLPeerDecryptError:
+ failf(data, "A decryption error occurred");
+ break;
+ case errSSLBadCipherSuite:
+ failf(data, "A bad SSL cipher suite was encountered");
+ break;
+ case errSSLCrypto:
+ failf(data, "An underlying cryptographic error was encountered");
+ break;
+#if CURL_BUILD_MAC_10_11 || CURL_BUILD_IOS_9
+ case errSSLWeakPeerEphemeralDHKey:
+ failf(data, "Indicates a weak ephemeral Diffie-Hellman key");
+ break;
+#endif
+
+ /* Problem with the message record validation */
+ case errSSLBadRecordMac:
+ case errSSLPeerBadRecordMac:
+ failf(data, "A record with a bad message authentication code (MAC) "
+ "was encountered");
+ break;
+ case errSSLRecordOverflow:
+ case errSSLPeerRecordOverflow:
+ failf(data, "A record overflow occurred");
+ break;
+
+ /* Problem with zlib decompression */
+ case errSSLPeerDecompressFail:
+ failf(data, "Decompression failed");
+ break;
+
+ /* Problem with access */
+ case errSSLPeerAccessDenied:
+ failf(data, "Access was denied");
+ break;
+ case errSSLPeerInsufficientSecurity:
+ failf(data, "There is insufficient security for this operation");
+ break;
+
+ /* These are all certificate problems with the server: */
+ case errSSLXCertChainInvalid:
+ failf(data, "SSL certificate problem: Invalid certificate chain");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLUnknownRootCert:
+ failf(data, "SSL certificate problem: Untrusted root certificate");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLNoRootCert:
+ failf(data, "SSL certificate problem: No root certificate");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLCertNotYetValid:
+ failf(data, "SSL certificate problem: The certificate chain had a "
+ "certificate that is not yet valid");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLCertExpired:
+ case errSSLPeerCertExpired:
+ failf(data, "SSL certificate problem: Certificate chain had an "
+ "expired certificate");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLBadCert:
+ case errSSLPeerBadCert:
+ failf(data, "SSL certificate problem: Couldn't understand the server "
+ "certificate format");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLPeerUnsupportedCert:
+ failf(data, "SSL certificate problem: An unsupported certificate "
+ "format was encountered");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLPeerCertRevoked:
+ failf(data, "SSL certificate problem: The certificate was revoked");
+ return CURLE_PEER_FAILED_VERIFICATION;
+ case errSSLPeerCertUnknown:
+ failf(data, "SSL certificate problem: The certificate is unknown");
+ return CURLE_PEER_FAILED_VERIFICATION;
+
+ /* These are all certificate problems with the client: */
+ case errSecAuthFailed:
+ failf(data, "SSL authentication failed");
+ break;
+ case errSSLPeerHandshakeFail:
+ failf(data, "SSL peer handshake failed, the server most likely "
+ "requires a client certificate to connect");
+ break;
+ case errSSLPeerUnknownCA:
+ failf(data, "SSL server rejected the client certificate due to "
+ "the certificate being signed by an unknown certificate "
+ "authority");
+ break;
+
+ /* This error is raised if the server's cert didn't match the server's
+ host name: */
+ case errSSLHostNameMismatch:
+ failf(data, "SSL certificate peer verification failed, the "
+ "certificate did not match \"%s\"\n", connssl->dispname);
+ return CURLE_PEER_FAILED_VERIFICATION;
+
+ /* Problem with SSL / TLS negotiation */
+ case errSSLNegotiation:
+ failf(data, "Could not negotiate an SSL cipher suite with the server");
+ break;
+ case errSSLBadConfiguration:
+ failf(data, "A configuration error occurred");
+ break;
+ case errSSLProtocol:
+ failf(data, "SSL protocol error");
+ break;
+ case errSSLPeerProtocolVersion:
+ failf(data, "A bad protocol version was encountered");
+ break;
+ case errSSLPeerNoRenegotiation:
+ failf(data, "No renegotiation is allowed");
+ break;
+
+ /* Generic handshake errors: */
+ case errSSLConnectionRefused:
+ failf(data, "Server dropped the connection during the SSL handshake");
+ break;
+ case errSSLClosedAbort:
+ failf(data, "Server aborted the SSL handshake");
+ break;
+ case errSSLClosedGraceful:
+ failf(data, "The connection closed gracefully");
+ break;
+ case errSSLClosedNoNotify:
+ failf(data, "The server closed the session with no notification");
+ break;
+ /* Sometimes paramErr happens with buggy ciphers: */
+ case paramErr:
+ case errSSLInternal:
+ case errSSLPeerInternalError:
+ failf(data, "Internal SSL engine error encountered during the "
+ "SSL handshake");
+ break;
+ case errSSLFatalAlert:
+ failf(data, "Fatal SSL engine error encountered during the SSL "
+ "handshake");
+ break;
+ /* Unclassified error */
+ case errSSLBufferOverflow:
+ failf(data, "An insufficient buffer was provided");
+ break;
+ case errSSLIllegalParam:
+ failf(data, "An illegal parameter was encountered");
+ break;
+ case errSSLModuleAttach:
+ failf(data, "Module attach failure");
+ break;
+ case errSSLSessionNotFound:
+ failf(data, "An attempt to restore an unknown session failed");
+ break;
+ case errSSLPeerExportRestriction:
+ failf(data, "An export restriction occurred");
+ break;
+ case errSSLPeerUserCancelled:
+ failf(data, "The user canceled the operation");
+ break;
+ case errSSLPeerUnexpectedMsg:
+ failf(data, "Peer rejected unexpected message");
+ break;
+#if CURL_BUILD_MAC_10_11 || CURL_BUILD_IOS_9
+ /* Treaing non-fatal error as fatal like before */
+ case errSSLClientHelloReceived:
+ failf(data, "A non-fatal result for providing a server name "
+ "indication");
+ break;
+#endif
+
+ /* Error codes defined in the enum but should never be returned.
+ We list them here just in case. */
+#if CURL_BUILD_MAC_10_6
+ /* Only returned when kSSLSessionOptionBreakOnCertRequested is set */
+ case errSSLClientCertRequested:
+ failf(data, "Server requested a client certificate during the "
+ "handshake");
+ return CURLE_SSL_CLIENTCERT;
+#endif
+#if CURL_BUILD_MAC_10_9
+ /* Alias for errSSLLast, end of error range */
+ case errSSLUnexpectedRecord:
+ failf(data, "Unexpected (skipped) record in DTLS");
+ break;
+#endif
+ default:
+ /* May also return codes listed in Security Framework Result Codes */
+ failf(data, "Unknown SSL protocol error in connection to %s:%d",
+ connssl->hostname, err);
+ break;
+ }
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else {
+ /* we have been connected fine, we're not waiting for anything else. */
+ connssl->connecting_state = ssl_connect_3;
+
+#ifdef SECTRANSP_PINNEDPUBKEY
+ if(data->set.str[STRING_SSL_PINNEDPUBLICKEY]) {
+ CURLcode result =
+ pkp_pin_peer_pubkey(data, backend->ssl_ctx,
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+ if(result) {
+ failf(data, "SSL: public key does not match pinned public key");
+ return result;
+ }
+ }
+#endif /* SECTRANSP_PINNEDPUBKEY */
+
+ /* Informational message */
+ (void)SSLGetNegotiatedCipher(backend->ssl_ctx, &cipher);
+ (void)SSLGetNegotiatedProtocolVersion(backend->ssl_ctx, &protocol);
+ switch(protocol) {
+ case kSSLProtocol2:
+ infof(data, "SSL 2.0 connection using %s",
+ TLSCipherNameForNumber(cipher));
+ break;
+ case kSSLProtocol3:
+ infof(data, "SSL 3.0 connection using %s",
+ TLSCipherNameForNumber(cipher));
+ break;
+ case kTLSProtocol1:
+ infof(data, "TLS 1.0 connection using %s",
+ TLSCipherNameForNumber(cipher));
+ break;
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ case kTLSProtocol11:
+ infof(data, "TLS 1.1 connection using %s",
+ TLSCipherNameForNumber(cipher));
+ break;
+ case kTLSProtocol12:
+ infof(data, "TLS 1.2 connection using %s",
+ TLSCipherNameForNumber(cipher));
+ break;
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+#if CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11
+ case kTLSProtocol13:
+ infof(data, "TLS 1.3 connection using %s",
+ TLSCipherNameForNumber(cipher));
+ break;
+#endif /* CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11 */
+ default:
+ infof(data, "Unknown protocol connection");
+ break;
+ }
+
+#if(CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1
+ if(cf->conn->bits.tls_enable_alpn) {
+ if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) {
+ CFArrayRef alpnArr = NULL;
+ CFStringRef chosenProtocol = NULL;
+ err = SSLCopyALPNProtocols(backend->ssl_ctx, &alpnArr);
+
+ if(err == noErr && alpnArr && CFArrayGetCount(alpnArr) >= 1)
+ chosenProtocol = CFArrayGetValueAtIndex(alpnArr, 0);
+
+#ifdef USE_HTTP2
+ if(chosenProtocol &&
+ !CFStringCompare(chosenProtocol, CFSTR(ALPN_H2), 0)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+ }
+ else
+#endif
+ if(chosenProtocol &&
+ !CFStringCompare(chosenProtocol, CFSTR(ALPN_HTTP_1_1), 0)) {
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+ }
+ else
+ infof(data, VTLS_INFOF_NO_ALPN);
+
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+
+ /* chosenProtocol is a reference to the string within alpnArr
+ and doesn't need to be freed separately */
+ if(alpnArr)
+ CFRelease(alpnArr);
+ }
+ }
+#endif
+
+ return CURLE_OK;
+ }
+}
+
+static CURLcode
+add_cert_to_certinfo(struct Curl_easy *data,
+ SecCertificateRef server_cert,
+ int idx)
+{
+ CURLcode result = CURLE_OK;
+ const char *beg;
+ const char *end;
+ CFDataRef cert_data = SecCertificateCopyData(server_cert);
+
+ if(!cert_data)
+ return CURLE_PEER_FAILED_VERIFICATION;
+
+ beg = (const char *)CFDataGetBytePtr(cert_data);
+ end = beg + CFDataGetLength(cert_data);
+ result = Curl_extract_certinfo(data, idx, beg, end);
+ CFRelease(cert_data);
+ return result;
+}
+
+static CURLcode
+collect_server_cert_single(struct Curl_cfilter *cf, struct Curl_easy *data,
+ SecCertificateRef server_cert,
+ CFIndex idx)
+{
+ CURLcode result = CURLE_OK;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ if(data->set.verbose) {
+ char *certp;
+ result = CopyCertSubject(data, server_cert, &certp);
+ if(!result) {
+ infof(data, "Server certificate: %s", certp);
+ free(certp);
+ }
+ }
+#endif
+ if(ssl_config->certinfo)
+ result = add_cert_to_certinfo(data, server_cert, (int)idx);
+ return result;
+}
+
+/* This should be called during step3 of the connection at the earliest */
+static CURLcode collect_server_cert(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+ const bool show_verbose_server_cert = data->set.verbose;
+#else
+ const bool show_verbose_server_cert = false;
+#endif
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ CURLcode result = ssl_config->certinfo ?
+ CURLE_PEER_FAILED_VERIFICATION : CURLE_OK;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ CFArrayRef server_certs = NULL;
+ SecCertificateRef server_cert;
+ OSStatus err;
+ CFIndex i, count;
+ SecTrustRef trust = NULL;
+
+ DEBUGASSERT(backend);
+
+ if(!show_verbose_server_cert && !ssl_config->certinfo)
+ return CURLE_OK;
+
+ if(!backend->ssl_ctx)
+ return result;
+
+#if CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS
+#if CURL_BUILD_IOS
+#pragma unused(server_certs)
+ err = SSLCopyPeerTrust(backend->ssl_ctx, &trust);
+ /* For some reason, SSLCopyPeerTrust() can return noErr and yet return
+ a null trust, so be on guard for that: */
+ if(err == noErr && trust) {
+ count = SecTrustGetCertificateCount(trust);
+ if(ssl_config->certinfo)
+ result = Curl_ssl_init_certinfo(data, (int)count);
+ for(i = 0L ; !result && (i < count) ; i++) {
+ server_cert = SecTrustGetCertificateAtIndex(trust, i);
+ result = collect_server_cert_single(cf, data, server_cert, i);
+ }
+ CFRelease(trust);
+ }
+#else
+ /* SSLCopyPeerCertificates() is deprecated as of Mountain Lion.
+ The function SecTrustGetCertificateAtIndex() is officially present
+ in Lion, but it is unfortunately also present in Snow Leopard as
+ private API and doesn't work as expected. So we have to look for
+ a different symbol to make sure this code is only executed under
+ Lion or later. */
+ if(SecTrustCopyPublicKey) {
+#pragma unused(server_certs)
+ err = SSLCopyPeerTrust(backend->ssl_ctx, &trust);
+ /* For some reason, SSLCopyPeerTrust() can return noErr and yet return
+ a null trust, so be on guard for that: */
+ if(err == noErr && trust) {
+ count = SecTrustGetCertificateCount(trust);
+ if(ssl_config->certinfo)
+ result = Curl_ssl_init_certinfo(data, (int)count);
+ for(i = 0L ; !result && (i < count) ; i++) {
+ server_cert = SecTrustGetCertificateAtIndex(trust, i);
+ result = collect_server_cert_single(cf, data, server_cert, i);
+ }
+ CFRelease(trust);
+ }
+ }
+ else {
+#if CURL_SUPPORT_MAC_10_8
+ err = SSLCopyPeerCertificates(backend->ssl_ctx, &server_certs);
+ /* Just in case SSLCopyPeerCertificates() returns null too... */
+ if(err == noErr && server_certs) {
+ count = CFArrayGetCount(server_certs);
+ if(ssl_config->certinfo)
+ result = Curl_ssl_init_certinfo(data, (int)count);
+ for(i = 0L ; !result && (i < count) ; i++) {
+ server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs,
+ i);
+ result = collect_server_cert_single(cf, data, server_cert, i);
+ }
+ CFRelease(server_certs);
+ }
+#endif /* CURL_SUPPORT_MAC_10_8 */
+ }
+#endif /* CURL_BUILD_IOS */
+#else
+#pragma unused(trust)
+ err = SSLCopyPeerCertificates(backend->ssl_ctx, &server_certs);
+ if(err == noErr) {
+ count = CFArrayGetCount(server_certs);
+ if(ssl_config->certinfo)
+ result = Curl_ssl_init_certinfo(data, (int)count);
+ for(i = 0L ; !result && (i < count) ; i++) {
+ server_cert = (SecCertificateRef)CFArrayGetValueAtIndex(server_certs, i);
+ result = collect_server_cert_single(cf, data, server_cert, i);
+ }
+ CFRelease(server_certs);
+ }
+#endif /* CURL_BUILD_MAC_10_7 || CURL_BUILD_IOS */
+ return result;
+}
+
+static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+
+ /* There is no step 3!
+ * Well, okay, let's collect server certificates, and if verbose mode is on,
+ * let's print the details of the server certificates. */
+ const CURLcode result = collect_server_cert(cf, data);
+ if(result)
+ return result;
+
+ connssl->connecting_state = ssl_connect_done;
+ return CURLE_OK;
+}
+
+static CURLcode
+sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data,
+ bool nonblocking,
+ bool *done)
+{
+ CURLcode result;
+ struct ssl_connect_data *connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ int what;
+
+ /* check if the connection has already been established */
+ if(ssl_connection_complete == connssl->state) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ if(ssl_connect_1 == connssl->connecting_state) {
+ /* Find out how much more time we're allowed */
+ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ result = sectransp_connect_step1(cf, data);
+ if(result)
+ return result;
+ }
+
+ while(ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state) {
+
+ /* check allowed time left */
+ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ /* if ssl is expecting something, check if it's available. */
+ if(connssl->connecting_state == ssl_connect_2_reading ||
+ connssl->connecting_state == ssl_connect_2_writing) {
+
+ curl_socket_t writefd = ssl_connect_2_writing ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ nonblocking ? 0 : timeout_ms);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else if(0 == what) {
+ if(nonblocking) {
+ *done = FALSE;
+ return CURLE_OK;
+ }
+ else {
+ /* timeout */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+ /* socket is readable or writable */
+ }
+
+ /* Run transaction, and return to the caller if it failed or if this
+ * connection is done nonblocking and this loop would execute again. This
+ * permits the owner of a multi handle to abort a connection attempt
+ * before step2 has completed while ensuring that a client using select()
+ * or epoll() will always have a valid fdset to wait on.
+ */
+ result = sectransp_connect_step2(cf, data);
+ if(result || (nonblocking &&
+ (ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state)))
+ return result;
+
+ } /* repeat step2 until all transactions are done. */
+
+
+ if(ssl_connect_3 == connssl->connecting_state) {
+ result = sectransp_connect_step3(cf, data);
+ if(result)
+ return result;
+ }
+
+ if(ssl_connect_done == connssl->connecting_state) {
+ connssl->state = ssl_connection_complete;
+ *done = TRUE;
+ }
+ else
+ *done = FALSE;
+
+ /* Reset our connect state machine */
+ connssl->connecting_state = ssl_connect_1;
+
+ return CURLE_OK;
+}
+
+static CURLcode sectransp_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return sectransp_connect_common(cf, data, TRUE, done);
+}
+
+static CURLcode sectransp_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode result;
+ bool done = FALSE;
+
+ result = sectransp_connect_common(cf, data, FALSE, &done);
+
+ if(result)
+ return result;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ (void) data;
+
+ DEBUGASSERT(backend);
+
+ if(backend->ssl_ctx) {
+ (void)SSLClose(backend->ssl_ctx);
+#if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
+ if(SSLCreateContext)
+ CFRelease(backend->ssl_ctx);
+#if CURL_SUPPORT_MAC_10_8
+ else
+ (void)SSLDisposeContext(backend->ssl_ctx);
+#endif /* CURL_SUPPORT_MAC_10_8 */
+#else
+ (void)SSLDisposeContext(backend->ssl_ctx);
+#endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
+ backend->ssl_ctx = NULL;
+ }
+}
+
+static int sectransp_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ ssize_t nread;
+ int what;
+ int rc;
+ char buf[120];
+ int loop = 10; /* avoid getting stuck */
+ CURLcode result;
+
+ DEBUGASSERT(backend);
+
+ if(!backend->ssl_ctx)
+ return 0;
+
+#ifndef CURL_DISABLE_FTP
+ if(data->set.ftp_ccc != CURLFTPSSL_CCC_ACTIVE)
+ return 0;
+#endif
+
+ sectransp_close(cf, data);
+
+ rc = 0;
+
+ what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], SSL_SHUTDOWN_TIMEOUT);
+
+ while(loop--) {
+ if(what < 0) {
+ /* anything that gets here is fatally bad */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ rc = -1;
+ break;
+ }
+
+ if(!what) { /* timeout */
+ failf(data, "SSL shutdown timeout");
+ break;
+ }
+
+ /* Something to read, let's do it and hope that it is the close
+ notify alert from the server. No way to SSL_Read now, so use read(). */
+
+ nread = Curl_conn_cf_recv(cf->next, data, buf, sizeof(buf), &result);
+
+ if(nread < 0) {
+ failf(data, "read: %s", curl_easy_strerror(result));
+ rc = -1;
+ }
+
+ if(nread <= 0)
+ break;
+
+ what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
+ }
+
+ return rc;
+}
+
+static void sectransp_session_free(void *ptr)
+{
+ /* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a
+ cached session ID inside the Security framework. There is a private
+ function that does this, but I don't want to have to explain to you why I
+ got your application rejected from the App Store due to the use of a
+ private API, so the best we can do is free up our own char array that we
+ created way back in sectransp_connect_step1... */
+ Curl_safefree(ptr);
+}
+
+static size_t sectransp_version(char *buffer, size_t size)
+{
+ return msnprintf(buffer, size, "SecureTransport");
+}
+
+/*
+ * This function uses SSLGetSessionState to determine connection status.
+ *
+ * Return codes:
+ * 1 means the connection is still in place
+ * 0 means the connection has been closed
+ * -1 means the connection status is unknown
+ */
+static int sectransp_check_cxn(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ OSStatus err;
+ SSLSessionState state;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ if(backend->ssl_ctx) {
+ err = SSLGetSessionState(backend->ssl_ctx, &state);
+ if(err == noErr)
+ return state == kSSLConnected || state == kSSLHandshake;
+ return -1;
+ }
+ return 0;
+}
+
+static bool sectransp_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ const struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ OSStatus err;
+ size_t buffer;
+
+ (void)data;
+ DEBUGASSERT(backend);
+
+ if(backend->ssl_ctx) { /* SSL is in use */
+ err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer);
+ if(err == noErr)
+ return buffer > 0UL;
+ return false;
+ }
+ else
+ return false;
+}
+
+static CURLcode sectransp_random(struct Curl_easy *data UNUSED_PARAM,
+ unsigned char *entropy, size_t length)
+{
+ /* arc4random_buf() isn't available on cats older than Lion, so let's
+ do this manually for the benefit of the older cats. */
+ size_t i;
+ u_int32_t random_number = 0;
+
+ (void)data;
+
+ for(i = 0 ; i < length ; i++) {
+ if(i % sizeof(u_int32_t) == 0)
+ random_number = arc4random();
+ entropy[i] = random_number & 0xFF;
+ random_number >>= 8;
+ }
+ i = random_number = 0;
+ return CURLE_OK;
+}
+
+static CURLcode sectransp_sha256sum(const unsigned char *tmp, /* input */
+ size_t tmplen,
+ unsigned char *sha256sum, /* output */
+ size_t sha256len)
+{
+ assert(sha256len >= CURL_SHA256_DIGEST_LENGTH);
+ (void)CC_SHA256(tmp, (CC_LONG)tmplen, sha256sum);
+ return CURLE_OK;
+}
+
+static bool sectransp_false_start(void)
+{
+#if CURL_BUILD_MAC_10_9 || CURL_BUILD_IOS_7
+ if(SSLSetSessionOption)
+ return TRUE;
+#endif
+ return FALSE;
+}
+
+static ssize_t sectransp_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ size_t processed = 0UL;
+ OSStatus err;
+
+ DEBUGASSERT(backend);
+
+ /* The SSLWrite() function works a little differently than expected. The
+ fourth argument (processed) is currently documented in Apple's
+ documentation as: "On return, the length, in bytes, of the data actually
+ written."
+
+ Now, one could interpret that as "written to the socket," but actually,
+ it returns the amount of data that was written to a buffer internal to
+ the SSLContextRef instead. So it's possible for SSLWrite() to return
+ errSSLWouldBlock and a number of bytes "written" because those bytes were
+ encrypted and written to a buffer, not to the socket.
+
+ So if this happens, then we need to keep calling SSLWrite() over and
+ over again with no new data until it quits returning errSSLWouldBlock. */
+
+ /* Do we have buffered data to write from the last time we were called? */
+ if(backend->ssl_write_buffered_length) {
+ /* Write the buffered data: */
+ err = SSLWrite(backend->ssl_ctx, NULL, 0UL, &processed);
+ switch(err) {
+ case noErr:
+ /* processed is always going to be 0 because we didn't write to
+ the buffer, so return how much was written to the socket */
+ processed = backend->ssl_write_buffered_length;
+ backend->ssl_write_buffered_length = 0UL;
+ break;
+ case errSSLWouldBlock: /* argh, try again */
+ *curlcode = CURLE_AGAIN;
+ return -1L;
+ default:
+ failf(data, "SSLWrite() returned error %d", err);
+ *curlcode = CURLE_SEND_ERROR;
+ return -1L;
+ }
+ }
+ else {
+ /* We've got new data to write: */
+ err = SSLWrite(backend->ssl_ctx, mem, len, &processed);
+ if(err != noErr) {
+ switch(err) {
+ case errSSLWouldBlock:
+ /* Data was buffered but not sent, we have to tell the caller
+ to try sending again, and remember how much was buffered */
+ backend->ssl_write_buffered_length = len;
+ *curlcode = CURLE_AGAIN;
+ return -1L;
+ default:
+ failf(data, "SSLWrite() returned error %d", err);
+ *curlcode = CURLE_SEND_ERROR;
+ return -1L;
+ }
+ }
+ }
+ return (ssize_t)processed;
+}
+
+static ssize_t sectransp_recv(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *buf,
+ size_t buffersize,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ size_t processed = 0UL;
+ OSStatus err;
+
+ DEBUGASSERT(backend);
+
+ again:
+ err = SSLRead(backend->ssl_ctx, buf, buffersize, &processed);
+
+ if(err != noErr) {
+ switch(err) {
+ case errSSLWouldBlock: /* return how much we read (if anything) */
+ if(processed)
+ return (ssize_t)processed;
+ *curlcode = CURLE_AGAIN;
+ return -1L;
+ break;
+
+ /* errSSLClosedGraceful - server gracefully shut down the SSL session
+ errSSLClosedNoNotify - server hung up on us instead of sending a
+ closure alert notice, read() is returning 0
+ Either way, inform the caller that the server disconnected. */
+ case errSSLClosedGraceful:
+ case errSSLClosedNoNotify:
+ *curlcode = CURLE_OK;
+ return -1L;
+ break;
+
+ /* The below is errSSLPeerAuthCompleted; it's not defined in
+ Leopard's headers */
+ case -9841:
+ if((conn_config->CAfile || conn_config->ca_info_blob) &&
+ conn_config->verifypeer) {
+ CURLcode result = verify_cert(data, conn_config->CAfile,
+ conn_config->ca_info_blob,
+ backend->ssl_ctx);
+ if(result)
+ return result;
+ }
+ goto again;
+ default:
+ failf(data, "SSLRead() return error %d", err);
+ *curlcode = CURLE_RECV_ERROR;
+ return -1L;
+ break;
+ }
+ }
+ return (ssize_t)processed;
+}
+
+static void *sectransp_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ (void)info;
+ DEBUGASSERT(backend);
+ return backend->ssl_ctx;
+}
+
+const struct Curl_ssl Curl_ssl_sectransp = {
+ { CURLSSLBACKEND_SECURETRANSPORT, "secure-transport" }, /* info */
+
+ SSLSUPP_CAINFO_BLOB |
+ SSLSUPP_CERTINFO |
+#ifdef SECTRANSP_PINNEDPUBKEY
+ SSLSUPP_PINNEDPUBKEY |
+#endif /* SECTRANSP_PINNEDPUBKEY */
+ SSLSUPP_HTTPS_PROXY,
+
+ sizeof(struct ssl_backend_data),
+
+ Curl_none_init, /* init */
+ Curl_none_cleanup, /* cleanup */
+ sectransp_version, /* version */
+ sectransp_check_cxn, /* check_cxn */
+ sectransp_shutdown, /* shutdown */
+ sectransp_data_pending, /* data_pending */
+ sectransp_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ sectransp_connect, /* connect */
+ sectransp_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ sectransp_get_internals, /* get_internals */
+ sectransp_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ sectransp_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ sectransp_false_start, /* false_start */
+ sectransp_sha256sum, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ sectransp_recv, /* recv decrypted data */
+ sectransp_send, /* send data to encrypt */
+};
+
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+#endif /* USE_SECTRANSP */
diff --git a/libs/libcurl/src/vtls/sectransp.h b/libs/libcurl/src/vtls/sectransp.h index 2d53b7c480..caa6374065 100644 --- a/libs/libcurl/src/vtls/sectransp.h +++ b/libs/libcurl/src/vtls/sectransp.h @@ -1,34 +1,34 @@ -#ifndef HEADER_CURL_SECTRANSP_H -#define HEADER_CURL_SECTRANSP_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 2012 - 2014, Nick Zitzmann, <nickzman@gmail.com>. - * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_SECTRANSP - -extern const struct Curl_ssl Curl_ssl_sectransp; - -#endif /* USE_SECTRANSP */ -#endif /* HEADER_CURL_SECTRANSP_H */ +#ifndef HEADER_CURL_SECTRANSP_H
+#define HEADER_CURL_SECTRANSP_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 2012 - 2014, Nick Zitzmann, <nickzman@gmail.com>.
+ * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_SECTRANSP
+
+extern const struct Curl_ssl Curl_ssl_sectransp;
+
+#endif /* USE_SECTRANSP */
+#endif /* HEADER_CURL_SECTRANSP_H */
diff --git a/libs/libcurl/src/vtls/vtls.c b/libs/libcurl/src/vtls/vtls.c index 9dee5aa3b3..6cd7b12983 100644 --- a/libs/libcurl/src/vtls/vtls.c +++ b/libs/libcurl/src/vtls/vtls.c @@ -1,1500 +1,1823 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* This file is for implementing all "generic" SSL functions that all libcurl - internals should use. It is then responsible for calling the proper - "backend" function. - - SSL-functions in libcurl should call functions in this source file, and not - to any specific SSL-layer. - - Curl_ssl_ - prefix for generic ones - - Note that this source code uses the functions of the configured SSL - backend via the global Curl_ssl instance. - - "SSL/TLS Strong Encryption: An Introduction" - https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html -*/ - -#include "curl_setup.h" - -#ifdef HAVE_SYS_TYPES_H -#include <sys/types.h> -#endif -#ifdef HAVE_SYS_STAT_H -#include <sys/stat.h> -#endif -#ifdef HAVE_FCNTL_H -#include <fcntl.h> -#endif - -#include "urldata.h" - -#include "vtls.h" /* generic SSL protos etc */ -#include "slist.h" -#include "sendf.h" -#include "strcase.h" -#include "url.h" -#include "progress.h" -#include "share.h" -#include "multiif.h" -#include "timeval.h" -#include "curl_md5.h" -#include "warnless.h" -#include "curl_base64.h" -#include "curl_printf.h" -#include "strdup.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -/* convenience macro to check if this handle is using a shared SSL session */ -#define SSLSESSION_SHARED(data) (data->share && \ - (data->share->specifier & \ - (1<<CURL_LOCK_DATA_SSL_SESSION))) - -#define CLONE_STRING(var) \ - do { \ - if(source->var) { \ - dest->var = strdup(source->var); \ - if(!dest->var) \ - return FALSE; \ - } \ - else \ - dest->var = NULL; \ - } while(0) - -#define CLONE_BLOB(var) \ - do { \ - if(blobdup(&dest->var, source->var)) \ - return FALSE; \ - } while(0) - -static CURLcode blobdup(struct curl_blob **dest, - struct curl_blob *src) -{ - DEBUGASSERT(dest); - DEBUGASSERT(!*dest); - if(src) { - /* only if there's data to dupe! */ - struct curl_blob *d; - d = malloc(sizeof(struct curl_blob) + src->len); - if(!d) - return CURLE_OUT_OF_MEMORY; - d->len = src->len; - /* Always duplicate because the connection may survive longer than the - handle that passed in the blob. */ - d->flags = CURL_BLOB_COPY; - d->data = (void *)((char *)d + sizeof(struct curl_blob)); - memcpy(d->data, src->data, src->len); - *dest = d; - } - return CURLE_OK; -} - -/* returns TRUE if the blobs are identical */ -static bool blobcmp(struct curl_blob *first, struct curl_blob *second) -{ - if(!first && !second) /* both are NULL */ - return TRUE; - if(!first || !second) /* one is NULL */ - return FALSE; - if(first->len != second->len) /* different sizes */ - return FALSE; - return !memcmp(first->data, second->data, first->len); /* same data */ -} - - -bool -Curl_ssl_config_matches(struct ssl_primary_config *data, - struct ssl_primary_config *needle) -{ - if((data->version == needle->version) && - (data->version_max == needle->version_max) && - (data->ssl_options == needle->ssl_options) && - (data->verifypeer == needle->verifypeer) && - (data->verifyhost == needle->verifyhost) && - (data->verifystatus == needle->verifystatus) && - blobcmp(data->cert_blob, needle->cert_blob) && - blobcmp(data->ca_info_blob, needle->ca_info_blob) && - blobcmp(data->issuercert_blob, needle->issuercert_blob) && - Curl_safecmp(data->CApath, needle->CApath) && - Curl_safecmp(data->CAfile, needle->CAfile) && - Curl_safecmp(data->issuercert, needle->issuercert) && - Curl_safecmp(data->clientcert, needle->clientcert) && -#ifdef USE_TLS_SRP - !Curl_timestrcmp(data->username, needle->username) && - !Curl_timestrcmp(data->password, needle->password) && - (data->authtype == needle->authtype) && -#endif - Curl_safe_strcasecompare(data->cipher_list, needle->cipher_list) && - Curl_safe_strcasecompare(data->cipher_list13, needle->cipher_list13) && - Curl_safe_strcasecompare(data->curves, needle->curves) && - Curl_safe_strcasecompare(data->CRLfile, needle->CRLfile) && - Curl_safe_strcasecompare(data->pinned_key, needle->pinned_key)) - return TRUE; - - return FALSE; -} - -bool -Curl_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest) -{ - dest->version = source->version; - dest->version_max = source->version_max; - dest->verifypeer = source->verifypeer; - dest->verifyhost = source->verifyhost; - dest->verifystatus = source->verifystatus; - dest->sessionid = source->sessionid; - dest->ssl_options = source->ssl_options; -#ifdef USE_TLS_SRP - dest->authtype = source->authtype; -#endif - - CLONE_BLOB(cert_blob); - CLONE_BLOB(ca_info_blob); - CLONE_BLOB(issuercert_blob); - CLONE_STRING(CApath); - CLONE_STRING(CAfile); - CLONE_STRING(issuercert); - CLONE_STRING(clientcert); - CLONE_STRING(cipher_list); - CLONE_STRING(cipher_list13); - CLONE_STRING(pinned_key); - CLONE_STRING(curves); - CLONE_STRING(CRLfile); -#ifdef USE_TLS_SRP - CLONE_STRING(username); - CLONE_STRING(password); -#endif - - return TRUE; -} - -void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc) -{ - Curl_safefree(sslc->CApath); - Curl_safefree(sslc->CAfile); - Curl_safefree(sslc->issuercert); - Curl_safefree(sslc->clientcert); - Curl_safefree(sslc->cipher_list); - Curl_safefree(sslc->cipher_list13); - Curl_safefree(sslc->pinned_key); - Curl_safefree(sslc->cert_blob); - Curl_safefree(sslc->ca_info_blob); - Curl_safefree(sslc->issuercert_blob); - Curl_safefree(sslc->curves); - Curl_safefree(sslc->CRLfile); -#ifdef USE_TLS_SRP - Curl_safefree(sslc->username); - Curl_safefree(sslc->password); -#endif -} - -#ifdef USE_SSL -static int multissl_setup(const struct Curl_ssl *backend); -#endif - -curl_sslbackend Curl_ssl_backend(void) -{ -#ifdef USE_SSL - multissl_setup(NULL); - return Curl_ssl->info.id; -#else - return CURLSSLBACKEND_NONE; -#endif -} - -#ifdef USE_SSL - -/* "global" init done? */ -static bool init_ssl = FALSE; - -/** - * Global SSL init - * - * @retval 0 error initializing SSL - * @retval 1 SSL initialized successfully - */ -int Curl_ssl_init(void) -{ - /* make sure this is only done once */ - if(init_ssl) - return 1; - init_ssl = TRUE; /* never again */ - - return Curl_ssl->init(); -} - -#if defined(CURL_WITH_MULTI_SSL) -static const struct Curl_ssl Curl_ssl_multi; -#endif - -/* Global cleanup */ -void Curl_ssl_cleanup(void) -{ - if(init_ssl) { - /* only cleanup if we did a previous init */ - Curl_ssl->cleanup(); -#if defined(CURL_WITH_MULTI_SSL) - Curl_ssl = &Curl_ssl_multi; -#endif - init_ssl = FALSE; - } -} - -static bool ssl_prefs_check(struct Curl_easy *data) -{ - /* check for CURLOPT_SSLVERSION invalid parameter value */ - const long sslver = data->set.ssl.primary.version; - if((sslver < 0) || (sslver >= CURL_SSLVERSION_LAST)) { - failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION"); - return FALSE; - } - - switch(data->set.ssl.primary.version_max) { - case CURL_SSLVERSION_MAX_NONE: - case CURL_SSLVERSION_MAX_DEFAULT: - break; - - default: - if((data->set.ssl.primary.version_max >> 16) < sslver) { - failf(data, "CURL_SSLVERSION_MAX incompatible with CURL_SSLVERSION"); - return FALSE; - } - } - - return TRUE; -} - -#ifndef CURL_DISABLE_PROXY -static CURLcode -ssl_connect_init_proxy(struct connectdata *conn, int sockindex) -{ - DEBUGASSERT(conn->bits.proxy_ssl_connected[sockindex]); - if(ssl_connection_complete == conn->ssl[sockindex].state && - !conn->proxy_ssl[sockindex].use) { - struct ssl_backend_data *pbdata; - - if(!(Curl_ssl->supports & SSLSUPP_HTTPS_PROXY)) - return CURLE_NOT_BUILT_IN; - - /* The pointers to the ssl backend data, which is opaque here, are swapped - rather than move the contents. */ - pbdata = conn->proxy_ssl[sockindex].backend; - conn->proxy_ssl[sockindex] = conn->ssl[sockindex]; - - DEBUGASSERT(pbdata != NULL); - - memset(&conn->ssl[sockindex], 0, sizeof(conn->ssl[sockindex])); - memset(pbdata, 0, Curl_ssl->sizeof_ssl_backend_data); - - conn->ssl[sockindex].backend = pbdata; - } - return CURLE_OK; -} -#endif - -CURLcode -Curl_ssl_connect(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - CURLcode result; - -#ifndef CURL_DISABLE_PROXY - if(conn->bits.proxy_ssl_connected[sockindex]) { - result = ssl_connect_init_proxy(conn, sockindex); - if(result) - return result; - } -#endif - - if(!ssl_prefs_check(data)) - return CURLE_SSL_CONNECT_ERROR; - - /* mark this is being ssl-enabled from here on. */ - conn->ssl[sockindex].use = TRUE; - conn->ssl[sockindex].state = ssl_connection_negotiating; - - result = Curl_ssl->connect_blocking(data, conn, sockindex); - - if(!result) - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ - else - conn->ssl[sockindex].use = FALSE; - - return result; -} - -CURLcode -Curl_ssl_connect_nonblocking(struct Curl_easy *data, struct connectdata *conn, - bool isproxy, int sockindex, bool *done) -{ - CURLcode result; - -#ifndef CURL_DISABLE_PROXY - if(conn->bits.proxy_ssl_connected[sockindex]) { - result = ssl_connect_init_proxy(conn, sockindex); - if(result) - return result; - } -#endif - if(!ssl_prefs_check(data)) - return CURLE_SSL_CONNECT_ERROR; - - /* mark this is being ssl requested from here on. */ - conn->ssl[sockindex].use = TRUE; - result = Curl_ssl->connect_nonblocking(data, conn, sockindex, done); - if(result) - conn->ssl[sockindex].use = FALSE; - else if(*done && !isproxy) - Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */ - return result; -} - -/* - * Lock shared SSL session data - */ -void Curl_ssl_sessionid_lock(struct Curl_easy *data) -{ - if(SSLSESSION_SHARED(data)) - Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE); -} - -/* - * Unlock shared SSL session data - */ -void Curl_ssl_sessionid_unlock(struct Curl_easy *data) -{ - if(SSLSESSION_SHARED(data)) - Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION); -} - -/* - * Check if there's a session ID for the given connection in the cache, and if - * there's one suitable, it is provided. Returns TRUE when no entry matched. - */ -bool Curl_ssl_getsessionid(struct Curl_easy *data, - struct connectdata *conn, - const bool isProxy, - void **ssl_sessionid, - size_t *idsize, /* set 0 if unknown */ - int sockindex) -{ - struct Curl_ssl_session *check; - size_t i; - long *general_age; - bool no_match = TRUE; - -#ifndef CURL_DISABLE_PROXY - struct ssl_primary_config * const ssl_config = isProxy ? - &conn->proxy_ssl_config : - &conn->ssl_config; - const char * const name = isProxy ? - conn->http_proxy.host.name : conn->host.name; - int port = isProxy ? (int)conn->port : conn->remote_port; -#else - /* no proxy support */ - struct ssl_primary_config * const ssl_config = &conn->ssl_config; - const char * const name = conn->host.name; - int port = conn->remote_port; -#endif - (void)sockindex; - *ssl_sessionid = NULL; - -#ifdef CURL_DISABLE_PROXY - if(isProxy) - return TRUE; -#endif - - DEBUGASSERT(SSL_SET_OPTION(primary.sessionid)); - - if(!SSL_SET_OPTION(primary.sessionid) || !data->state.session) - /* session ID re-use is disabled or the session cache has not been - setup */ - return TRUE; - - /* Lock if shared */ - if(SSLSESSION_SHARED(data)) - general_age = &data->share->sessionage; - else - general_age = &data->state.sessionage; - - for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { - check = &data->state.session[i]; - if(!check->sessionid) - /* not session ID means blank entry */ - continue; - if(strcasecompare(name, check->name) && - ((!conn->bits.conn_to_host && !check->conn_to_host) || - (conn->bits.conn_to_host && check->conn_to_host && - strcasecompare(conn->conn_to_host.name, check->conn_to_host))) && - ((!conn->bits.conn_to_port && check->conn_to_port == -1) || - (conn->bits.conn_to_port && check->conn_to_port != -1 && - conn->conn_to_port == check->conn_to_port)) && - (port == check->remote_port) && - strcasecompare(conn->handler->scheme, check->scheme) && - Curl_ssl_config_matches(ssl_config, &check->ssl_config)) { - /* yes, we have a session ID! */ - (*general_age)++; /* increase general age */ - check->age = *general_age; /* set this as used in this age */ - *ssl_sessionid = check->sessionid; - if(idsize) - *idsize = check->idsize; - no_match = FALSE; - break; - } - } - - DEBUGF(infof(data, "%s Session ID in cache for %s %s://%s:%d", - no_match? "Didn't find": "Found", - isProxy ? "proxy" : "host", - conn->handler->scheme, name, port)); - return no_match; -} - -/* - * Kill a single session ID entry in the cache. - */ -void Curl_ssl_kill_session(struct Curl_ssl_session *session) -{ - if(session->sessionid) { - /* defensive check */ - - /* free the ID the SSL-layer specific way */ - Curl_ssl->session_free(session->sessionid); - - session->sessionid = NULL; - session->age = 0; /* fresh */ - - Curl_free_primary_ssl_config(&session->ssl_config); - - Curl_safefree(session->name); - Curl_safefree(session->conn_to_host); - } -} - -/* - * Delete the given session ID from the cache. - */ -void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid) -{ - size_t i; - - for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) { - struct Curl_ssl_session *check = &data->state.session[i]; - - if(check->sessionid == ssl_sessionid) { - Curl_ssl_kill_session(check); - break; - } - } -} - -/* - * Store session id in the session cache. The ID passed on to this function - * must already have been extracted and allocated the proper way for the SSL - * layer. Curl_XXXX_session_free() will be called to free/kill the session ID - * later on. - */ -CURLcode Curl_ssl_addsessionid(struct Curl_easy *data, - struct connectdata *conn, - const bool isProxy, - void *ssl_sessionid, - size_t idsize, - int sockindex, - bool *added) -{ - size_t i; - struct Curl_ssl_session *store; - long oldest_age; - char *clone_host; - char *clone_conn_to_host; - int conn_to_port; - long *general_age; -#ifndef CURL_DISABLE_PROXY - struct ssl_primary_config * const ssl_config = isProxy ? - &conn->proxy_ssl_config : - &conn->ssl_config; - const char *hostname = isProxy ? conn->http_proxy.host.name : - conn->host.name; -#else - struct ssl_primary_config * const ssl_config = &conn->ssl_config; - const char *hostname = conn->host.name; -#endif - (void)sockindex; - - if(added) - *added = FALSE; - - if(!data->state.session) - return CURLE_OK; - - store = &data->state.session[0]; - oldest_age = data->state.session[0].age; /* zero if unused */ - DEBUGASSERT(SSL_SET_OPTION(primary.sessionid)); - - clone_host = strdup(hostname); - if(!clone_host) - return CURLE_OUT_OF_MEMORY; /* bail out */ - - if(conn->bits.conn_to_host) { - clone_conn_to_host = strdup(conn->conn_to_host.name); - if(!clone_conn_to_host) { - free(clone_host); - return CURLE_OUT_OF_MEMORY; /* bail out */ - } - } - else - clone_conn_to_host = NULL; - - if(conn->bits.conn_to_port) - conn_to_port = conn->conn_to_port; - else - conn_to_port = -1; - - /* Now we should add the session ID and the host name to the cache, (remove - the oldest if necessary) */ - - /* If using shared SSL session, lock! */ - if(SSLSESSION_SHARED(data)) { - general_age = &data->share->sessionage; - } - else { - general_age = &data->state.sessionage; - } - - /* find an empty slot for us, or find the oldest */ - for(i = 1; (i < data->set.general_ssl.max_ssl_sessions) && - data->state.session[i].sessionid; i++) { - if(data->state.session[i].age < oldest_age) { - oldest_age = data->state.session[i].age; - store = &data->state.session[i]; - } - } - if(i == data->set.general_ssl.max_ssl_sessions) - /* cache is full, we must "kill" the oldest entry! */ - Curl_ssl_kill_session(store); - else - store = &data->state.session[i]; /* use this slot */ - - /* now init the session struct wisely */ - store->sessionid = ssl_sessionid; - store->idsize = idsize; - store->age = *general_age; /* set current age */ - /* free it if there's one already present */ - free(store->name); - free(store->conn_to_host); - store->name = clone_host; /* clone host name */ - store->conn_to_host = clone_conn_to_host; /* clone connect to host name */ - store->conn_to_port = conn_to_port; /* connect to port number */ - /* port number */ - store->remote_port = isProxy ? (int)conn->port : conn->remote_port; - store->scheme = conn->handler->scheme; - - if(!Curl_clone_primary_ssl_config(ssl_config, &store->ssl_config)) { - Curl_free_primary_ssl_config(&store->ssl_config); - store->sessionid = NULL; /* let caller free sessionid */ - free(clone_host); - free(clone_conn_to_host); - return CURLE_OUT_OF_MEMORY; - } - - if(added) - *added = TRUE; - - DEBUGF(infof(data, "Added Session ID to cache for %s://%s:%d [%s]", - store->scheme, store->name, store->remote_port, - isProxy ? "PROXY" : "server")); - return CURLE_OK; -} - -void Curl_ssl_associate_conn(struct Curl_easy *data, - struct connectdata *conn) -{ - if(Curl_ssl->associate_connection) { - Curl_ssl->associate_connection(data, conn, FIRSTSOCKET); - if((conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) && - conn->bits.sock_accepted) - Curl_ssl->associate_connection(data, conn, SECONDARYSOCKET); - } -} - -void Curl_ssl_detach_conn(struct Curl_easy *data, - struct connectdata *conn) -{ - if(Curl_ssl->disassociate_connection) { - Curl_ssl->disassociate_connection(data, FIRSTSOCKET); - if((conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) && - conn->bits.sock_accepted) - Curl_ssl->disassociate_connection(data, SECONDARYSOCKET); - } -} - -void Curl_ssl_close_all(struct Curl_easy *data) -{ - /* kill the session ID cache if not shared */ - if(data->state.session && !SSLSESSION_SHARED(data)) { - size_t i; - for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) - /* the single-killer function handles empty table slots */ - Curl_ssl_kill_session(&data->state.session[i]); - - /* free the cache data */ - Curl_safefree(data->state.session); - } - - Curl_ssl->close_all(data); -} - -int Curl_ssl_getsock(struct connectdata *conn, curl_socket_t *socks) -{ - struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; - - if(connssl->connecting_state == ssl_connect_2_writing) { - /* write mode */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); - } - if(connssl->connecting_state == ssl_connect_2_reading) { - /* read mode */ - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_READSOCK(0); - } - - return GETSOCK_BLANK; -} - -void Curl_ssl_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - DEBUGASSERT((sockindex <= 1) && (sockindex >= -1)); - Curl_ssl->close_one(data, conn, sockindex); - conn->ssl[sockindex].state = ssl_connection_none; -} - -CURLcode Curl_ssl_shutdown(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - if(Curl_ssl->shut_down(data, conn, sockindex)) - return CURLE_SSL_SHUTDOWN_FAILED; - - conn->ssl[sockindex].use = FALSE; /* get back to ordinary socket usage */ - conn->ssl[sockindex].state = ssl_connection_none; - - conn->recv[sockindex] = Curl_recv_plain; - conn->send[sockindex] = Curl_send_plain; - - return CURLE_OK; -} - -/* Selects an SSL crypto engine - */ -CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine) -{ - return Curl_ssl->set_engine(data, engine); -} - -/* Selects the default SSL crypto engine - */ -CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data) -{ - return Curl_ssl->set_engine_default(data); -} - -/* Return list of OpenSSL crypto engine names. */ -struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data) -{ - return Curl_ssl->engines_list(data); -} - -/* - * This sets up a session ID cache to the specified size. Make sure this code - * is agnostic to what underlying SSL technology we use. - */ -CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount) -{ - struct Curl_ssl_session *session; - - if(data->state.session) - /* this is just a precaution to prevent multiple inits */ - return CURLE_OK; - - session = calloc(amount, sizeof(struct Curl_ssl_session)); - if(!session) - return CURLE_OUT_OF_MEMORY; - - /* store the info in the SSL section */ - data->set.general_ssl.max_ssl_sessions = amount; - data->state.session = session; - data->state.sessionage = 1; /* this is brand new */ - return CURLE_OK; -} - -static size_t multissl_version(char *buffer, size_t size); - -void Curl_ssl_version(char *buffer, size_t size) -{ -#ifdef CURL_WITH_MULTI_SSL - (void)multissl_version(buffer, size); -#else - (void)Curl_ssl->version(buffer, size); -#endif -} - -/* - * This function tries to determine connection status. - * - * Return codes: - * 1 means the connection is still in place - * 0 means the connection has been closed - * -1 means the connection status is unknown - */ -int Curl_ssl_check_cxn(struct connectdata *conn) -{ - return Curl_ssl->check_cxn(conn); -} - -bool Curl_ssl_data_pending(const struct connectdata *conn, - int connindex) -{ - return Curl_ssl->data_pending(conn, connindex); -} - -void Curl_ssl_free_certinfo(struct Curl_easy *data) -{ - struct curl_certinfo *ci = &data->info.certs; - - if(ci->num_of_certs) { - /* free all individual lists used */ - int i; - for(i = 0; i<ci->num_of_certs; i++) { - curl_slist_free_all(ci->certinfo[i]); - ci->certinfo[i] = NULL; - } - - free(ci->certinfo); /* free the actual array too */ - ci->certinfo = NULL; - ci->num_of_certs = 0; - } -} - -CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num) -{ - struct curl_certinfo *ci = &data->info.certs; - struct curl_slist **table; - - /* Free any previous certificate information structures */ - Curl_ssl_free_certinfo(data); - - /* Allocate the required certificate information structures */ - table = calloc((size_t) num, sizeof(struct curl_slist *)); - if(!table) - return CURLE_OUT_OF_MEMORY; - - ci->num_of_certs = num; - ci->certinfo = table; - - return CURLE_OK; -} - -/* - * 'value' is NOT a null-terminated string - */ -CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, - int certnum, - const char *label, - const char *value, - size_t valuelen) -{ - struct curl_certinfo *ci = &data->info.certs; - char *output; - struct curl_slist *nl; - CURLcode result = CURLE_OK; - size_t labellen = strlen(label); - size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */ - - output = malloc(outlen); - if(!output) - return CURLE_OUT_OF_MEMORY; - - /* sprintf the label and colon */ - msnprintf(output, outlen, "%s:", label); - - /* memcpy the value (it might not be null-terminated) */ - memcpy(&output[labellen + 1], value, valuelen); - - /* null-terminate the output */ - output[labellen + 1 + valuelen] = 0; - - nl = Curl_slist_append_nodup(ci->certinfo[certnum], output); - if(!nl) { - free(output); - curl_slist_free_all(ci->certinfo[certnum]); - result = CURLE_OUT_OF_MEMORY; - } - - ci->certinfo[certnum] = nl; - return result; -} - -/* - * This is a convenience function for push_certinfo_len that takes a zero - * terminated value. - */ -CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data, - int certnum, - const char *label, - const char *value) -{ - size_t valuelen = strlen(value); - - return Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen); -} - -CURLcode Curl_ssl_random(struct Curl_easy *data, - unsigned char *entropy, - size_t length) -{ - return Curl_ssl->random(data, entropy, length); -} - -/* - * Curl_ssl_snihost() converts the input host name to a suitable SNI name put - * in data->state.buffer. Returns a pointer to the name (or NULL if a problem) - * and stores the new length in 'olen'. - * - * SNI fields must not have any trailing dot and while RFC 6066 section 3 says - * the SNI field is case insensitive, browsers always send the data lowercase - * and subsequently there are numerous servers out there that don't work - * unless the name is lowercased. - */ - -char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen) -{ - size_t len = strlen(host); - if(len && (host[len-1] == '.')) - len--; - if(len >= data->set.buffer_size) - return NULL; - - Curl_strntolower(data->state.buffer, host, len); - data->state.buffer[len] = 0; - if(olen) - *olen = len; - return data->state.buffer; -} - -/* - * Public key pem to der conversion - */ - -static CURLcode pubkey_pem_to_der(const char *pem, - unsigned char **der, size_t *der_len) -{ - char *stripped_pem, *begin_pos, *end_pos; - size_t pem_count, stripped_pem_count = 0, pem_len; - CURLcode result; - - /* if no pem, exit. */ - if(!pem) - return CURLE_BAD_CONTENT_ENCODING; - - begin_pos = strstr(pem, "-----BEGIN PUBLIC KEY-----"); - if(!begin_pos) - return CURLE_BAD_CONTENT_ENCODING; - - pem_count = begin_pos - pem; - /* Invalid if not at beginning AND not directly following \n */ - if(0 != pem_count && '\n' != pem[pem_count - 1]) - return CURLE_BAD_CONTENT_ENCODING; - - /* 26 is length of "-----BEGIN PUBLIC KEY-----" */ - pem_count += 26; - - /* Invalid if not directly following \n */ - end_pos = strstr(pem + pem_count, "\n-----END PUBLIC KEY-----"); - if(!end_pos) - return CURLE_BAD_CONTENT_ENCODING; - - pem_len = end_pos - pem; - - stripped_pem = malloc(pem_len - pem_count + 1); - if(!stripped_pem) - return CURLE_OUT_OF_MEMORY; - - /* - * Here we loop through the pem array one character at a time between the - * correct indices, and place each character that is not '\n' or '\r' - * into the stripped_pem array, which should represent the raw base64 string - */ - while(pem_count < pem_len) { - if('\n' != pem[pem_count] && '\r' != pem[pem_count]) - stripped_pem[stripped_pem_count++] = pem[pem_count]; - ++pem_count; - } - /* Place the null terminator in the correct place */ - stripped_pem[stripped_pem_count] = '\0'; - - result = Curl_base64_decode(stripped_pem, der, der_len); - - Curl_safefree(stripped_pem); - - return result; -} - -/* - * Generic pinned public key check. - */ - -CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, - const char *pinnedpubkey, - const unsigned char *pubkey, size_t pubkeylen) -{ - FILE *fp; - unsigned char *buf = NULL, *pem_ptr = NULL; - CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; - - /* if a path wasn't specified, don't pin */ - if(!pinnedpubkey) - return CURLE_OK; - if(!pubkey || !pubkeylen) - return result; - - /* only do this if pinnedpubkey starts with "sha256//", length 8 */ - if(strncmp(pinnedpubkey, "sha256//", 8) == 0) { - CURLcode encode; - size_t encodedlen, pinkeylen; - char *encoded, *pinkeycopy, *begin_pos, *end_pos; - unsigned char *sha256sumdigest; - - if(!Curl_ssl->sha256sum) { - /* without sha256 support, this cannot match */ - return result; - } - - /* compute sha256sum of public key */ - sha256sumdigest = malloc(CURL_SHA256_DIGEST_LENGTH); - if(!sha256sumdigest) - return CURLE_OUT_OF_MEMORY; - encode = Curl_ssl->sha256sum(pubkey, pubkeylen, - sha256sumdigest, CURL_SHA256_DIGEST_LENGTH); - - if(encode != CURLE_OK) - return encode; - - encode = Curl_base64_encode((char *)sha256sumdigest, - CURL_SHA256_DIGEST_LENGTH, &encoded, - &encodedlen); - Curl_safefree(sha256sumdigest); - - if(encode) - return encode; - - infof(data, " public key hash: sha256//%s", encoded); - - /* it starts with sha256//, copy so we can modify it */ - pinkeylen = strlen(pinnedpubkey) + 1; - pinkeycopy = malloc(pinkeylen); - if(!pinkeycopy) { - Curl_safefree(encoded); - return CURLE_OUT_OF_MEMORY; - } - memcpy(pinkeycopy, pinnedpubkey, pinkeylen); - /* point begin_pos to the copy, and start extracting keys */ - begin_pos = pinkeycopy; - do { - end_pos = strstr(begin_pos, ";sha256//"); - /* - * if there is an end_pos, null terminate, - * otherwise it'll go to the end of the original string - */ - if(end_pos) - end_pos[0] = '\0'; - - /* compare base64 sha256 digests, 8 is the length of "sha256//" */ - if(encodedlen == strlen(begin_pos + 8) && - !memcmp(encoded, begin_pos + 8, encodedlen)) { - result = CURLE_OK; - break; - } - - /* - * change back the null-terminator we changed earlier, - * and look for next begin - */ - if(end_pos) { - end_pos[0] = ';'; - begin_pos = strstr(end_pos, "sha256//"); - } - } while(end_pos && begin_pos); - Curl_safefree(encoded); - Curl_safefree(pinkeycopy); - return result; - } - - fp = fopen(pinnedpubkey, "rb"); - if(!fp) - return result; - - do { - long filesize; - size_t size, pem_len; - CURLcode pem_read; - - /* Determine the file's size */ - if(fseek(fp, 0, SEEK_END)) - break; - filesize = ftell(fp); - if(fseek(fp, 0, SEEK_SET)) - break; - if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE) - break; - - /* - * if the size of our certificate is bigger than the file - * size then it can't match - */ - size = curlx_sotouz((curl_off_t) filesize); - if(pubkeylen > size) - break; - - /* - * Allocate buffer for the pinned key - * With 1 additional byte for null terminator in case of PEM key - */ - buf = malloc(size + 1); - if(!buf) - break; - - /* Returns number of elements read, which should be 1 */ - if((int) fread(buf, size, 1, fp) != 1) - break; - - /* If the sizes are the same, it can't be base64 encoded, must be der */ - if(pubkeylen == size) { - if(!memcmp(pubkey, buf, pubkeylen)) - result = CURLE_OK; - break; - } - - /* - * Otherwise we will assume it's PEM and try to decode it - * after placing null terminator - */ - buf[size] = '\0'; - pem_read = pubkey_pem_to_der((const char *)buf, &pem_ptr, &pem_len); - /* if it wasn't read successfully, exit */ - if(pem_read) - break; - - /* - * if the size of our certificate doesn't match the size of - * the decoded file, they can't be the same, otherwise compare - */ - if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen)) - result = CURLE_OK; - } while(0); - - Curl_safefree(buf); - Curl_safefree(pem_ptr); - fclose(fp); - - return result; -} - -/* - * Check whether the SSL backend supports the status_request extension. - */ -bool Curl_ssl_cert_status_request(void) -{ - return Curl_ssl->cert_status_request(); -} - -/* - * Check whether the SSL backend supports false start. - */ -bool Curl_ssl_false_start(void) -{ - return Curl_ssl->false_start(); -} - -/* - * Check whether the SSL backend supports setting TLS 1.3 cipher suites - */ -bool Curl_ssl_tls13_ciphersuites(void) -{ - return Curl_ssl->supports & SSLSUPP_TLS13_CIPHERSUITES; -} - -/* - * Default implementations for unsupported functions. - */ - -int Curl_none_init(void) -{ - return 1; -} - -void Curl_none_cleanup(void) -{ } - -int Curl_none_shutdown(struct Curl_easy *data UNUSED_PARAM, - struct connectdata *conn UNUSED_PARAM, - int sockindex UNUSED_PARAM) -{ - (void)data; - (void)conn; - (void)sockindex; - return 0; -} - -int Curl_none_check_cxn(struct connectdata *conn UNUSED_PARAM) -{ - (void)conn; - return -1; -} - -CURLcode Curl_none_random(struct Curl_easy *data UNUSED_PARAM, - unsigned char *entropy UNUSED_PARAM, - size_t length UNUSED_PARAM) -{ - (void)data; - (void)entropy; - (void)length; - return CURLE_NOT_BUILT_IN; -} - -void Curl_none_close_all(struct Curl_easy *data UNUSED_PARAM) -{ - (void)data; -} - -void Curl_none_session_free(void *ptr UNUSED_PARAM) -{ - (void)ptr; -} - -bool Curl_none_data_pending(const struct connectdata *conn UNUSED_PARAM, - int connindex UNUSED_PARAM) -{ - (void)conn; - (void)connindex; - return 0; -} - -bool Curl_none_cert_status_request(void) -{ - return FALSE; -} - -CURLcode Curl_none_set_engine(struct Curl_easy *data UNUSED_PARAM, - const char *engine UNUSED_PARAM) -{ - (void)data; - (void)engine; - return CURLE_NOT_BUILT_IN; -} - -CURLcode Curl_none_set_engine_default(struct Curl_easy *data UNUSED_PARAM) -{ - (void)data; - return CURLE_NOT_BUILT_IN; -} - -struct curl_slist *Curl_none_engines_list(struct Curl_easy *data UNUSED_PARAM) -{ - (void)data; - return (struct curl_slist *)NULL; -} - -bool Curl_none_false_start(void) -{ - return FALSE; -} - -static int multissl_init(void) -{ - if(multissl_setup(NULL)) - return 1; - return Curl_ssl->init(); -} - -static CURLcode multissl_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - if(multissl_setup(NULL)) - return CURLE_FAILED_INIT; - return Curl_ssl->connect_blocking(data, conn, sockindex); -} - -static CURLcode multissl_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - if(multissl_setup(NULL)) - return CURLE_FAILED_INIT; - return Curl_ssl->connect_nonblocking(data, conn, sockindex, done); -} - -static int multissl_getsock(struct connectdata *conn, curl_socket_t *socks) -{ - if(multissl_setup(NULL)) - return 0; - return Curl_ssl->getsock(conn, socks); -} - -static void *multissl_get_internals(struct ssl_connect_data *connssl, - CURLINFO info) -{ - if(multissl_setup(NULL)) - return NULL; - return Curl_ssl->get_internals(connssl, info); -} - -static void multissl_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - if(multissl_setup(NULL)) - return; - Curl_ssl->close_one(data, conn, sockindex); -} - -static const struct Curl_ssl Curl_ssl_multi = { - { CURLSSLBACKEND_NONE, "multi" }, /* info */ - 0, /* supports nothing */ - (size_t)-1, /* something insanely large to be on the safe side */ - - multissl_init, /* init */ - Curl_none_cleanup, /* cleanup */ - multissl_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - Curl_none_shutdown, /* shutdown */ - Curl_none_data_pending, /* data_pending */ - Curl_none_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - multissl_connect, /* connect */ - multissl_connect_nonblocking, /* connect_nonblocking */ - multissl_getsock, /* getsock */ - multissl_get_internals, /* get_internals */ - multissl_close, /* close_one */ - Curl_none_close_all, /* close_all */ - Curl_none_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - NULL, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -const struct Curl_ssl *Curl_ssl = -#if defined(CURL_WITH_MULTI_SSL) - &Curl_ssl_multi; -#elif defined(USE_WOLFSSL) - &Curl_ssl_wolfssl; -#elif defined(USE_SECTRANSP) - &Curl_ssl_sectransp; -#elif defined(USE_GNUTLS) - &Curl_ssl_gnutls; -#elif defined(USE_GSKIT) - &Curl_ssl_gskit; -#elif defined(USE_MBEDTLS) - &Curl_ssl_mbedtls; -#elif defined(USE_NSS) - &Curl_ssl_nss; -#elif defined(USE_RUSTLS) - &Curl_ssl_rustls; -#elif defined(USE_OPENSSL) - &Curl_ssl_openssl; -#elif defined(USE_SCHANNEL) - &Curl_ssl_schannel; -#elif defined(USE_BEARSSL) - &Curl_ssl_bearssl; -#else -#error "Missing struct Curl_ssl for selected SSL backend" -#endif - -static const struct Curl_ssl *available_backends[] = { -#if defined(USE_WOLFSSL) - &Curl_ssl_wolfssl, -#endif -#if defined(USE_SECTRANSP) - &Curl_ssl_sectransp, -#endif -#if defined(USE_GNUTLS) - &Curl_ssl_gnutls, -#endif -#if defined(USE_GSKIT) - &Curl_ssl_gskit, -#endif -#if defined(USE_MBEDTLS) - &Curl_ssl_mbedtls, -#endif -#if defined(USE_NSS) - &Curl_ssl_nss, -#endif -#if defined(USE_OPENSSL) - &Curl_ssl_openssl, -#endif -#if defined(USE_SCHANNEL) - &Curl_ssl_schannel, -#endif -#if defined(USE_BEARSSL) - &Curl_ssl_bearssl, -#endif -#if defined(USE_RUSTLS) - &Curl_ssl_rustls, -#endif - NULL -}; - -static size_t multissl_version(char *buffer, size_t size) -{ - static const struct Curl_ssl *selected; - static char backends[200]; - static size_t backends_len; - const struct Curl_ssl *current; - - current = Curl_ssl == &Curl_ssl_multi ? available_backends[0] : Curl_ssl; - - if(current != selected) { - char *p = backends; - char *end = backends + sizeof(backends); - int i; - - selected = current; - - backends[0] = '\0'; - - for(i = 0; available_backends[i]; ++i) { - char vb[200]; - bool paren = (selected != available_backends[i]); - - if(available_backends[i]->version(vb, sizeof(vb))) { - p += msnprintf(p, end - p, "%s%s%s%s", (p != backends ? " " : ""), - (paren ? "(" : ""), vb, (paren ? ")" : "")); - } - } - - backends_len = p - backends; - } - - if(!size) - return 0; - - if(size <= backends_len) { - strncpy(buffer, backends, size - 1); - buffer[size - 1] = '\0'; - return size - 1; - } - - strcpy(buffer, backends); - return backends_len; -} - -static int multissl_setup(const struct Curl_ssl *backend) -{ - const char *env; - char *env_tmp; - - if(Curl_ssl != &Curl_ssl_multi) - return 1; - - if(backend) { - Curl_ssl = backend; - return 0; - } - - if(!available_backends[0]) - return 1; - - env = env_tmp = curl_getenv("CURL_SSL_BACKEND"); -#ifdef CURL_DEFAULT_SSL_BACKEND - if(!env) - env = CURL_DEFAULT_SSL_BACKEND; -#endif - if(env) { - int i; - for(i = 0; available_backends[i]; i++) { - if(strcasecompare(env, available_backends[i]->info.name)) { - Curl_ssl = available_backends[i]; - free(env_tmp); - return 0; - } - } - } - - /* Fall back to first available backend */ - Curl_ssl = available_backends[0]; - free(env_tmp); - return 0; -} - -/* This function is used to select the SSL backend to use. It is called by - curl_global_sslset (easy.c) which uses the global init lock. */ -CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, - const curl_ssl_backend ***avail) -{ - int i; - - if(avail) - *avail = (const curl_ssl_backend **)&available_backends; - - if(Curl_ssl != &Curl_ssl_multi) - return id == Curl_ssl->info.id || - (name && strcasecompare(name, Curl_ssl->info.name)) ? - CURLSSLSET_OK : -#if defined(CURL_WITH_MULTI_SSL) - CURLSSLSET_TOO_LATE; -#else - CURLSSLSET_UNKNOWN_BACKEND; -#endif - - for(i = 0; available_backends[i]; i++) { - if(available_backends[i]->info.id == id || - (name && strcasecompare(available_backends[i]->info.name, name))) { - multissl_setup(available_backends[i]); - return CURLSSLSET_OK; - } - } - - return CURLSSLSET_UNKNOWN_BACKEND; -} - -#else /* USE_SSL */ -CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, - const curl_ssl_backend ***avail) -{ - (void)id; - (void)name; - (void)avail; - return CURLSSLSET_NO_BACKENDS; -} - -#endif /* !USE_SSL */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/* This file is for implementing all "generic" SSL functions that all libcurl
+ internals should use. It is then responsible for calling the proper
+ "backend" function.
+
+ SSL-functions in libcurl should call functions in this source file, and not
+ to any specific SSL-layer.
+
+ Curl_ssl_ - prefix for generic ones
+
+ Note that this source code uses the functions of the configured SSL
+ backend via the global Curl_ssl instance.
+
+ "SSL/TLS Strong Encryption: An Introduction"
+ https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html
+*/
+
+#include "curl_setup.h"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include "urldata.h"
+#include "cfilters.h"
+
+#include "vtls.h" /* generic SSL protos etc */
+#include "vtls_int.h"
+#include "slist.h"
+#include "sendf.h"
+#include "strcase.h"
+#include "url.h"
+#include "progress.h"
+#include "share.h"
+#include "multiif.h"
+#include "timeval.h"
+#include "curl_md5.h"
+#include "warnless.h"
+#include "curl_base64.h"
+#include "curl_printf.h"
+#include "strdup.h"
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* convenience macro to check if this handle is using a shared SSL session */
+#define SSLSESSION_SHARED(data) (data->share && \
+ (data->share->specifier & \
+ (1<<CURL_LOCK_DATA_SSL_SESSION)))
+
+#define CLONE_STRING(var) \
+ do { \
+ if(source->var) { \
+ dest->var = strdup(source->var); \
+ if(!dest->var) \
+ return FALSE; \
+ } \
+ else \
+ dest->var = NULL; \
+ } while(0)
+
+#define CLONE_BLOB(var) \
+ do { \
+ if(blobdup(&dest->var, source->var)) \
+ return FALSE; \
+ } while(0)
+
+static CURLcode blobdup(struct curl_blob **dest,
+ struct curl_blob *src)
+{
+ DEBUGASSERT(dest);
+ DEBUGASSERT(!*dest);
+ if(src) {
+ /* only if there's data to dupe! */
+ struct curl_blob *d;
+ d = malloc(sizeof(struct curl_blob) + src->len);
+ if(!d)
+ return CURLE_OUT_OF_MEMORY;
+ d->len = src->len;
+ /* Always duplicate because the connection may survive longer than the
+ handle that passed in the blob. */
+ d->flags = CURL_BLOB_COPY;
+ d->data = (void *)((char *)d + sizeof(struct curl_blob));
+ memcpy(d->data, src->data, src->len);
+ *dest = d;
+ }
+ return CURLE_OK;
+}
+
+/* returns TRUE if the blobs are identical */
+static bool blobcmp(struct curl_blob *first, struct curl_blob *second)
+{
+ if(!first && !second) /* both are NULL */
+ return TRUE;
+ if(!first || !second) /* one is NULL */
+ return FALSE;
+ if(first->len != second->len) /* different sizes */
+ return FALSE;
+ return !memcmp(first->data, second->data, first->len); /* same data */
+}
+
+
+bool
+Curl_ssl_config_matches(struct ssl_primary_config *data,
+ struct ssl_primary_config *needle)
+{
+ if((data->version == needle->version) &&
+ (data->version_max == needle->version_max) &&
+ (data->ssl_options == needle->ssl_options) &&
+ (data->verifypeer == needle->verifypeer) &&
+ (data->verifyhost == needle->verifyhost) &&
+ (data->verifystatus == needle->verifystatus) &&
+ blobcmp(data->cert_blob, needle->cert_blob) &&
+ blobcmp(data->ca_info_blob, needle->ca_info_blob) &&
+ blobcmp(data->issuercert_blob, needle->issuercert_blob) &&
+ Curl_safecmp(data->CApath, needle->CApath) &&
+ Curl_safecmp(data->CAfile, needle->CAfile) &&
+ Curl_safecmp(data->issuercert, needle->issuercert) &&
+ Curl_safecmp(data->clientcert, needle->clientcert) &&
+#ifdef USE_TLS_SRP
+ !Curl_timestrcmp(data->username, needle->username) &&
+ !Curl_timestrcmp(data->password, needle->password) &&
+ (data->authtype == needle->authtype) &&
+#endif
+ strcasecompare(data->cipher_list, needle->cipher_list) &&
+ strcasecompare(data->cipher_list13, needle->cipher_list13) &&
+ strcasecompare(data->curves, needle->curves) &&
+ strcasecompare(data->CRLfile, needle->CRLfile) &&
+ strcasecompare(data->pinned_key, needle->pinned_key))
+ return TRUE;
+
+ return FALSE;
+}
+
+bool
+Curl_clone_primary_ssl_config(struct ssl_primary_config *source,
+ struct ssl_primary_config *dest)
+{
+ dest->version = source->version;
+ dest->version_max = source->version_max;
+ dest->verifypeer = source->verifypeer;
+ dest->verifyhost = source->verifyhost;
+ dest->verifystatus = source->verifystatus;
+ dest->sessionid = source->sessionid;
+ dest->ssl_options = source->ssl_options;
+#ifdef USE_TLS_SRP
+ dest->authtype = source->authtype;
+#endif
+
+ CLONE_BLOB(cert_blob);
+ CLONE_BLOB(ca_info_blob);
+ CLONE_BLOB(issuercert_blob);
+ CLONE_STRING(CApath);
+ CLONE_STRING(CAfile);
+ CLONE_STRING(issuercert);
+ CLONE_STRING(clientcert);
+ CLONE_STRING(cipher_list);
+ CLONE_STRING(cipher_list13);
+ CLONE_STRING(pinned_key);
+ CLONE_STRING(curves);
+ CLONE_STRING(CRLfile);
+#ifdef USE_TLS_SRP
+ CLONE_STRING(username);
+ CLONE_STRING(password);
+#endif
+
+ return TRUE;
+}
+
+void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc)
+{
+ Curl_safefree(sslc->CApath);
+ Curl_safefree(sslc->CAfile);
+ Curl_safefree(sslc->issuercert);
+ Curl_safefree(sslc->clientcert);
+ Curl_safefree(sslc->cipher_list);
+ Curl_safefree(sslc->cipher_list13);
+ Curl_safefree(sslc->pinned_key);
+ Curl_safefree(sslc->cert_blob);
+ Curl_safefree(sslc->ca_info_blob);
+ Curl_safefree(sslc->issuercert_blob);
+ Curl_safefree(sslc->curves);
+ Curl_safefree(sslc->CRLfile);
+#ifdef USE_TLS_SRP
+ Curl_safefree(sslc->username);
+ Curl_safefree(sslc->password);
+#endif
+}
+
+#ifdef USE_SSL
+static int multissl_setup(const struct Curl_ssl *backend);
+#endif
+
+curl_sslbackend Curl_ssl_backend(void)
+{
+#ifdef USE_SSL
+ multissl_setup(NULL);
+ return Curl_ssl->info.id;
+#else
+ return CURLSSLBACKEND_NONE;
+#endif
+}
+
+#ifdef USE_SSL
+
+/* "global" init done? */
+static bool init_ssl = FALSE;
+
+/**
+ * Global SSL init
+ *
+ * @retval 0 error initializing SSL
+ * @retval 1 SSL initialized successfully
+ */
+int Curl_ssl_init(void)
+{
+ /* make sure this is only done once */
+ if(init_ssl)
+ return 1;
+ init_ssl = TRUE; /* never again */
+
+ return Curl_ssl->init();
+}
+
+#if defined(CURL_WITH_MULTI_SSL)
+static const struct Curl_ssl Curl_ssl_multi;
+#endif
+
+/* Global cleanup */
+void Curl_ssl_cleanup(void)
+{
+ if(init_ssl) {
+ /* only cleanup if we did a previous init */
+ Curl_ssl->cleanup();
+#if defined(CURL_WITH_MULTI_SSL)
+ Curl_ssl = &Curl_ssl_multi;
+#endif
+ init_ssl = FALSE;
+ }
+}
+
+static bool ssl_prefs_check(struct Curl_easy *data)
+{
+ /* check for CURLOPT_SSLVERSION invalid parameter value */
+ const long sslver = data->set.ssl.primary.version;
+ if((sslver < 0) || (sslver >= CURL_SSLVERSION_LAST)) {
+ failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION");
+ return FALSE;
+ }
+
+ switch(data->set.ssl.primary.version_max) {
+ case CURL_SSLVERSION_MAX_NONE:
+ case CURL_SSLVERSION_MAX_DEFAULT:
+ break;
+
+ default:
+ if((data->set.ssl.primary.version_max >> 16) < sslver) {
+ failf(data, "CURL_SSLVERSION_MAX incompatible with CURL_SSLVERSION");
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx;
+
+ (void)data;
+ ctx = calloc(1, sizeof(*ctx));
+ if(!ctx)
+ return NULL;
+
+ ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data);
+ if(!ctx->backend) {
+ free(ctx);
+ return NULL;
+ }
+ return ctx;
+}
+
+static void cf_ctx_free(struct ssl_connect_data *ctx)
+{
+ if(ctx) {
+ free(ctx->backend);
+ free(ctx);
+ }
+}
+
+static void cf_ctx_set_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ if(cf->ctx)
+ ((struct ssl_connect_data *)cf->ctx)->call_data = data;
+}
+
+static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ CURLcode result;
+
+ if(!ssl_prefs_check(data))
+ return CURLE_SSL_CONNECT_ERROR;
+
+ /* mark this is being ssl-enabled from here on. */
+ connssl->state = ssl_connection_negotiating;
+
+ result = Curl_ssl->connect_blocking(cf, data);
+
+ if(!result) {
+ Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */
+ DEBUGASSERT(connssl->state == ssl_connection_complete);
+ }
+
+ return result;
+}
+
+static CURLcode
+ssl_connect_nonblocking(struct Curl_cfilter *cf, struct Curl_easy *data,
+ bool *done)
+{
+ if(!ssl_prefs_check(data))
+ return CURLE_SSL_CONNECT_ERROR;
+
+ /* mark this is being ssl requested from here on. */
+ return Curl_ssl->connect_nonblocking(cf, data, done);
+}
+
+/*
+ * Lock shared SSL session data
+ */
+void Curl_ssl_sessionid_lock(struct Curl_easy *data)
+{
+ if(SSLSESSION_SHARED(data))
+ Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
+}
+
+/*
+ * Unlock shared SSL session data
+ */
+void Curl_ssl_sessionid_unlock(struct Curl_easy *data)
+{
+ if(SSLSESSION_SHARED(data))
+ Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
+}
+
+/*
+ * Check if there's a session ID for the given connection in the cache, and if
+ * there's one suitable, it is provided. Returns TRUE when no entry matched.
+ */
+bool Curl_ssl_getsessionid(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ void **ssl_sessionid,
+ size_t *idsize) /* set 0 if unknown */
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ struct Curl_ssl_session *check;
+ size_t i;
+ long *general_age;
+ bool no_match = TRUE;
+
+ *ssl_sessionid = NULL;
+ if(!ssl_config)
+ return TRUE;
+
+ DEBUGASSERT(ssl_config->primary.sessionid);
+
+ if(!ssl_config->primary.sessionid || !data->state.session)
+ /* session ID re-use is disabled or the session cache has not been
+ setup */
+ return TRUE;
+
+ /* Lock if shared */
+ if(SSLSESSION_SHARED(data))
+ general_age = &data->share->sessionage;
+ else
+ general_age = &data->state.sessionage;
+
+ for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) {
+ check = &data->state.session[i];
+ if(!check->sessionid)
+ /* not session ID means blank entry */
+ continue;
+ if(strcasecompare(connssl->hostname, check->name) &&
+ ((!cf->conn->bits.conn_to_host && !check->conn_to_host) ||
+ (cf->conn->bits.conn_to_host && check->conn_to_host &&
+ strcasecompare(cf->conn->conn_to_host.name, check->conn_to_host))) &&
+ ((!cf->conn->bits.conn_to_port && check->conn_to_port == -1) ||
+ (cf->conn->bits.conn_to_port && check->conn_to_port != -1 &&
+ cf->conn->conn_to_port == check->conn_to_port)) &&
+ (connssl->port == check->remote_port) &&
+ strcasecompare(cf->conn->handler->scheme, check->scheme) &&
+ Curl_ssl_config_matches(conn_config, &check->ssl_config)) {
+ /* yes, we have a session ID! */
+ (*general_age)++; /* increase general age */
+ check->age = *general_age; /* set this as used in this age */
+ *ssl_sessionid = check->sessionid;
+ if(idsize)
+ *idsize = check->idsize;
+ no_match = FALSE;
+ break;
+ }
+ }
+
+ DEBUGF(infof(data, DMSG(data, "%s Session ID in cache for %s %s://%s:%d"),
+ no_match? "Didn't find": "Found",
+ Curl_ssl_cf_is_proxy(cf) ? "proxy" : "host",
+ cf->conn->handler->scheme, connssl->hostname, connssl->port));
+ return no_match;
+}
+
+/*
+ * Kill a single session ID entry in the cache.
+ */
+void Curl_ssl_kill_session(struct Curl_ssl_session *session)
+{
+ if(session->sessionid) {
+ /* defensive check */
+
+ /* free the ID the SSL-layer specific way */
+ Curl_ssl->session_free(session->sessionid);
+
+ session->sessionid = NULL;
+ session->age = 0; /* fresh */
+
+ Curl_free_primary_ssl_config(&session->ssl_config);
+
+ Curl_safefree(session->name);
+ Curl_safefree(session->conn_to_host);
+ }
+}
+
+/*
+ * Delete the given session ID from the cache.
+ */
+void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid)
+{
+ size_t i;
+
+ for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++) {
+ struct Curl_ssl_session *check = &data->state.session[i];
+
+ if(check->sessionid == ssl_sessionid) {
+ Curl_ssl_kill_session(check);
+ break;
+ }
+ }
+}
+
+/*
+ * Store session id in the session cache. The ID passed on to this function
+ * must already have been extracted and allocated the proper way for the SSL
+ * layer. Curl_XXXX_session_free() will be called to free/kill the session ID
+ * later on.
+ */
+CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ void *ssl_sessionid,
+ size_t idsize,
+ bool *added)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ size_t i;
+ struct Curl_ssl_session *store;
+ long oldest_age;
+ char *clone_host;
+ char *clone_conn_to_host;
+ int conn_to_port;
+ long *general_age;
+
+ if(added)
+ *added = FALSE;
+
+ if(!data->state.session)
+ return CURLE_OK;
+
+ store = &data->state.session[0];
+ oldest_age = data->state.session[0].age; /* zero if unused */
+ (void)ssl_config;
+ DEBUGASSERT(ssl_config->primary.sessionid);
+
+ clone_host = strdup(connssl->hostname);
+ if(!clone_host)
+ return CURLE_OUT_OF_MEMORY; /* bail out */
+
+ if(cf->conn->bits.conn_to_host) {
+ clone_conn_to_host = strdup(cf->conn->conn_to_host.name);
+ if(!clone_conn_to_host) {
+ free(clone_host);
+ return CURLE_OUT_OF_MEMORY; /* bail out */
+ }
+ }
+ else
+ clone_conn_to_host = NULL;
+
+ if(cf->conn->bits.conn_to_port)
+ conn_to_port = cf->conn->conn_to_port;
+ else
+ conn_to_port = -1;
+
+ /* Now we should add the session ID and the host name to the cache, (remove
+ the oldest if necessary) */
+
+ /* If using shared SSL session, lock! */
+ if(SSLSESSION_SHARED(data)) {
+ general_age = &data->share->sessionage;
+ }
+ else {
+ general_age = &data->state.sessionage;
+ }
+
+ /* find an empty slot for us, or find the oldest */
+ for(i = 1; (i < data->set.general_ssl.max_ssl_sessions) &&
+ data->state.session[i].sessionid; i++) {
+ if(data->state.session[i].age < oldest_age) {
+ oldest_age = data->state.session[i].age;
+ store = &data->state.session[i];
+ }
+ }
+ if(i == data->set.general_ssl.max_ssl_sessions)
+ /* cache is full, we must "kill" the oldest entry! */
+ Curl_ssl_kill_session(store);
+ else
+ store = &data->state.session[i]; /* use this slot */
+
+ /* now init the session struct wisely */
+ store->sessionid = ssl_sessionid;
+ store->idsize = idsize;
+ store->age = *general_age; /* set current age */
+ /* free it if there's one already present */
+ free(store->name);
+ free(store->conn_to_host);
+ store->name = clone_host; /* clone host name */
+ store->conn_to_host = clone_conn_to_host; /* clone connect to host name */
+ store->conn_to_port = conn_to_port; /* connect to port number */
+ /* port number */
+ store->remote_port = connssl->port;
+ store->scheme = cf->conn->handler->scheme;
+
+ if(!Curl_clone_primary_ssl_config(conn_config, &store->ssl_config)) {
+ Curl_free_primary_ssl_config(&store->ssl_config);
+ store->sessionid = NULL; /* let caller free sessionid */
+ free(clone_host);
+ free(clone_conn_to_host);
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ if(added)
+ *added = TRUE;
+
+ DEBUGF(infof(data, DMSG(data, "Added Session ID to cache for %s://%s:%d"
+ " [%s]"), store->scheme, store->name, store->remote_port,
+ Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"));
+ return CURLE_OK;
+}
+
+void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend)
+{
+ if(Curl_ssl->free_multi_ssl_backend_data && mbackend)
+ Curl_ssl->free_multi_ssl_backend_data(mbackend);
+}
+
+void Curl_ssl_close_all(struct Curl_easy *data)
+{
+ /* kill the session ID cache if not shared */
+ if(data->state.session && !SSLSESSION_SHARED(data)) {
+ size_t i;
+ for(i = 0; i < data->set.general_ssl.max_ssl_sessions; i++)
+ /* the single-killer function handles empty table slots */
+ Curl_ssl_kill_session(&data->state.session[i]);
+
+ /* free the cache data */
+ Curl_safefree(data->state.session);
+ }
+
+ Curl_ssl->close_all(data);
+}
+
+int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data,
+ curl_socket_t *socks)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+
+ (void)data;
+ if(connssl->connecting_state == ssl_connect_2_writing) {
+ /* write mode */
+ socks[0] = cf->conn->sock[FIRSTSOCKET];
+ return GETSOCK_WRITESOCK(0);
+ }
+ if(connssl->connecting_state == ssl_connect_2_reading) {
+ /* read mode */
+ socks[0] = cf->conn->sock[FIRSTSOCKET];
+ return GETSOCK_READSOCK(0);
+ }
+
+ return GETSOCK_BLANK;
+}
+
+/* Selects an SSL crypto engine
+ */
+CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine)
+{
+ return Curl_ssl->set_engine(data, engine);
+}
+
+/* Selects the default SSL crypto engine
+ */
+CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data)
+{
+ return Curl_ssl->set_engine_default(data);
+}
+
+/* Return list of OpenSSL crypto engine names. */
+struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data)
+{
+ return Curl_ssl->engines_list(data);
+}
+
+/*
+ * This sets up a session ID cache to the specified size. Make sure this code
+ * is agnostic to what underlying SSL technology we use.
+ */
+CURLcode Curl_ssl_initsessions(struct Curl_easy *data, size_t amount)
+{
+ struct Curl_ssl_session *session;
+
+ if(data->state.session)
+ /* this is just a precaution to prevent multiple inits */
+ return CURLE_OK;
+
+ session = calloc(amount, sizeof(struct Curl_ssl_session));
+ if(!session)
+ return CURLE_OUT_OF_MEMORY;
+
+ /* store the info in the SSL section */
+ data->set.general_ssl.max_ssl_sessions = amount;
+ data->state.session = session;
+ data->state.sessionage = 1; /* this is brand new */
+ return CURLE_OK;
+}
+
+static size_t multissl_version(char *buffer, size_t size);
+
+void Curl_ssl_version(char *buffer, size_t size)
+{
+#ifdef CURL_WITH_MULTI_SSL
+ (void)multissl_version(buffer, size);
+#else
+ (void)Curl_ssl->version(buffer, size);
+#endif
+}
+
+/*
+ * This function tries to determine connection status.
+ *
+ * Return codes:
+ * 1 means the connection is still in place
+ * 0 means the connection has been closed
+ * -1 means the connection status is unknown
+ */
+int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn)
+{
+ struct Curl_cfilter *cf = Curl_ssl_cf_get_ssl(conn->cfilter[FIRSTSOCKET]);
+ return cf? Curl_ssl->check_cxn(cf, data) : -1;
+}
+
+void Curl_ssl_free_certinfo(struct Curl_easy *data)
+{
+ struct curl_certinfo *ci = &data->info.certs;
+
+ if(ci->num_of_certs) {
+ /* free all individual lists used */
+ int i;
+ for(i = 0; i<ci->num_of_certs; i++) {
+ curl_slist_free_all(ci->certinfo[i]);
+ ci->certinfo[i] = NULL;
+ }
+
+ free(ci->certinfo); /* free the actual array too */
+ ci->certinfo = NULL;
+ ci->num_of_certs = 0;
+ }
+}
+
+CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num)
+{
+ struct curl_certinfo *ci = &data->info.certs;
+ struct curl_slist **table;
+
+ /* Free any previous certificate information structures */
+ Curl_ssl_free_certinfo(data);
+
+ /* Allocate the required certificate information structures */
+ table = calloc((size_t) num, sizeof(struct curl_slist *));
+ if(!table)
+ return CURLE_OUT_OF_MEMORY;
+
+ ci->num_of_certs = num;
+ ci->certinfo = table;
+
+ return CURLE_OK;
+}
+
+/*
+ * 'value' is NOT a null-terminated string
+ */
+CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data,
+ int certnum,
+ const char *label,
+ const char *value,
+ size_t valuelen)
+{
+ struct curl_certinfo *ci = &data->info.certs;
+ char *output;
+ struct curl_slist *nl;
+ CURLcode result = CURLE_OK;
+ size_t labellen = strlen(label);
+ size_t outlen = labellen + 1 + valuelen + 1; /* label:value\0 */
+
+ output = malloc(outlen);
+ if(!output)
+ return CURLE_OUT_OF_MEMORY;
+
+ /* sprintf the label and colon */
+ msnprintf(output, outlen, "%s:", label);
+
+ /* memcpy the value (it might not be null-terminated) */
+ memcpy(&output[labellen + 1], value, valuelen);
+
+ /* null-terminate the output */
+ output[labellen + 1 + valuelen] = 0;
+
+ nl = Curl_slist_append_nodup(ci->certinfo[certnum], output);
+ if(!nl) {
+ free(output);
+ curl_slist_free_all(ci->certinfo[certnum]);
+ result = CURLE_OUT_OF_MEMORY;
+ }
+
+ ci->certinfo[certnum] = nl;
+ return result;
+}
+
+/*
+ * This is a convenience function for push_certinfo_len that takes a zero
+ * terminated value.
+ */
+CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data,
+ int certnum,
+ const char *label,
+ const char *value)
+{
+ size_t valuelen = strlen(value);
+
+ return Curl_ssl_push_certinfo_len(data, certnum, label, value, valuelen);
+}
+
+CURLcode Curl_ssl_random(struct Curl_easy *data,
+ unsigned char *entropy,
+ size_t length)
+{
+ return Curl_ssl->random(data, entropy, length);
+}
+
+/*
+ * Curl_ssl_snihost() converts the input host name to a suitable SNI name put
+ * in data->state.buffer. Returns a pointer to the name (or NULL if a problem)
+ * and stores the new length in 'olen'.
+ *
+ * SNI fields must not have any trailing dot and while RFC 6066 section 3 says
+ * the SNI field is case insensitive, browsers always send the data lowercase
+ * and subsequently there are numerous servers out there that don't work
+ * unless the name is lowercased.
+ */
+
+char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen)
+{
+ size_t len = strlen(host);
+ if(len && (host[len-1] == '.'))
+ len--;
+ if(len >= data->set.buffer_size)
+ return NULL;
+
+ Curl_strntolower(data->state.buffer, host, len);
+ data->state.buffer[len] = 0;
+ if(olen)
+ *olen = len;
+ return data->state.buffer;
+}
+
+/*
+ * Public key pem to der conversion
+ */
+
+static CURLcode pubkey_pem_to_der(const char *pem,
+ unsigned char **der, size_t *der_len)
+{
+ char *stripped_pem, *begin_pos, *end_pos;
+ size_t pem_count, stripped_pem_count = 0, pem_len;
+ CURLcode result;
+
+ /* if no pem, exit. */
+ if(!pem)
+ return CURLE_BAD_CONTENT_ENCODING;
+
+ begin_pos = strstr(pem, "-----BEGIN PUBLIC KEY-----");
+ if(!begin_pos)
+ return CURLE_BAD_CONTENT_ENCODING;
+
+ pem_count = begin_pos - pem;
+ /* Invalid if not at beginning AND not directly following \n */
+ if(0 != pem_count && '\n' != pem[pem_count - 1])
+ return CURLE_BAD_CONTENT_ENCODING;
+
+ /* 26 is length of "-----BEGIN PUBLIC KEY-----" */
+ pem_count += 26;
+
+ /* Invalid if not directly following \n */
+ end_pos = strstr(pem + pem_count, "\n-----END PUBLIC KEY-----");
+ if(!end_pos)
+ return CURLE_BAD_CONTENT_ENCODING;
+
+ pem_len = end_pos - pem;
+
+ stripped_pem = malloc(pem_len - pem_count + 1);
+ if(!stripped_pem)
+ return CURLE_OUT_OF_MEMORY;
+
+ /*
+ * Here we loop through the pem array one character at a time between the
+ * correct indices, and place each character that is not '\n' or '\r'
+ * into the stripped_pem array, which should represent the raw base64 string
+ */
+ while(pem_count < pem_len) {
+ if('\n' != pem[pem_count] && '\r' != pem[pem_count])
+ stripped_pem[stripped_pem_count++] = pem[pem_count];
+ ++pem_count;
+ }
+ /* Place the null terminator in the correct place */
+ stripped_pem[stripped_pem_count] = '\0';
+
+ result = Curl_base64_decode(stripped_pem, der, der_len);
+
+ Curl_safefree(stripped_pem);
+
+ return result;
+}
+
+/*
+ * Generic pinned public key check.
+ */
+
+CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data,
+ const char *pinnedpubkey,
+ const unsigned char *pubkey, size_t pubkeylen)
+{
+ FILE *fp;
+ unsigned char *buf = NULL, *pem_ptr = NULL;
+ CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+ /* if a path wasn't specified, don't pin */
+ if(!pinnedpubkey)
+ return CURLE_OK;
+ if(!pubkey || !pubkeylen)
+ return result;
+
+ /* only do this if pinnedpubkey starts with "sha256//", length 8 */
+ if(strncmp(pinnedpubkey, "sha256//", 8) == 0) {
+ CURLcode encode;
+ size_t encodedlen, pinkeylen;
+ char *encoded, *pinkeycopy, *begin_pos, *end_pos;
+ unsigned char *sha256sumdigest;
+
+ if(!Curl_ssl->sha256sum) {
+ /* without sha256 support, this cannot match */
+ return result;
+ }
+
+ /* compute sha256sum of public key */
+ sha256sumdigest = malloc(CURL_SHA256_DIGEST_LENGTH);
+ if(!sha256sumdigest)
+ return CURLE_OUT_OF_MEMORY;
+ encode = Curl_ssl->sha256sum(pubkey, pubkeylen,
+ sha256sumdigest, CURL_SHA256_DIGEST_LENGTH);
+
+ if(encode != CURLE_OK)
+ return encode;
+
+ encode = Curl_base64_encode((char *)sha256sumdigest,
+ CURL_SHA256_DIGEST_LENGTH, &encoded,
+ &encodedlen);
+ Curl_safefree(sha256sumdigest);
+
+ if(encode)
+ return encode;
+
+ infof(data, " public key hash: sha256//%s", encoded);
+
+ /* it starts with sha256//, copy so we can modify it */
+ pinkeylen = strlen(pinnedpubkey) + 1;
+ pinkeycopy = malloc(pinkeylen);
+ if(!pinkeycopy) {
+ Curl_safefree(encoded);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ memcpy(pinkeycopy, pinnedpubkey, pinkeylen);
+ /* point begin_pos to the copy, and start extracting keys */
+ begin_pos = pinkeycopy;
+ do {
+ end_pos = strstr(begin_pos, ";sha256//");
+ /*
+ * if there is an end_pos, null terminate,
+ * otherwise it'll go to the end of the original string
+ */
+ if(end_pos)
+ end_pos[0] = '\0';
+
+ /* compare base64 sha256 digests, 8 is the length of "sha256//" */
+ if(encodedlen == strlen(begin_pos + 8) &&
+ !memcmp(encoded, begin_pos + 8, encodedlen)) {
+ result = CURLE_OK;
+ break;
+ }
+
+ /*
+ * change back the null-terminator we changed earlier,
+ * and look for next begin
+ */
+ if(end_pos) {
+ end_pos[0] = ';';
+ begin_pos = strstr(end_pos, "sha256//");
+ }
+ } while(end_pos && begin_pos);
+ Curl_safefree(encoded);
+ Curl_safefree(pinkeycopy);
+ return result;
+ }
+
+ fp = fopen(pinnedpubkey, "rb");
+ if(!fp)
+ return result;
+
+ do {
+ long filesize;
+ size_t size, pem_len;
+ CURLcode pem_read;
+
+ /* Determine the file's size */
+ if(fseek(fp, 0, SEEK_END))
+ break;
+ filesize = ftell(fp);
+ if(fseek(fp, 0, SEEK_SET))
+ break;
+ if(filesize < 0 || filesize > MAX_PINNED_PUBKEY_SIZE)
+ break;
+
+ /*
+ * if the size of our certificate is bigger than the file
+ * size then it can't match
+ */
+ size = curlx_sotouz((curl_off_t) filesize);
+ if(pubkeylen > size)
+ break;
+
+ /*
+ * Allocate buffer for the pinned key
+ * With 1 additional byte for null terminator in case of PEM key
+ */
+ buf = malloc(size + 1);
+ if(!buf)
+ break;
+
+ /* Returns number of elements read, which should be 1 */
+ if((int) fread(buf, size, 1, fp) != 1)
+ break;
+
+ /* If the sizes are the same, it can't be base64 encoded, must be der */
+ if(pubkeylen == size) {
+ if(!memcmp(pubkey, buf, pubkeylen))
+ result = CURLE_OK;
+ break;
+ }
+
+ /*
+ * Otherwise we will assume it's PEM and try to decode it
+ * after placing null terminator
+ */
+ buf[size] = '\0';
+ pem_read = pubkey_pem_to_der((const char *)buf, &pem_ptr, &pem_len);
+ /* if it wasn't read successfully, exit */
+ if(pem_read)
+ break;
+
+ /*
+ * if the size of our certificate doesn't match the size of
+ * the decoded file, they can't be the same, otherwise compare
+ */
+ if(pubkeylen == pem_len && !memcmp(pubkey, pem_ptr, pubkeylen))
+ result = CURLE_OK;
+ } while(0);
+
+ Curl_safefree(buf);
+ Curl_safefree(pem_ptr);
+ fclose(fp);
+
+ return result;
+}
+
+/*
+ * Check whether the SSL backend supports the status_request extension.
+ */
+bool Curl_ssl_cert_status_request(void)
+{
+ return Curl_ssl->cert_status_request();
+}
+
+/*
+ * Check whether the SSL backend supports false start.
+ */
+bool Curl_ssl_false_start(struct Curl_easy *data)
+{
+ (void)data;
+ return Curl_ssl->false_start();
+}
+
+/*
+ * Default implementations for unsupported functions.
+ */
+
+int Curl_none_init(void)
+{
+ return 1;
+}
+
+void Curl_none_cleanup(void)
+{ }
+
+int Curl_none_shutdown(struct Curl_cfilter *cf UNUSED_PARAM,
+ struct Curl_easy *data UNUSED_PARAM)
+{
+ (void)data;
+ (void)cf;
+ return 0;
+}
+
+int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ (void)cf;
+ (void)data;
+ return -1;
+}
+
+CURLcode Curl_none_random(struct Curl_easy *data UNUSED_PARAM,
+ unsigned char *entropy UNUSED_PARAM,
+ size_t length UNUSED_PARAM)
+{
+ (void)data;
+ (void)entropy;
+ (void)length;
+ return CURLE_NOT_BUILT_IN;
+}
+
+void Curl_none_close_all(struct Curl_easy *data UNUSED_PARAM)
+{
+ (void)data;
+}
+
+void Curl_none_session_free(void *ptr UNUSED_PARAM)
+{
+ (void)ptr;
+}
+
+bool Curl_none_data_pending(struct Curl_cfilter *cf UNUSED_PARAM,
+ const struct Curl_easy *data UNUSED_PARAM)
+{
+ (void)cf;
+ (void)data;
+ return 0;
+}
+
+bool Curl_none_cert_status_request(void)
+{
+ return FALSE;
+}
+
+CURLcode Curl_none_set_engine(struct Curl_easy *data UNUSED_PARAM,
+ const char *engine UNUSED_PARAM)
+{
+ (void)data;
+ (void)engine;
+ return CURLE_NOT_BUILT_IN;
+}
+
+CURLcode Curl_none_set_engine_default(struct Curl_easy *data UNUSED_PARAM)
+{
+ (void)data;
+ return CURLE_NOT_BUILT_IN;
+}
+
+struct curl_slist *Curl_none_engines_list(struct Curl_easy *data UNUSED_PARAM)
+{
+ (void)data;
+ return (struct curl_slist *)NULL;
+}
+
+bool Curl_none_false_start(void)
+{
+ return FALSE;
+}
+
+static int multissl_init(void)
+{
+ if(multissl_setup(NULL))
+ return 1;
+ return Curl_ssl->init();
+}
+
+static CURLcode multissl_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ if(multissl_setup(NULL))
+ return CURLE_FAILED_INIT;
+ return Curl_ssl->connect_blocking(cf, data);
+}
+
+static CURLcode multissl_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ if(multissl_setup(NULL))
+ return CURLE_FAILED_INIT;
+ return Curl_ssl->connect_nonblocking(cf, data, done);
+}
+
+static int multissl_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
+{
+ if(multissl_setup(NULL))
+ return 0;
+ return Curl_ssl->get_select_socks(cf, data, socks);
+}
+
+static void *multissl_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info)
+{
+ if(multissl_setup(NULL))
+ return NULL;
+ return Curl_ssl->get_internals(connssl, info);
+}
+
+static void multissl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ if(multissl_setup(NULL))
+ return;
+ Curl_ssl->close(cf, data);
+}
+
+static ssize_t multissl_recv_plain(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *code)
+{
+ if(multissl_setup(NULL))
+ return CURLE_FAILED_INIT;
+ return Curl_ssl->recv_plain(cf, data, buf, len, code);
+}
+
+static ssize_t multissl_send_plain(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const void *mem, size_t len,
+ CURLcode *code)
+{
+ if(multissl_setup(NULL))
+ return CURLE_FAILED_INIT;
+ return Curl_ssl->send_plain(cf, data, mem, len, code);
+}
+
+static const struct Curl_ssl Curl_ssl_multi = {
+ { CURLSSLBACKEND_NONE, "multi" }, /* info */
+ 0, /* supports nothing */
+ (size_t)-1, /* something insanely large to be on the safe side */
+
+ multissl_init, /* init */
+ Curl_none_cleanup, /* cleanup */
+ multissl_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ Curl_none_shutdown, /* shutdown */
+ Curl_none_data_pending, /* data_pending */
+ Curl_none_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ multissl_connect, /* connect */
+ multissl_connect_nonblocking, /* connect_nonblocking */
+ multissl_get_select_socks, /* getsock */
+ multissl_get_internals, /* get_internals */
+ multissl_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ Curl_none_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ NULL, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ multissl_recv_plain, /* recv decrypted data */
+ multissl_send_plain, /* send data to encrypt */
+};
+
+const struct Curl_ssl *Curl_ssl =
+#if defined(CURL_WITH_MULTI_SSL)
+ &Curl_ssl_multi;
+#elif defined(USE_WOLFSSL)
+ &Curl_ssl_wolfssl;
+#elif defined(USE_SECTRANSP)
+ &Curl_ssl_sectransp;
+#elif defined(USE_GNUTLS)
+ &Curl_ssl_gnutls;
+#elif defined(USE_GSKIT)
+ &Curl_ssl_gskit;
+#elif defined(USE_MBEDTLS)
+ &Curl_ssl_mbedtls;
+#elif defined(USE_NSS)
+ &Curl_ssl_nss;
+#elif defined(USE_RUSTLS)
+ &Curl_ssl_rustls;
+#elif defined(USE_OPENSSL)
+ &Curl_ssl_openssl;
+#elif defined(USE_SCHANNEL)
+ &Curl_ssl_schannel;
+#elif defined(USE_BEARSSL)
+ &Curl_ssl_bearssl;
+#else
+#error "Missing struct Curl_ssl for selected SSL backend"
+#endif
+
+static const struct Curl_ssl *available_backends[] = {
+#if defined(USE_WOLFSSL)
+ &Curl_ssl_wolfssl,
+#endif
+#if defined(USE_SECTRANSP)
+ &Curl_ssl_sectransp,
+#endif
+#if defined(USE_GNUTLS)
+ &Curl_ssl_gnutls,
+#endif
+#if defined(USE_GSKIT)
+ &Curl_ssl_gskit,
+#endif
+#if defined(USE_MBEDTLS)
+ &Curl_ssl_mbedtls,
+#endif
+#if defined(USE_NSS)
+ &Curl_ssl_nss,
+#endif
+#if defined(USE_OPENSSL)
+ &Curl_ssl_openssl,
+#endif
+#if defined(USE_SCHANNEL)
+ &Curl_ssl_schannel,
+#endif
+#if defined(USE_BEARSSL)
+ &Curl_ssl_bearssl,
+#endif
+#if defined(USE_RUSTLS)
+ &Curl_ssl_rustls,
+#endif
+ NULL
+};
+
+static size_t multissl_version(char *buffer, size_t size)
+{
+ static const struct Curl_ssl *selected;
+ static char backends[200];
+ static size_t backends_len;
+ const struct Curl_ssl *current;
+
+ current = Curl_ssl == &Curl_ssl_multi ? available_backends[0] : Curl_ssl;
+
+ if(current != selected) {
+ char *p = backends;
+ char *end = backends + sizeof(backends);
+ int i;
+
+ selected = current;
+
+ backends[0] = '\0';
+
+ for(i = 0; available_backends[i]; ++i) {
+ char vb[200];
+ bool paren = (selected != available_backends[i]);
+
+ if(available_backends[i]->version(vb, sizeof(vb))) {
+ p += msnprintf(p, end - p, "%s%s%s%s", (p != backends ? " " : ""),
+ (paren ? "(" : ""), vb, (paren ? ")" : ""));
+ }
+ }
+
+ backends_len = p - backends;
+ }
+
+ if(!size)
+ return 0;
+
+ if(size <= backends_len) {
+ strncpy(buffer, backends, size - 1);
+ buffer[size - 1] = '\0';
+ return size - 1;
+ }
+
+ strcpy(buffer, backends);
+ return backends_len;
+}
+
+static int multissl_setup(const struct Curl_ssl *backend)
+{
+ const char *env;
+ char *env_tmp;
+
+ if(Curl_ssl != &Curl_ssl_multi)
+ return 1;
+
+ if(backend) {
+ Curl_ssl = backend;
+ return 0;
+ }
+
+ if(!available_backends[0])
+ return 1;
+
+ env = env_tmp = curl_getenv("CURL_SSL_BACKEND");
+#ifdef CURL_DEFAULT_SSL_BACKEND
+ if(!env)
+ env = CURL_DEFAULT_SSL_BACKEND;
+#endif
+ if(env) {
+ int i;
+ for(i = 0; available_backends[i]; i++) {
+ if(strcasecompare(env, available_backends[i]->info.name)) {
+ Curl_ssl = available_backends[i];
+ free(env_tmp);
+ return 0;
+ }
+ }
+ }
+
+ /* Fall back to first available backend */
+ Curl_ssl = available_backends[0];
+ free(env_tmp);
+ return 0;
+}
+
+/* This function is used to select the SSL backend to use. It is called by
+ curl_global_sslset (easy.c) which uses the global init lock. */
+CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
+ const curl_ssl_backend ***avail)
+{
+ int i;
+
+ if(avail)
+ *avail = (const curl_ssl_backend **)&available_backends;
+
+ if(Curl_ssl != &Curl_ssl_multi)
+ return id == Curl_ssl->info.id ||
+ (name && strcasecompare(name, Curl_ssl->info.name)) ?
+ CURLSSLSET_OK :
+#if defined(CURL_WITH_MULTI_SSL)
+ CURLSSLSET_TOO_LATE;
+#else
+ CURLSSLSET_UNKNOWN_BACKEND;
+#endif
+
+ for(i = 0; available_backends[i]; i++) {
+ if(available_backends[i]->info.id == id ||
+ (name && strcasecompare(available_backends[i]->info.name, name))) {
+ multissl_setup(available_backends[i]);
+ return CURLSSLSET_OK;
+ }
+ }
+
+ return CURLSSLSET_UNKNOWN_BACKEND;
+}
+
+#else /* USE_SSL */
+CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
+ const curl_ssl_backend ***avail)
+{
+ (void)id;
+ (void)name;
+ (void)avail;
+ return CURLSSLSET_NO_BACKENDS;
+}
+
+#endif /* !USE_SSL */
+
+#ifdef USE_SSL
+
+static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ /* TODO: close_one closes BOTH conn->ssl AND conn->proxy_ssl for this
+ * sockindex (if in use). Gladly, it is safe to call more than once. */
+ if(connssl) {
+ Curl_ssl->close(cf, data);
+ connssl->state = ssl_connection_none;
+ }
+ cf->connected = FALSE;
+}
+
+static void reinit_hostname(struct Curl_cfilter *cf)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+
+#ifndef CURL_DISABLE_PROXY
+ if(Curl_ssl_cf_is_proxy(cf)) {
+ /* TODO: there is not definition for a proxy setup on a secondary conn */
+ connssl->hostname = cf->conn->http_proxy.host.name;
+ connssl->dispname = cf->conn->http_proxy.host.dispname;
+ connssl->port = cf->conn->http_proxy.port;
+ }
+ else
+#endif
+ {
+ /* TODO: secondaryhostname is set to the IP address we connect to
+ * in the FTP handler, it is assumed that host verification uses the
+ * hostname from FIRSTSOCKET */
+ if(cf->sockindex == SECONDARYSOCKET && 0) {
+ connssl->hostname = cf->conn->secondaryhostname;
+ connssl->dispname = connssl->hostname;
+ connssl->port = cf->conn->secondary_port;
+ }
+ else {
+ connssl->hostname = cf->conn->host.name;
+ connssl->dispname = cf->conn->host.dispname;
+ connssl->port = cf->conn->remote_port;
+ }
+ }
+ DEBUGASSERT(connssl->hostname);
+}
+
+static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ cf_ctx_set_data(cf, data);
+ cf_close(cf, data);
+ cf_ctx_free(cf->ctx);
+ cf->ctx = NULL;
+}
+
+static void ssl_cf_close(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ cf_ctx_set_data(cf, data);
+ cf_close(cf, data);
+ cf->next->cft->close(cf->next, data);
+ cf_ctx_set_data(cf, NULL);
+}
+
+static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool blocking, bool *done)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ CURLcode result;
+
+ if(cf->connected) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ cf_ctx_set_data(cf, data);
+ (void)connssl;
+ DEBUGASSERT(data->conn);
+ DEBUGASSERT(data->conn == cf->conn);
+ DEBUGASSERT(connssl);
+ DEBUGASSERT(cf->conn->host.name);
+
+ result = cf->next->cft->connect(cf->next, data, blocking, done);
+ if(result || !*done)
+ goto out;
+
+ /* TODO: right now we do not fully control when hostname is set,
+ * assign it on each connect call. */
+ reinit_hostname(cf);
+ *done = FALSE;
+
+ if(blocking) {
+ result = ssl_connect(cf, data);
+ *done = (result == CURLE_OK);
+ }
+ else {
+ result = ssl_connect_nonblocking(cf, data, done);
+ }
+
+ if(!result && *done) {
+ cf->connected = TRUE;
+ if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf))
+ Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */
+ DEBUGASSERT(connssl->state == ssl_connection_complete);
+ }
+out:
+ cf_ctx_set_data(cf, NULL);
+ return result;
+}
+
+static bool ssl_cf_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ bool result;
+
+ cf_ctx_set_data(cf, (struct Curl_easy *)data);
+ if(cf->ctx && Curl_ssl->data_pending(cf, data))
+ result = TRUE;
+ else
+ result = cf->next->cft->has_data_pending(cf->next, data);
+ cf_ctx_set_data(cf, NULL);
+ return result;
+}
+
+static ssize_t ssl_cf_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data, const void *buf, size_t len,
+ CURLcode *err)
+{
+ ssize_t nwritten;
+
+ *err = CURLE_OK;
+ cf_ctx_set_data(cf, data);
+ nwritten = Curl_ssl->send_plain(cf, data, buf, len, err);
+ cf_ctx_set_data(cf, NULL);
+ return nwritten;
+}
+
+static ssize_t ssl_cf_recv(struct Curl_cfilter *cf,
+ struct Curl_easy *data, char *buf, size_t len,
+ CURLcode *err)
+{
+ ssize_t nread;
+
+ *err = CURLE_OK;
+ cf_ctx_set_data(cf, data);
+ nread = Curl_ssl->recv_plain(cf, data, buf, len, err);
+ cf_ctx_set_data(cf, NULL);
+ return nread;
+}
+
+static int ssl_cf_get_select_socks(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ curl_socket_t *socks)
+{
+ int result;
+
+ cf_ctx_set_data(cf, data);
+ result = Curl_ssl->get_select_socks(cf, data, socks);
+ cf_ctx_set_data(cf, NULL);
+ return result;
+}
+
+static void ssl_cf_attach_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ if(Curl_ssl->attach_data) {
+ cf_ctx_set_data(cf, data);
+ Curl_ssl->attach_data(cf, data);
+ cf_ctx_set_data(cf, NULL);
+ }
+}
+
+static void ssl_cf_detach_data(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ if(Curl_ssl->detach_data) {
+ cf_ctx_set_data(cf, data);
+ Curl_ssl->detach_data(cf, data);
+ cf_ctx_set_data(cf, NULL);
+ }
+}
+
+static const struct Curl_cftype cft_ssl = {
+ "SSL",
+ CF_TYPE_SSL,
+ ssl_cf_destroy,
+ Curl_cf_def_setup,
+ ssl_cf_connect,
+ ssl_cf_close,
+ Curl_cf_def_get_host,
+ ssl_cf_get_select_socks,
+ ssl_cf_data_pending,
+ ssl_cf_send,
+ ssl_cf_recv,
+ ssl_cf_attach_data,
+ ssl_cf_detach_data,
+};
+
+static const struct Curl_cftype cft_ssl_proxy = {
+ "SSL-PROXY",
+ CF_TYPE_SSL,
+ ssl_cf_destroy,
+ Curl_cf_def_setup,
+ ssl_cf_connect,
+ ssl_cf_close,
+ Curl_cf_def_get_host,
+ ssl_cf_get_select_socks,
+ ssl_cf_data_pending,
+ ssl_cf_send,
+ ssl_cf_recv,
+ ssl_cf_attach_data,
+ ssl_cf_detach_data,
+};
+
+CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf;
+ struct ssl_connect_data *ctx;
+ CURLcode result;
+
+ DEBUGASSERT(data->conn);
+ ctx = cf_ctx_new(data);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+
+ result = Curl_cf_create(&cf, &cft_ssl, ctx);
+ if(result)
+ goto out;
+
+ Curl_conn_cf_add(data, conn, sockindex, cf);
+
+ result = CURLE_OK;
+
+out:
+ if(result)
+ cf_ctx_free(ctx);
+ return result;
+}
+
+#ifndef CURL_DISABLE_PROXY
+CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf;
+ struct ssl_connect_data *ctx;
+ CURLcode result;
+
+ ctx = cf_ctx_new(data);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+
+ result = Curl_cf_create(&cf, &cft_ssl_proxy, ctx);
+ if(result)
+ goto out;
+
+ Curl_conn_cf_add(data, conn, sockindex, cf);
+
+ result = CURLE_OK;
+
+out:
+ if(result)
+ cf_ctx_free(ctx);
+ return result;
+}
+
+#endif /* !CURL_DISABLE_PROXY */
+
+bool Curl_ssl_supports(struct Curl_easy *data, int option)
+{
+ (void)data;
+ return (Curl_ssl->supports & option)? TRUE : FALSE;
+}
+
+void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
+ CURLINFO info, int n)
+{
+ void *result = NULL;
+ (void)n;
+ if(data->conn) {
+ struct Curl_cfilter *cf;
+ /* get first filter in chain, if any is present */
+ cf = Curl_ssl_cf_get_ssl(data->conn->cfilter[sockindex]);
+ if(cf) {
+ cf_ctx_set_data(cf, data);
+ result = Curl_ssl->get_internals(cf->ctx, info);
+ cf_ctx_set_data(cf, NULL);
+ }
+ }
+ return result;
+}
+
+CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
+ int sockindex)
+{
+ struct Curl_cfilter *cf = data->conn? data->conn->cfilter[sockindex] : NULL;
+ CURLcode result = CURLE_OK;
+
+ (void)data;
+ for(; cf; cf = cf->next) {
+ if(cf->cft == &cft_ssl) {
+ if(Curl_ssl->shut_down(cf, data))
+ result = CURLE_SSL_SHUTDOWN_FAILED;
+ Curl_conn_cf_discard(cf, data);
+ break;
+ }
+ }
+ return result;
+}
+
+static struct Curl_cfilter *get_ssl_cf_engaged(struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf, *lowest_ssl_cf = NULL;
+
+ for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) {
+ if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy) {
+ lowest_ssl_cf = cf;
+ if(cf->connected || (cf->next && cf->next->connected)) {
+ /* connected or about to start */
+ return cf;
+ }
+ }
+ }
+ return lowest_ssl_cf;
+}
+
+bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf)
+{
+ return (cf->cft == &cft_ssl_proxy);
+}
+
+struct ssl_config_data *
+Curl_ssl_cf_get_config(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+#ifdef CURL_DISABLE_PROXY
+ (void)cf;
+ return &data->set.ssl;
+#else
+ return Curl_ssl_cf_is_proxy(cf)? &data->set.proxy_ssl : &data->set.ssl;
+#endif
+}
+
+struct ssl_config_data *
+Curl_ssl_get_config(struct Curl_easy *data, int sockindex)
+{
+ struct Curl_cfilter *cf;
+
+ (void)data;
+ DEBUGASSERT(data->conn);
+ cf = get_ssl_cf_engaged(data->conn, sockindex);
+ return cf? Curl_ssl_cf_get_config(cf, data) : &data->set.ssl;
+}
+
+struct ssl_primary_config *
+Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf)
+{
+#ifdef CURL_DISABLE_PROXY
+ return &cf->conn->ssl_config;
+#else
+ return Curl_ssl_cf_is_proxy(cf)?
+ &cf->conn->proxy_ssl_config : &cf->conn->ssl_config;
+#endif
+}
+
+struct ssl_primary_config *
+Curl_ssl_get_primary_config(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex)
+{
+ struct Curl_cfilter *cf;
+
+ (void)data;
+ DEBUGASSERT(conn);
+ cf = get_ssl_cf_engaged(conn, sockindex);
+ return cf? Curl_ssl_cf_get_primary_config(cf) : NULL;
+}
+
+struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf)
+{
+ for(; cf; cf = cf->next) {
+ if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy)
+ return cf;
+ }
+ return NULL;
+}
+
+#endif /* USE_SSL */
diff --git a/libs/libcurl/src/vtls/vtls.h b/libs/libcurl/src/vtls/vtls.h index 50c53b3fbd..0bd5a27d1e 100644 --- a/libs/libcurl/src/vtls/vtls.h +++ b/libs/libcurl/src/vtls/vtls.h @@ -1,344 +1,235 @@ -#ifndef HEADER_CURL_VTLS_H -#define HEADER_CURL_VTLS_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -struct connectdata; -struct ssl_connect_data; - -#define SSLSUPP_CA_PATH (1<<0) /* supports CAPATH */ -#define SSLSUPP_CERTINFO (1<<1) /* supports CURLOPT_CERTINFO */ -#define SSLSUPP_PINNEDPUBKEY (1<<2) /* supports CURLOPT_PINNEDPUBLICKEY */ -#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) - -#define ALPN_ACCEPTED "ALPN: server accepted " - -#define VTLS_INFOF_NO_ALPN \ - "ALPN: server did not agree on a protocol. Uses default." -#define VTLS_INFOF_ALPN_OFFER_1STR \ - "ALPN: offers %s" -#define VTLS_INFOF_ALPN_ACCEPTED_1STR \ - ALPN_ACCEPTED "%s" -#define VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR \ - ALPN_ACCEPTED "%.*s" - -struct Curl_ssl { - /* - * This *must* be the first entry to allow returning the list of available - * backends in curl_global_sslset(). - */ - curl_ssl_backend info; - unsigned int supports; /* bitfield, see above */ - size_t sizeof_ssl_backend_data; - - int (*init)(void); - void (*cleanup)(void); - - size_t (*version)(char *buffer, size_t size); - int (*check_cxn)(struct connectdata *cxn); - int (*shut_down)(struct Curl_easy *data, struct connectdata *conn, - int sockindex); - bool (*data_pending)(const struct connectdata *conn, - int connindex); - - /* return 0 if a find random is filled in */ - CURLcode (*random)(struct Curl_easy *data, unsigned char *entropy, - size_t length); - bool (*cert_status_request)(void); - - CURLcode (*connect_blocking)(struct Curl_easy *data, - struct connectdata *conn, int sockindex); - CURLcode (*connect_nonblocking)(struct Curl_easy *data, - struct connectdata *conn, int sockindex, - bool *done); - - /* If the SSL backend wants to read or write on this connection during a - handshake, set socks[0] to the connection's FIRSTSOCKET, and return - a bitmap indicating read or write with GETSOCK_WRITESOCK(0) or - GETSOCK_READSOCK(0). Otherwise return GETSOCK_BLANK. - Mandatory. */ - int (*getsock)(struct connectdata *conn, curl_socket_t *socks); - - void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info); - void (*close_one)(struct Curl_easy *data, struct connectdata *conn, - int sockindex); - void (*close_all)(struct Curl_easy *data); - void (*session_free)(void *ptr); - - CURLcode (*set_engine)(struct Curl_easy *data, const char *engine); - CURLcode (*set_engine_default)(struct Curl_easy *data); - struct curl_slist *(*engines_list)(struct Curl_easy *data); - - bool (*false_start)(void); - CURLcode (*sha256sum)(const unsigned char *input, size_t inputlen, - unsigned char *sha256sum, size_t sha256sumlen); - - bool (*associate_connection)(struct Curl_easy *data, - struct connectdata *conn, - int sockindex); - void (*disassociate_connection)(struct Curl_easy *data, int sockindex); -}; - -#ifdef USE_SSL -extern const struct Curl_ssl *Curl_ssl; -#endif - -int Curl_none_init(void); -void Curl_none_cleanup(void); -int Curl_none_shutdown(struct Curl_easy *data, struct connectdata *conn, - int sockindex); -int Curl_none_check_cxn(struct connectdata *conn); -CURLcode Curl_none_random(struct Curl_easy *data, unsigned char *entropy, - size_t length); -void Curl_none_close_all(struct Curl_easy *data); -void Curl_none_session_free(void *ptr); -bool Curl_none_data_pending(const struct connectdata *conn, int connindex); -bool Curl_none_cert_status_request(void); -CURLcode Curl_none_set_engine(struct Curl_easy *data, const char *engine); -CURLcode Curl_none_set_engine_default(struct Curl_easy *data); -struct curl_slist *Curl_none_engines_list(struct Curl_easy *data); -bool Curl_none_false_start(void); -bool Curl_ssl_tls13_ciphersuites(void); - -CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name, - const curl_ssl_backend ***avail); - -#include "openssl.h" /* OpenSSL versions */ -#include "gtls.h" /* GnuTLS versions */ -#include "nssg.h" /* NSS versions */ -#include "gskit.h" /* Global Secure ToolKit versions */ -#include "wolfssl.h" /* wolfSSL versions */ -#include "schannel.h" /* Schannel SSPI version */ -#include "sectransp.h" /* SecureTransport (Darwin) version */ -#include "mbedtls.h" /* mbedTLS versions */ -#include "bearssl.h" /* BearSSL versions */ -#include "rustls.h" /* rustls versions */ - -#ifndef MAX_PINNED_PUBKEY_SIZE -#define MAX_PINNED_PUBKEY_SIZE 1048576 /* 1MB */ -#endif - -#ifndef CURL_SHA256_DIGEST_LENGTH -#define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */ -#endif - -/* see https://www.iana.org/assignments/tls-extensiontype-values/ */ -#define ALPN_HTTP_1_1_LENGTH 8 -#define ALPN_HTTP_1_1 "http/1.1" -#define ALPN_H2_LENGTH 2 -#define ALPN_H2 "h2" - -/* set of helper macros for the backends to access the correct fields. For the - proxy or for the remote host - to properly support HTTPS proxy */ -#ifndef CURL_DISABLE_PROXY -#define SSL_IS_PROXY() \ - (CURLPROXY_HTTPS == conn->http_proxy.proxytype && \ - ssl_connection_complete != \ - conn->proxy_ssl[conn->sock[SECONDARYSOCKET] == \ - CURL_SOCKET_BAD ? FIRSTSOCKET : SECONDARYSOCKET].state) -#define SSL_SET_OPTION(var) \ - (SSL_IS_PROXY() ? data->set.proxy_ssl.var : data->set.ssl.var) -#define SSL_SET_OPTION_LVALUE(var) \ - (*(SSL_IS_PROXY() ? &data->set.proxy_ssl.var : &data->set.ssl.var)) -#define SSL_CONN_CONFIG(var) \ - (SSL_IS_PROXY() ? conn->proxy_ssl_config.var : conn->ssl_config.var) -#define SSL_HOST_NAME() \ - (SSL_IS_PROXY() ? conn->http_proxy.host.name : conn->host.name) -#define SSL_HOST_DISPNAME() \ - (SSL_IS_PROXY() ? conn->http_proxy.host.dispname : conn->host.dispname) -#define SSL_HOST_PORT() \ - (SSL_IS_PROXY() ? conn->port : conn->remote_port) -#define SSL_PINNED_PUB_KEY() (SSL_IS_PROXY() \ - ? data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] \ - : data->set.str[STRING_SSL_PINNEDPUBLICKEY]) -#else -#define SSL_IS_PROXY() FALSE -#define SSL_SET_OPTION(var) data->set.ssl.var -#define SSL_SET_OPTION_LVALUE(var) data->set.ssl.var -#define SSL_CONN_CONFIG(var) conn->ssl_config.var -#define SSL_HOST_NAME() conn->host.name -#define SSL_HOST_DISPNAME() conn->host.dispname -#define SSL_HOST_PORT() conn->remote_port -#define SSL_PINNED_PUB_KEY() \ - data->set.str[STRING_SSL_PINNEDPUBLICKEY] -#endif - -char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen); -bool Curl_ssl_config_matches(struct ssl_primary_config *data, - struct ssl_primary_config *needle); -bool Curl_clone_primary_ssl_config(struct ssl_primary_config *source, - struct ssl_primary_config *dest); -void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc); -/* An implementation of the getsock field of Curl_ssl that relies - on the ssl_connect_state enum. Asks for read or write depending - on whether conn->state is ssl_connect_2_reading or - ssl_connect_2_writing. */ -int Curl_ssl_getsock(struct connectdata *conn, curl_socket_t *socks); - -curl_sslbackend Curl_ssl_backend(void); - -#ifdef USE_SSL -int Curl_ssl_init(void); -void Curl_ssl_cleanup(void); -CURLcode Curl_ssl_connect(struct Curl_easy *data, struct connectdata *conn, - int sockindex); -CURLcode Curl_ssl_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - bool isproxy, - int sockindex, - bool *done); -/* tell the SSL stuff to close down all open information regarding - connections (and thus session ID caching etc) */ -void Curl_ssl_close_all(struct Curl_easy *data); -void Curl_ssl_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex); -CURLcode Curl_ssl_shutdown(struct Curl_easy *data, struct connectdata *conn, - int sockindex); -CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine); -/* Sets engine as default for all SSL operations */ -CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data); -struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data); - -/* init the SSL session ID cache */ -CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t); -void Curl_ssl_version(char *buffer, size_t size); -bool Curl_ssl_data_pending(const struct connectdata *conn, - int connindex); -int Curl_ssl_check_cxn(struct connectdata *conn); - -/* Certificate information list handling. */ - -void Curl_ssl_free_certinfo(struct Curl_easy *data); -CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num); -CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, int certnum, - const char *label, const char *value, - size_t valuelen); -CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data, int certnum, - const char *label, const char *value); - -/* Functions to be used by SSL library adaptation functions */ - -/* Lock session cache mutex. - * Call this before calling other Curl_ssl_*session* functions - * Caller should unlock this mutex as soon as possible, as it may block - * other SSL connection from making progress. - * The purpose of explicitly locking SSL session cache data is to allow - * individual SSL engines to manage session lifetime in their specific way. - */ -void Curl_ssl_sessionid_lock(struct Curl_easy *data); - -/* Unlock session cache mutex */ -void Curl_ssl_sessionid_unlock(struct Curl_easy *data); - -/* extract a session ID - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * Caller must make sure that the ownership of returned sessionid object - * is properly taken (e.g. its refcount is incremented - * under sessionid mutex). - */ -bool Curl_ssl_getsessionid(struct Curl_easy *data, - struct connectdata *conn, - const bool isProxy, - void **ssl_sessionid, - size_t *idsize, /* set 0 if unknown */ - int sockindex); -/* add a new session ID - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * Caller must ensure that it has properly shared ownership of this sessionid - * object with cache (e.g. incrementing refcount on success) - */ -CURLcode Curl_ssl_addsessionid(struct Curl_easy *data, - struct connectdata *conn, - const bool isProxy, - void *ssl_sessionid, - size_t idsize, - int sockindex, - bool *added); -/* Kill a single session ID entry in the cache - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * This will call engine-specific curlssl_session_free function, which must - * take sessionid object ownership from sessionid cache - * (e.g. decrement refcount). - */ -void Curl_ssl_kill_session(struct Curl_ssl_session *session); -/* delete a session from the cache - * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock). - * This will call engine-specific curlssl_session_free function, which must - * take sessionid object ownership from sessionid cache - * (e.g. decrement refcount). - */ -void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid); - -/* get N random bytes into the buffer */ -CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *buffer, - size_t length); -/* Check pinned public key. */ -CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data, - const char *pinnedpubkey, - const unsigned char *pubkey, size_t pubkeylen); - -bool Curl_ssl_cert_status_request(void); - -bool Curl_ssl_false_start(void); - -void Curl_ssl_associate_conn(struct Curl_easy *data, - struct connectdata *conn); -void Curl_ssl_detach_conn(struct Curl_easy *data, - struct connectdata *conn); - -#define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */ - -#else /* if not USE_SSL */ - -/* When SSL support is not present, just define away these function calls */ -#define Curl_ssl_init() 1 -#define Curl_ssl_cleanup() Curl_nop_stmt -#define Curl_ssl_connect(x,y,z) CURLE_NOT_BUILT_IN -#define Curl_ssl_close_all(x) Curl_nop_stmt -#define Curl_ssl_close(x,y,z) Curl_nop_stmt -#define Curl_ssl_shutdown(x,y,z) CURLE_NOT_BUILT_IN -#define Curl_ssl_set_engine(x,y) CURLE_NOT_BUILT_IN -#define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN -#define Curl_ssl_engines_list(x) NULL -#define Curl_ssl_send(a,b,c,d,e) -1 -#define Curl_ssl_recv(a,b,c,d,e) -1 -#define Curl_ssl_initsessions(x,y) CURLE_OK -#define Curl_ssl_data_pending(x,y) 0 -#define Curl_ssl_check_cxn(x) 0 -#define Curl_ssl_free_certinfo(x) Curl_nop_stmt -#define Curl_ssl_connect_nonblocking(x,y,z,w,a) CURLE_NOT_BUILT_IN -#define Curl_ssl_kill_session(x) Curl_nop_stmt -#define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN) -#define Curl_ssl_cert_status_request() FALSE -#define Curl_ssl_false_start() FALSE -#define Curl_ssl_tls13_ciphersuites() FALSE -#define Curl_ssl_associate_conn(a,b) Curl_nop_stmt -#define Curl_ssl_detach_conn(a,b) Curl_nop_stmt -#endif - -#endif /* HEADER_CURL_VTLS_H */ +#ifndef HEADER_CURL_VTLS_H
+#define HEADER_CURL_VTLS_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+struct connectdata;
+struct ssl_config_data;
+struct ssl_connect_data;
+struct ssl_primary_config;
+struct Curl_ssl_session;
+
+#define SSLSUPP_CA_PATH (1<<0) /* supports CAPATH */
+#define SSLSUPP_CERTINFO (1<<1) /* supports CURLOPT_CERTINFO */
+#define SSLSUPP_PINNEDPUBKEY (1<<2) /* supports CURLOPT_PINNEDPUBLICKEY */
+#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)
+
+#define ALPN_ACCEPTED "ALPN: server accepted "
+
+#define VTLS_INFOF_NO_ALPN \
+ "ALPN: server did not agree on a protocol. Uses default."
+#define VTLS_INFOF_ALPN_OFFER_1STR \
+ "ALPN: offers %s"
+#define VTLS_INFOF_ALPN_ACCEPTED_1STR \
+ ALPN_ACCEPTED "%s"
+#define VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR \
+ ALPN_ACCEPTED "%.*s"
+
+/* Curl_multi SSL backend-specific data; declared differently by each SSL
+ backend */
+struct multi_ssl_backend_data;
+
+CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
+ const curl_ssl_backend ***avail);
+
+#ifndef MAX_PINNED_PUBKEY_SIZE
+#define MAX_PINNED_PUBKEY_SIZE 1048576 /* 1MB */
+#endif
+
+#ifndef CURL_SHA256_DIGEST_LENGTH
+#define CURL_SHA256_DIGEST_LENGTH 32 /* fixed size */
+#endif
+
+/* see https://www.iana.org/assignments/tls-extensiontype-values/ */
+#define ALPN_HTTP_1_1_LENGTH 8
+#define ALPN_HTTP_1_1 "http/1.1"
+#define ALPN_H2_LENGTH 2
+#define ALPN_H2 "h2"
+
+
+char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen);
+bool Curl_ssl_config_matches(struct ssl_primary_config *data,
+ struct ssl_primary_config *needle);
+bool Curl_clone_primary_ssl_config(struct ssl_primary_config *source,
+ struct ssl_primary_config *dest);
+void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc);
+
+curl_sslbackend Curl_ssl_backend(void);
+
+#ifdef USE_SSL
+int Curl_ssl_init(void);
+void Curl_ssl_cleanup(void);
+/* tell the SSL stuff to close down all open information regarding
+ connections (and thus session ID caching etc) */
+void Curl_ssl_close_all(struct Curl_easy *data);
+CURLcode Curl_ssl_set_engine(struct Curl_easy *data, const char *engine);
+/* Sets engine as default for all SSL operations */
+CURLcode Curl_ssl_set_engine_default(struct Curl_easy *data);
+struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data);
+
+/* init the SSL session ID cache */
+CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t);
+void Curl_ssl_version(char *buffer, size_t size);
+int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn);
+
+/* Certificate information list handling. */
+
+void Curl_ssl_free_certinfo(struct Curl_easy *data);
+CURLcode Curl_ssl_init_certinfo(struct Curl_easy *data, int num);
+CURLcode Curl_ssl_push_certinfo_len(struct Curl_easy *data, int certnum,
+ const char *label, const char *value,
+ size_t valuelen);
+CURLcode Curl_ssl_push_certinfo(struct Curl_easy *data, int certnum,
+ const char *label, const char *value);
+
+/* Functions to be used by SSL library adaptation functions */
+
+/* Lock session cache mutex.
+ * Call this before calling other Curl_ssl_*session* functions
+ * Caller should unlock this mutex as soon as possible, as it may block
+ * other SSL connection from making progress.
+ * The purpose of explicitly locking SSL session cache data is to allow
+ * individual SSL engines to manage session lifetime in their specific way.
+ */
+void Curl_ssl_sessionid_lock(struct Curl_easy *data);
+
+/* Unlock session cache mutex */
+void Curl_ssl_sessionid_unlock(struct Curl_easy *data);
+
+/* Kill a single session ID entry in the cache
+ * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
+ * This will call engine-specific curlssl_session_free function, which must
+ * take sessionid object ownership from sessionid cache
+ * (e.g. decrement refcount).
+ */
+void Curl_ssl_kill_session(struct Curl_ssl_session *session);
+/* delete a session from the cache
+ * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
+ * This will call engine-specific curlssl_session_free function, which must
+ * take sessionid object ownership from sessionid cache
+ * (e.g. decrement refcount).
+ */
+void Curl_ssl_delsessionid(struct Curl_easy *data, void *ssl_sessionid);
+
+/* get N random bytes into the buffer */
+CURLcode Curl_ssl_random(struct Curl_easy *data, unsigned char *buffer,
+ size_t length);
+/* Check pinned public key. */
+CURLcode Curl_pin_peer_pubkey(struct Curl_easy *data,
+ const char *pinnedpubkey,
+ const unsigned char *pubkey, size_t pubkeylen);
+
+bool Curl_ssl_cert_status_request(void);
+
+bool Curl_ssl_false_start(struct Curl_easy *data);
+
+void Curl_free_multi_ssl_backend_data(struct multi_ssl_backend_data *mbackend);
+
+#define SSL_SHUTDOWN_TIMEOUT 10000 /* ms */
+
+CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
+
+CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
+ int sockindex);
+
+#ifndef CURL_DISABLE_PROXY
+CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
+#endif /* !CURL_DISABLE_PROXY */
+
+/**
+ * Get the SSL configuration that is used on the connection.
+ * This returns NULL if no SSL is configured.
+ * Otherwise it returns the config of the first (highest) one that is
+ * either connected, in handshake or about to start
+ * (e.g. all filters below it are connected). If SSL filters are present,
+ * but neither can start operating, return the config of the lowest one
+ * that will first come into effect when connecting.
+ */
+struct ssl_config_data *Curl_ssl_get_config(struct Curl_easy *data,
+ int sockindex);
+
+/**
+ * Get the primary SSL configuration from the connection.
+ * This returns NULL if no SSL is configured.
+ * Otherwise it returns the config of the first (highest) one that is
+ * either connected, in handshake or about to start
+ * (e.g. all filters below it are connected). If SSL filters are present,
+ * but neither can start operating, return the config of the lowest one
+ * that will first come into effect when connecting.
+ */
+struct ssl_primary_config *
+Curl_ssl_get_primary_config(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex);
+
+/**
+ * True iff the underlying SSL implementation supports the option.
+ * Option is one of the defined SSLSUPP_* values.
+ * `data` maybe NULL for the features of the default implementation.
+ */
+bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option);
+
+/**
+ * Get the internal ssl instance (like OpenSSL's SSL*) from the filter
+ * chain at `sockindex` of type specified by `info`.
+ * For `n` == 0, the first active (top down) instance is returned.
+ * 1 gives the second active, etc.
+ * NULL is returned when no active SSL filter is present.
+ */
+void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
+ CURLINFO info, int n);
+
+#else /* if not USE_SSL */
+
+/* When SSL support is not present, just define away these function calls */
+#define Curl_ssl_init() 1
+#define Curl_ssl_cleanup() Curl_nop_stmt
+#define Curl_ssl_close_all(x) Curl_nop_stmt
+#define Curl_ssl_set_engine(x,y) CURLE_NOT_BUILT_IN
+#define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN
+#define Curl_ssl_engines_list(x) NULL
+#define Curl_ssl_initsessions(x,y) CURLE_OK
+#define Curl_ssl_check_cxn(d,x) 0
+#define Curl_ssl_free_certinfo(x) Curl_nop_stmt
+#define Curl_ssl_kill_session(x) Curl_nop_stmt
+#define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN)
+#define Curl_ssl_cert_status_request() FALSE
+#define Curl_ssl_false_start(a) FALSE
+#define Curl_ssl_get_internals(a,b,c,d) NULL
+#define Curl_ssl_supports(a,b) FALSE
+#define Curl_ssl_cfilter_add(a,b,c) CURLE_NOT_BUILT_IN
+#define Curl_ssl_cfilter_proxy_add(a,b,c) CURLE_NOT_BUILT_IN
+#define Curl_ssl_get_config(a,b) NULL
+#define Curl_ssl_cfilter_remove(a,b) CURLE_OK
+#endif
+
+#endif /* HEADER_CURL_VTLS_H */
diff --git a/libs/libcurl/src/vtls/vtls_int.h b/libs/libcurl/src/vtls/vtls_int.h new file mode 100644 index 0000000000..0f04390216 --- /dev/null +++ b/libs/libcurl/src/vtls/vtls_int.h @@ -0,0 +1,190 @@ +#ifndef HEADER_CURL_VTLS_INT_H
+#define HEADER_CURL_VTLS_INT_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+#include "cfilters.h"
+#include "urldata.h"
+
+#ifdef USE_SSL
+
+/* Information in each SSL cfilter context: cf->ctx */
+struct ssl_connect_data {
+ ssl_connection_state state;
+ ssl_connect_state connecting_state;
+ const char *hostname; /* hostnaem for verification */
+ const char *dispname; /* display version of hostname */
+ int port; /* remote port at origin */
+ struct ssl_backend_data *backend; /* vtls backend specific props */
+ struct Curl_easy *call_data; /* data handle used in current call,
+ * same as parameter passed, but available
+ * here for backend internal callbacks
+ * that need it. NULLed after at the
+ * end of each vtls filter invcocation. */
+};
+
+
+/* Definitions for SSL Implementations */
+
+struct Curl_ssl {
+ /*
+ * This *must* be the first entry to allow returning the list of available
+ * backends in curl_global_sslset().
+ */
+ curl_ssl_backend info;
+ unsigned int supports; /* bitfield, see above */
+ size_t sizeof_ssl_backend_data;
+
+ int (*init)(void);
+ void (*cleanup)(void);
+
+ size_t (*version)(char *buffer, size_t size);
+ int (*check_cxn)(struct Curl_cfilter *cf, struct Curl_easy *data);
+ int (*shut_down)(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+ bool (*data_pending)(struct Curl_cfilter *cf,
+ const struct Curl_easy *data);
+
+ /* return 0 if a find random is filled in */
+ CURLcode (*random)(struct Curl_easy *data, unsigned char *entropy,
+ size_t length);
+ bool (*cert_status_request)(void);
+
+ CURLcode (*connect_blocking)(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+ CURLcode (*connect_nonblocking)(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done);
+
+ /* If the SSL backend wants to read or write on this connection during a
+ handshake, set socks[0] to the connection's FIRSTSOCKET, and return
+ a bitmap indicating read or write with GETSOCK_WRITESOCK(0) or
+ GETSOCK_READSOCK(0). Otherwise return GETSOCK_BLANK.
+ Mandatory. */
+ int (*get_select_socks)(struct Curl_cfilter *cf, struct Curl_easy *data,
+ curl_socket_t *socks);
+
+ void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info);
+ void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data);
+ void (*close_all)(struct Curl_easy *data);
+ void (*session_free)(void *ptr);
+
+ CURLcode (*set_engine)(struct Curl_easy *data, const char *engine);
+ CURLcode (*set_engine_default)(struct Curl_easy *data);
+ struct curl_slist *(*engines_list)(struct Curl_easy *data);
+
+ bool (*false_start)(void);
+ CURLcode (*sha256sum)(const unsigned char *input, size_t inputlen,
+ unsigned char *sha256sum, size_t sha256sumlen);
+
+ bool (*attach_data)(struct Curl_cfilter *cf, struct Curl_easy *data);
+ void (*detach_data)(struct Curl_cfilter *cf, struct Curl_easy *data);
+
+ void (*free_multi_ssl_backend_data)(struct multi_ssl_backend_data *mbackend);
+
+ ssize_t (*recv_plain)(struct Curl_cfilter *cf, struct Curl_easy *data,
+ char *buf, size_t len, CURLcode *code);
+ ssize_t (*send_plain)(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const void *mem, size_t len, CURLcode *code);
+
+};
+
+extern const struct Curl_ssl *Curl_ssl;
+
+
+int Curl_none_init(void);
+void Curl_none_cleanup(void);
+int Curl_none_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data);
+int Curl_none_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data);
+CURLcode Curl_none_random(struct Curl_easy *data, unsigned char *entropy,
+ size_t length);
+void Curl_none_close_all(struct Curl_easy *data);
+void Curl_none_session_free(void *ptr);
+bool Curl_none_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data);
+bool Curl_none_cert_status_request(void);
+CURLcode Curl_none_set_engine(struct Curl_easy *data, const char *engine);
+CURLcode Curl_none_set_engine_default(struct Curl_easy *data);
+struct curl_slist *Curl_none_engines_list(struct Curl_easy *data);
+bool Curl_none_false_start(void);
+int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data,
+ curl_socket_t *socks);
+
+/**
+ * Get the ssl_config_data in `data` that is relevant for cfilter `cf`.
+ */
+struct ssl_config_data *Curl_ssl_cf_get_config(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+
+/**
+ * Get the primary config relevant for the filter from its connection.
+ */
+struct ssl_primary_config *
+ Curl_ssl_cf_get_primary_config(struct Curl_cfilter *cf);
+
+/**
+ * Get the first SSL filter in the chain starting with `cf`, or NULL.
+ */
+struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf);
+
+/**
+ * Get the SSL filter below the given one or NULL if there is none.
+ */
+bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf);
+
+/* extract a session ID
+ * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
+ * Caller must make sure that the ownership of returned sessionid object
+ * is properly taken (e.g. its refcount is incremented
+ * under sessionid mutex).
+ */
+bool Curl_ssl_getsessionid(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ void **ssl_sessionid,
+ size_t *idsize); /* set 0 if unknown */
+/* add a new session ID
+ * Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
+ * Caller must ensure that it has properly shared ownership of this sessionid
+ * object with cache (e.g. incrementing refcount on success)
+ */
+CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ void *ssl_sessionid,
+ size_t idsize,
+ bool *added);
+
+#include "openssl.h" /* OpenSSL versions */
+#include "gtls.h" /* GnuTLS versions */
+#include "nssg.h" /* NSS versions */
+#include "gskit.h" /* Global Secure ToolKit versions */
+#include "wolfssl.h" /* wolfSSL versions */
+#include "schannel.h" /* Schannel SSPI version */
+#include "sectransp.h" /* SecureTransport (Darwin) version */
+#include "mbedtls.h" /* mbedTLS versions */
+#include "bearssl.h" /* BearSSL versions */
+#include "rustls.h" /* rustls versions */
+
+#endif /* USE_SSL */
+
+#endif /* HEADER_CURL_VTLS_INT_H */
diff --git a/libs/libcurl/src/vtls/wolfssl.c b/libs/libcurl/src/vtls/wolfssl.c index 594c39a324..5ae0bc9b11 100644 --- a/libs/libcurl/src/vtls/wolfssl.c +++ b/libs/libcurl/src/vtls/wolfssl.c @@ -1,1247 +1,1372 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -/* - * Source file for all wolfSSL specific code for the TLS/SSL layer. No code - * but vtls.c should ever call or use these functions. - * - */ - -#include "curl_setup.h" - -#ifdef USE_WOLFSSL - -#define WOLFSSL_OPTIONS_IGNORE_SYS -#include <wolfssl/version.h> -#include <wolfssl/options.h> - -/* To determine what functions are available we rely on one or both of: - - the user's options.h generated by wolfSSL - - the symbols detected by curl's configure - Since they are markedly different from one another, and one or the other may - not be available, we do some checking below to bring things in sync. */ - -/* HAVE_ALPN is wolfSSL's build time symbol for enabling ALPN in options.h. */ -#ifndef HAVE_ALPN -#ifdef HAVE_WOLFSSL_USEALPN -#define HAVE_ALPN -#endif -#endif - -#include <limits.h> - -#include "urldata.h" -#include "sendf.h" -#include "inet_pton.h" -#include "vtls.h" -#include "keylog.h" -#include "parsedate.h" -#include "connect.h" /* for the connect timeout */ -#include "select.h" -#include "strcase.h" -#include "x509asn1.h" -#include "curl_printf.h" -#include "multiif.h" - -#include <wolfssl/openssl/ssl.h> -#include <wolfssl/ssl.h> -#include <wolfssl/error-ssl.h> -#include "wolfssl.h" - -/* The last #include files should be: */ -#include "curl_memory.h" -#include "memdebug.h" - -/* KEEP_PEER_CERT is a product of the presence of build time symbol - OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is - in wolfSSL's settings.h, and the latter two are build time symbols in - options.h. */ -#ifndef KEEP_PEER_CERT -#if defined(HAVE_WOLFSSL_GET_PEER_CERTIFICATE) || \ - (defined(OPENSSL_EXTRA) && !defined(NO_CERTS)) -#define KEEP_PEER_CERT -#endif -#endif - -struct ssl_backend_data { - SSL_CTX* ctx; - SSL* handle; -}; - -static Curl_recv wolfssl_recv; -static Curl_send wolfssl_send; - -#ifdef OPENSSL_EXTRA -/* - * Availability note: - * The TLS 1.3 secret callback (wolfSSL_set_tls13_secret_cb) was added in - * WolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that - * option is not set, then TLS 1.3 will not be logged. - * For TLS 1.2 and before, we use wolfSSL_get_keys(). - * SSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA - * (--enable-opensslextra or --enable-all). - */ -#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) -static int -wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret, - int secretSz, void *ctx) -{ - const char *label; - unsigned char client_random[SSL3_RANDOM_SIZE]; - (void)ctx; - - if(!ssl || !Curl_tls_keylog_enabled()) { - return 0; - } - - switch(id) { - case CLIENT_EARLY_TRAFFIC_SECRET: - label = "CLIENT_EARLY_TRAFFIC_SECRET"; - break; - case CLIENT_HANDSHAKE_TRAFFIC_SECRET: - label = "CLIENT_HANDSHAKE_TRAFFIC_SECRET"; - break; - case SERVER_HANDSHAKE_TRAFFIC_SECRET: - label = "SERVER_HANDSHAKE_TRAFFIC_SECRET"; - break; - case CLIENT_TRAFFIC_SECRET: - label = "CLIENT_TRAFFIC_SECRET_0"; - break; - case SERVER_TRAFFIC_SECRET: - label = "SERVER_TRAFFIC_SECRET_0"; - break; - case EARLY_EXPORTER_SECRET: - label = "EARLY_EXPORTER_SECRET"; - break; - case EXPORTER_SECRET: - label = "EXPORTER_SECRET"; - break; - default: - return 0; - } - - if(SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE) == 0) { - /* Should never happen as wolfSSL_KeepArrays() was called before. */ - return 0; - } - - Curl_tls_keylog_write(label, client_random, secret, secretSz); - return 0; -} -#endif /* defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) */ - -static void -wolfssl_log_tls12_secret(SSL *ssl) -{ - unsigned char *ms, *sr, *cr; - unsigned int msLen, srLen, crLen, i, x = 0; - -#if LIBWOLFSSL_VERSION_HEX >= 0x0300d000 /* >= 3.13.0 */ - /* wolfSSL_GetVersion is available since 3.13, we use it instead of - * SSL_version since the latter relies on OPENSSL_ALL (--enable-opensslall or - * --enable-all). Failing to perform this check could result in an unusable - * key log line when TLS 1.3 is actually negotiated. */ - switch(wolfSSL_GetVersion(ssl)) { - case WOLFSSL_SSLV3: - case WOLFSSL_TLSV1: - case WOLFSSL_TLSV1_1: - case WOLFSSL_TLSV1_2: - break; - default: - /* TLS 1.3 does not use this mechanism, the "master secret" returned below - * is not directly usable. */ - return; - } -#endif - - if(SSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != SSL_SUCCESS) { - return; - } - - /* Check for a missing master secret and skip logging. That can happen if - * curl rejects the server certificate and aborts the handshake. - */ - for(i = 0; i < msLen; i++) { - x |= ms[i]; - } - if(x == 0) { - return; - } - - Curl_tls_keylog_write("CLIENT_RANDOM", cr, ms, msLen); -} -#endif /* OPENSSL_EXTRA */ - -static int do_file_type(const char *type) -{ - if(!type || !type[0]) - return SSL_FILETYPE_PEM; - if(strcasecompare(type, "PEM")) - return SSL_FILETYPE_PEM; - if(strcasecompare(type, "DER")) - return SSL_FILETYPE_ASN1; - return -1; -} - -#ifdef HAVE_LIBOQS -struct group_name_map { - const word16 group; - const char *name; -}; - -static const struct group_name_map gnm[] = { - { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" }, - { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" }, - { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" }, - { WOLFSSL_NTRU_HPS_LEVEL1, "NTRU_HPS_LEVEL1" }, - { WOLFSSL_NTRU_HPS_LEVEL3, "NTRU_HPS_LEVEL3" }, - { WOLFSSL_NTRU_HPS_LEVEL5, "NTRU_HPS_LEVEL5" }, - { WOLFSSL_NTRU_HRSS_LEVEL3, "NTRU_HRSS_LEVEL3" }, - { WOLFSSL_SABER_LEVEL1, "SABER_LEVEL1" }, - { WOLFSSL_SABER_LEVEL3, "SABER_LEVEL3" }, - { WOLFSSL_SABER_LEVEL5, "SABER_LEVEL5" }, - { WOLFSSL_KYBER_90S_LEVEL1, "KYBER_90S_LEVEL1" }, - { WOLFSSL_KYBER_90S_LEVEL3, "KYBER_90S_LEVEL3" }, - { WOLFSSL_KYBER_90S_LEVEL5, "KYBER_90S_LEVEL5" }, - { WOLFSSL_P256_NTRU_HPS_LEVEL1, "P256_NTRU_HPS_LEVEL1" }, - { WOLFSSL_P384_NTRU_HPS_LEVEL3, "P384_NTRU_HPS_LEVEL3" }, - { WOLFSSL_P521_NTRU_HPS_LEVEL5, "P521_NTRU_HPS_LEVEL5" }, - { WOLFSSL_P384_NTRU_HRSS_LEVEL3, "P384_NTRU_HRSS_LEVEL3" }, - { WOLFSSL_P256_SABER_LEVEL1, "P256_SABER_LEVEL1" }, - { WOLFSSL_P384_SABER_LEVEL3, "P384_SABER_LEVEL3" }, - { WOLFSSL_P521_SABER_LEVEL5, "P521_SABER_LEVEL5" }, - { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" }, - { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" }, - { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" }, - { WOLFSSL_P256_KYBER_90S_LEVEL1, "P256_KYBER_90S_LEVEL1" }, - { WOLFSSL_P384_KYBER_90S_LEVEL3, "P384_KYBER_90S_LEVEL3" }, - { WOLFSSL_P521_KYBER_90S_LEVEL5, "P521_KYBER_90S_LEVEL5" }, - { 0, NULL } -}; -#endif - -/* - * This function loads all the client/CA certificates and CRLs. Setup the TLS - * layer and do all necessary magic. - */ -static CURLcode -wolfssl_connect_step1(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - char *ciphers, *curves; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - SSL_METHOD* req_method = NULL; - curl_socket_t sockfd = conn->sock[sockindex]; -#ifdef HAVE_LIBOQS - word16 oqsAlg = 0; - size_t idx = 0; -#endif -#ifdef HAVE_SNI - bool sni = FALSE; -#define use_sni(x) sni = (x) -#else -#define use_sni(x) Curl_nop_stmt -#endif - - DEBUGASSERT(backend); - - if(connssl->state == ssl_connection_complete) - return CURLE_OK; - - if(SSL_CONN_CONFIG(version_max) != CURL_SSLVERSION_MAX_NONE) { - failf(data, "wolfSSL does not support to set maximum SSL/TLS version"); - return CURLE_SSL_CONNECT_ERROR; - } - - /* check to see if we've been told to use an explicit SSL/TLS version */ - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if LIBWOLFSSL_VERSION_HEX >= 0x03003000 /* >= 3.3.0 */ - /* minimum protocol version is set later after the CTX object is created */ - req_method = SSLv23_client_method(); -#else - infof(data, "wolfSSL <3.3.0 cannot be configured to use TLS 1.0-1.2, " - "TLS 1.0 is used exclusively"); - req_method = TLSv1_client_method(); -#endif - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_0: -#if defined(WOLFSSL_ALLOW_TLSV10) && !defined(NO_OLD_TLS) - req_method = TLSv1_client_method(); - use_sni(TRUE); -#else - failf(data, "wolfSSL does not support TLS 1.0"); - return CURLE_NOT_BUILT_IN; -#endif - break; - case CURL_SSLVERSION_TLSv1_1: -#ifndef NO_OLD_TLS - req_method = TLSv1_1_client_method(); - use_sni(TRUE); -#else - failf(data, "wolfSSL does not support TLS 1.1"); - return CURLE_NOT_BUILT_IN; -#endif - break; - case CURL_SSLVERSION_TLSv1_2: - req_method = TLSv1_2_client_method(); - use_sni(TRUE); - break; - case CURL_SSLVERSION_TLSv1_3: -#ifdef WOLFSSL_TLS13 - req_method = wolfTLSv1_3_client_method(); - use_sni(TRUE); - break; -#else - failf(data, "wolfSSL: TLS 1.3 is not yet supported"); - return CURLE_SSL_CONNECT_ERROR; -#endif - default: - failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION"); - return CURLE_SSL_CONNECT_ERROR; - } - - if(!req_method) { - failf(data, "SSL: couldn't create a method"); - return CURLE_OUT_OF_MEMORY; - } - - if(backend->ctx) - SSL_CTX_free(backend->ctx); - backend->ctx = SSL_CTX_new(req_method); - - if(!backend->ctx) { - failf(data, "SSL: couldn't create a context"); - return CURLE_OUT_OF_MEMORY; - } - - switch(SSL_CONN_CONFIG(version)) { - case CURL_SSLVERSION_DEFAULT: - case CURL_SSLVERSION_TLSv1: -#if LIBWOLFSSL_VERSION_HEX > 0x03004006 /* > 3.4.6 */ - /* Versions 3.3.0 to 3.4.6 we know the minimum protocol version is - * whatever minimum version of TLS was built in and at least TLS 1.0. For - * later library versions that could change (eg TLS 1.0 built in but - * defaults to TLS 1.1) so we have this short circuit evaluation to find - * the minimum supported TLS version. - */ - if((wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1) != 1) && - (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_1) != 1) && - (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_2) != 1) -#ifdef WOLFSSL_TLS13 - && (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_3) != 1) -#endif - ) { - failf(data, "SSL: couldn't set the minimum protocol version"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif - break; - } - - ciphers = SSL_CONN_CONFIG(cipher_list); - if(ciphers) { - if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) { - failf(data, "failed setting cipher list: %s", ciphers); - return CURLE_SSL_CIPHER; - } - infof(data, "Cipher selection: %s", ciphers); - } - - curves = SSL_CONN_CONFIG(curves); - if(curves) { - -#ifdef HAVE_LIBOQS - for(idx = 0; gnm[idx].name != NULL; idx++) { - if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) { - oqsAlg = gnm[idx].group; - break; - } - } - - if(oqsAlg == 0) -#endif - { - if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) { - failf(data, "failed setting curves list: '%s'", curves); - return CURLE_SSL_CIPHER; - } - } - } -#ifndef NO_FILESYSTEM - /* load trusted cacert */ - if(SSL_CONN_CONFIG(CAfile)) { - if(1 != SSL_CTX_load_verify_locations(backend->ctx, - SSL_CONN_CONFIG(CAfile), - SSL_CONN_CONFIG(CApath))) { - if(SSL_CONN_CONFIG(verifypeer)) { - /* Fail if we insist on successfully verifying the server. */ - failf(data, "error setting certificate verify locations:" - " CAfile: %s CApath: %s", - SSL_CONN_CONFIG(CAfile)? - SSL_CONN_CONFIG(CAfile): "none", - SSL_CONN_CONFIG(CApath)? - SSL_CONN_CONFIG(CApath) : "none"); - return CURLE_SSL_CACERT_BADFILE; - } - else { - /* Just continue with a warning if no strict certificate - verification is required. */ - infof(data, "error setting certificate verify locations," - " continuing anyway:"); - } - } - else { - /* Everything is fine. */ - infof(data, "successfully set certificate verify locations:"); - } - infof(data, " CAfile: %s", - SSL_CONN_CONFIG(CAfile) ? SSL_CONN_CONFIG(CAfile) : "none"); - infof(data, " CApath: %s", - SSL_CONN_CONFIG(CApath) ? SSL_CONN_CONFIG(CApath) : "none"); - } - - /* Load the client certificate, and private key */ - if(SSL_SET_OPTION(primary.clientcert) && SSL_SET_OPTION(key)) { - int file_type = do_file_type(SSL_SET_OPTION(cert_type)); - - if(SSL_CTX_use_certificate_file(backend->ctx, - SSL_SET_OPTION(primary.clientcert), - file_type) != 1) { - failf(data, "unable to use client certificate (no key or wrong pass" - " phrase?)"); - return CURLE_SSL_CONNECT_ERROR; - } - - file_type = do_file_type(SSL_SET_OPTION(key_type)); - if(SSL_CTX_use_PrivateKey_file(backend->ctx, SSL_SET_OPTION(key), - file_type) != 1) { - failf(data, "unable to set private key"); - return CURLE_SSL_CONNECT_ERROR; - } - } -#endif /* !NO_FILESYSTEM */ - - /* SSL always tries to verify the peer, this only says whether it should - * fail to connect if the verification fails, or if it should continue - * anyway. In the latter case the result of the verification is checked with - * SSL_get_verify_result() below. */ - SSL_CTX_set_verify(backend->ctx, - SSL_CONN_CONFIG(verifypeer)?SSL_VERIFY_PEER: - SSL_VERIFY_NONE, - NULL); - -#ifdef HAVE_SNI - if(sni) { - struct in_addr addr4; -#ifdef ENABLE_IPV6 - struct in6_addr addr6; -#endif - const char * const hostname = SSL_HOST_NAME(); - size_t hostname_len = strlen(hostname); - if((hostname_len < USHRT_MAX) && - !Curl_inet_pton(AF_INET, hostname, &addr4) -#ifdef ENABLE_IPV6 - && !Curl_inet_pton(AF_INET6, hostname, &addr6) -#endif - ) { - size_t snilen; - char *snihost = Curl_ssl_snihost(data, hostname, &snilen); - if(!snihost || - wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, snihost, - (unsigned short)snilen) != 1) { - failf(data, "Failed to set SNI"); - return CURLE_SSL_CONNECT_ERROR; - } - } - } -#endif - - /* give application a chance to interfere with SSL set up. */ - if(data->set.ssl.fsslctx) { - CURLcode result = (*data->set.ssl.fsslctx)(data, backend->ctx, - data->set.ssl.fsslctxp); - if(result) { - failf(data, "error signaled by ssl ctx callback"); - return result; - } - } -#ifdef NO_FILESYSTEM - else if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, "SSL: Certificates can't be loaded because wolfSSL was built" - " with \"no filesystem\". Either disable peer verification" - " (insecure) or if you are building an application with libcurl you" - " can load certificates via CURLOPT_SSL_CTX_FUNCTION."); - return CURLE_SSL_CONNECT_ERROR; - } -#endif - - /* Let's make an SSL structure */ - if(backend->handle) - SSL_free(backend->handle); - backend->handle = SSL_new(backend->ctx); - if(!backend->handle) { - failf(data, "SSL: couldn't create a handle"); - return CURLE_OUT_OF_MEMORY; - } - -#ifdef HAVE_LIBOQS - if(oqsAlg) { - if(wolfSSL_UseKeyShare(backend->handle, oqsAlg) != WOLFSSL_SUCCESS) { - failf(data, "unable to use oqs KEM"); - } - } -#endif - -#ifdef HAVE_ALPN - if(conn->bits.tls_enable_alpn) { - char protocols[128]; - *protocols = '\0'; - - /* wolfSSL's ALPN protocol name list format is a comma separated string of - protocols in descending order of preference, eg: "h2,http/1.1" */ - -#ifdef USE_HTTP2 - if(data->state.httpwant >= CURL_HTTP_VERSION_2) { - strcpy(protocols + strlen(protocols), ALPN_H2 ","); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2); - } -#endif - - strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1); - infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1); - - if(wolfSSL_UseALPN(backend->handle, protocols, - (unsigned)strlen(protocols), - WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) { - failf(data, "SSL: failed setting ALPN protocols"); - return CURLE_SSL_CONNECT_ERROR; - } - } -#endif /* HAVE_ALPN */ - -#ifdef OPENSSL_EXTRA - if(Curl_tls_keylog_enabled()) { - /* Ensure the Client Random is preserved. */ - wolfSSL_KeepArrays(backend->handle); -#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) - wolfSSL_set_tls13_secret_cb(backend->handle, - wolfssl_tls13_secret_callback, NULL); -#endif - } -#endif /* OPENSSL_EXTRA */ - -#ifdef HAVE_SECURE_RENEGOTIATION - if(wolfSSL_UseSecureRenegotiation(backend->handle) != SSL_SUCCESS) { - failf(data, "SSL: failed setting secure renegotiation"); - return CURLE_SSL_CONNECT_ERROR; - } -#endif /* HAVE_SECURE_RENEGOTIATION */ - - /* Check if there's a cached ID we can/should use here! */ - if(SSL_SET_OPTION(primary.sessionid)) { - void *ssl_sessionid = NULL; - - Curl_ssl_sessionid_lock(data); - if(!Curl_ssl_getsessionid(data, conn, - SSL_IS_PROXY() ? TRUE : FALSE, - &ssl_sessionid, NULL, sockindex)) { - /* we got a session id, use it! */ - if(!SSL_set_session(backend->handle, ssl_sessionid)) { - Curl_ssl_delsessionid(data, ssl_sessionid); - infof(data, "Can't use session ID, going on without"); - } - else - infof(data, "SSL re-using session ID"); - } - Curl_ssl_sessionid_unlock(data); - } - - /* pass the raw socket into the SSL layer */ - if(!SSL_set_fd(backend->handle, (int)sockfd)) { - failf(data, "SSL: SSL_set_fd failed"); - return CURLE_SSL_CONNECT_ERROR; - } - - connssl->connecting_state = ssl_connect_2; - return CURLE_OK; -} - - -static CURLcode -wolfssl_connect_step2(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - int ret = -1; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - const char * const dispname = SSL_HOST_DISPNAME(); - const char * const pinnedpubkey = SSL_PINNED_PUB_KEY(); - - DEBUGASSERT(backend); - - ERR_clear_error(); - - conn->recv[sockindex] = wolfssl_recv; - conn->send[sockindex] = wolfssl_send; - - /* Enable RFC2818 checks */ - if(SSL_CONN_CONFIG(verifyhost)) { - char *snihost = Curl_ssl_snihost(data, SSL_HOST_NAME(), NULL); - if(!snihost || - (wolfSSL_check_domain_name(backend->handle, snihost) == SSL_FAILURE)) - return CURLE_SSL_CONNECT_ERROR; - } - - ret = SSL_connect(backend->handle); - -#ifdef OPENSSL_EXTRA - if(Curl_tls_keylog_enabled()) { - /* If key logging is enabled, wait for the handshake to complete and then - * proceed with logging secrets (for TLS 1.2 or older). - * - * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits - * for the server response. At that point the master secret is not yet - * available, so we must not try to read it. - * To log the secret on completion with a handshake failure, detect - * completion via the observation that there is nothing to read or write. - * Note that OpenSSL SSL_want_read() is always true here. If wolfSSL ever - * changes, the worst case is that no key is logged on error. - */ - if(ret == SSL_SUCCESS || - (!wolfSSL_want_read(backend->handle) && - !wolfSSL_want_write(backend->handle))) { - wolfssl_log_tls12_secret(backend->handle); - /* Client Random and master secrets are no longer needed, erase these. - * Ignored while the handshake is still in progress. */ - wolfSSL_FreeArrays(backend->handle); - } - } -#endif /* OPENSSL_EXTRA */ - - if(ret != 1) { - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; - int detail = SSL_get_error(backend->handle, ret); - - if(SSL_ERROR_WANT_READ == detail) { - connssl->connecting_state = ssl_connect_2_reading; - return CURLE_OK; - } - else if(SSL_ERROR_WANT_WRITE == detail) { - connssl->connecting_state = ssl_connect_2_writing; - return CURLE_OK; - } - /* There is no easy way to override only the CN matching. - * This will enable the override of both mismatching SubjectAltNames - * as also mismatching CN fields */ - else if(DOMAIN_NAME_MISMATCH == detail) { -#if 1 - failf(data, " subject alt name(s) or common name do not match \"%s\"", - dispname); - return CURLE_PEER_FAILED_VERIFICATION; -#else - /* When the wolfssl_check_domain_name() is used and you desire to - * continue on a DOMAIN_NAME_MISMATCH, i.e. 'conn->ssl_config.verifyhost - * == 0', CyaSSL version 2.4.0 will fail with an INCOMPLETE_DATA - * error. The only way to do this is currently to switch the - * Wolfssl_check_domain_name() in and out based on the - * 'conn->ssl_config.verifyhost' value. */ - if(SSL_CONN_CONFIG(verifyhost)) { - failf(data, - " subject alt name(s) or common name do not match \"%s\"\n", - dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - else { - infof(data, - " subject alt name(s) and/or common name do not match \"%s\"", - dispname); - return CURLE_OK; - } -#endif - } -#if LIBWOLFSSL_VERSION_HEX >= 0x02007000 /* 2.7.0 */ - else if(ASN_NO_SIGNER_E == detail) { - if(SSL_CONN_CONFIG(verifypeer)) { - failf(data, " CA signer not available for verification"); - return CURLE_SSL_CACERT_BADFILE; - } - else { - /* Just continue with a warning if no strict certificate - verification is required. */ - infof(data, "CA signer not available for verification, " - "continuing anyway"); - } - } -#endif - else { - failf(data, "SSL_connect failed with error %d: %s", detail, - ERR_error_string(detail, error_buffer)); - return CURLE_SSL_CONNECT_ERROR; - } - } - - if(pinnedpubkey) { -#ifdef KEEP_PEER_CERT - X509 *x509; - const char *x509_der; - int x509_der_len; - struct Curl_X509certificate x509_parsed; - struct Curl_asn1Element *pubkey; - CURLcode result; - - x509 = SSL_get_peer_certificate(backend->handle); - if(!x509) { - failf(data, "SSL: failed retrieving server certificate"); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - x509_der = (const char *)wolfSSL_X509_get_der(x509, &x509_der_len); - if(!x509_der) { - failf(data, "SSL: failed retrieving ASN.1 server certificate"); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - memset(&x509_parsed, 0, sizeof(x509_parsed)); - if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len)) - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - - pubkey = &x509_parsed.subjectPublicKeyInfo; - if(!pubkey->header || pubkey->end <= pubkey->header) { - failf(data, "SSL: failed retrieving public key from server certificate"); - return CURLE_SSL_PINNEDPUBKEYNOTMATCH; - } - - result = Curl_pin_peer_pubkey(data, - pinnedpubkey, - (const unsigned char *)pubkey->header, - (size_t)(pubkey->end - pubkey->header)); - if(result) { - failf(data, "SSL: public key does not match pinned public key"); - return result; - } -#else - failf(data, "Library lacks pinning support built-in"); - return CURLE_NOT_BUILT_IN; -#endif - } - -#ifdef HAVE_ALPN - if(conn->bits.tls_enable_alpn) { - int rc; - char *protocol = NULL; - unsigned short protocol_len = 0; - - rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len); - - if(rc == SSL_SUCCESS) { - infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, protocol_len, protocol); - - if(protocol_len == ALPN_HTTP_1_1_LENGTH && - !memcmp(protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH)) - conn->alpn = CURL_HTTP_VERSION_1_1; -#ifdef USE_HTTP2 - else if(data->state.httpwant >= CURL_HTTP_VERSION_2 && - protocol_len == ALPN_H2_LENGTH && - !memcmp(protocol, ALPN_H2, ALPN_H2_LENGTH)) - conn->alpn = CURL_HTTP_VERSION_2; -#endif - else - infof(data, "ALPN, unrecognized protocol %.*s", protocol_len, - protocol); - Curl_multiuse_state(data, conn->alpn == CURL_HTTP_VERSION_2 ? - BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE); - } - else if(rc == SSL_ALPN_NOT_FOUND) - infof(data, VTLS_INFOF_NO_ALPN); - else { - failf(data, "ALPN, failure getting protocol, error %d", rc); - return CURLE_SSL_CONNECT_ERROR; - } - } -#endif /* HAVE_ALPN */ - - connssl->connecting_state = ssl_connect_3; -#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010) - infof(data, "SSL connection using %s / %s", - wolfSSL_get_version(backend->handle), - wolfSSL_get_cipher_name(backend->handle)); -#else - infof(data, "SSL connected"); -#endif - - return CURLE_OK; -} - - -static CURLcode -wolfssl_connect_step3(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - CURLcode result = CURLE_OK; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - - DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); - DEBUGASSERT(backend); - - if(SSL_SET_OPTION(primary.sessionid)) { - bool incache; - bool added = FALSE; - void *old_ssl_sessionid = NULL; - /* SSL_get1_session allocates memory that has to be freed. */ - SSL_SESSION *our_ssl_sessionid = SSL_get1_session(backend->handle); - bool isproxy = SSL_IS_PROXY() ? TRUE : FALSE; - - if(our_ssl_sessionid) { - Curl_ssl_sessionid_lock(data); - incache = !(Curl_ssl_getsessionid(data, conn, isproxy, - &old_ssl_sessionid, NULL, sockindex)); - if(incache) { - if(old_ssl_sessionid != our_ssl_sessionid) { - infof(data, "old SSL session ID is stale, removing"); - Curl_ssl_delsessionid(data, old_ssl_sessionid); - incache = FALSE; - } - } - - if(!incache) { - result = Curl_ssl_addsessionid(data, conn, isproxy, our_ssl_sessionid, - 0, sockindex, NULL); - if(result) { - Curl_ssl_sessionid_unlock(data); - SSL_SESSION_free(our_ssl_sessionid); - failf(data, "failed to store ssl session"); - return result; - } - else { - added = TRUE; - } - } - Curl_ssl_sessionid_unlock(data); - - if(!added) { - /* If the session info wasn't added to the cache, free our copy. */ - SSL_SESSION_free(our_ssl_sessionid); - } - } - } - - connssl->connecting_state = ssl_connect_done; - - return result; -} - - -static ssize_t wolfssl_send(struct Curl_easy *data, - int sockindex, - const void *mem, - size_t len, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; - int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len; - int rc; - - DEBUGASSERT(backend); - - ERR_clear_error(); - - rc = SSL_write(backend->handle, mem, memlen); - - if(rc <= 0) { - int err = SSL_get_error(backend->handle, rc); - - switch(err) { - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_write() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - failf(data, "SSL write: %s, errno %d", - ERR_error_string(err, error_buffer), - SOCKERRNO); - *curlcode = CURLE_SEND_ERROR; - return -1; - } - } - return rc; -} - -static void wolfssl_close(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - - (void) data; - - DEBUGASSERT(backend); - - if(backend->handle) { - char buf[32]; - /* Maybe the server has already sent a close notify alert. - Read it to avoid an RST on the TCP connection. */ - (void)SSL_read(backend->handle, buf, (int)sizeof(buf)); - (void)SSL_shutdown(backend->handle); - SSL_free(backend->handle); - backend->handle = NULL; - } - if(backend->ctx) { - SSL_CTX_free(backend->ctx); - backend->ctx = NULL; - } -} - -static ssize_t wolfssl_recv(struct Curl_easy *data, - int num, - char *buf, - size_t buffersize, - CURLcode *curlcode) -{ - struct connectdata *conn = data->conn; - struct ssl_connect_data *connssl = &conn->ssl[num]; - struct ssl_backend_data *backend = connssl->backend; - char error_buffer[WOLFSSL_MAX_ERROR_SZ]; - int buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize; - int nread; - - DEBUGASSERT(backend); - - ERR_clear_error(); - - nread = SSL_read(backend->handle, buf, buffsize); - - if(nread <= 0) { - int err = SSL_get_error(backend->handle, nread); - - switch(err) { - case SSL_ERROR_ZERO_RETURN: /* no more data */ - break; - case SSL_ERROR_NONE: - /* FALLTHROUGH */ - case SSL_ERROR_WANT_READ: - /* FALLTHROUGH */ - case SSL_ERROR_WANT_WRITE: - /* there's data pending, re-invoke SSL_read() */ - *curlcode = CURLE_AGAIN; - return -1; - default: - failf(data, "SSL read: %s, errno %d", - ERR_error_string(err, error_buffer), SOCKERRNO); - *curlcode = CURLE_RECV_ERROR; - return -1; - } - } - return nread; -} - - -static void wolfssl_session_free(void *ptr) -{ - SSL_SESSION_free(ptr); -} - - -static size_t wolfssl_version(char *buffer, size_t size) -{ -#if LIBWOLFSSL_VERSION_HEX >= 0x03006000 - return msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version()); -#elif defined(WOLFSSL_VERSION) - return msnprintf(buffer, size, "wolfSSL/%s", WOLFSSL_VERSION); -#endif -} - - -static int wolfssl_init(void) -{ -#ifdef OPENSSL_EXTRA - Curl_tls_keylog_open(); -#endif - return (wolfSSL_Init() == SSL_SUCCESS); -} - - -static void wolfssl_cleanup(void) -{ - wolfSSL_Cleanup(); -#ifdef OPENSSL_EXTRA - Curl_tls_keylog_close(); -#endif -} - - -static bool wolfssl_data_pending(const struct connectdata *conn, - int connindex) -{ - const struct ssl_connect_data *connssl = &conn->ssl[connindex]; - struct ssl_backend_data *backend = connssl->backend; - DEBUGASSERT(backend); - if(backend->handle) /* SSL is in use */ - return (0 != SSL_pending(backend->handle)) ? TRUE : FALSE; - else - return FALSE; -} - - -/* - * This function is called to shut down the SSL layer but keep the - * socket open (CCC - Clear Command Channel) - */ -static int wolfssl_shutdown(struct Curl_easy *data, struct connectdata *conn, - int sockindex) -{ - int retval = 0; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct ssl_backend_data *backend = connssl->backend; - - (void) data; - - DEBUGASSERT(backend); - - if(backend->handle) { - ERR_clear_error(); - SSL_free(backend->handle); - backend->handle = NULL; - } - return retval; -} - - -static CURLcode -wolfssl_connect_common(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, - bool nonblocking, - bool *done) -{ - CURLcode result; - struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - curl_socket_t sockfd = conn->sock[sockindex]; - int what; - - /* check if the connection has already been established */ - if(ssl_connection_complete == connssl->state) { - *done = TRUE; - return CURLE_OK; - } - - if(ssl_connect_1 == connssl->connecting_state) { - /* Find out how much more time we're allowed */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - result = wolfssl_connect_step1(data, conn, sockindex); - if(result) - return result; - } - - while(ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state) { - - /* check allowed time left */ - const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE); - - if(timeout_ms < 0) { - /* no need to continue if time already is up */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - - /* if ssl is expecting something, check if it's available. */ - if(connssl->connecting_state == ssl_connect_2_reading - || connssl->connecting_state == ssl_connect_2_writing) { - - curl_socket_t writefd = ssl_connect_2_writing == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - curl_socket_t readfd = ssl_connect_2_reading == - connssl->connecting_state?sockfd:CURL_SOCKET_BAD; - - what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd, - nonblocking?0:timeout_ms); - if(what < 0) { - /* fatal error */ - failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); - return CURLE_SSL_CONNECT_ERROR; - } - else if(0 == what) { - if(nonblocking) { - *done = FALSE; - return CURLE_OK; - } - else { - /* timeout */ - failf(data, "SSL connection timeout"); - return CURLE_OPERATION_TIMEDOUT; - } - } - /* socket is readable or writable */ - } - - /* Run transaction, and return to the caller if it failed or if - * this connection is part of a multi handle and this loop would - * execute again. This permits the owner of a multi handle to - * abort a connection attempt before step2 has completed while - * ensuring that a client using select() or epoll() will always - * have a valid fdset to wait on. - */ - result = wolfssl_connect_step2(data, conn, sockindex); - if(result || (nonblocking && - (ssl_connect_2 == connssl->connecting_state || - ssl_connect_2_reading == connssl->connecting_state || - ssl_connect_2_writing == connssl->connecting_state))) - return result; - } /* repeat step2 until all transactions are done. */ - - if(ssl_connect_3 == connssl->connecting_state) { - result = wolfssl_connect_step3(data, conn, sockindex); - if(result) - return result; - } - - if(ssl_connect_done == connssl->connecting_state) { - connssl->state = ssl_connection_complete; - conn->recv[sockindex] = wolfssl_recv; - conn->send[sockindex] = wolfssl_send; - *done = TRUE; - } - else - *done = FALSE; - - /* Reset our connect state machine */ - connssl->connecting_state = ssl_connect_1; - - return CURLE_OK; -} - - -static CURLcode wolfssl_connect_nonblocking(struct Curl_easy *data, - struct connectdata *conn, - int sockindex, bool *done) -{ - return wolfssl_connect_common(data, conn, sockindex, TRUE, done); -} - - -static CURLcode wolfssl_connect(struct Curl_easy *data, - struct connectdata *conn, int sockindex) -{ - CURLcode result; - bool done = FALSE; - - result = wolfssl_connect_common(data, conn, sockindex, FALSE, &done); - if(result) - return result; - - DEBUGASSERT(done); - - return CURLE_OK; -} - -static CURLcode wolfssl_random(struct Curl_easy *data, - unsigned char *entropy, size_t length) -{ - WC_RNG rng; - (void)data; - if(wc_InitRng(&rng)) - return CURLE_FAILED_INIT; - if(length > UINT_MAX) - return CURLE_FAILED_INIT; - if(wc_RNG_GenerateBlock(&rng, entropy, (unsigned)length)) - return CURLE_FAILED_INIT; - if(wc_FreeRng(&rng)) - return CURLE_FAILED_INIT; - return CURLE_OK; -} - -static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */ - size_t tmplen, - unsigned char *sha256sum /* output */, - size_t unused) -{ - wc_Sha256 SHA256pw; - (void)unused; - wc_InitSha256(&SHA256pw); - wc_Sha256Update(&SHA256pw, tmp, (word32)tmplen); - wc_Sha256Final(&SHA256pw, sha256sum); - return CURLE_OK; -} - -static void *wolfssl_get_internals(struct ssl_connect_data *connssl, - CURLINFO info UNUSED_PARAM) -{ - struct ssl_backend_data *backend = connssl->backend; - (void)info; - DEBUGASSERT(backend); - return backend->handle; -} - -const struct Curl_ssl Curl_ssl_wolfssl = { - { CURLSSLBACKEND_WOLFSSL, "WolfSSL" }, /* info */ - -#ifdef KEEP_PEER_CERT - SSLSUPP_PINNEDPUBKEY | -#endif - SSLSUPP_SSL_CTX, - - sizeof(struct ssl_backend_data), - - wolfssl_init, /* init */ - wolfssl_cleanup, /* cleanup */ - wolfssl_version, /* version */ - Curl_none_check_cxn, /* check_cxn */ - wolfssl_shutdown, /* shutdown */ - wolfssl_data_pending, /* data_pending */ - wolfssl_random, /* random */ - Curl_none_cert_status_request, /* cert_status_request */ - wolfssl_connect, /* connect */ - wolfssl_connect_nonblocking, /* connect_nonblocking */ - Curl_ssl_getsock, /* getsock */ - wolfssl_get_internals, /* get_internals */ - wolfssl_close, /* close_one */ - Curl_none_close_all, /* close_all */ - wolfssl_session_free, /* session_free */ - Curl_none_set_engine, /* set_engine */ - Curl_none_set_engine_default, /* set_engine_default */ - Curl_none_engines_list, /* engines_list */ - Curl_none_false_start, /* false_start */ - wolfssl_sha256sum, /* sha256sum */ - NULL, /* associate_connection */ - NULL /* disassociate_connection */ -}; - -#endif +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+/*
+ * Source file for all wolfSSL specific code for the TLS/SSL layer. No code
+ * but vtls.c should ever call or use these functions.
+ *
+ */
+
+#include "curl_setup.h"
+
+#ifdef USE_WOLFSSL
+
+#define WOLFSSL_OPTIONS_IGNORE_SYS
+#include <wolfssl/version.h>
+#include <wolfssl/options.h>
+
+/* To determine what functions are available we rely on one or both of:
+ - the user's options.h generated by wolfSSL
+ - the symbols detected by curl's configure
+ Since they are markedly different from one another, and one or the other may
+ not be available, we do some checking below to bring things in sync. */
+
+/* HAVE_ALPN is wolfSSL's build time symbol for enabling ALPN in options.h. */
+#ifndef HAVE_ALPN
+#ifdef HAVE_WOLFSSL_USEALPN
+#define HAVE_ALPN
+#endif
+#endif
+
+#include <limits.h>
+
+#include "urldata.h"
+#include "sendf.h"
+#include "inet_pton.h"
+#include "vtls.h"
+#include "vtls_int.h"
+#include "keylog.h"
+#include "parsedate.h"
+#include "connect.h" /* for the connect timeout */
+#include "select.h"
+#include "strcase.h"
+#include "x509asn1.h"
+#include "curl_printf.h"
+#include "multiif.h"
+
+#include <wolfssl/openssl/ssl.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/error-ssl.h>
+#include "wolfssl.h"
+
+/* The last #include files should be: */
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* KEEP_PEER_CERT is a product of the presence of build time symbol
+ OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is
+ in wolfSSL's settings.h, and the latter two are build time symbols in
+ options.h. */
+#ifndef KEEP_PEER_CERT
+#if defined(HAVE_WOLFSSL_GET_PEER_CERTIFICATE) || \
+ (defined(OPENSSL_EXTRA) && !defined(NO_CERTS))
+#define KEEP_PEER_CERT
+#endif
+#endif
+
+#if defined(HAVE_WOLFSSL_FULL_BIO) && HAVE_WOLFSSL_FULL_BIO
+#define USE_BIO_CHAIN
+#else
+#undef USE_BIO_CHAIN
+#endif
+
+struct ssl_backend_data {
+ SSL_CTX* ctx;
+ SSL* handle;
+};
+
+#ifdef OPENSSL_EXTRA
+/*
+ * Availability note:
+ * The TLS 1.3 secret callback (wolfSSL_set_tls13_secret_cb) was added in
+ * WolfSSL 4.4.0, but requires the -DHAVE_SECRET_CALLBACK build option. If that
+ * option is not set, then TLS 1.3 will not be logged.
+ * For TLS 1.2 and before, we use wolfSSL_get_keys().
+ * SSL_get_client_random and wolfSSL_get_keys require OPENSSL_EXTRA
+ * (--enable-opensslextra or --enable-all).
+ */
+#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13)
+static int
+wolfssl_tls13_secret_callback(SSL *ssl, int id, const unsigned char *secret,
+ int secretSz, void *ctx)
+{
+ const char *label;
+ unsigned char client_random[SSL3_RANDOM_SIZE];
+ (void)ctx;
+
+ if(!ssl || !Curl_tls_keylog_enabled()) {
+ return 0;
+ }
+
+ switch(id) {
+ case CLIENT_EARLY_TRAFFIC_SECRET:
+ label = "CLIENT_EARLY_TRAFFIC_SECRET";
+ break;
+ case CLIENT_HANDSHAKE_TRAFFIC_SECRET:
+ label = "CLIENT_HANDSHAKE_TRAFFIC_SECRET";
+ break;
+ case SERVER_HANDSHAKE_TRAFFIC_SECRET:
+ label = "SERVER_HANDSHAKE_TRAFFIC_SECRET";
+ break;
+ case CLIENT_TRAFFIC_SECRET:
+ label = "CLIENT_TRAFFIC_SECRET_0";
+ break;
+ case SERVER_TRAFFIC_SECRET:
+ label = "SERVER_TRAFFIC_SECRET_0";
+ break;
+ case EARLY_EXPORTER_SECRET:
+ label = "EARLY_EXPORTER_SECRET";
+ break;
+ case EXPORTER_SECRET:
+ label = "EXPORTER_SECRET";
+ break;
+ default:
+ return 0;
+ }
+
+ if(SSL_get_client_random(ssl, client_random, SSL3_RANDOM_SIZE) == 0) {
+ /* Should never happen as wolfSSL_KeepArrays() was called before. */
+ return 0;
+ }
+
+ Curl_tls_keylog_write(label, client_random, secret, secretSz);
+ return 0;
+}
+#endif /* defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) */
+
+static void
+wolfssl_log_tls12_secret(SSL *ssl)
+{
+ unsigned char *ms, *sr, *cr;
+ unsigned int msLen, srLen, crLen, i, x = 0;
+
+#if LIBWOLFSSL_VERSION_HEX >= 0x0300d000 /* >= 3.13.0 */
+ /* wolfSSL_GetVersion is available since 3.13, we use it instead of
+ * SSL_version since the latter relies on OPENSSL_ALL (--enable-opensslall or
+ * --enable-all). Failing to perform this check could result in an unusable
+ * key log line when TLS 1.3 is actually negotiated. */
+ switch(wolfSSL_GetVersion(ssl)) {
+ case WOLFSSL_SSLV3:
+ case WOLFSSL_TLSV1:
+ case WOLFSSL_TLSV1_1:
+ case WOLFSSL_TLSV1_2:
+ break;
+ default:
+ /* TLS 1.3 does not use this mechanism, the "master secret" returned below
+ * is not directly usable. */
+ return;
+ }
+#endif
+
+ if(SSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != SSL_SUCCESS) {
+ return;
+ }
+
+ /* Check for a missing master secret and skip logging. That can happen if
+ * curl rejects the server certificate and aborts the handshake.
+ */
+ for(i = 0; i < msLen; i++) {
+ x |= ms[i];
+ }
+ if(x == 0) {
+ return;
+ }
+
+ Curl_tls_keylog_write("CLIENT_RANDOM", cr, ms, msLen);
+}
+#endif /* OPENSSL_EXTRA */
+
+static int do_file_type(const char *type)
+{
+ if(!type || !type[0])
+ return SSL_FILETYPE_PEM;
+ if(strcasecompare(type, "PEM"))
+ return SSL_FILETYPE_PEM;
+ if(strcasecompare(type, "DER"))
+ return SSL_FILETYPE_ASN1;
+ return -1;
+}
+
+#ifdef HAVE_LIBOQS
+struct group_name_map {
+ const word16 group;
+ const char *name;
+};
+
+static const struct group_name_map gnm[] = {
+ { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" },
+ { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" },
+ { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" },
+ { WOLFSSL_NTRU_HPS_LEVEL1, "NTRU_HPS_LEVEL1" },
+ { WOLFSSL_NTRU_HPS_LEVEL3, "NTRU_HPS_LEVEL3" },
+ { WOLFSSL_NTRU_HPS_LEVEL5, "NTRU_HPS_LEVEL5" },
+ { WOLFSSL_NTRU_HRSS_LEVEL3, "NTRU_HRSS_LEVEL3" },
+ { WOLFSSL_SABER_LEVEL1, "SABER_LEVEL1" },
+ { WOLFSSL_SABER_LEVEL3, "SABER_LEVEL3" },
+ { WOLFSSL_SABER_LEVEL5, "SABER_LEVEL5" },
+ { WOLFSSL_KYBER_90S_LEVEL1, "KYBER_90S_LEVEL1" },
+ { WOLFSSL_KYBER_90S_LEVEL3, "KYBER_90S_LEVEL3" },
+ { WOLFSSL_KYBER_90S_LEVEL5, "KYBER_90S_LEVEL5" },
+ { WOLFSSL_P256_NTRU_HPS_LEVEL1, "P256_NTRU_HPS_LEVEL1" },
+ { WOLFSSL_P384_NTRU_HPS_LEVEL3, "P384_NTRU_HPS_LEVEL3" },
+ { WOLFSSL_P521_NTRU_HPS_LEVEL5, "P521_NTRU_HPS_LEVEL5" },
+ { WOLFSSL_P384_NTRU_HRSS_LEVEL3, "P384_NTRU_HRSS_LEVEL3" },
+ { WOLFSSL_P256_SABER_LEVEL1, "P256_SABER_LEVEL1" },
+ { WOLFSSL_P384_SABER_LEVEL3, "P384_SABER_LEVEL3" },
+ { WOLFSSL_P521_SABER_LEVEL5, "P521_SABER_LEVEL5" },
+ { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" },
+ { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" },
+ { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" },
+ { WOLFSSL_P256_KYBER_90S_LEVEL1, "P256_KYBER_90S_LEVEL1" },
+ { WOLFSSL_P384_KYBER_90S_LEVEL3, "P384_KYBER_90S_LEVEL3" },
+ { WOLFSSL_P521_KYBER_90S_LEVEL5, "P521_KYBER_90S_LEVEL5" },
+ { 0, NULL }
+};
+#endif
+
+#ifdef USE_BIO_CHAIN
+
+static int bio_cf_create(WOLFSSL_BIO *bio)
+{
+ wolfSSL_BIO_set_shutdown(bio, 1);
+ wolfSSL_BIO_set_init(bio, 1);
+ wolfSSL_BIO_set_data(bio, NULL);
+ return 1;
+}
+
+static int bio_cf_destroy(WOLFSSL_BIO *bio)
+{
+ if(!bio)
+ return 0;
+ return 1;
+}
+
+static long bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr)
+{
+ struct Curl_cfilter *cf = BIO_get_data(bio);
+ long ret = 1;
+
+ (void)cf;
+ (void)ptr;
+ switch(cmd) {
+ case BIO_CTRL_GET_CLOSE:
+ ret = (long)wolfSSL_BIO_get_shutdown(bio);
+ break;
+ case BIO_CTRL_SET_CLOSE:
+ wolfSSL_BIO_set_shutdown(bio, (int)num);
+ break;
+ case BIO_CTRL_FLUSH:
+ /* we do no delayed writes, but if we ever would, this
+ * needs to trigger it. */
+ ret = 1;
+ break;
+ case BIO_CTRL_DUP:
+ ret = 1;
+ break;
+#ifdef BIO_CTRL_EOF
+ case BIO_CTRL_EOF:
+ /* EOF has been reached on input? */
+ return (!cf->next || !cf->next->connected);
+#endif
+ default:
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static int bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen)
+{
+ struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nwritten;
+ CURLcode result = CURLE_OK;
+
+ DEBUGASSERT(data);
+ nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
+ wolfSSL_BIO_clear_retry_flags(bio);
+ if(nwritten < 0 && CURLE_AGAIN == result)
+ BIO_set_retry_read(bio);
+ return (int)nwritten;
+}
+
+static int bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen)
+{
+ struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct Curl_easy *data = connssl->call_data;
+ ssize_t nread;
+ CURLcode result = CURLE_OK;
+
+ DEBUGASSERT(data);
+ /* OpenSSL catches this case, so should we. */
+ if(!buf)
+ return 0;
+
+ nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
+ wolfSSL_BIO_clear_retry_flags(bio);
+ if(nread < 0 && CURLE_AGAIN == result)
+ BIO_set_retry_read(bio);
+ return (int)nread;
+}
+
+static WOLFSSL_BIO_METHOD *bio_cf_method = NULL;
+
+static void bio_cf_init_methods(void)
+{
+ bio_cf_method = wolfSSL_BIO_meth_new(BIO_TYPE_MEM, "wolfSSL CF BIO");
+ wolfSSL_BIO_meth_set_write(bio_cf_method, &bio_cf_out_write);
+ wolfSSL_BIO_meth_set_read(bio_cf_method, &bio_cf_in_read);
+ wolfSSL_BIO_meth_set_ctrl(bio_cf_method, &bio_cf_ctrl);
+ wolfSSL_BIO_meth_set_create(bio_cf_method, &bio_cf_create);
+ wolfSSL_BIO_meth_set_destroy(bio_cf_method, &bio_cf_destroy);
+}
+
+static void bio_cf_free_methods(void)
+{
+ wolfSSL_BIO_meth_free(bio_cf_method);
+}
+
+#else /* USE_BIO_CHAIN */
+
+#define bio_cf_init_methods() Curl_nop_stmt
+#define bio_cf_free_methods() Curl_nop_stmt
+
+#endif /* !USE_BIO_CHAIN */
+
+/*
+ * This function loads all the client/CA certificates and CRLs. Setup the TLS
+ * layer and do all necessary magic.
+ */
+static CURLcode
+wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ char *ciphers, *curves;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+ SSL_METHOD* req_method = NULL;
+#ifdef HAVE_LIBOQS
+ word16 oqsAlg = 0;
+ size_t idx = 0;
+#endif
+#ifdef HAVE_SNI
+ bool sni = FALSE;
+#define use_sni(x) sni = (x)
+#else
+#define use_sni(x) Curl_nop_stmt
+#endif
+
+ DEBUGASSERT(backend);
+
+ if(connssl->state == ssl_connection_complete)
+ return CURLE_OK;
+
+ if(conn_config->version_max != CURL_SSLVERSION_MAX_NONE) {
+ failf(data, "wolfSSL does not support to set maximum SSL/TLS version");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ /* check to see if we've been told to use an explicit SSL/TLS version */
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+#if LIBWOLFSSL_VERSION_HEX >= 0x03003000 /* >= 3.3.0 */
+ /* minimum protocol version is set later after the CTX object is created */
+ req_method = SSLv23_client_method();
+#else
+ infof(data, "wolfSSL <3.3.0 cannot be configured to use TLS 1.0-1.2, "
+ "TLS 1.0 is used exclusively");
+ req_method = TLSv1_client_method();
+#endif
+ use_sni(TRUE);
+ break;
+ case CURL_SSLVERSION_TLSv1_0:
+#if defined(WOLFSSL_ALLOW_TLSV10) && !defined(NO_OLD_TLS)
+ req_method = TLSv1_client_method();
+ use_sni(TRUE);
+#else
+ failf(data, "wolfSSL does not support TLS 1.0");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ break;
+ case CURL_SSLVERSION_TLSv1_1:
+#ifndef NO_OLD_TLS
+ req_method = TLSv1_1_client_method();
+ use_sni(TRUE);
+#else
+ failf(data, "wolfSSL does not support TLS 1.1");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ break;
+ case CURL_SSLVERSION_TLSv1_2:
+ req_method = TLSv1_2_client_method();
+ use_sni(TRUE);
+ break;
+ case CURL_SSLVERSION_TLSv1_3:
+#ifdef WOLFSSL_TLS13
+ req_method = wolfTLSv1_3_client_method();
+ use_sni(TRUE);
+ break;
+#else
+ failf(data, "wolfSSL: TLS 1.3 is not yet supported");
+ return CURLE_SSL_CONNECT_ERROR;
+#endif
+ default:
+ failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ if(!req_method) {
+ failf(data, "SSL: couldn't create a method");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ if(backend->ctx)
+ SSL_CTX_free(backend->ctx);
+ backend->ctx = SSL_CTX_new(req_method);
+
+ if(!backend->ctx) {
+ failf(data, "SSL: couldn't create a context");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+ switch(conn_config->version) {
+ case CURL_SSLVERSION_DEFAULT:
+ case CURL_SSLVERSION_TLSv1:
+#if LIBWOLFSSL_VERSION_HEX > 0x03004006 /* > 3.4.6 */
+ /* Versions 3.3.0 to 3.4.6 we know the minimum protocol version is
+ * whatever minimum version of TLS was built in and at least TLS 1.0. For
+ * later library versions that could change (eg TLS 1.0 built in but
+ * defaults to TLS 1.1) so we have this short circuit evaluation to find
+ * the minimum supported TLS version.
+ */
+ if((wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1) != 1) &&
+ (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_1) != 1) &&
+ (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_2) != 1)
+#ifdef WOLFSSL_TLS13
+ && (wolfSSL_CTX_SetMinVersion(backend->ctx, WOLFSSL_TLSV1_3) != 1)
+#endif
+ ) {
+ failf(data, "SSL: couldn't set the minimum protocol version");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif
+ break;
+ }
+
+ ciphers = conn_config->cipher_list;
+ if(ciphers) {
+ if(!SSL_CTX_set_cipher_list(backend->ctx, ciphers)) {
+ failf(data, "failed setting cipher list: %s", ciphers);
+ return CURLE_SSL_CIPHER;
+ }
+ infof(data, "Cipher selection: %s", ciphers);
+ }
+
+ curves = conn_config->curves;
+ if(curves) {
+
+#ifdef HAVE_LIBOQS
+ for(idx = 0; gnm[idx].name != NULL; idx++) {
+ if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) {
+ oqsAlg = gnm[idx].group;
+ break;
+ }
+ }
+
+ if(oqsAlg == 0)
+#endif
+ {
+ if(!SSL_CTX_set1_curves_list(backend->ctx, curves)) {
+ failf(data, "failed setting curves list: '%s'", curves);
+ return CURLE_SSL_CIPHER;
+ }
+ }
+ }
+#ifndef NO_FILESYSTEM
+ /* load trusted cacert */
+ if(conn_config->CAfile) {
+ if(1 != SSL_CTX_load_verify_locations(backend->ctx,
+ conn_config->CAfile,
+ conn_config->CApath)) {
+ if(conn_config->verifypeer) {
+ /* Fail if we insist on successfully verifying the server. */
+ failf(data, "error setting certificate verify locations:"
+ " CAfile: %s CApath: %s",
+ conn_config->CAfile?
+ conn_config->CAfile: "none",
+ conn_config->CApath?
+ conn_config->CApath : "none");
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ else {
+ /* Just continue with a warning if no strict certificate
+ verification is required. */
+ infof(data, "error setting certificate verify locations,"
+ " continuing anyway:");
+ }
+ }
+ else {
+ /* Everything is fine. */
+ infof(data, "successfully set certificate verify locations:");
+ }
+ infof(data, " CAfile: %s",
+ conn_config->CAfile ? conn_config->CAfile : "none");
+ infof(data, " CApath: %s",
+ conn_config->CApath ? conn_config->CApath : "none");
+ }
+
+ /* Load the client certificate, and private key */
+ if(ssl_config->primary.clientcert && ssl_config->key) {
+ int file_type = do_file_type(ssl_config->cert_type);
+
+ if(SSL_CTX_use_certificate_file(backend->ctx,
+ ssl_config->primary.clientcert,
+ file_type) != 1) {
+ failf(data, "unable to use client certificate (no key or wrong pass"
+ " phrase?)");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ file_type = do_file_type(ssl_config->key_type);
+ if(SSL_CTX_use_PrivateKey_file(backend->ctx, ssl_config->key,
+ file_type) != 1) {
+ failf(data, "unable to set private key");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+#endif /* !NO_FILESYSTEM */
+
+ /* SSL always tries to verify the peer, this only says whether it should
+ * fail to connect if the verification fails, or if it should continue
+ * anyway. In the latter case the result of the verification is checked with
+ * SSL_get_verify_result() below. */
+ SSL_CTX_set_verify(backend->ctx,
+ conn_config->verifypeer?SSL_VERIFY_PEER:
+ SSL_VERIFY_NONE,
+ NULL);
+
+#ifdef HAVE_SNI
+ if(sni) {
+ struct in_addr addr4;
+#ifdef ENABLE_IPV6
+ struct in6_addr addr6;
+#endif
+ size_t hostname_len = strlen(connssl->hostname);
+
+ if((hostname_len < USHRT_MAX) &&
+ !Curl_inet_pton(AF_INET, connssl->hostname, &addr4)
+#ifdef ENABLE_IPV6
+ && !Curl_inet_pton(AF_INET6, connssl->hostname, &addr6)
+#endif
+ ) {
+ size_t snilen;
+ char *snihost = Curl_ssl_snihost(data, connssl->hostname, &snilen);
+ if(!snihost ||
+ wolfSSL_CTX_UseSNI(backend->ctx, WOLFSSL_SNI_HOST_NAME, snihost,
+ (unsigned short)snilen) != 1) {
+ failf(data, "Failed to set SNI");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+ }
+#endif
+
+ /* give application a chance to interfere with SSL set up. */
+ if(data->set.ssl.fsslctx) {
+ CURLcode result = (*data->set.ssl.fsslctx)(data, backend->ctx,
+ data->set.ssl.fsslctxp);
+ if(result) {
+ failf(data, "error signaled by ssl ctx callback");
+ return result;
+ }
+ }
+#ifdef NO_FILESYSTEM
+ else if(conn_config->verifypeer) {
+ failf(data, "SSL: Certificates can't be loaded because wolfSSL was built"
+ " with \"no filesystem\". Either disable peer verification"
+ " (insecure) or if you are building an application with libcurl you"
+ " can load certificates via CURLOPT_SSL_CTX_FUNCTION.");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif
+
+ /* Let's make an SSL structure */
+ if(backend->handle)
+ SSL_free(backend->handle);
+ backend->handle = SSL_new(backend->ctx);
+ if(!backend->handle) {
+ failf(data, "SSL: couldn't create a handle");
+ return CURLE_OUT_OF_MEMORY;
+ }
+
+#ifdef HAVE_LIBOQS
+ if(oqsAlg) {
+ if(wolfSSL_UseKeyShare(backend->handle, oqsAlg) != WOLFSSL_SUCCESS) {
+ failf(data, "unable to use oqs KEM");
+ }
+ }
+#endif
+
+#ifdef HAVE_ALPN
+ if(cf->conn->bits.tls_enable_alpn) {
+ char protocols[128];
+ *protocols = '\0';
+
+ /* wolfSSL's ALPN protocol name list format is a comma separated string of
+ protocols in descending order of preference, eg: "h2,http/1.1" */
+
+#ifdef USE_HTTP2
+ if(data->state.httpwant >= CURL_HTTP_VERSION_2) {
+ strcpy(protocols + strlen(protocols), ALPN_H2 ",");
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+ }
+#endif
+
+ strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1);
+ infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+
+ if(wolfSSL_UseALPN(backend->handle, protocols,
+ (unsigned)strlen(protocols),
+ WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) {
+ failf(data, "SSL: failed setting ALPN protocols");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+#endif /* HAVE_ALPN */
+
+#ifdef OPENSSL_EXTRA
+ if(Curl_tls_keylog_enabled()) {
+ /* Ensure the Client Random is preserved. */
+ wolfSSL_KeepArrays(backend->handle);
+#if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13)
+ wolfSSL_set_tls13_secret_cb(backend->handle,
+ wolfssl_tls13_secret_callback, NULL);
+#endif
+ }
+#endif /* OPENSSL_EXTRA */
+
+#ifdef HAVE_SECURE_RENEGOTIATION
+ if(wolfSSL_UseSecureRenegotiation(backend->handle) != SSL_SUCCESS) {
+ failf(data, "SSL: failed setting secure renegotiation");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif /* HAVE_SECURE_RENEGOTIATION */
+
+ /* Check if there's a cached ID we can/should use here! */
+ if(ssl_config->primary.sessionid) {
+ void *ssl_sessionid = NULL;
+
+ Curl_ssl_sessionid_lock(data);
+ if(!Curl_ssl_getsessionid(cf, data, &ssl_sessionid, NULL)) {
+ /* we got a session id, use it! */
+ if(!SSL_set_session(backend->handle, ssl_sessionid)) {
+ Curl_ssl_delsessionid(data, ssl_sessionid);
+ infof(data, "Can't use session ID, going on without");
+ }
+ else
+ infof(data, "SSL re-using session ID");
+ }
+ Curl_ssl_sessionid_unlock(data);
+ }
+
+#ifdef USE_BIO_CHAIN
+ {
+ WOLFSSL_BIO *bio;
+
+ bio = BIO_new(bio_cf_method);
+ if(!bio)
+ return CURLE_OUT_OF_MEMORY;
+
+ wolfSSL_BIO_set_data(bio, cf);
+ wolfSSL_set_bio(backend->handle, bio, bio);
+ }
+#else /* USE_BIO_CHAIN */
+ /* pass the raw socket into the SSL layer */
+ if(!SSL_set_fd(backend->handle, (int)cf->conn->sock[cf->sockindex])) {
+ failf(data, "SSL: SSL_set_fd failed");
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+#endif /* !USE_BIO_CHAIN */
+
+ connssl->connecting_state = ssl_connect_2;
+ return CURLE_OK;
+}
+
+
+static CURLcode
+wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ int ret = -1;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf)?
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY]:
+ data->set.str[STRING_SSL_PINNEDPUBLICKEY];
+
+ DEBUGASSERT(backend);
+
+ ERR_clear_error();
+
+ /* Enable RFC2818 checks */
+ if(conn_config->verifyhost) {
+ char *snihost = Curl_ssl_snihost(data, connssl->hostname, NULL);
+ if(!snihost ||
+ (wolfSSL_check_domain_name(backend->handle, snihost) == SSL_FAILURE))
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+
+ ret = SSL_connect(backend->handle);
+
+#ifdef OPENSSL_EXTRA
+ if(Curl_tls_keylog_enabled()) {
+ /* If key logging is enabled, wait for the handshake to complete and then
+ * proceed with logging secrets (for TLS 1.2 or older).
+ *
+ * During the handshake (ret==-1), wolfSSL_want_read() is true as it waits
+ * for the server response. At that point the master secret is not yet
+ * available, so we must not try to read it.
+ * To log the secret on completion with a handshake failure, detect
+ * completion via the observation that there is nothing to read or write.
+ * Note that OpenSSL SSL_want_read() is always true here. If wolfSSL ever
+ * changes, the worst case is that no key is logged on error.
+ */
+ if(ret == SSL_SUCCESS ||
+ (!wolfSSL_want_read(backend->handle) &&
+ !wolfSSL_want_write(backend->handle))) {
+ wolfssl_log_tls12_secret(backend->handle);
+ /* Client Random and master secrets are no longer needed, erase these.
+ * Ignored while the handshake is still in progress. */
+ wolfSSL_FreeArrays(backend->handle);
+ }
+ }
+#endif /* OPENSSL_EXTRA */
+
+ if(ret != 1) {
+ char error_buffer[WOLFSSL_MAX_ERROR_SZ];
+ int detail = SSL_get_error(backend->handle, ret);
+
+ if(SSL_ERROR_WANT_READ == detail) {
+ connssl->connecting_state = ssl_connect_2_reading;
+ return CURLE_OK;
+ }
+ else if(SSL_ERROR_WANT_WRITE == detail) {
+ connssl->connecting_state = ssl_connect_2_writing;
+ return CURLE_OK;
+ }
+ /* There is no easy way to override only the CN matching.
+ * This will enable the override of both mismatching SubjectAltNames
+ * as also mismatching CN fields */
+ else if(DOMAIN_NAME_MISMATCH == detail) {
+#if 1
+ failf(data, " subject alt name(s) or common name do not match \"%s\"",
+ connssl->dispname);
+ return CURLE_PEER_FAILED_VERIFICATION;
+#else
+ /* When the wolfssl_check_domain_name() is used and you desire to
+ * continue on a DOMAIN_NAME_MISMATCH, i.e. 'ssl_config.verifyhost
+ * == 0', CyaSSL version 2.4.0 will fail with an INCOMPLETE_DATA
+ * error. The only way to do this is currently to switch the
+ * Wolfssl_check_domain_name() in and out based on the
+ * 'ssl_config.verifyhost' value. */
+ if(conn_config->verifyhost) {
+ failf(data,
+ " subject alt name(s) or common name do not match \"%s\"\n",
+ connssl->dispname);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ else {
+ infof(data,
+ " subject alt name(s) and/or common name do not match \"%s\"",
+ connssl->dispname);
+ return CURLE_OK;
+ }
+#endif
+ }
+#if LIBWOLFSSL_VERSION_HEX >= 0x02007000 /* 2.7.0 */
+ else if(ASN_NO_SIGNER_E == detail) {
+ if(conn_config->verifypeer) {
+ failf(data, " CA signer not available for verification");
+ return CURLE_SSL_CACERT_BADFILE;
+ }
+ else {
+ /* Just continue with a warning if no strict certificate
+ verification is required. */
+ infof(data, "CA signer not available for verification, "
+ "continuing anyway");
+ }
+ }
+#endif
+ else {
+ failf(data, "SSL_connect failed with error %d: %s", detail,
+ ERR_error_string(detail, error_buffer));
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+
+ if(pinnedpubkey) {
+#ifdef KEEP_PEER_CERT
+ X509 *x509;
+ const char *x509_der;
+ int x509_der_len;
+ struct Curl_X509certificate x509_parsed;
+ struct Curl_asn1Element *pubkey;
+ CURLcode result;
+
+ x509 = SSL_get_peer_certificate(backend->handle);
+ if(!x509) {
+ failf(data, "SSL: failed retrieving server certificate");
+ return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ }
+
+ x509_der = (const char *)wolfSSL_X509_get_der(x509, &x509_der_len);
+ if(!x509_der) {
+ failf(data, "SSL: failed retrieving ASN.1 server certificate");
+ return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ }
+
+ memset(&x509_parsed, 0, sizeof(x509_parsed));
+ if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len))
+ return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+
+ pubkey = &x509_parsed.subjectPublicKeyInfo;
+ if(!pubkey->header || pubkey->end <= pubkey->header) {
+ failf(data, "SSL: failed retrieving public key from server certificate");
+ return CURLE_SSL_PINNEDPUBKEYNOTMATCH;
+ }
+
+ result = Curl_pin_peer_pubkey(data,
+ pinnedpubkey,
+ (const unsigned char *)pubkey->header,
+ (size_t)(pubkey->end - pubkey->header));
+ if(result) {
+ failf(data, "SSL: public key does not match pinned public key");
+ return result;
+ }
+#else
+ failf(data, "Library lacks pinning support built-in");
+ return CURLE_NOT_BUILT_IN;
+#endif
+ }
+
+#ifdef HAVE_ALPN
+ if(cf->conn->bits.tls_enable_alpn) {
+ int rc;
+ char *protocol = NULL;
+ unsigned short protocol_len = 0;
+
+ rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len);
+
+ if(rc == SSL_SUCCESS) {
+ infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, protocol_len, protocol);
+
+ if(protocol_len == ALPN_HTTP_1_1_LENGTH &&
+ !memcmp(protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH))
+ cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+#ifdef USE_HTTP2
+ else if(data->state.httpwant >= CURL_HTTP_VERSION_2 &&
+ protocol_len == ALPN_H2_LENGTH &&
+ !memcmp(protocol, ALPN_H2, ALPN_H2_LENGTH))
+ cf->conn->alpn = CURL_HTTP_VERSION_2;
+#endif
+ else
+ infof(data, "ALPN, unrecognized protocol %.*s", protocol_len,
+ protocol);
+ Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
+ BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+ }
+ else if(rc == SSL_ALPN_NOT_FOUND)
+ infof(data, VTLS_INFOF_NO_ALPN);
+ else {
+ failf(data, "ALPN, failure getting protocol, error %d", rc);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ }
+#endif /* HAVE_ALPN */
+
+ connssl->connecting_state = ssl_connect_3;
+#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010)
+ infof(data, "SSL connection using %s / %s",
+ wolfSSL_get_version(backend->handle),
+ wolfSSL_get_cipher_name(backend->handle));
+#else
+ infof(data, "SSL connected");
+#endif
+
+ return CURLE_OK;
+}
+
+
+static CURLcode
+wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ CURLcode result = CURLE_OK;
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ const struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
+
+ DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
+ DEBUGASSERT(backend);
+
+ if(ssl_config->primary.sessionid) {
+ bool incache;
+ bool added = FALSE;
+ void *old_ssl_sessionid = NULL;
+ /* SSL_get1_session allocates memory that has to be freed. */
+ SSL_SESSION *our_ssl_sessionid = SSL_get1_session(backend->handle);
+
+ if(our_ssl_sessionid) {
+ Curl_ssl_sessionid_lock(data);
+ incache = !(Curl_ssl_getsessionid(cf, data, &old_ssl_sessionid, NULL));
+ if(incache) {
+ if(old_ssl_sessionid != our_ssl_sessionid) {
+ infof(data, "old SSL session ID is stale, removing");
+ Curl_ssl_delsessionid(data, old_ssl_sessionid);
+ incache = FALSE;
+ }
+ }
+
+ if(!incache) {
+ result = Curl_ssl_addsessionid(cf, data, our_ssl_sessionid, 0, NULL);
+ if(result) {
+ Curl_ssl_sessionid_unlock(data);
+ SSL_SESSION_free(our_ssl_sessionid);
+ failf(data, "failed to store ssl session");
+ return result;
+ }
+ else {
+ added = TRUE;
+ }
+ }
+ Curl_ssl_sessionid_unlock(data);
+
+ if(!added) {
+ /* If the session info wasn't added to the cache, free our copy. */
+ SSL_SESSION_free(our_ssl_sessionid);
+ }
+ }
+ }
+
+ connssl->connecting_state = ssl_connect_done;
+
+ return result;
+}
+
+
+static ssize_t wolfssl_send(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ char error_buffer[WOLFSSL_MAX_ERROR_SZ];
+ int memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
+ int rc;
+
+ DEBUGASSERT(backend);
+
+ ERR_clear_error();
+
+ rc = SSL_write(backend->handle, mem, memlen);
+
+ if(rc <= 0) {
+ int err = SSL_get_error(backend->handle, rc);
+
+ switch(err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ /* there's data pending, re-invoke SSL_write() */
+ *curlcode = CURLE_AGAIN;
+ return -1;
+ default:
+ failf(data, "SSL write: %s, errno %d",
+ ERR_error_string(err, error_buffer),
+ SOCKERRNO);
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+ }
+ return rc;
+}
+
+static void wolfssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+
+ (void) data;
+
+ DEBUGASSERT(backend);
+
+ if(backend->handle) {
+ char buf[32];
+ /* Maybe the server has already sent a close notify alert.
+ Read it to avoid an RST on the TCP connection. */
+ (void)SSL_read(backend->handle, buf, (int)sizeof(buf));
+ (void)SSL_shutdown(backend->handle);
+ SSL_free(backend->handle);
+ backend->handle = NULL;
+ }
+ if(backend->ctx) {
+ SSL_CTX_free(backend->ctx);
+ backend->ctx = NULL;
+ }
+}
+
+static ssize_t wolfssl_recv(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ char *buf,
+ size_t buffersize,
+ CURLcode *curlcode)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_backend_data *backend = connssl->backend;
+ char error_buffer[WOLFSSL_MAX_ERROR_SZ];
+ int buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
+ int nread;
+
+ DEBUGASSERT(backend);
+
+ ERR_clear_error();
+
+ nread = SSL_read(backend->handle, buf, buffsize);
+
+ if(nread <= 0) {
+ int err = SSL_get_error(backend->handle, nread);
+
+ switch(err) {
+ case SSL_ERROR_ZERO_RETURN: /* no more data */
+ break;
+ case SSL_ERROR_NONE:
+ /* FALLTHROUGH */
+ case SSL_ERROR_WANT_READ:
+ /* FALLTHROUGH */
+ case SSL_ERROR_WANT_WRITE:
+ /* there's data pending, re-invoke SSL_read() */
+ *curlcode = CURLE_AGAIN;
+ return -1;
+ default:
+ failf(data, "SSL read: %s, errno %d",
+ ERR_error_string(err, error_buffer), SOCKERRNO);
+ *curlcode = CURLE_RECV_ERROR;
+ return -1;
+ }
+ }
+ return nread;
+}
+
+
+static void wolfssl_session_free(void *ptr)
+{
+ SSL_SESSION_free(ptr);
+}
+
+
+static size_t wolfssl_version(char *buffer, size_t size)
+{
+#if LIBWOLFSSL_VERSION_HEX >= 0x03006000
+ return msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version());
+#elif defined(WOLFSSL_VERSION)
+ return msnprintf(buffer, size, "wolfSSL/%s", WOLFSSL_VERSION);
+#endif
+}
+
+
+static int wolfssl_init(void)
+{
+ int ret;
+
+#ifdef OPENSSL_EXTRA
+ Curl_tls_keylog_open();
+#endif
+ ret = (wolfSSL_Init() == SSL_SUCCESS);
+ bio_cf_init_methods();
+ return ret;
+}
+
+
+static void wolfssl_cleanup(void)
+{
+ bio_cf_free_methods();
+ wolfSSL_Cleanup();
+#ifdef OPENSSL_EXTRA
+ Curl_tls_keylog_close();
+#endif
+}
+
+
+static bool wolfssl_data_pending(struct Curl_cfilter *cf,
+ const struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx = cf->ctx;
+
+ (void)data;
+ DEBUGASSERT(ctx && ctx->backend);
+ if(ctx->backend->handle) /* SSL is in use */
+ return (0 != SSL_pending(ctx->backend->handle)) ? TRUE : FALSE;
+ else
+ return FALSE;
+}
+
+
+/*
+ * This function is called to shut down the SSL layer but keep the
+ * socket open (CCC - Clear Command Channel)
+ */
+static int wolfssl_shutdown(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ struct ssl_connect_data *ctx = cf->ctx;
+ int retval = 0;
+
+ (void)data;
+ DEBUGASSERT(ctx && ctx->backend);
+
+ if(ctx->backend->handle) {
+ ERR_clear_error();
+ SSL_free(ctx->backend->handle);
+ ctx->backend->handle = NULL;
+ }
+ return retval;
+}
+
+
+static CURLcode
+wolfssl_connect_common(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool nonblocking,
+ bool *done)
+{
+ CURLcode result;
+ struct ssl_connect_data *connssl = cf->ctx;
+ curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+ int what;
+
+ /* check if the connection has already been established */
+ if(ssl_connection_complete == connssl->state) {
+ *done = TRUE;
+ return CURLE_OK;
+ }
+
+ if(ssl_connect_1 == connssl->connecting_state) {
+ /* Find out how much more time we're allowed */
+ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ result = wolfssl_connect_step1(cf, data);
+ if(result)
+ return result;
+ }
+
+ while(ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state) {
+
+ /* check allowed time left */
+ const timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+
+ if(timeout_ms < 0) {
+ /* no need to continue if time already is up */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+
+ /* if ssl is expecting something, check if it's available. */
+ if(connssl->connecting_state == ssl_connect_2_reading
+ || connssl->connecting_state == ssl_connect_2_writing) {
+
+ curl_socket_t writefd = ssl_connect_2_writing ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+ curl_socket_t readfd = ssl_connect_2_reading ==
+ connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
+
+ what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
+ nonblocking?0:timeout_ms);
+ if(what < 0) {
+ /* fatal error */
+ failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
+ return CURLE_SSL_CONNECT_ERROR;
+ }
+ else if(0 == what) {
+ if(nonblocking) {
+ *done = FALSE;
+ return CURLE_OK;
+ }
+ else {
+ /* timeout */
+ failf(data, "SSL connection timeout");
+ return CURLE_OPERATION_TIMEDOUT;
+ }
+ }
+ /* socket is readable or writable */
+ }
+
+ /* Run transaction, and return to the caller if it failed or if
+ * this connection is part of a multi handle and this loop would
+ * execute again. This permits the owner of a multi handle to
+ * abort a connection attempt before step2 has completed while
+ * ensuring that a client using select() or epoll() will always
+ * have a valid fdset to wait on.
+ */
+ result = wolfssl_connect_step2(cf, data);
+ if(result || (nonblocking &&
+ (ssl_connect_2 == connssl->connecting_state ||
+ ssl_connect_2_reading == connssl->connecting_state ||
+ ssl_connect_2_writing == connssl->connecting_state)))
+ return result;
+ } /* repeat step2 until all transactions are done. */
+
+ if(ssl_connect_3 == connssl->connecting_state) {
+ result = wolfssl_connect_step3(cf, data);
+ if(result)
+ return result;
+ }
+
+ if(ssl_connect_done == connssl->connecting_state) {
+ connssl->state = ssl_connection_complete;
+ *done = TRUE;
+ }
+ else
+ *done = FALSE;
+
+ /* Reset our connect state machine */
+ connssl->connecting_state = ssl_connect_1;
+
+ return CURLE_OK;
+}
+
+
+static CURLcode wolfssl_connect_nonblocking(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ bool *done)
+{
+ return wolfssl_connect_common(cf, data, TRUE, done);
+}
+
+
+static CURLcode wolfssl_connect(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ CURLcode result;
+ bool done = FALSE;
+
+ result = wolfssl_connect_common(cf, data, FALSE, &done);
+ if(result)
+ return result;
+
+ DEBUGASSERT(done);
+
+ return CURLE_OK;
+}
+
+static CURLcode wolfssl_random(struct Curl_easy *data,
+ unsigned char *entropy, size_t length)
+{
+ WC_RNG rng;
+ (void)data;
+ if(wc_InitRng(&rng))
+ return CURLE_FAILED_INIT;
+ if(length > UINT_MAX)
+ return CURLE_FAILED_INIT;
+ if(wc_RNG_GenerateBlock(&rng, entropy, (unsigned)length))
+ return CURLE_FAILED_INIT;
+ if(wc_FreeRng(&rng))
+ return CURLE_FAILED_INIT;
+ return CURLE_OK;
+}
+
+static CURLcode wolfssl_sha256sum(const unsigned char *tmp, /* input */
+ size_t tmplen,
+ unsigned char *sha256sum /* output */,
+ size_t unused)
+{
+ wc_Sha256 SHA256pw;
+ (void)unused;
+ wc_InitSha256(&SHA256pw);
+ wc_Sha256Update(&SHA256pw, tmp, (word32)tmplen);
+ wc_Sha256Final(&SHA256pw, sha256sum);
+ return CURLE_OK;
+}
+
+static void *wolfssl_get_internals(struct ssl_connect_data *connssl,
+ CURLINFO info UNUSED_PARAM)
+{
+ struct ssl_backend_data *backend = connssl->backend;
+ (void)info;
+ DEBUGASSERT(backend);
+ return backend->handle;
+}
+
+const struct Curl_ssl Curl_ssl_wolfssl = {
+ { CURLSSLBACKEND_WOLFSSL, "WolfSSL" }, /* info */
+
+#ifdef KEEP_PEER_CERT
+ SSLSUPP_PINNEDPUBKEY |
+#endif
+#ifdef USE_BIO_CHAIN
+ SSLSUPP_HTTPS_PROXY |
+#endif
+ SSLSUPP_SSL_CTX,
+
+ sizeof(struct ssl_backend_data),
+
+ wolfssl_init, /* init */
+ wolfssl_cleanup, /* cleanup */
+ wolfssl_version, /* version */
+ Curl_none_check_cxn, /* check_cxn */
+ wolfssl_shutdown, /* shutdown */
+ wolfssl_data_pending, /* data_pending */
+ wolfssl_random, /* random */
+ Curl_none_cert_status_request, /* cert_status_request */
+ wolfssl_connect, /* connect */
+ wolfssl_connect_nonblocking, /* connect_nonblocking */
+ Curl_ssl_get_select_socks, /* getsock */
+ wolfssl_get_internals, /* get_internals */
+ wolfssl_close, /* close_one */
+ Curl_none_close_all, /* close_all */
+ wolfssl_session_free, /* session_free */
+ Curl_none_set_engine, /* set_engine */
+ Curl_none_set_engine_default, /* set_engine_default */
+ Curl_none_engines_list, /* engines_list */
+ Curl_none_false_start, /* false_start */
+ wolfssl_sha256sum, /* sha256sum */
+ NULL, /* associate_connection */
+ NULL, /* disassociate_connection */
+ NULL, /* free_multi_ssl_backend_data */
+ wolfssl_recv, /* recv decrypted data */
+ wolfssl_send, /* send data to encrypt */
+};
+
+#endif
diff --git a/libs/libcurl/src/vtls/wolfssl.h b/libs/libcurl/src/vtls/wolfssl.h index b2e7c3fde2..78bf07aa92 100644 --- a/libs/libcurl/src/vtls/wolfssl.h +++ b/libs/libcurl/src/vtls/wolfssl.h @@ -1,33 +1,33 @@ -#ifndef HEADER_CURL_WOLFSSL_H -#define HEADER_CURL_WOLFSSL_H -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ -#include "curl_setup.h" - -#ifdef USE_WOLFSSL - -extern const struct Curl_ssl Curl_ssl_wolfssl; - -#endif /* USE_WOLFSSL */ -#endif /* HEADER_CURL_WOLFSSL_H */ +#ifndef HEADER_CURL_WOLFSSL_H
+#define HEADER_CURL_WOLFSSL_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_WOLFSSL
+
+extern const struct Curl_ssl Curl_ssl_wolfssl;
+
+#endif /* USE_WOLFSSL */
+#endif /* HEADER_CURL_WOLFSSL_H */
diff --git a/libs/libcurl/src/vtls/x509asn1.c b/libs/libcurl/src/vtls/x509asn1.c index 0cfcbe87da..3ebcf16ce9 100644 --- a/libs/libcurl/src/vtls/x509asn1.c +++ b/libs/libcurl/src/vtls/x509asn1.c @@ -1,1426 +1,1428 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) - -#if defined(USE_GSKIT) || defined(USE_WOLFSSL) || defined(USE_SCHANNEL) -#define WANT_PARSEX509 /* uses Curl_parseX509() */ -#endif - -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_SCHANNEL) || defined(USE_SECTRANSP) -#define WANT_EXTRACT_CERTINFO /* uses Curl_extract_certinfo() */ -#define WANT_PARSEX509 /* ... uses Curl_parseX509() */ -#endif - -#if defined(USE_GSKIT) -#define WANT_VERIFYHOST /* uses Curl_verifyhost () */ -#define WANT_PARSEX509 /* ... uses Curl_parseX509() */ -#endif - -#include <curl/curl.h> -#include "urldata.h" -#include "strcase.h" -#include "curl_ctype.h" -#include "hostcheck.h" -#include "vtls/vtls.h" -#include "sendf.h" -#include "inet_pton.h" -#include "curl_base64.h" -#include "x509asn1.h" -#include "dynbuf.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* - * Constants. - */ - -/* Largest supported ASN.1 structure. */ -#define CURL_ASN1_MAX ((size_t) 0x40000) /* 256K */ - -/* ASN.1 classes. */ -#define CURL_ASN1_UNIVERSAL 0 -#define CURL_ASN1_APPLICATION 1 -#define CURL_ASN1_CONTEXT_SPECIFIC 2 -#define CURL_ASN1_PRIVATE 3 - -/* ASN.1 types. */ -#define CURL_ASN1_BOOLEAN 1 -#define CURL_ASN1_INTEGER 2 -#define CURL_ASN1_BIT_STRING 3 -#define CURL_ASN1_OCTET_STRING 4 -#define CURL_ASN1_NULL 5 -#define CURL_ASN1_OBJECT_IDENTIFIER 6 -#define CURL_ASN1_OBJECT_DESCRIPTOR 7 -#define CURL_ASN1_INSTANCE_OF 8 -#define CURL_ASN1_REAL 9 -#define CURL_ASN1_ENUMERATED 10 -#define CURL_ASN1_EMBEDDED 11 -#define CURL_ASN1_UTF8_STRING 12 -#define CURL_ASN1_RELATIVE_OID 13 -#define CURL_ASN1_SEQUENCE 16 -#define CURL_ASN1_SET 17 -#define CURL_ASN1_NUMERIC_STRING 18 -#define CURL_ASN1_PRINTABLE_STRING 19 -#define CURL_ASN1_TELETEX_STRING 20 -#define CURL_ASN1_VIDEOTEX_STRING 21 -#define CURL_ASN1_IA5_STRING 22 -#define CURL_ASN1_UTC_TIME 23 -#define CURL_ASN1_GENERALIZED_TIME 24 -#define CURL_ASN1_GRAPHIC_STRING 25 -#define CURL_ASN1_VISIBLE_STRING 26 -#define CURL_ASN1_GENERAL_STRING 27 -#define CURL_ASN1_UNIVERSAL_STRING 28 -#define CURL_ASN1_CHARACTER_STRING 29 -#define CURL_ASN1_BMP_STRING 30 - -#ifdef WANT_EXTRACT_CERTINFO -/* ASN.1 OID table entry. */ -struct Curl_OID { - const char *numoid; /* Dotted-numeric OID. */ - const char *textoid; /* OID name. */ -}; - -/* ASN.1 OIDs. */ -static const char cnOID[] = "2.5.4.3"; /* Common name. */ -static const char sanOID[] = "2.5.29.17"; /* Subject alternative name. */ - -static const struct Curl_OID OIDtable[] = { - { "1.2.840.10040.4.1", "dsa" }, - { "1.2.840.10040.4.3", "dsa-with-sha1" }, - { "1.2.840.10045.2.1", "ecPublicKey" }, - { "1.2.840.10045.3.0.1", "c2pnb163v1" }, - { "1.2.840.10045.4.1", "ecdsa-with-SHA1" }, - { "1.2.840.10046.2.1", "dhpublicnumber" }, - { "1.2.840.113549.1.1.1", "rsaEncryption" }, - { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" }, - { "1.2.840.113549.1.1.4", "md5WithRSAEncryption" }, - { "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" }, - { "1.2.840.113549.1.1.10", "RSASSA-PSS" }, - { "1.2.840.113549.1.1.14", "sha224WithRSAEncryption" }, - { "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" }, - { "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" }, - { "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" }, - { "1.2.840.113549.2.2", "md2" }, - { "1.2.840.113549.2.5", "md5" }, - { "1.3.14.3.2.26", "sha1" }, - { cnOID, "CN" }, - { "2.5.4.4", "SN" }, - { "2.5.4.5", "serialNumber" }, - { "2.5.4.6", "C" }, - { "2.5.4.7", "L" }, - { "2.5.4.8", "ST" }, - { "2.5.4.9", "streetAddress" }, - { "2.5.4.10", "O" }, - { "2.5.4.11", "OU" }, - { "2.5.4.12", "title" }, - { "2.5.4.13", "description" }, - { "2.5.4.17", "postalCode" }, - { "2.5.4.41", "name" }, - { "2.5.4.42", "givenName" }, - { "2.5.4.43", "initials" }, - { "2.5.4.44", "generationQualifier" }, - { "2.5.4.45", "X500UniqueIdentifier" }, - { "2.5.4.46", "dnQualifier" }, - { "2.5.4.65", "pseudonym" }, - { "1.2.840.113549.1.9.1", "emailAddress" }, - { "2.5.4.72", "role" }, - { sanOID, "subjectAltName" }, - { "2.5.29.18", "issuerAltName" }, - { "2.5.29.19", "basicConstraints" }, - { "2.16.840.1.101.3.4.2.4", "sha224" }, - { "2.16.840.1.101.3.4.2.1", "sha256" }, - { "2.16.840.1.101.3.4.2.2", "sha384" }, - { "2.16.840.1.101.3.4.2.3", "sha512" }, - { (const char *) NULL, (const char *) NULL } -}; - -#endif /* WANT_EXTRACT_CERTINFO */ - -/* - * Lightweight ASN.1 parser. - * In particular, it does not check for syntactic/lexical errors. - * It is intended to support certificate information gathering for SSL backends - * that offer a mean to get certificates as a whole, but do not supply - * entry points to get particular certificate sub-fields. - * Please note there is no pretention here to rewrite a full SSL library. - */ - -static const char *getASN1Element(struct Curl_asn1Element *elem, - const char *beg, const char *end) - WARN_UNUSED_RESULT; - -static const char *getASN1Element(struct Curl_asn1Element *elem, - const char *beg, const char *end) -{ - unsigned char b; - unsigned long len; - struct Curl_asn1Element lelem; - - /* Get a single ASN.1 element into `elem', parse ASN.1 string at `beg' - ending at `end'. - Returns a pointer in source string after the parsed element, or NULL - if an error occurs. */ - if(!beg || !end || beg >= end || !*beg || - (size_t)(end - beg) > CURL_ASN1_MAX) - return NULL; - - /* Process header byte. */ - elem->header = beg; - b = (unsigned char) *beg++; - elem->constructed = (b & 0x20) != 0; - elem->class = (b >> 6) & 3; - b &= 0x1F; - if(b == 0x1F) - return NULL; /* Long tag values not supported here. */ - elem->tag = b; - - /* Process length. */ - if(beg >= end) - return NULL; - b = (unsigned char) *beg++; - if(!(b & 0x80)) - len = b; - else if(!(b &= 0x7F)) { - /* Unspecified length. Since we have all the data, we can determine the - effective length by skipping element until an end element is found. */ - if(!elem->constructed) - return NULL; - elem->beg = beg; - while(beg < end && *beg) { - beg = getASN1Element(&lelem, beg, end); - if(!beg) - return NULL; - } - if(beg >= end) - return NULL; - elem->end = beg; - return beg + 1; - } - else if((unsigned)b > (size_t)(end - beg)) - return NULL; /* Does not fit in source. */ - else { - /* Get long length. */ - len = 0; - do { - if(len & 0xFF000000L) - return NULL; /* Lengths > 32 bits are not supported. */ - len = (len << 8) | (unsigned char) *beg++; - } while(--b); - } - if(len > (size_t)(end - beg)) - return NULL; /* Element data does not fit in source. */ - elem->beg = beg; - elem->end = beg + len; - return elem->end; -} - -#ifdef WANT_EXTRACT_CERTINFO - -/* - * Search the null terminated OID or OID identifier in local table. - * Return the table entry pointer or NULL if not found. - */ -static const struct Curl_OID *searchOID(const char *oid) -{ - const struct Curl_OID *op; - for(op = OIDtable; op->numoid; op++) - if(!strcmp(op->numoid, oid) || strcasecompare(op->textoid, oid)) - return op; - - return NULL; -} - -/* - * Convert an ASN.1 Boolean value into its string representation. Return the - * dynamically allocated string, or NULL if source is not an ASN.1 Boolean - * value. - */ - -static const char *bool2str(const char *beg, const char *end) -{ - if(end - beg != 1) - return NULL; - return strdup(*beg? "TRUE": "FALSE"); -} - -/* - * Convert an ASN.1 octet string to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *octet2str(const char *beg, const char *end) -{ - struct dynbuf buf; - CURLcode result; - - Curl_dyn_init(&buf, 3 * CURL_ASN1_MAX + 1); - result = Curl_dyn_addn(&buf, "", 0); - - while(!result && beg < end) - result = Curl_dyn_addf(&buf, "%02x:", (unsigned char) *beg++); - - return Curl_dyn_ptr(&buf); -} - -static const char *bit2str(const char *beg, const char *end) -{ - /* Convert an ASN.1 bit string to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ - - if(++beg > end) - return NULL; - return octet2str(beg, end); -} - -/* - * Convert an ASN.1 integer value into its string representation. - * Return the dynamically allocated string, or NULL if source is not an - * ASN.1 integer value. - */ -static const char *int2str(const char *beg, const char *end) -{ - unsigned long val = 0; - size_t n = end - beg; - - if(!n) - return NULL; - - if(n > 4) - return octet2str(beg, end); - - /* Represent integers <= 32-bit as a single value. */ - if(*beg & 0x80) - val = ~val; - - do - val = (val << 8) | *(const unsigned char *) beg++; - while(beg < end); - return curl_maprintf("%s%lx", val >= 10? "0x": "", val); -} - -/* - * Perform a lazy conversion from an ASN.1 typed string to UTF8. Allocate the - * destination buffer dynamically. The allocation size will normally be too - * large: this is to avoid buffer overflows. - * Terminate the string with a nul byte and return the converted - * string length. - */ -static ssize_t -utf8asn1str(char **to, int type, const char *from, const char *end) -{ - size_t inlength = end - from; - int size = 1; - size_t outlength; - char *buf; - - *to = NULL; - switch(type) { - case CURL_ASN1_BMP_STRING: - size = 2; - break; - case CURL_ASN1_UNIVERSAL_STRING: - size = 4; - break; - case CURL_ASN1_NUMERIC_STRING: - case CURL_ASN1_PRINTABLE_STRING: - case CURL_ASN1_TELETEX_STRING: - case CURL_ASN1_IA5_STRING: - case CURL_ASN1_VISIBLE_STRING: - case CURL_ASN1_UTF8_STRING: - break; - default: - return -1; /* Conversion not supported. */ - } - - if(inlength % size) - return -1; /* Length inconsistent with character size. */ - if(inlength / size > (SIZE_T_MAX - 1) / 4) - return -1; /* Too big. */ - buf = malloc(4 * (inlength / size) + 1); - if(!buf) - return -1; /* Not enough memory. */ - - if(type == CURL_ASN1_UTF8_STRING) { - /* Just copy. */ - outlength = inlength; - if(outlength) - memcpy(buf, from, outlength); - } - else { - for(outlength = 0; from < end;) { - int charsize; - unsigned int wc; - - wc = 0; - switch(size) { - case 4: - wc = (wc << 8) | *(const unsigned char *) from++; - wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ - case 2: - wc = (wc << 8) | *(const unsigned char *) from++; - /* FALLTHROUGH */ - default: /* case 1: */ - wc = (wc << 8) | *(const unsigned char *) from++; - } - charsize = 1; - if(wc >= 0x00000080) { - if(wc >= 0x00000800) { - if(wc >= 0x00010000) { - if(wc >= 0x00200000) { - free(buf); - return -1; /* Invalid char. size for target encoding. */ - } - buf[outlength + 3] = (char) (0x80 | (wc & 0x3F)); - wc = (wc >> 6) | 0x00010000; - charsize++; - } - buf[outlength + 2] = (char) (0x80 | (wc & 0x3F)); - wc = (wc >> 6) | 0x00000800; - charsize++; - } - buf[outlength + 1] = (char) (0x80 | (wc & 0x3F)); - wc = (wc >> 6) | 0x000000C0; - charsize++; - } - buf[outlength] = (char) wc; - outlength += charsize; - } - } - buf[outlength] = '\0'; - *to = buf; - return outlength; -} - -/* - * Convert an ASN.1 String into its UTF-8 string representation. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *string2str(int type, const char *beg, const char *end) -{ - char *buf; - if(utf8asn1str(&buf, type, beg, end) < 0) - return NULL; - return buf; -} - -/* - * Decimal ASCII encode unsigned integer `x' into the buflen sized buffer at - * buf. Return the total number of encoded digits, even if larger than - * `buflen'. - */ -static size_t encodeUint(char *buf, size_t buflen, unsigned int x) -{ - size_t i = 0; - unsigned int y = x / 10; - - if(y) { - i = encodeUint(buf, buflen, y); - x -= y * 10; - } - if(i < buflen) - buf[i] = (char) ('0' + x); - i++; - if(i < buflen) - buf[i] = '\0'; /* Store a terminator if possible. */ - return i; -} - -/* - * Convert an ASN.1 OID into its dotted string representation. - * Store the result in th `n'-byte buffer at `buf'. - * Return the converted string length, or 0 on errors. - */ -static size_t encodeOID(char *buf, size_t buflen, - const char *beg, const char *end) -{ - size_t i; - unsigned int x; - unsigned int y; - - /* Process the first two numbers. */ - y = *(const unsigned char *) beg++; - x = y / 40; - y -= x * 40; - i = encodeUint(buf, buflen, x); - if(i < buflen) - buf[i] = '.'; - i++; - if(i >= buflen) - i += encodeUint(NULL, 0, y); - else - i += encodeUint(buf + i, buflen - i, y); - - /* Process the trailing numbers. */ - while(beg < end) { - if(i < buflen) - buf[i] = '.'; - i++; - x = 0; - do { - if(x & 0xFF000000) - return 0; - y = *(const unsigned char *) beg++; - x = (x << 7) | (y & 0x7F); - } while(y & 0x80); - if(i >= buflen) - i += encodeUint(NULL, 0, x); - else - i += encodeUint(buf + i, buflen - i, x); - } - if(i < buflen) - buf[i] = '\0'; - return i; -} - -/* - * Convert an ASN.1 OID into its dotted or symbolic string representation. - * Return the dynamically allocated string, or NULL if an error occurs. - */ - -static const char *OID2str(const char *beg, const char *end, bool symbolic) -{ - char *buf = NULL; - if(beg < end) { - size_t buflen = encodeOID(NULL, 0, beg, end); - if(buflen) { - buf = malloc(buflen + 1); /* one extra for the zero byte */ - if(buf) { - encodeOID(buf, buflen, beg, end); - buf[buflen] = '\0'; - - if(symbolic) { - const struct Curl_OID *op = searchOID(buf); - if(op) { - free(buf); - buf = strdup(op->textoid); - } - } - } - } - } - return buf; -} - -static const char *GTime2str(const char *beg, const char *end) -{ - const char *tzp; - const char *fracp; - char sec1, sec2; - size_t fracl; - size_t tzl; - const char *sep = ""; - - /* Convert an ASN.1 Generalized time to a printable string. - Return the dynamically allocated string, or NULL if an error occurs. */ - - for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++) - ; - - /* Get seconds digits. */ - sec1 = '0'; - switch(fracp - beg - 12) { - case 0: - sec2 = '0'; - break; - case 2: - sec1 = fracp[-2]; - /* FALLTHROUGH */ - case 1: - sec2 = fracp[-1]; - break; - default: - return NULL; - } - - /* Scan for timezone, measure fractional seconds. */ - tzp = fracp; - fracl = 0; - if(fracp < end && (*fracp == '.' || *fracp == ',')) { - fracp++; - do - tzp++; - while(tzp < end && *tzp >= '0' && *tzp <= '9'); - /* Strip leading zeroes in fractional seconds. */ - for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--) - ; - } - - /* Process timezone. */ - if(tzp >= end) - ; /* Nothing to do. */ - else if(*tzp == 'Z') { - tzp = " GMT"; - end = tzp + 4; - } - else { - sep = " "; - tzp++; - } - - tzl = end - tzp; - return curl_maprintf("%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s", - beg, beg + 4, beg + 6, - beg + 8, beg + 10, sec1, sec2, - fracl? ".": "", (int)fracl, fracp, - sep, (int)tzl, tzp); -} - -/* - * Convert an ASN.1 UTC time to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *UTime2str(const char *beg, const char *end) -{ - const char *tzp; - size_t tzl; - const char *sec; - - for(tzp = beg; tzp < end && *tzp >= '0' && *tzp <= '9'; tzp++) - ; - /* Get the seconds. */ - sec = beg + 10; - switch(tzp - sec) { - case 0: - sec = "00"; - case 2: - break; - default: - return NULL; - } - - /* Process timezone. */ - if(tzp >= end) - return NULL; - if(*tzp == 'Z') { - tzp = "GMT"; - end = tzp + 3; - } - else - tzp++; - - tzl = end - tzp; - return curl_maprintf("%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s", - 20 - (*beg >= '5'), beg, beg + 2, beg + 4, - beg + 6, beg + 8, sec, - (int)tzl, tzp); -} - -/* - * Convert an ASN.1 element to a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *ASN1tostr(struct Curl_asn1Element *elem, int type) -{ - if(elem->constructed) - return NULL; /* No conversion of structured elements. */ - - if(!type) - type = elem->tag; /* Type not forced: use element tag as type. */ - - switch(type) { - case CURL_ASN1_BOOLEAN: - return bool2str(elem->beg, elem->end); - case CURL_ASN1_INTEGER: - case CURL_ASN1_ENUMERATED: - return int2str(elem->beg, elem->end); - case CURL_ASN1_BIT_STRING: - return bit2str(elem->beg, elem->end); - case CURL_ASN1_OCTET_STRING: - return octet2str(elem->beg, elem->end); - case CURL_ASN1_NULL: - return strdup(""); - case CURL_ASN1_OBJECT_IDENTIFIER: - return OID2str(elem->beg, elem->end, TRUE); - case CURL_ASN1_UTC_TIME: - return UTime2str(elem->beg, elem->end); - case CURL_ASN1_GENERALIZED_TIME: - return GTime2str(elem->beg, elem->end); - case CURL_ASN1_UTF8_STRING: - case CURL_ASN1_NUMERIC_STRING: - case CURL_ASN1_PRINTABLE_STRING: - case CURL_ASN1_TELETEX_STRING: - case CURL_ASN1_IA5_STRING: - case CURL_ASN1_VISIBLE_STRING: - case CURL_ASN1_UNIVERSAL_STRING: - case CURL_ASN1_BMP_STRING: - return string2str(type, elem->beg, elem->end); - } - - return NULL; /* Unsupported. */ -} - -/* - * ASCII encode distinguished name at `dn' into the `buflen'-sized buffer at - * `buf'. - * - * Returns the total string length, even if larger than `buflen' or -1 on - * error. - */ -static ssize_t encodeDN(char *buf, size_t buflen, struct Curl_asn1Element *dn) -{ - struct Curl_asn1Element rdn; - struct Curl_asn1Element atv; - struct Curl_asn1Element oid; - struct Curl_asn1Element value; - size_t l = 0; - const char *p1; - const char *p2; - const char *p3; - const char *str; - - for(p1 = dn->beg; p1 < dn->end;) { - p1 = getASN1Element(&rdn, p1, dn->end); - if(!p1) - return -1; - for(p2 = rdn.beg; p2 < rdn.end;) { - p2 = getASN1Element(&atv, p2, rdn.end); - if(!p2) - return -1; - p3 = getASN1Element(&oid, atv.beg, atv.end); - if(!p3) - return -1; - if(!getASN1Element(&value, p3, atv.end)) - return -1; - str = ASN1tostr(&oid, 0); - if(!str) - return -1; - - /* Encode delimiter. - If attribute has a short uppercase name, delimiter is ", ". */ - if(l) { - for(p3 = str; ISUPPER(*p3); p3++) - ; - for(p3 = (*p3 || p3 - str > 2)? "/": ", "; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - } - - /* Encode attribute name. */ - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); - - /* Generate equal sign. */ - if(l < buflen) - buf[l] = '='; - l++; - - /* Generate value. */ - str = ASN1tostr(&value, 0); - if(!str) - return -1; - for(p3 = str; *p3; p3++) { - if(l < buflen) - buf[l] = *p3; - l++; - } - free((char *) str); - } - } - - return l; -} - -#endif /* WANT_EXTRACT_CERTINFO */ - -#ifdef WANT_PARSEX509 -/* - * ASN.1 parse an X509 certificate into structure subfields. - * Syntax is assumed to have already been checked by the SSL backend. - * See RFC 5280. - */ -int Curl_parseX509(struct Curl_X509certificate *cert, - const char *beg, const char *end) -{ - struct Curl_asn1Element elem; - struct Curl_asn1Element tbsCertificate; - const char *ccp; - static const char defaultVersion = 0; /* v1. */ - - cert->certificate.header = NULL; - cert->certificate.beg = beg; - cert->certificate.end = end; - - /* Get the sequence content. */ - if(!getASN1Element(&elem, beg, end)) - return -1; /* Invalid bounds/size. */ - beg = elem.beg; - end = elem.end; - - /* Get tbsCertificate. */ - beg = getASN1Element(&tbsCertificate, beg, end); - if(!beg) - return -1; - /* Skip the signatureAlgorithm. */ - beg = getASN1Element(&cert->signatureAlgorithm, beg, end); - if(!beg) - return -1; - /* Get the signatureValue. */ - if(!getASN1Element(&cert->signature, beg, end)) - return -1; - - /* Parse TBSCertificate. */ - beg = tbsCertificate.beg; - end = tbsCertificate.end; - /* Get optional version, get serialNumber. */ - cert->version.header = NULL; - cert->version.beg = &defaultVersion; - cert->version.end = &defaultVersion + sizeof(defaultVersion); - beg = getASN1Element(&elem, beg, end); - if(!beg) - return -1; - if(elem.tag == 0) { - if(!getASN1Element(&cert->version, elem.beg, elem.end)) - return -1; - beg = getASN1Element(&elem, beg, end); - if(!beg) - return -1; - } - cert->serialNumber = elem; - /* Get signature algorithm. */ - beg = getASN1Element(&cert->signatureAlgorithm, beg, end); - /* Get issuer. */ - beg = getASN1Element(&cert->issuer, beg, end); - if(!beg) - return -1; - /* Get notBefore and notAfter. */ - beg = getASN1Element(&elem, beg, end); - if(!beg) - return -1; - ccp = getASN1Element(&cert->notBefore, elem.beg, elem.end); - if(!ccp) - return -1; - if(!getASN1Element(&cert->notAfter, ccp, elem.end)) - return -1; - /* Get subject. */ - beg = getASN1Element(&cert->subject, beg, end); - if(!beg) - return -1; - /* Get subjectPublicKeyAlgorithm and subjectPublicKey. */ - beg = getASN1Element(&cert->subjectPublicKeyInfo, beg, end); - if(!beg) - return -1; - ccp = getASN1Element(&cert->subjectPublicKeyAlgorithm, - cert->subjectPublicKeyInfo.beg, - cert->subjectPublicKeyInfo.end); - if(!ccp) - return -1; - if(!getASN1Element(&cert->subjectPublicKey, ccp, - cert->subjectPublicKeyInfo.end)) - return -1; - /* Get optional issuerUiqueID, subjectUniqueID and extensions. */ - cert->issuerUniqueID.tag = cert->subjectUniqueID.tag = 0; - cert->extensions.tag = elem.tag = 0; - cert->issuerUniqueID.header = cert->subjectUniqueID.header = NULL; - cert->issuerUniqueID.beg = cert->issuerUniqueID.end = ""; - cert->subjectUniqueID.beg = cert->subjectUniqueID.end = ""; - cert->extensions.header = NULL; - cert->extensions.beg = cert->extensions.end = ""; - if(beg < end) { - beg = getASN1Element(&elem, beg, end); - if(!beg) - return -1; - } - if(elem.tag == 1) { - cert->issuerUniqueID = elem; - if(beg < end) { - beg = getASN1Element(&elem, beg, end); - if(!beg) - return -1; - } - } - if(elem.tag == 2) { - cert->subjectUniqueID = elem; - if(beg < end) { - beg = getASN1Element(&elem, beg, end); - if(!beg) - return -1; - } - } - if(elem.tag == 3) - if(!getASN1Element(&cert->extensions, elem.beg, elem.end)) - return -1; - return 0; -} - -#endif /* WANT_PARSEX509 */ - -#ifdef WANT_EXTRACT_CERTINFO - -/* - * Copy at most 64-characters, terminate with a newline and returns the - * effective number of stored characters. - */ -static size_t copySubstring(char *to, const char *from) -{ - size_t i; - for(i = 0; i < 64; i++) { - to[i] = *from; - if(!*from++) - break; - } - - to[i++] = '\n'; - return i; -} - -static const char *dumpAlgo(struct Curl_asn1Element *param, - const char *beg, const char *end) -{ - struct Curl_asn1Element oid; - - /* Get algorithm parameters and return algorithm name. */ - - beg = getASN1Element(&oid, beg, end); - if(!beg) - return NULL; - param->header = NULL; - param->tag = 0; - param->beg = param->end = end; - if(beg < end) - if(!getASN1Element(param, beg, end)) - return NULL; - return OID2str(oid.beg, oid.end, TRUE); -} - -/* return 0 on success, 1 on error */ -static int do_pubkey_field(struct Curl_easy *data, int certnum, - const char *label, struct Curl_asn1Element *elem) -{ - const char *output; - CURLcode result = CURLE_OK; - - /* Generate a certificate information record for the public key. */ - - output = ASN1tostr(elem, 0); - if(output) { - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, label, output); - if(!certnum && !result) - infof(data, " %s: %s", label, output); - free((char *) output); - } - return result ? 1 : 0; -} - -/* return 0 on success, 1 on error */ -static int do_pubkey(struct Curl_easy *data, int certnum, - const char *algo, struct Curl_asn1Element *param, - struct Curl_asn1Element *pubkey) -{ - struct Curl_asn1Element elem; - struct Curl_asn1Element pk; - const char *p; - - /* Generate all information records for the public key. */ - - if(strcasecompare(algo, "ecPublicKey")) { - /* - * ECC public key is all the data, a value of type BIT STRING mapped to - * OCTET STRING and should not be parsed as an ASN.1 value. - */ - const unsigned long len = - (unsigned long)((pubkey->end - pubkey->beg - 2) * 4); - if(!certnum) - infof(data, " ECC Public Key (%lu bits)", len); - if(data->set.ssl.certinfo) { - char q[sizeof(len) * 8 / 3 + 1]; - (void)msnprintf(q, sizeof(q), "%lu", len); - if(Curl_ssl_push_certinfo(data, certnum, "ECC Public Key", q)) - return 1; - } - return do_pubkey_field(data, certnum, "ecPublicKey", pubkey); - } - - /* Get the public key (single element). */ - if(!getASN1Element(&pk, pubkey->beg + 1, pubkey->end)) - return 1; - - if(strcasecompare(algo, "rsaEncryption")) { - const char *q; - unsigned long len; - - p = getASN1Element(&elem, pk.beg, pk.end); - if(!p) - return 1; - - /* Compute key length. */ - for(q = elem.beg; !*q && q < elem.end; q++) - ; - len = (unsigned long)((elem.end - q) * 8); - if(len) { - unsigned int i; - for(i = *(unsigned char *) q; !(i & 0x80); i <<= 1) - len--; - } - if(len > 32) - elem.beg = q; /* Strip leading zero bytes. */ - if(!certnum) - infof(data, " RSA Public Key (%lu bits)", len); - if(data->set.ssl.certinfo) { - char r[sizeof(len) * 8 / 3 + 1]; - msnprintf(r, sizeof(r), "%lu", len); - if(Curl_ssl_push_certinfo(data, certnum, "RSA Public Key", r)) - return 1; - } - /* Generate coefficients. */ - if(do_pubkey_field(data, certnum, "rsa(n)", &elem)) - return 1; - if(!getASN1Element(&elem, p, pk.end)) - return 1; - if(do_pubkey_field(data, certnum, "rsa(e)", &elem)) - return 1; - } - else if(strcasecompare(algo, "dsa")) { - p = getASN1Element(&elem, param->beg, param->end); - if(p) { - if(do_pubkey_field(data, certnum, "dsa(p)", &elem)) - return 1; - p = getASN1Element(&elem, p, param->end); - if(p) { - if(do_pubkey_field(data, certnum, "dsa(q)", &elem)) - return 1; - if(getASN1Element(&elem, p, param->end)) { - if(do_pubkey_field(data, certnum, "dsa(g)", &elem)) - return 1; - if(do_pubkey_field(data, certnum, "dsa(pub_key)", &pk)) - return 1; - } - } - } - } - else if(strcasecompare(algo, "dhpublicnumber")) { - p = getASN1Element(&elem, param->beg, param->end); - if(p) { - if(do_pubkey_field(data, certnum, "dh(p)", &elem)) - return 1; - if(getASN1Element(&elem, param->beg, param->end)) { - if(do_pubkey_field(data, certnum, "dh(g)", &elem)) - return 1; - if(do_pubkey_field(data, certnum, "dh(pub_key)", &pk)) - return 1; - } - } - } - return 0; -} - -/* - * Convert an ASN.1 distinguished name into a printable string. - * Return the dynamically allocated string, or NULL if an error occurs. - */ -static const char *DNtostr(struct Curl_asn1Element *dn) -{ - char *buf = NULL; - ssize_t buflen = encodeDN(NULL, 0, dn); - - if(buflen >= 0) { - buf = malloc(buflen + 1); - if(buf) { - if(encodeDN(buf, buflen + 1, dn) == -1) { - free(buf); - return NULL; - } - buf[buflen] = '\0'; - } - } - return buf; -} - -CURLcode Curl_extract_certinfo(struct Curl_easy *data, - int certnum, - const char *beg, - const char *end) -{ - struct Curl_X509certificate cert; - struct Curl_asn1Element param; - const char *ccp; - char *cp1; - size_t cl1; - char *cp2; - CURLcode result = CURLE_OK; - unsigned long version; - size_t i; - size_t j; - - if(!data->set.ssl.certinfo) - if(certnum) - return CURLE_OK; - - /* Prepare the certificate information for curl_easy_getinfo(). */ - - /* Extract the certificate ASN.1 elements. */ - if(Curl_parseX509(&cert, beg, end)) - return CURLE_PEER_FAILED_VERIFICATION; - - /* Subject. */ - ccp = DNtostr(&cert.subject); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) { - result = Curl_ssl_push_certinfo(data, certnum, "Subject", ccp); - if(result) - return result; - } - if(!certnum) - infof(data, "%2d Subject: %s", certnum, ccp); - free((char *) ccp); - - /* Issuer. */ - ccp = DNtostr(&cert.issuer); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) { - result = Curl_ssl_push_certinfo(data, certnum, "Issuer", ccp); - } - if(!certnum) - infof(data, " Issuer: %s", ccp); - free((char *) ccp); - if(result) - return result; - - /* Version (always fits in less than 32 bits). */ - version = 0; - for(ccp = cert.version.beg; ccp < cert.version.end; ccp++) - version = (version << 8) | *(const unsigned char *) ccp; - if(data->set.ssl.certinfo) { - ccp = curl_maprintf("%lx", version); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - result = Curl_ssl_push_certinfo(data, certnum, "Version", ccp); - free((char *) ccp); - if(result) - return result; - } - if(!certnum) - infof(data, " Version: %lu (0x%lx)", version + 1, version); - - /* Serial number. */ - ccp = ASN1tostr(&cert.serialNumber, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, "Serial Number", ccp); - if(!certnum) - infof(data, " Serial Number: %s", ccp); - free((char *) ccp); - if(result) - return result; - - /* Signature algorithm .*/ - ccp = dumpAlgo(¶m, cert.signatureAlgorithm.beg, - cert.signatureAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp); - if(!certnum) - infof(data, " Signature Algorithm: %s", ccp); - free((char *) ccp); - if(result) - return result; - - /* Start Date. */ - ccp = ASN1tostr(&cert.notBefore, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, "Start Date", ccp); - if(!certnum) - infof(data, " Start Date: %s", ccp); - free((char *) ccp); - if(result) - return result; - - /* Expire Date. */ - ccp = ASN1tostr(&cert.notAfter, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, "Expire Date", ccp); - if(!certnum) - infof(data, " Expire Date: %s", ccp); - free((char *) ccp); - if(result) - return result; - - /* Public Key Algorithm. */ - ccp = dumpAlgo(¶m, cert.subjectPublicKeyAlgorithm.beg, - cert.subjectPublicKeyAlgorithm.end); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, "Public Key Algorithm", - ccp); - if(!result) { - int ret; - if(!certnum) - infof(data, " Public Key Algorithm: %s", ccp); - ret = do_pubkey(data, certnum, ccp, ¶m, &cert.subjectPublicKey); - if(ret) - result = CURLE_OUT_OF_MEMORY; /* the most likely error */ - } - free((char *) ccp); - if(result) - return result; - - /* Signature. */ - ccp = ASN1tostr(&cert.signature, 0); - if(!ccp) - return CURLE_OUT_OF_MEMORY; - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, "Signature", ccp); - if(!certnum) - infof(data, " Signature: %s", ccp); - free((char *) ccp); - if(result) - return result; - - /* Generate PEM certificate. */ - result = Curl_base64_encode(cert.certificate.beg, - cert.certificate.end - cert.certificate.beg, - &cp1, &cl1); - if(result) - return result; - /* Compute the number of characters in final certificate string. Format is: - -----BEGIN CERTIFICATE-----\n - <max 64 base64 characters>\n - . - . - . - -----END CERTIFICATE-----\n - */ - i = 28 + cl1 + (cl1 + 64 - 1) / 64 + 26; - cp2 = malloc(i + 1); - if(!cp2) { - free(cp1); - return CURLE_OUT_OF_MEMORY; - } - /* Build the certificate string. */ - i = copySubstring(cp2, "-----BEGIN CERTIFICATE-----"); - for(j = 0; j < cl1; j += 64) - i += copySubstring(cp2 + i, cp1 + j); - i += copySubstring(cp2 + i, "-----END CERTIFICATE-----"); - cp2[i] = '\0'; - free(cp1); - if(data->set.ssl.certinfo) - result = Curl_ssl_push_certinfo(data, certnum, "Cert", cp2); - if(!certnum) - infof(data, "%s", cp2); - free(cp2); - return result; -} - -#endif /* WANT_EXTRACT_CERTINFO */ - -#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL - * or USE_SECTRANSP */ - -#ifdef WANT_VERIFYHOST - -static const char *checkOID(const char *beg, const char *end, - const char *oid) -{ - struct Curl_asn1Element e; - const char *ccp; - const char *p; - bool matched; - - /* Check if first ASN.1 element at `beg' is the given OID. - Return a pointer in the source after the OID if found, else NULL. */ - - ccp = getASN1Element(&e, beg, end); - if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER) - return NULL; - - p = OID2str(e.beg, e.end, FALSE); - if(!p) - return NULL; - - matched = !strcmp(p, oid); - free((char *) p); - return matched? ccp: NULL; -} - -CURLcode Curl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - const char *beg, const char *end) -{ - struct Curl_X509certificate cert; - struct Curl_asn1Element dn; - struct Curl_asn1Element elem; - struct Curl_asn1Element ext; - struct Curl_asn1Element name; - const char *p; - const char *q; - char *dnsname; - int matched = -1; - size_t addrlen = (size_t) -1; - ssize_t len; - const char * const hostname = SSL_HOST_NAME(); - const char * const dispname = SSL_HOST_DISPNAME(); - size_t hostlen = strlen(hostname); -#ifdef ENABLE_IPV6 - struct in6_addr addr; -#else - struct in_addr addr; -#endif - - /* Verify that connection server matches info in X509 certificate at - `beg'..`end'. */ - - if(!SSL_CONN_CONFIG(verifyhost)) - return CURLE_OK; - - if(Curl_parseX509(&cert, beg, end)) - return CURLE_PEER_FAILED_VERIFICATION; - - /* Get the server IP address. */ -#ifdef ENABLE_IPV6 - if(conn->bits.ipv6_ip && Curl_inet_pton(AF_INET6, hostname, &addr)) - addrlen = sizeof(struct in6_addr); - else -#endif - if(Curl_inet_pton(AF_INET, hostname, &addr)) - addrlen = sizeof(struct in_addr); - - /* Process extensions. */ - for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) { - p = getASN1Element(&ext, p, cert.extensions.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - - /* Check if extension is a subjectAlternativeName. */ - ext.beg = checkOID(ext.beg, ext.end, sanOID); - if(ext.beg) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - /* Skip critical if present. */ - if(elem.tag == CURL_ASN1_BOOLEAN) { - ext.beg = getASN1Element(&elem, ext.beg, ext.end); - if(!ext.beg) - return CURLE_PEER_FAILED_VERIFICATION; - } - /* Parse the octet string contents: is a single sequence. */ - if(!getASN1Element(&elem, elem.beg, elem.end)) - return CURLE_PEER_FAILED_VERIFICATION; - /* Check all GeneralNames. */ - for(q = elem.beg; matched != 1 && q < elem.end;) { - q = getASN1Element(&name, q, elem.end); - if(!q) - break; - switch(name.tag) { - case 2: /* DNS name. */ - len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING, - name.beg, name.end); - if(len > 0 && (size_t)len == strlen(dnsname)) - matched = Curl_cert_hostcheck(dnsname, - (size_t)len, hostname, hostlen); - else - matched = 0; - free(dnsname); - break; - - case 7: /* IP address. */ - matched = (size_t) (name.end - name.beg) == addrlen && - !memcmp(&addr, name.beg, addrlen); - break; - } - } - } - } - - switch(matched) { - case 1: - /* an alternative name matched the server hostname */ - infof(data, " subjectAltName: %s matched", dispname); - return CURLE_OK; - case 0: - /* an alternative name field existed, but didn't match and then - we MUST fail */ - infof(data, " subjectAltName does not match %s", dispname); - return CURLE_PEER_FAILED_VERIFICATION; - } - - /* Process subject. */ - name.header = NULL; - name.beg = name.end = ""; - q = cert.subject.beg; - /* we have to look to the last occurrence of a commonName in the - distinguished one to get the most significant one. */ - while(q < cert.subject.end) { - q = getASN1Element(&dn, q, cert.subject.end); - if(!q) - break; - for(p = dn.beg; p < dn.end;) { - p = getASN1Element(&elem, p, dn.end); - if(!p) - return CURLE_PEER_FAILED_VERIFICATION; - /* We have a DN's AttributeTypeAndValue: check it in case it's a CN. */ - elem.beg = checkOID(elem.beg, elem.end, cnOID); - if(elem.beg) - name = elem; /* Latch CN. */ - } - } - - /* Check the CN if found. */ - if(!getASN1Element(&elem, name.beg, name.end)) - failf(data, "SSL: unable to obtain common name from peer certificate"); - else { - len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end); - if(len < 0) { - free(dnsname); - return CURLE_OUT_OF_MEMORY; - } - if(strlen(dnsname) != (size_t) len) /* Nul byte in string ? */ - failf(data, "SSL: illegal cert name field"); - else if(Curl_cert_hostcheck((const char *) dnsname, - len, hostname, hostlen)) { - infof(data, " common name: %s (matched)", dnsname); - free(dnsname); - return CURLE_OK; - } - else - failf(data, "SSL: certificate subject name '%s' does not match " - "target host name '%s'", dnsname, dispname); - free(dnsname); - } - - return CURLE_PEER_FAILED_VERIFICATION; -} - -#endif /* WANT_VERIFYHOST */ +/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \
+ defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP)
+
+#if defined(USE_GSKIT) || defined(USE_WOLFSSL) || defined(USE_SCHANNEL)
+#define WANT_PARSEX509 /* uses Curl_parseX509() */
+#endif
+
+#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \
+ defined(USE_SCHANNEL) || defined(USE_SECTRANSP)
+#define WANT_EXTRACT_CERTINFO /* uses Curl_extract_certinfo() */
+#define WANT_PARSEX509 /* ... uses Curl_parseX509() */
+#endif
+
+#if defined(USE_GSKIT)
+#define WANT_VERIFYHOST /* uses Curl_verifyhost () */
+#define WANT_PARSEX509 /* ... uses Curl_parseX509() */
+#endif
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "strcase.h"
+#include "curl_ctype.h"
+#include "hostcheck.h"
+#include "vtls/vtls.h"
+#include "sendf.h"
+#include "inet_pton.h"
+#include "curl_base64.h"
+#include "x509asn1.h"
+#include "dynbuf.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/*
+ * Constants.
+ */
+
+/* Largest supported ASN.1 structure. */
+#define CURL_ASN1_MAX ((size_t) 0x40000) /* 256K */
+
+/* ASN.1 classes. */
+#define CURL_ASN1_UNIVERSAL 0
+#define CURL_ASN1_APPLICATION 1
+#define CURL_ASN1_CONTEXT_SPECIFIC 2
+#define CURL_ASN1_PRIVATE 3
+
+/* ASN.1 types. */
+#define CURL_ASN1_BOOLEAN 1
+#define CURL_ASN1_INTEGER 2
+#define CURL_ASN1_BIT_STRING 3
+#define CURL_ASN1_OCTET_STRING 4
+#define CURL_ASN1_NULL 5
+#define CURL_ASN1_OBJECT_IDENTIFIER 6
+#define CURL_ASN1_OBJECT_DESCRIPTOR 7
+#define CURL_ASN1_INSTANCE_OF 8
+#define CURL_ASN1_REAL 9
+#define CURL_ASN1_ENUMERATED 10
+#define CURL_ASN1_EMBEDDED 11
+#define CURL_ASN1_UTF8_STRING 12
+#define CURL_ASN1_RELATIVE_OID 13
+#define CURL_ASN1_SEQUENCE 16
+#define CURL_ASN1_SET 17
+#define CURL_ASN1_NUMERIC_STRING 18
+#define CURL_ASN1_PRINTABLE_STRING 19
+#define CURL_ASN1_TELETEX_STRING 20
+#define CURL_ASN1_VIDEOTEX_STRING 21
+#define CURL_ASN1_IA5_STRING 22
+#define CURL_ASN1_UTC_TIME 23
+#define CURL_ASN1_GENERALIZED_TIME 24
+#define CURL_ASN1_GRAPHIC_STRING 25
+#define CURL_ASN1_VISIBLE_STRING 26
+#define CURL_ASN1_GENERAL_STRING 27
+#define CURL_ASN1_UNIVERSAL_STRING 28
+#define CURL_ASN1_CHARACTER_STRING 29
+#define CURL_ASN1_BMP_STRING 30
+
+#ifdef WANT_EXTRACT_CERTINFO
+/* ASN.1 OID table entry. */
+struct Curl_OID {
+ const char *numoid; /* Dotted-numeric OID. */
+ const char *textoid; /* OID name. */
+};
+
+/* ASN.1 OIDs. */
+static const char cnOID[] = "2.5.4.3"; /* Common name. */
+static const char sanOID[] = "2.5.29.17"; /* Subject alternative name. */
+
+static const struct Curl_OID OIDtable[] = {
+ { "1.2.840.10040.4.1", "dsa" },
+ { "1.2.840.10040.4.3", "dsa-with-sha1" },
+ { "1.2.840.10045.2.1", "ecPublicKey" },
+ { "1.2.840.10045.3.0.1", "c2pnb163v1" },
+ { "1.2.840.10045.4.1", "ecdsa-with-SHA1" },
+ { "1.2.840.10046.2.1", "dhpublicnumber" },
+ { "1.2.840.113549.1.1.1", "rsaEncryption" },
+ { "1.2.840.113549.1.1.2", "md2WithRSAEncryption" },
+ { "1.2.840.113549.1.1.4", "md5WithRSAEncryption" },
+ { "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" },
+ { "1.2.840.113549.1.1.10", "RSASSA-PSS" },
+ { "1.2.840.113549.1.1.14", "sha224WithRSAEncryption" },
+ { "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" },
+ { "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" },
+ { "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" },
+ { "1.2.840.113549.2.2", "md2" },
+ { "1.2.840.113549.2.5", "md5" },
+ { "1.3.14.3.2.26", "sha1" },
+ { cnOID, "CN" },
+ { "2.5.4.4", "SN" },
+ { "2.5.4.5", "serialNumber" },
+ { "2.5.4.6", "C" },
+ { "2.5.4.7", "L" },
+ { "2.5.4.8", "ST" },
+ { "2.5.4.9", "streetAddress" },
+ { "2.5.4.10", "O" },
+ { "2.5.4.11", "OU" },
+ { "2.5.4.12", "title" },
+ { "2.5.4.13", "description" },
+ { "2.5.4.17", "postalCode" },
+ { "2.5.4.41", "name" },
+ { "2.5.4.42", "givenName" },
+ { "2.5.4.43", "initials" },
+ { "2.5.4.44", "generationQualifier" },
+ { "2.5.4.45", "X500UniqueIdentifier" },
+ { "2.5.4.46", "dnQualifier" },
+ { "2.5.4.65", "pseudonym" },
+ { "1.2.840.113549.1.9.1", "emailAddress" },
+ { "2.5.4.72", "role" },
+ { sanOID, "subjectAltName" },
+ { "2.5.29.18", "issuerAltName" },
+ { "2.5.29.19", "basicConstraints" },
+ { "2.16.840.1.101.3.4.2.4", "sha224" },
+ { "2.16.840.1.101.3.4.2.1", "sha256" },
+ { "2.16.840.1.101.3.4.2.2", "sha384" },
+ { "2.16.840.1.101.3.4.2.3", "sha512" },
+ { (const char *) NULL, (const char *) NULL }
+};
+
+#endif /* WANT_EXTRACT_CERTINFO */
+
+/*
+ * Lightweight ASN.1 parser.
+ * In particular, it does not check for syntactic/lexical errors.
+ * It is intended to support certificate information gathering for SSL backends
+ * that offer a mean to get certificates as a whole, but do not supply
+ * entry points to get particular certificate sub-fields.
+ * Please note there is no pretention here to rewrite a full SSL library.
+ */
+
+static const char *getASN1Element(struct Curl_asn1Element *elem,
+ const char *beg, const char *end)
+ WARN_UNUSED_RESULT;
+
+static const char *getASN1Element(struct Curl_asn1Element *elem,
+ const char *beg, const char *end)
+{
+ unsigned char b;
+ size_t len;
+ struct Curl_asn1Element lelem;
+
+ /* Get a single ASN.1 element into `elem', parse ASN.1 string at `beg'
+ ending at `end'.
+ Returns a pointer in source string after the parsed element, or NULL
+ if an error occurs. */
+ if(!beg || !end || beg >= end || !*beg ||
+ (size_t)(end - beg) > CURL_ASN1_MAX)
+ return NULL;
+
+ /* Process header byte. */
+ elem->header = beg;
+ b = (unsigned char) *beg++;
+ elem->constructed = (b & 0x20) != 0;
+ elem->class = (b >> 6) & 3;
+ b &= 0x1F;
+ if(b == 0x1F)
+ return NULL; /* Long tag values not supported here. */
+ elem->tag = b;
+
+ /* Process length. */
+ if(beg >= end)
+ return NULL;
+ b = (unsigned char) *beg++;
+ if(!(b & 0x80))
+ len = b;
+ else if(!(b &= 0x7F)) {
+ /* Unspecified length. Since we have all the data, we can determine the
+ effective length by skipping element until an end element is found. */
+ if(!elem->constructed)
+ return NULL;
+ elem->beg = beg;
+ while(beg < end && *beg) {
+ beg = getASN1Element(&lelem, beg, end);
+ if(!beg)
+ return NULL;
+ }
+ if(beg >= end)
+ return NULL;
+ elem->end = beg;
+ return beg + 1;
+ }
+ else if((unsigned)b > (size_t)(end - beg))
+ return NULL; /* Does not fit in source. */
+ else {
+ /* Get long length. */
+ len = 0;
+ do {
+ if(len & 0xFF000000L)
+ return NULL; /* Lengths > 32 bits are not supported. */
+ len = (len << 8) | (unsigned char) *beg++;
+ } while(--b);
+ }
+ if(len > (size_t)(end - beg))
+ return NULL; /* Element data does not fit in source. */
+ elem->beg = beg;
+ elem->end = beg + len;
+ return elem->end;
+}
+
+#ifdef WANT_EXTRACT_CERTINFO
+
+/*
+ * Search the null terminated OID or OID identifier in local table.
+ * Return the table entry pointer or NULL if not found.
+ */
+static const struct Curl_OID *searchOID(const char *oid)
+{
+ const struct Curl_OID *op;
+ for(op = OIDtable; op->numoid; op++)
+ if(!strcmp(op->numoid, oid) || strcasecompare(op->textoid, oid))
+ return op;
+
+ return NULL;
+}
+
+/*
+ * Convert an ASN.1 Boolean value into its string representation. Return the
+ * dynamically allocated string, or NULL if source is not an ASN.1 Boolean
+ * value.
+ */
+
+static const char *bool2str(const char *beg, const char *end)
+{
+ if(end - beg != 1)
+ return NULL;
+ return strdup(*beg? "TRUE": "FALSE");
+}
+
+/*
+ * Convert an ASN.1 octet string to a printable string.
+ * Return the dynamically allocated string, or NULL if an error occurs.
+ */
+static const char *octet2str(const char *beg, const char *end)
+{
+ struct dynbuf buf;
+ CURLcode result;
+
+ Curl_dyn_init(&buf, 3 * CURL_ASN1_MAX + 1);
+ result = Curl_dyn_addn(&buf, "", 0);
+
+ while(!result && beg < end)
+ result = Curl_dyn_addf(&buf, "%02x:", (unsigned char) *beg++);
+
+ return Curl_dyn_ptr(&buf);
+}
+
+static const char *bit2str(const char *beg, const char *end)
+{
+ /* Convert an ASN.1 bit string to a printable string.
+ Return the dynamically allocated string, or NULL if an error occurs. */
+
+ if(++beg > end)
+ return NULL;
+ return octet2str(beg, end);
+}
+
+/*
+ * Convert an ASN.1 integer value into its string representation.
+ * Return the dynamically allocated string, or NULL if source is not an
+ * ASN.1 integer value.
+ */
+static const char *int2str(const char *beg, const char *end)
+{
+ unsigned int val = 0;
+ size_t n = end - beg;
+
+ if(!n)
+ return NULL;
+
+ if(n > 4)
+ return octet2str(beg, end);
+
+ /* Represent integers <= 32-bit as a single value. */
+ if(*beg & 0x80)
+ val = ~val;
+
+ do
+ val = (val << 8) | *(const unsigned char *) beg++;
+ while(beg < end);
+ return curl_maprintf("%s%x", val >= 10? "0x": "", val);
+}
+
+/*
+ * Perform a lazy conversion from an ASN.1 typed string to UTF8. Allocate the
+ * destination buffer dynamically. The allocation size will normally be too
+ * large: this is to avoid buffer overflows.
+ * Terminate the string with a nul byte and return the converted
+ * string length.
+ */
+static ssize_t
+utf8asn1str(char **to, int type, const char *from, const char *end)
+{
+ size_t inlength = end - from;
+ int size = 1;
+ size_t outlength;
+ char *buf;
+
+ *to = NULL;
+ switch(type) {
+ case CURL_ASN1_BMP_STRING:
+ size = 2;
+ break;
+ case CURL_ASN1_UNIVERSAL_STRING:
+ size = 4;
+ break;
+ case CURL_ASN1_NUMERIC_STRING:
+ case CURL_ASN1_PRINTABLE_STRING:
+ case CURL_ASN1_TELETEX_STRING:
+ case CURL_ASN1_IA5_STRING:
+ case CURL_ASN1_VISIBLE_STRING:
+ case CURL_ASN1_UTF8_STRING:
+ break;
+ default:
+ return -1; /* Conversion not supported. */
+ }
+
+ if(inlength % size)
+ return -1; /* Length inconsistent with character size. */
+ if(inlength / size > (SIZE_T_MAX - 1) / 4)
+ return -1; /* Too big. */
+ buf = malloc(4 * (inlength / size) + 1);
+ if(!buf)
+ return -1; /* Not enough memory. */
+
+ if(type == CURL_ASN1_UTF8_STRING) {
+ /* Just copy. */
+ outlength = inlength;
+ if(outlength)
+ memcpy(buf, from, outlength);
+ }
+ else {
+ for(outlength = 0; from < end;) {
+ int charsize;
+ unsigned int wc;
+
+ wc = 0;
+ switch(size) {
+ case 4:
+ wc = (wc << 8) | *(const unsigned char *) from++;
+ wc = (wc << 8) | *(const unsigned char *) from++;
+ /* FALLTHROUGH */
+ case 2:
+ wc = (wc << 8) | *(const unsigned char *) from++;
+ /* FALLTHROUGH */
+ default: /* case 1: */
+ wc = (wc << 8) | *(const unsigned char *) from++;
+ }
+ charsize = 1;
+ if(wc >= 0x00000080) {
+ if(wc >= 0x00000800) {
+ if(wc >= 0x00010000) {
+ if(wc >= 0x00200000) {
+ free(buf);
+ return -1; /* Invalid char. size for target encoding. */
+ }
+ buf[outlength + 3] = (char) (0x80 | (wc & 0x3F));
+ wc = (wc >> 6) | 0x00010000;
+ charsize++;
+ }
+ buf[outlength + 2] = (char) (0x80 | (wc & 0x3F));
+ wc = (wc >> 6) | 0x00000800;
+ charsize++;
+ }
+ buf[outlength + 1] = (char) (0x80 | (wc & 0x3F));
+ wc = (wc >> 6) | 0x000000C0;
+ charsize++;
+ }
+ buf[outlength] = (char) wc;
+ outlength += charsize;
+ }
+ }
+ buf[outlength] = '\0';
+ *to = buf;
+ return outlength;
+}
+
+/*
+ * Convert an ASN.1 String into its UTF-8 string representation.
+ * Return the dynamically allocated string, or NULL if an error occurs.
+ */
+static const char *string2str(int type, const char *beg, const char *end)
+{
+ char *buf;
+ if(utf8asn1str(&buf, type, beg, end) < 0)
+ return NULL;
+ return buf;
+}
+
+/*
+ * Decimal ASCII encode unsigned integer `x' into the buflen sized buffer at
+ * buf. Return the total number of encoded digits, even if larger than
+ * `buflen'.
+ */
+static size_t encodeUint(char *buf, size_t buflen, unsigned int x)
+{
+ size_t i = 0;
+ unsigned int y = x / 10;
+
+ if(y) {
+ i = encodeUint(buf, buflen, y);
+ x -= y * 10;
+ }
+ if(i < buflen)
+ buf[i] = (char) ('0' + x);
+ i++;
+ if(i < buflen)
+ buf[i] = '\0'; /* Store a terminator if possible. */
+ return i;
+}
+
+/*
+ * Convert an ASN.1 OID into its dotted string representation.
+ * Store the result in th `n'-byte buffer at `buf'.
+ * Return the converted string length, or 0 on errors.
+ */
+static size_t encodeOID(char *buf, size_t buflen,
+ const char *beg, const char *end)
+{
+ size_t i;
+ unsigned int x;
+ unsigned int y;
+
+ /* Process the first two numbers. */
+ y = *(const unsigned char *) beg++;
+ x = y / 40;
+ y -= x * 40;
+ i = encodeUint(buf, buflen, x);
+ if(i < buflen)
+ buf[i] = '.';
+ i++;
+ if(i >= buflen)
+ i += encodeUint(NULL, 0, y);
+ else
+ i += encodeUint(buf + i, buflen - i, y);
+
+ /* Process the trailing numbers. */
+ while(beg < end) {
+ if(i < buflen)
+ buf[i] = '.';
+ i++;
+ x = 0;
+ do {
+ if(x & 0xFF000000)
+ return 0;
+ y = *(const unsigned char *) beg++;
+ x = (x << 7) | (y & 0x7F);
+ } while(y & 0x80);
+ if(i >= buflen)
+ i += encodeUint(NULL, 0, x);
+ else
+ i += encodeUint(buf + i, buflen - i, x);
+ }
+ if(i < buflen)
+ buf[i] = '\0';
+ return i;
+}
+
+/*
+ * Convert an ASN.1 OID into its dotted or symbolic string representation.
+ * Return the dynamically allocated string, or NULL if an error occurs.
+ */
+
+static const char *OID2str(const char *beg, const char *end, bool symbolic)
+{
+ char *buf = NULL;
+ if(beg < end) {
+ size_t buflen = encodeOID(NULL, 0, beg, end);
+ if(buflen) {
+ buf = malloc(buflen + 1); /* one extra for the zero byte */
+ if(buf) {
+ encodeOID(buf, buflen, beg, end);
+ buf[buflen] = '\0';
+
+ if(symbolic) {
+ const struct Curl_OID *op = searchOID(buf);
+ if(op) {
+ free(buf);
+ buf = strdup(op->textoid);
+ }
+ }
+ }
+ }
+ }
+ return buf;
+}
+
+static const char *GTime2str(const char *beg, const char *end)
+{
+ const char *tzp;
+ const char *fracp;
+ char sec1, sec2;
+ size_t fracl;
+ size_t tzl;
+ const char *sep = "";
+
+ /* Convert an ASN.1 Generalized time to a printable string.
+ Return the dynamically allocated string, or NULL if an error occurs. */
+
+ for(fracp = beg; fracp < end && *fracp >= '0' && *fracp <= '9'; fracp++)
+ ;
+
+ /* Get seconds digits. */
+ sec1 = '0';
+ switch(fracp - beg - 12) {
+ case 0:
+ sec2 = '0';
+ break;
+ case 2:
+ sec1 = fracp[-2];
+ /* FALLTHROUGH */
+ case 1:
+ sec2 = fracp[-1];
+ break;
+ default:
+ return NULL;
+ }
+
+ /* Scan for timezone, measure fractional seconds. */
+ tzp = fracp;
+ fracl = 0;
+ if(fracp < end && (*fracp == '.' || *fracp == ',')) {
+ fracp++;
+ do
+ tzp++;
+ while(tzp < end && *tzp >= '0' && *tzp <= '9');
+ /* Strip leading zeroes in fractional seconds. */
+ for(fracl = tzp - fracp - 1; fracl && fracp[fracl - 1] == '0'; fracl--)
+ ;
+ }
+
+ /* Process timezone. */
+ if(tzp >= end)
+ ; /* Nothing to do. */
+ else if(*tzp == 'Z') {
+ tzp = " GMT";
+ end = tzp + 4;
+ }
+ else {
+ sep = " ";
+ tzp++;
+ }
+
+ tzl = end - tzp;
+ return curl_maprintf("%.4s-%.2s-%.2s %.2s:%.2s:%c%c%s%.*s%s%.*s",
+ beg, beg + 4, beg + 6,
+ beg + 8, beg + 10, sec1, sec2,
+ fracl? ".": "", (int)fracl, fracp,
+ sep, (int)tzl, tzp);
+}
+
+/*
+ * Convert an ASN.1 UTC time to a printable string.
+ * Return the dynamically allocated string, or NULL if an error occurs.
+ */
+static const char *UTime2str(const char *beg, const char *end)
+{
+ const char *tzp;
+ size_t tzl;
+ const char *sec;
+
+ for(tzp = beg; tzp < end && *tzp >= '0' && *tzp <= '9'; tzp++)
+ ;
+ /* Get the seconds. */
+ sec = beg + 10;
+ switch(tzp - sec) {
+ case 0:
+ sec = "00";
+ case 2:
+ break;
+ default:
+ return NULL;
+ }
+
+ /* Process timezone. */
+ if(tzp >= end)
+ return NULL;
+ if(*tzp == 'Z') {
+ tzp = "GMT";
+ end = tzp + 3;
+ }
+ else
+ tzp++;
+
+ tzl = end - tzp;
+ return curl_maprintf("%u%.2s-%.2s-%.2s %.2s:%.2s:%.2s %.*s",
+ 20 - (*beg >= '5'), beg, beg + 2, beg + 4,
+ beg + 6, beg + 8, sec,
+ (int)tzl, tzp);
+}
+
+/*
+ * Convert an ASN.1 element to a printable string.
+ * Return the dynamically allocated string, or NULL if an error occurs.
+ */
+static const char *ASN1tostr(struct Curl_asn1Element *elem, int type)
+{
+ if(elem->constructed)
+ return NULL; /* No conversion of structured elements. */
+
+ if(!type)
+ type = elem->tag; /* Type not forced: use element tag as type. */
+
+ switch(type) {
+ case CURL_ASN1_BOOLEAN:
+ return bool2str(elem->beg, elem->end);
+ case CURL_ASN1_INTEGER:
+ case CURL_ASN1_ENUMERATED:
+ return int2str(elem->beg, elem->end);
+ case CURL_ASN1_BIT_STRING:
+ return bit2str(elem->beg, elem->end);
+ case CURL_ASN1_OCTET_STRING:
+ return octet2str(elem->beg, elem->end);
+ case CURL_ASN1_NULL:
+ return strdup("");
+ case CURL_ASN1_OBJECT_IDENTIFIER:
+ return OID2str(elem->beg, elem->end, TRUE);
+ case CURL_ASN1_UTC_TIME:
+ return UTime2str(elem->beg, elem->end);
+ case CURL_ASN1_GENERALIZED_TIME:
+ return GTime2str(elem->beg, elem->end);
+ case CURL_ASN1_UTF8_STRING:
+ case CURL_ASN1_NUMERIC_STRING:
+ case CURL_ASN1_PRINTABLE_STRING:
+ case CURL_ASN1_TELETEX_STRING:
+ case CURL_ASN1_IA5_STRING:
+ case CURL_ASN1_VISIBLE_STRING:
+ case CURL_ASN1_UNIVERSAL_STRING:
+ case CURL_ASN1_BMP_STRING:
+ return string2str(type, elem->beg, elem->end);
+ }
+
+ return NULL; /* Unsupported. */
+}
+
+/*
+ * ASCII encode distinguished name at `dn' into the `buflen'-sized buffer at
+ * `buf'.
+ *
+ * Returns the total string length, even if larger than `buflen' or -1 on
+ * error.
+ */
+static ssize_t encodeDN(char *buf, size_t buflen, struct Curl_asn1Element *dn)
+{
+ struct Curl_asn1Element rdn;
+ struct Curl_asn1Element atv;
+ struct Curl_asn1Element oid;
+ struct Curl_asn1Element value;
+ size_t l = 0;
+ const char *p1;
+ const char *p2;
+ const char *p3;
+ const char *str;
+
+ for(p1 = dn->beg; p1 < dn->end;) {
+ p1 = getASN1Element(&rdn, p1, dn->end);
+ if(!p1)
+ return -1;
+ for(p2 = rdn.beg; p2 < rdn.end;) {
+ p2 = getASN1Element(&atv, p2, rdn.end);
+ if(!p2)
+ return -1;
+ p3 = getASN1Element(&oid, atv.beg, atv.end);
+ if(!p3)
+ return -1;
+ if(!getASN1Element(&value, p3, atv.end))
+ return -1;
+ str = ASN1tostr(&oid, 0);
+ if(!str)
+ return -1;
+
+ /* Encode delimiter.
+ If attribute has a short uppercase name, delimiter is ", ". */
+ if(l) {
+ for(p3 = str; ISUPPER(*p3); p3++)
+ ;
+ for(p3 = (*p3 || p3 - str > 2)? "/": ", "; *p3; p3++) {
+ if(l < buflen)
+ buf[l] = *p3;
+ l++;
+ }
+ }
+
+ /* Encode attribute name. */
+ for(p3 = str; *p3; p3++) {
+ if(l < buflen)
+ buf[l] = *p3;
+ l++;
+ }
+ free((char *) str);
+
+ /* Generate equal sign. */
+ if(l < buflen)
+ buf[l] = '=';
+ l++;
+
+ /* Generate value. */
+ str = ASN1tostr(&value, 0);
+ if(!str)
+ return -1;
+ for(p3 = str; *p3; p3++) {
+ if(l < buflen)
+ buf[l] = *p3;
+ l++;
+ }
+ free((char *) str);
+ }
+ }
+
+ return l;
+}
+
+#endif /* WANT_EXTRACT_CERTINFO */
+
+#ifdef WANT_PARSEX509
+/*
+ * ASN.1 parse an X509 certificate into structure subfields.
+ * Syntax is assumed to have already been checked by the SSL backend.
+ * See RFC 5280.
+ */
+int Curl_parseX509(struct Curl_X509certificate *cert,
+ const char *beg, const char *end)
+{
+ struct Curl_asn1Element elem;
+ struct Curl_asn1Element tbsCertificate;
+ const char *ccp;
+ static const char defaultVersion = 0; /* v1. */
+
+ cert->certificate.header = NULL;
+ cert->certificate.beg = beg;
+ cert->certificate.end = end;
+
+ /* Get the sequence content. */
+ if(!getASN1Element(&elem, beg, end))
+ return -1; /* Invalid bounds/size. */
+ beg = elem.beg;
+ end = elem.end;
+
+ /* Get tbsCertificate. */
+ beg = getASN1Element(&tbsCertificate, beg, end);
+ if(!beg)
+ return -1;
+ /* Skip the signatureAlgorithm. */
+ beg = getASN1Element(&cert->signatureAlgorithm, beg, end);
+ if(!beg)
+ return -1;
+ /* Get the signatureValue. */
+ if(!getASN1Element(&cert->signature, beg, end))
+ return -1;
+
+ /* Parse TBSCertificate. */
+ beg = tbsCertificate.beg;
+ end = tbsCertificate.end;
+ /* Get optional version, get serialNumber. */
+ cert->version.header = NULL;
+ cert->version.beg = &defaultVersion;
+ cert->version.end = &defaultVersion + sizeof(defaultVersion);
+ beg = getASN1Element(&elem, beg, end);
+ if(!beg)
+ return -1;
+ if(elem.tag == 0) {
+ if(!getASN1Element(&cert->version, elem.beg, elem.end))
+ return -1;
+ beg = getASN1Element(&elem, beg, end);
+ if(!beg)
+ return -1;
+ }
+ cert->serialNumber = elem;
+ /* Get signature algorithm. */
+ beg = getASN1Element(&cert->signatureAlgorithm, beg, end);
+ /* Get issuer. */
+ beg = getASN1Element(&cert->issuer, beg, end);
+ if(!beg)
+ return -1;
+ /* Get notBefore and notAfter. */
+ beg = getASN1Element(&elem, beg, end);
+ if(!beg)
+ return -1;
+ ccp = getASN1Element(&cert->notBefore, elem.beg, elem.end);
+ if(!ccp)
+ return -1;
+ if(!getASN1Element(&cert->notAfter, ccp, elem.end))
+ return -1;
+ /* Get subject. */
+ beg = getASN1Element(&cert->subject, beg, end);
+ if(!beg)
+ return -1;
+ /* Get subjectPublicKeyAlgorithm and subjectPublicKey. */
+ beg = getASN1Element(&cert->subjectPublicKeyInfo, beg, end);
+ if(!beg)
+ return -1;
+ ccp = getASN1Element(&cert->subjectPublicKeyAlgorithm,
+ cert->subjectPublicKeyInfo.beg,
+ cert->subjectPublicKeyInfo.end);
+ if(!ccp)
+ return -1;
+ if(!getASN1Element(&cert->subjectPublicKey, ccp,
+ cert->subjectPublicKeyInfo.end))
+ return -1;
+ /* Get optional issuerUiqueID, subjectUniqueID and extensions. */
+ cert->issuerUniqueID.tag = cert->subjectUniqueID.tag = 0;
+ cert->extensions.tag = elem.tag = 0;
+ cert->issuerUniqueID.header = cert->subjectUniqueID.header = NULL;
+ cert->issuerUniqueID.beg = cert->issuerUniqueID.end = "";
+ cert->subjectUniqueID.beg = cert->subjectUniqueID.end = "";
+ cert->extensions.header = NULL;
+ cert->extensions.beg = cert->extensions.end = "";
+ if(beg < end) {
+ beg = getASN1Element(&elem, beg, end);
+ if(!beg)
+ return -1;
+ }
+ if(elem.tag == 1) {
+ cert->issuerUniqueID = elem;
+ if(beg < end) {
+ beg = getASN1Element(&elem, beg, end);
+ if(!beg)
+ return -1;
+ }
+ }
+ if(elem.tag == 2) {
+ cert->subjectUniqueID = elem;
+ if(beg < end) {
+ beg = getASN1Element(&elem, beg, end);
+ if(!beg)
+ return -1;
+ }
+ }
+ if(elem.tag == 3)
+ if(!getASN1Element(&cert->extensions, elem.beg, elem.end))
+ return -1;
+ return 0;
+}
+
+#endif /* WANT_PARSEX509 */
+
+#ifdef WANT_EXTRACT_CERTINFO
+
+/*
+ * Copy at most 64-characters, terminate with a newline and returns the
+ * effective number of stored characters.
+ */
+static size_t copySubstring(char *to, const char *from)
+{
+ size_t i;
+ for(i = 0; i < 64; i++) {
+ to[i] = *from;
+ if(!*from++)
+ break;
+ }
+
+ to[i++] = '\n';
+ return i;
+}
+
+static const char *dumpAlgo(struct Curl_asn1Element *param,
+ const char *beg, const char *end)
+{
+ struct Curl_asn1Element oid;
+
+ /* Get algorithm parameters and return algorithm name. */
+
+ beg = getASN1Element(&oid, beg, end);
+ if(!beg)
+ return NULL;
+ param->header = NULL;
+ param->tag = 0;
+ param->beg = param->end = end;
+ if(beg < end)
+ if(!getASN1Element(param, beg, end))
+ return NULL;
+ return OID2str(oid.beg, oid.end, TRUE);
+}
+
+/* return 0 on success, 1 on error */
+static int do_pubkey_field(struct Curl_easy *data, int certnum,
+ const char *label, struct Curl_asn1Element *elem)
+{
+ const char *output;
+ CURLcode result = CURLE_OK;
+
+ /* Generate a certificate information record for the public key. */
+
+ output = ASN1tostr(elem, 0);
+ if(output) {
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, label, output);
+ if(!certnum && !result)
+ infof(data, " %s: %s", label, output);
+ free((char *) output);
+ }
+ return result ? 1 : 0;
+}
+
+/* return 0 on success, 1 on error */
+static int do_pubkey(struct Curl_easy *data, int certnum,
+ const char *algo, struct Curl_asn1Element *param,
+ struct Curl_asn1Element *pubkey)
+{
+ struct Curl_asn1Element elem;
+ struct Curl_asn1Element pk;
+ const char *p;
+
+ /* Generate all information records for the public key. */
+
+ if(strcasecompare(algo, "ecPublicKey")) {
+ /*
+ * ECC public key is all the data, a value of type BIT STRING mapped to
+ * OCTET STRING and should not be parsed as an ASN.1 value.
+ */
+ const size_t len = ((pubkey->end - pubkey->beg - 2) * 4);
+ if(!certnum)
+ infof(data, " ECC Public Key (%lu bits)", len);
+ if(data->set.ssl.certinfo) {
+ char q[sizeof(len) * 8 / 3 + 1];
+ (void)msnprintf(q, sizeof(q), "%lu", len);
+ if(Curl_ssl_push_certinfo(data, certnum, "ECC Public Key", q))
+ return 1;
+ }
+ return do_pubkey_field(data, certnum, "ecPublicKey", pubkey);
+ }
+
+ /* Get the public key (single element). */
+ if(!getASN1Element(&pk, pubkey->beg + 1, pubkey->end))
+ return 1;
+
+ if(strcasecompare(algo, "rsaEncryption")) {
+ const char *q;
+ size_t len;
+
+ p = getASN1Element(&elem, pk.beg, pk.end);
+ if(!p)
+ return 1;
+
+ /* Compute key length. */
+ for(q = elem.beg; !*q && q < elem.end; q++)
+ ;
+ len = ((elem.end - q) * 8);
+ if(len) {
+ unsigned int i;
+ for(i = *(unsigned char *) q; !(i & 0x80); i <<= 1)
+ len--;
+ }
+ if(len > 32)
+ elem.beg = q; /* Strip leading zero bytes. */
+ if(!certnum)
+ infof(data, " RSA Public Key (%lu bits)", len);
+ if(data->set.ssl.certinfo) {
+ char r[sizeof(len) * 8 / 3 + 1];
+ msnprintf(r, sizeof(r), "%lu", len);
+ if(Curl_ssl_push_certinfo(data, certnum, "RSA Public Key", r))
+ return 1;
+ }
+ /* Generate coefficients. */
+ if(do_pubkey_field(data, certnum, "rsa(n)", &elem))
+ return 1;
+ if(!getASN1Element(&elem, p, pk.end))
+ return 1;
+ if(do_pubkey_field(data, certnum, "rsa(e)", &elem))
+ return 1;
+ }
+ else if(strcasecompare(algo, "dsa")) {
+ p = getASN1Element(&elem, param->beg, param->end);
+ if(p) {
+ if(do_pubkey_field(data, certnum, "dsa(p)", &elem))
+ return 1;
+ p = getASN1Element(&elem, p, param->end);
+ if(p) {
+ if(do_pubkey_field(data, certnum, "dsa(q)", &elem))
+ return 1;
+ if(getASN1Element(&elem, p, param->end)) {
+ if(do_pubkey_field(data, certnum, "dsa(g)", &elem))
+ return 1;
+ if(do_pubkey_field(data, certnum, "dsa(pub_key)", &pk))
+ return 1;
+ }
+ }
+ }
+ }
+ else if(strcasecompare(algo, "dhpublicnumber")) {
+ p = getASN1Element(&elem, param->beg, param->end);
+ if(p) {
+ if(do_pubkey_field(data, certnum, "dh(p)", &elem))
+ return 1;
+ if(getASN1Element(&elem, param->beg, param->end)) {
+ if(do_pubkey_field(data, certnum, "dh(g)", &elem))
+ return 1;
+ if(do_pubkey_field(data, certnum, "dh(pub_key)", &pk))
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * Convert an ASN.1 distinguished name into a printable string.
+ * Return the dynamically allocated string, or NULL if an error occurs.
+ */
+static const char *DNtostr(struct Curl_asn1Element *dn)
+{
+ char *buf = NULL;
+ ssize_t buflen = encodeDN(NULL, 0, dn);
+
+ if(buflen >= 0) {
+ buf = malloc(buflen + 1);
+ if(buf) {
+ if(encodeDN(buf, buflen + 1, dn) == -1) {
+ free(buf);
+ return NULL;
+ }
+ buf[buflen] = '\0';
+ }
+ }
+ return buf;
+}
+
+CURLcode Curl_extract_certinfo(struct Curl_easy *data,
+ int certnum,
+ const char *beg,
+ const char *end)
+{
+ struct Curl_X509certificate cert;
+ struct Curl_asn1Element param;
+ const char *ccp;
+ char *cp1;
+ size_t cl1;
+ char *cp2;
+ CURLcode result = CURLE_OK;
+ unsigned int version;
+ size_t i;
+ size_t j;
+
+ if(!data->set.ssl.certinfo)
+ if(certnum)
+ return CURLE_OK;
+
+ /* Prepare the certificate information for curl_easy_getinfo(). */
+
+ /* Extract the certificate ASN.1 elements. */
+ if(Curl_parseX509(&cert, beg, end))
+ return CURLE_PEER_FAILED_VERIFICATION;
+
+ /* Subject. */
+ ccp = DNtostr(&cert.subject);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo) {
+ result = Curl_ssl_push_certinfo(data, certnum, "Subject", ccp);
+ if(result)
+ return result;
+ }
+ if(!certnum)
+ infof(data, "%2d Subject: %s", certnum, ccp);
+ free((char *) ccp);
+
+ /* Issuer. */
+ ccp = DNtostr(&cert.issuer);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo) {
+ result = Curl_ssl_push_certinfo(data, certnum, "Issuer", ccp);
+ }
+ if(!certnum)
+ infof(data, " Issuer: %s", ccp);
+ free((char *) ccp);
+ if(result)
+ return result;
+
+ /* Version (always fits in less than 32 bits). */
+ version = 0;
+ for(ccp = cert.version.beg; ccp < cert.version.end; ccp++)
+ version = (version << 8) | *(const unsigned char *) ccp;
+ if(data->set.ssl.certinfo) {
+ ccp = curl_maprintf("%lx", version);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ result = Curl_ssl_push_certinfo(data, certnum, "Version", ccp);
+ free((char *) ccp);
+ if(result)
+ return result;
+ }
+ if(!certnum)
+ infof(data, " Version: %lu (0x%lx)", version + 1, version);
+
+ /* Serial number. */
+ ccp = ASN1tostr(&cert.serialNumber, 0);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, "Serial Number", ccp);
+ if(!certnum)
+ infof(data, " Serial Number: %s", ccp);
+ free((char *) ccp);
+ if(result)
+ return result;
+
+ /* Signature algorithm .*/
+ ccp = dumpAlgo(¶m, cert.signatureAlgorithm.beg,
+ cert.signatureAlgorithm.end);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, "Signature Algorithm", ccp);
+ if(!certnum)
+ infof(data, " Signature Algorithm: %s", ccp);
+ free((char *) ccp);
+ if(result)
+ return result;
+
+ /* Start Date. */
+ ccp = ASN1tostr(&cert.notBefore, 0);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, "Start Date", ccp);
+ if(!certnum)
+ infof(data, " Start Date: %s", ccp);
+ free((char *) ccp);
+ if(result)
+ return result;
+
+ /* Expire Date. */
+ ccp = ASN1tostr(&cert.notAfter, 0);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, "Expire Date", ccp);
+ if(!certnum)
+ infof(data, " Expire Date: %s", ccp);
+ free((char *) ccp);
+ if(result)
+ return result;
+
+ /* Public Key Algorithm. */
+ ccp = dumpAlgo(¶m, cert.subjectPublicKeyAlgorithm.beg,
+ cert.subjectPublicKeyAlgorithm.end);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, "Public Key Algorithm",
+ ccp);
+ if(!result) {
+ int ret;
+ if(!certnum)
+ infof(data, " Public Key Algorithm: %s", ccp);
+ ret = do_pubkey(data, certnum, ccp, ¶m, &cert.subjectPublicKey);
+ if(ret)
+ result = CURLE_OUT_OF_MEMORY; /* the most likely error */
+ }
+ free((char *) ccp);
+ if(result)
+ return result;
+
+ /* Signature. */
+ ccp = ASN1tostr(&cert.signature, 0);
+ if(!ccp)
+ return CURLE_OUT_OF_MEMORY;
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, "Signature", ccp);
+ if(!certnum)
+ infof(data, " Signature: %s", ccp);
+ free((char *) ccp);
+ if(result)
+ return result;
+
+ /* Generate PEM certificate. */
+ result = Curl_base64_encode(cert.certificate.beg,
+ cert.certificate.end - cert.certificate.beg,
+ &cp1, &cl1);
+ if(result)
+ return result;
+ /* Compute the number of characters in final certificate string. Format is:
+ -----BEGIN CERTIFICATE-----\n
+ <max 64 base64 characters>\n
+ .
+ .
+ .
+ -----END CERTIFICATE-----\n
+ */
+ i = 28 + cl1 + (cl1 + 64 - 1) / 64 + 26;
+ cp2 = malloc(i + 1);
+ if(!cp2) {
+ free(cp1);
+ return CURLE_OUT_OF_MEMORY;
+ }
+ /* Build the certificate string. */
+ i = copySubstring(cp2, "-----BEGIN CERTIFICATE-----");
+ for(j = 0; j < cl1; j += 64)
+ i += copySubstring(cp2 + i, cp1 + j);
+ i += copySubstring(cp2 + i, "-----END CERTIFICATE-----");
+ cp2[i] = '\0';
+ free(cp1);
+ if(data->set.ssl.certinfo)
+ result = Curl_ssl_push_certinfo(data, certnum, "Cert", cp2);
+ if(!certnum)
+ infof(data, "%s", cp2);
+ free(cp2);
+ return result;
+}
+
+#endif /* WANT_EXTRACT_CERTINFO */
+
+#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL
+ * or USE_SECTRANSP */
+
+#ifdef WANT_VERIFYHOST
+
+static const char *checkOID(const char *beg, const char *end,
+ const char *oid)
+{
+ struct Curl_asn1Element e;
+ const char *ccp;
+ const char *p;
+ bool matched;
+
+ /* Check if first ASN.1 element at `beg' is the given OID.
+ Return a pointer in the source after the OID if found, else NULL. */
+
+ ccp = getASN1Element(&e, beg, end);
+ if(!ccp || e.tag != CURL_ASN1_OBJECT_IDENTIFIER)
+ return NULL;
+
+ p = OID2str(e.beg, e.end, FALSE);
+ if(!p)
+ return NULL;
+
+ matched = !strcmp(p, oid);
+ free((char *) p);
+ return matched? ccp: NULL;
+}
+
+CURLcode Curl_verifyhost(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ const char *beg, const char *end)
+{
+ struct ssl_connect_data *connssl = cf->ctx;
+ struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
+ struct Curl_X509certificate cert;
+ struct Curl_asn1Element dn;
+ struct Curl_asn1Element elem;
+ struct Curl_asn1Element ext;
+ struct Curl_asn1Element name;
+ const char *p;
+ const char *q;
+ char *dnsname;
+ int matched = -1;
+ size_t addrlen = (size_t) -1;
+ ssize_t len;
+ size_t hostlen;
+
+#ifdef ENABLE_IPV6
+ struct in6_addr addr;
+#else
+ struct in_addr addr;
+#endif
+
+ /* Verify that connection server matches info in X509 certificate at
+ `beg'..`end'. */
+
+ if(!conn_config->verifyhost)
+ return CURLE_OK;
+
+ if(Curl_parseX509(&cert, beg, end))
+ return CURLE_PEER_FAILED_VERIFICATION;
+
+ hostlen = strlen(connssl->hostname);
+
+ /* Get the server IP address. */
+#ifdef ENABLE_IPV6
+ if(conn->bits.ipv6_ip && Curl_inet_pton(AF_INET6, connssl->hostname, &addr))
+ addrlen = sizeof(struct in6_addr);
+ else
+#endif
+ if(Curl_inet_pton(AF_INET, connssl->hostname, &addr))
+ addrlen = sizeof(struct in_addr);
+
+ /* Process extensions. */
+ for(p = cert.extensions.beg; p < cert.extensions.end && matched != 1;) {
+ p = getASN1Element(&ext, p, cert.extensions.end);
+ if(!p)
+ return CURLE_PEER_FAILED_VERIFICATION;
+
+ /* Check if extension is a subjectAlternativeName. */
+ ext.beg = checkOID(ext.beg, ext.end, sanOID);
+ if(ext.beg) {
+ ext.beg = getASN1Element(&elem, ext.beg, ext.end);
+ if(!ext.beg)
+ return CURLE_PEER_FAILED_VERIFICATION;
+ /* Skip critical if present. */
+ if(elem.tag == CURL_ASN1_BOOLEAN) {
+ ext.beg = getASN1Element(&elem, ext.beg, ext.end);
+ if(!ext.beg)
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ /* Parse the octet string contents: is a single sequence. */
+ if(!getASN1Element(&elem, elem.beg, elem.end))
+ return CURLE_PEER_FAILED_VERIFICATION;
+ /* Check all GeneralNames. */
+ for(q = elem.beg; matched != 1 && q < elem.end;) {
+ q = getASN1Element(&name, q, elem.end);
+ if(!q)
+ break;
+ switch(name.tag) {
+ case 2: /* DNS name. */
+ matched = 0;
+ len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING,
+ name.beg, name.end);
+ if(len > 0) {
+ if(size_t)len == strlen(dnsname)
+ matched = Curl_cert_hostcheck(dnsname, (size_t)len,
+ connssl->hostname, hostlen);
+ free(dnsname);
+ }
+ break;
+
+ case 7: /* IP address. */
+ matched = (name.end - name.beg) == addrlen &&
+ !memcmp(&addr, name.beg, addrlen);
+ break;
+ }
+ }
+ }
+ }
+
+ switch(matched) {
+ case 1:
+ /* an alternative name matched the server hostname */
+ infof(data, " subjectAltName: %s matched", connssl->dispname);
+ return CURLE_OK;
+ case 0:
+ /* an alternative name field existed, but didn't match and then
+ we MUST fail */
+ infof(data, " subjectAltName does not match %s", connssl->dispname);
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+
+ /* Process subject. */
+ name.header = NULL;
+ name.beg = name.end = "";
+ q = cert.subject.beg;
+ /* we have to look to the last occurrence of a commonName in the
+ distinguished one to get the most significant one. */
+ while(q < cert.subject.end) {
+ q = getASN1Element(&dn, q, cert.subject.end);
+ if(!q)
+ break;
+ for(p = dn.beg; p < dn.end;) {
+ p = getASN1Element(&elem, p, dn.end);
+ if(!p)
+ return CURLE_PEER_FAILED_VERIFICATION;
+ /* We have a DN's AttributeTypeAndValue: check it in case it's a CN. */
+ elem.beg = checkOID(elem.beg, elem.end, cnOID);
+ if(elem.beg)
+ name = elem; /* Latch CN. */
+ }
+ }
+
+ /* Check the CN if found. */
+ if(!getASN1Element(&elem, name.beg, name.end))
+ failf(data, "SSL: unable to obtain common name from peer certificate");
+ else {
+ len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end);
+ if(len < 0)
+ return CURLE_OUT_OF_MEMORY;
+ if(strlen(dnsname) != (size_t) len) /* Nul byte in string ? */
+ failf(data, "SSL: illegal cert name field");
+ else if(Curl_cert_hostcheck((const char *) dnsname,
+ len, connssl->hostname, hostlen)) {
+ infof(data, " common name: %s (matched)", dnsname);
+ free(dnsname);
+ return CURLE_OK;
+ }
+ else
+ failf(data, "SSL: certificate subject name '%s' does not match "
+ "target host name '%s'", dnsname, connssl->dispname);
+ free(dnsname);
+ }
+
+ return CURLE_PEER_FAILED_VERIFICATION;
+}
+
+#endif /* WANT_VERIFYHOST */
diff --git a/libs/libcurl/src/vtls/x509asn1.h b/libs/libcurl/src/vtls/x509asn1.h index a18fa11530..e582f72464 100644 --- a/libs/libcurl/src/vtls/x509asn1.h +++ b/libs/libcurl/src/vtls/x509asn1.h @@ -1,80 +1,81 @@ -#ifndef HEADER_CURL_X509ASN1_H -#define HEADER_CURL_X509ASN1_H - -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. - * - * This software is licensed as described in the file COPYING, which - * you should have received as part of this distribution. The terms - * are also available at https://curl.se/docs/copyright.html. - * - * You may opt to use, copy, modify, merge, publish, distribute and/or sell - * copies of the Software, and permit persons to whom the Software is - * furnished to do so, under the terms of the COPYING file. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - * SPDX-License-Identifier: curl - * - ***************************************************************************/ - -#include "curl_setup.h" - -#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \ - defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP) - -#include "urldata.h" - -/* - * Types. - */ - -/* ASN.1 parsed element. */ -struct Curl_asn1Element { - const char *header; /* Pointer to header byte. */ - const char *beg; /* Pointer to element data. */ - const char *end; /* Pointer to 1st byte after element. */ - unsigned char class; /* ASN.1 element class. */ - unsigned char tag; /* ASN.1 element tag. */ - bool constructed; /* Element is constructed. */ -}; - -/* X509 certificate: RFC 5280. */ -struct Curl_X509certificate { - struct Curl_asn1Element certificate; - struct Curl_asn1Element version; - struct Curl_asn1Element serialNumber; - struct Curl_asn1Element signatureAlgorithm; - struct Curl_asn1Element signature; - struct Curl_asn1Element issuer; - struct Curl_asn1Element notBefore; - struct Curl_asn1Element notAfter; - struct Curl_asn1Element subject; - struct Curl_asn1Element subjectPublicKeyInfo; - struct Curl_asn1Element subjectPublicKeyAlgorithm; - struct Curl_asn1Element subjectPublicKey; - struct Curl_asn1Element issuerUniqueID; - struct Curl_asn1Element subjectUniqueID; - struct Curl_asn1Element extensions; -}; - -/* - * Prototypes. - */ - -int Curl_parseX509(struct Curl_X509certificate *cert, - const char *beg, const char *end); -CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum, - const char *beg, const char *end); -CURLcode Curl_verifyhost(struct Curl_easy *data, struct connectdata *conn, - const char *beg, const char *end); -#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL - * or USE_SECTRANSP */ -#endif /* HEADER_CURL_X509ASN1_H */ +#ifndef HEADER_CURL_X509ASN1_H
+#define HEADER_CURL_X509ASN1_H
+
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if defined(USE_GSKIT) || defined(USE_NSS) || defined(USE_GNUTLS) || \
+ defined(USE_WOLFSSL) || defined(USE_SCHANNEL) || defined(USE_SECTRANSP)
+
+#include "cfilters.h"
+#include "urldata.h"
+
+/*
+ * Types.
+ */
+
+/* ASN.1 parsed element. */
+struct Curl_asn1Element {
+ const char *header; /* Pointer to header byte. */
+ const char *beg; /* Pointer to element data. */
+ const char *end; /* Pointer to 1st byte after element. */
+ unsigned char class; /* ASN.1 element class. */
+ unsigned char tag; /* ASN.1 element tag. */
+ bool constructed; /* Element is constructed. */
+};
+
+/* X509 certificate: RFC 5280. */
+struct Curl_X509certificate {
+ struct Curl_asn1Element certificate;
+ struct Curl_asn1Element version;
+ struct Curl_asn1Element serialNumber;
+ struct Curl_asn1Element signatureAlgorithm;
+ struct Curl_asn1Element signature;
+ struct Curl_asn1Element issuer;
+ struct Curl_asn1Element notBefore;
+ struct Curl_asn1Element notAfter;
+ struct Curl_asn1Element subject;
+ struct Curl_asn1Element subjectPublicKeyInfo;
+ struct Curl_asn1Element subjectPublicKeyAlgorithm;
+ struct Curl_asn1Element subjectPublicKey;
+ struct Curl_asn1Element issuerUniqueID;
+ struct Curl_asn1Element subjectUniqueID;
+ struct Curl_asn1Element extensions;
+};
+
+/*
+ * Prototypes.
+ */
+
+int Curl_parseX509(struct Curl_X509certificate *cert,
+ const char *beg, const char *end);
+CURLcode Curl_extract_certinfo(struct Curl_easy *data, int certnum,
+ const char *beg, const char *end);
+CURLcode Curl_verifyhost(struct Curl_cfilter *cf, struct Curl_easy *data,
+ const char *beg, const char *end);
+#endif /* USE_GSKIT or USE_NSS or USE_GNUTLS or USE_WOLFSSL or USE_SCHANNEL
+ * or USE_SECTRANSP */
+#endif /* HEADER_CURL_X509ASN1_H */
|