| /*************************************************************************** |
| * _ _ ____ _ |
| * Project ___| | | | _ \| | |
| * / __| | | | |_) | | |
| * | (__| |_| | _ <| |___ |
| * \___|\___/|_| \_\_____| |
| * |
| * Copyright (C) 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 |
| |
| #include <wolfssl/options.h> |
| #include <wolfssl/version.h> |
| |
| #if LIBWOLFSSL_VERSION_HEX < 0x05000000 /* wolfSSL 5.0.0 (2021-11-01) */ |
| #error "wolfSSL version should be at least 5.0.0" |
| #endif |
| #if defined(OPENSSL_COEXIST) && LIBWOLFSSL_VERSION_HEX < 0x05007006 |
| #error "wolfSSL 5.7.6 or newer is required to coexist with OpenSSL" |
| #endif |
| |
| /* 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 "urldata.h" |
| #include "curl_trc.h" |
| #include "httpsrr.h" |
| #include "cf-dns.h" |
| #include "vtls/vtls.h" |
| #include "vtls/vtls_int.h" |
| #include "vtls/vtls_scache.h" |
| #include "vtls/keylog.h" |
| #include "connect.h" /* for the connect timeout */ |
| #include "progress.h" |
| #include "curlx/strdup.h" |
| #include "curlx/strcopy.h" |
| #include "vtls/x509asn1.h" |
| #ifdef USE_ECH |
| #include "curlx/base64.h" |
| #endif |
| |
| #include <wolfssl/ssl.h> |
| #include <wolfssl/error-ssl.h> |
| |
| #include "vtls/wolfssl.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 |
| |
| #ifdef HAVE_WOLFSSL_BIO_NEW |
| #define USE_BIO_CHAIN |
| #ifdef HAVE_WOLFSSL_BIO_SET_SHUTDOWN |
| #define USE_FULL_BIO |
| #else /* HAVE_WOLFSSL_BIO_SET_SHUTDOWN */ |
| #undef USE_FULL_BIO |
| #endif |
| /* wolfSSL 5.7.4 and older do not have these symbols, but only the |
| * OpenSSL ones. */ |
| #ifndef WOLFSSL_BIO_CTRL_GET_CLOSE |
| #define WOLFSSL_BIO_CTRL_GET_CLOSE BIO_CTRL_GET_CLOSE |
| #define WOLFSSL_BIO_CTRL_SET_CLOSE BIO_CTRL_SET_CLOSE |
| #define WOLFSSL_BIO_CTRL_FLUSH BIO_CTRL_FLUSH |
| #define WOLFSSL_BIO_CTRL_DUP BIO_CTRL_DUP |
| #define wolfSSL_BIO_set_retry_write BIO_set_retry_write |
| #define wolfSSL_BIO_set_retry_read BIO_set_retry_read |
| #endif /* !WOLFSSL_BIO_CTRL_GET_CLOSE */ |
| |
| #else /* HAVE_WOLFSSL_BIO_NEW */ |
| #undef USE_BIO_CHAIN |
| #endif |
| |
| #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 is not logged. |
| * For TLS 1.2 and before, we use wolfSSL_get_keys(). |
| * wolfSSL_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 wssl_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(wolfSSL_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 /* HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */ |
| |
| static void wssl_log_tls12_secret(WOLFSSL *ssl) |
| { |
| unsigned char *ms, *sr, *cr; |
| unsigned int msLen, srLen, crLen, i, x = 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; |
| } |
| |
| if(wolfSSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) != |
| WOLFSSL_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 wssl_do_file_type(const char *type) |
| { |
| if(!type || !type[0]) |
| return WOLFSSL_FILETYPE_PEM; |
| if(curl_strequal(type, "PEM")) |
| return WOLFSSL_FILETYPE_PEM; |
| if(curl_strequal(type, "DER")) |
| return WOLFSSL_FILETYPE_ASN1; |
| return -1; |
| } |
| |
| #ifdef WOLFSSL_HAVE_KYBER |
| struct group_name_map { |
| const word16 group; |
| const char *name; |
| }; |
| |
| static const struct group_name_map gnm[] = { |
| { WOLFSSL_ML_KEM_512, "ML_KEM_512" }, |
| { WOLFSSL_ML_KEM_768, "ML_KEM_768" }, |
| { WOLFSSL_ML_KEM_1024, "ML_KEM_1024" }, |
| { WOLFSSL_SECP256R1MLKEM512, "SecP256r1MLKEM512" }, |
| { WOLFSSL_SECP384R1MLKEM768, "SecP384r1MLKEM768" }, |
| { WOLFSSL_SECP521R1MLKEM1024, "SecP521r1MLKEM1024" }, |
| { WOLFSSL_SECP256R1MLKEM768, "SecP256r1MLKEM768" }, |
| { WOLFSSL_SECP384R1MLKEM1024, "SecP384r1MLKEM1024" }, |
| { WOLFSSL_X25519MLKEM768, "X25519MLKEM768" }, |
| { 0, NULL } |
| }; |
| #endif |
| |
| #ifdef USE_BIO_CHAIN |
| |
| static int wssl_bio_cf_create(WOLFSSL_BIO *bio) |
| { |
| #ifdef USE_FULL_BIO |
| wolfSSL_BIO_set_shutdown(bio, 1); |
| #endif |
| wolfSSL_BIO_set_data(bio, NULL); |
| return 1; |
| } |
| |
| static int wssl_bio_cf_destroy(WOLFSSL_BIO *bio) |
| { |
| if(!bio) |
| return 0; |
| return 1; |
| } |
| |
| static long wssl_bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr) |
| { |
| struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio); |
| long ret = 1; |
| |
| (void)cf; |
| (void)ptr; |
| (void)num; |
| switch(cmd) { |
| case WOLFSSL_BIO_CTRL_GET_CLOSE: |
| #ifdef USE_FULL_BIO |
| ret = (long)wolfSSL_BIO_get_shutdown(bio); |
| #else |
| ret = 0; |
| #endif |
| break; |
| case WOLFSSL_BIO_CTRL_SET_CLOSE: |
| #ifdef USE_FULL_BIO |
| wolfSSL_BIO_set_shutdown(bio, (int)num); |
| #endif |
| break; |
| case WOLFSSL_BIO_CTRL_FLUSH: |
| /* we do no delayed writes, but if we ever would, this |
| * needs to trigger it. */ |
| ret = 1; |
| break; |
| case WOLFSSL_BIO_CTRL_DUP: |
| ret = 1; |
| break; |
| #ifdef WOLFSSL_BIO_CTRL_EOF |
| case WOLFSSL_BIO_CTRL_EOF: { |
| /* EOF has been reached on input? */ |
| struct ssl_connect_data *connssl = cf->ctx; |
| return connssl->peer_closed; |
| } |
| #endif |
| default: |
| ret = 0; |
| break; |
| } |
| return ret; |
| } |
| |
| static int wssl_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 wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| struct Curl_easy *data = CF_DATA_CURRENT(cf); |
| size_t nwritten, skiplen = 0; |
| CURLcode result = CURLE_OK; |
| |
| DEBUGASSERT(data); |
| if(wssl->shutting_down && wssl->io_send_blocked_len && |
| (wssl->io_send_blocked_len < blen)) { |
| /* bug in wolfSSL: <https://github.com/wolfSSL/wolfssl/issues/7784> |
| * It adds the close notify message again every time we retry |
| * sending during shutdown. */ |
| CURL_TRC_CF(data, cf, "bio_write, shutdown restrict send of %d" |
| " to %d bytes", blen, wssl->io_send_blocked_len); |
| skiplen = (ssize_t)(blen - wssl->io_send_blocked_len); |
| blen = wssl->io_send_blocked_len; |
| } |
| result = Curl_conn_cf_send(cf->next, data, |
| (const uint8_t *)buf, blen, FALSE, &nwritten); |
| wssl->io_result = result; |
| CURL_TRC_CF(data, cf, "bio_write(len=%d) -> %d, %zu", |
| blen, result, nwritten); |
| #ifdef USE_FULL_BIO |
| wolfSSL_BIO_clear_retry_flags(bio); |
| #endif |
| if(result == CURLE_AGAIN) { |
| wolfSSL_BIO_set_retry_write(bio); |
| if(wssl->shutting_down && !wssl->io_send_blocked_len) |
| wssl->io_send_blocked_len = blen; |
| } |
| else if(!result && skiplen) |
| nwritten += skiplen; |
| return result ? -1 : (int)nwritten; |
| } |
| |
| static int wssl_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 wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| struct Curl_easy *data = CF_DATA_CURRENT(cf); |
| size_t nread = 0; |
| CURLcode result = CURLE_OK; |
| |
| DEBUGASSERT(data); |
| if(!data || (blen < 0)) { |
| wssl->io_result = CURLE_FAILED_INIT; |
| return -1; |
| } |
| if(!buf || !blen) |
| return 0; |
| |
| if((connssl->connecting_state == ssl_connect_2) && |
| !wssl->x509_store_setup) { |
| /* During handshake, init the x509 store before receiving the |
| * server response. This allows sending of ClientHello without delay. */ |
| result = Curl_wssl_setup_x509_store(cf, data, wssl); |
| if(result) { |
| CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", result); |
| wssl->io_result = result; |
| return -1; |
| } |
| } |
| |
| result = Curl_conn_cf_recv(cf->next, data, buf, blen, &nread); |
| wssl->io_result = result; |
| CURL_TRC_CF(data, cf, "bio_read(len=%d) -> %d, %zu", blen, result, nread); |
| #ifdef USE_FULL_BIO |
| wolfSSL_BIO_clear_retry_flags(bio); |
| #endif |
| if(result == CURLE_AGAIN) |
| wolfSSL_BIO_set_retry_read(bio); |
| else if(nread == 0) |
| connssl->peer_closed = TRUE; |
| return result ? -1 : (int)nread; |
| } |
| |
| static WOLFSSL_BIO_METHOD *wssl_bio_cf_method = NULL; |
| |
| static int wssl_bio_cf_init_methods(void) |
| { |
| wssl_bio_cf_method = wolfSSL_BIO_meth_new(WOLFSSL_BIO_MEMORY, |
| "wolfSSL CF BIO"); |
| if(!wssl_bio_cf_method) |
| return FALSE; /* error */ |
| wolfSSL_BIO_meth_set_write(wssl_bio_cf_method, &wssl_bio_cf_out_write); |
| wolfSSL_BIO_meth_set_read(wssl_bio_cf_method, &wssl_bio_cf_in_read); |
| wolfSSL_BIO_meth_set_ctrl(wssl_bio_cf_method, &wssl_bio_cf_ctrl); |
| wolfSSL_BIO_meth_set_create(wssl_bio_cf_method, &wssl_bio_cf_create); |
| wolfSSL_BIO_meth_set_destroy(wssl_bio_cf_method, &wssl_bio_cf_destroy); |
| return TRUE; /* fine */ |
| } |
| |
| static void wssl_bio_cf_free_methods(void) |
| { |
| wolfSSL_BIO_meth_free(wssl_bio_cf_method); |
| wssl_bio_cf_method = NULL; |
| } |
| |
| #else /* USE_BIO_CHAIN */ |
| |
| #define wssl_bio_cf_init_methods() TRUE |
| #define wssl_bio_cf_free_methods() Curl_nop_stmt |
| |
| #endif /* !USE_BIO_CHAIN */ |
| |
| #ifdef HAVE_EX_DATA |
| CURLcode Curl_wssl_cache_session(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| const char *ssl_peer_key, |
| WOLFSSL_SESSION *session, |
| int ietf_tls_id, |
| const char *alpn, |
| unsigned char *quic_tp, |
| size_t quic_tp_len) |
| { |
| CURLcode result = CURLE_OK; |
| struct Curl_ssl_session *sc_session = NULL; |
| unsigned char *sdata = NULL, *sdata_ptr, *qtp_clone = NULL; |
| unsigned int sdata_len; |
| unsigned int earlydata_max = 0; |
| |
| if(!session) |
| goto out; |
| |
| sdata_len = wolfSSL_i2d_SSL_SESSION(session, NULL); |
| if(sdata_len <= 0) { |
| CURL_TRC_CF(data, cf, "fail to assess session length: %u", sdata_len); |
| result = CURLE_FAILED_INIT; |
| goto out; |
| } |
| sdata = sdata_ptr = curlx_calloc(1, sdata_len); |
| if(!sdata) { |
| failf(data, "unable to allocate session buffer of %u bytes", sdata_len); |
| result = CURLE_OUT_OF_MEMORY; |
| goto out; |
| } |
| /* wolfSSL right now does not change the last parameter here, but it |
| * might one day decide to do so for OpenSSL compatibility. */ |
| sdata_len = wolfSSL_i2d_SSL_SESSION(session, &sdata_ptr); |
| if(sdata_len <= 0) { |
| CURL_TRC_CF(data, cf, "fail to serialize session: %u", sdata_len); |
| result = CURLE_FAILED_INIT; |
| goto out; |
| } |
| if(quic_tp && quic_tp_len) { |
| qtp_clone = curlx_memdup0((const char *)quic_tp, quic_tp_len); |
| if(!qtp_clone) { |
| curlx_free(sdata); |
| return CURLE_OUT_OF_MEMORY; |
| } |
| } |
| #ifdef WOLFSSL_EARLY_DATA |
| earlydata_max = wolfSSL_SESSION_get_max_early_data(session); |
| #endif |
| |
| result = Curl_ssl_session_create2(sdata, sdata_len, |
| ietf_tls_id, alpn, |
| (curl_off_t)time(NULL) + |
| wolfSSL_SESSION_get_timeout(session), |
| earlydata_max, qtp_clone, quic_tp_len, |
| &sc_session); |
| sdata = NULL; /* took ownership of sdata */ |
| if(!result) { |
| result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session); |
| /* took ownership of `sc_session` */ |
| } |
| |
| out: |
| curlx_free(sdata); |
| return result; |
| } |
| |
| static int wssl_vtls_new_session_cb(WOLFSSL *ssl, WOLFSSL_SESSION *session) |
| { |
| struct Curl_cfilter *cf; |
| |
| cf = (struct Curl_cfilter *)wolfSSL_get_app_data(ssl); |
| DEBUGASSERT(cf != NULL); |
| if(cf && session) { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct Curl_easy *data = CF_DATA_CURRENT(cf); |
| DEBUGASSERT(connssl); |
| DEBUGASSERT(data); |
| if(connssl && data) { |
| (void)Curl_wssl_cache_session(cf, data, connssl->peer.scache_key, |
| session, wolfSSL_version(ssl), |
| connssl->negotiated.alpn, NULL, 0); |
| } |
| } |
| return 0; |
| } |
| #endif |
| |
| static CURLcode wssl_on_session_reuse(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct alpn_spec *alpns, |
| struct Curl_ssl_session *scs, |
| bool *do_early_data) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| #ifdef WOLFSSL_EARLY_DATA |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| |
| connssl->earlydata_max = wolfSSL_SESSION_get_max_early_data( |
| wolfSSL_get_session(wssl->ssl)); |
| #else |
| connssl->earlydata_max = 0; |
| #endif |
| |
| /* Seems to be no wolfSSL way to signal no EarlyData in session */ |
| return Curl_on_session_reuse(cf, data, alpns, scs, do_early_data, |
| connssl->earlydata_max); |
| } |
| |
| static CURLcode |
| wssl_setup_session(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct wssl_ctx *wss, |
| struct alpn_spec *alpns, |
| const char *ssl_peer_key, |
| Curl_wssl_init_session_reuse_cb *sess_reuse_cb) |
| { |
| struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); |
| struct Curl_ssl_session *scs = NULL; |
| CURLcode result; |
| |
| result = Curl_ssl_scache_take(cf, data, ssl_peer_key, &scs); |
| if(!result && scs && scs->sdata && scs->sdata_len && |
| (!scs->alpn || Curl_alpn_contains_proto(alpns, scs->alpn))) { |
| WOLFSSL_SESSION *session; |
| /* wolfSSL changes the passed pointer for whatever reasons, yikes */ |
| const unsigned char *sdata = scs->sdata; |
| session = wolfSSL_d2i_SSL_SESSION(NULL, &sdata, (long)scs->sdata_len); |
| if(session) { |
| int ret = wolfSSL_set_session(wss->ssl, session); |
| if(ret != WOLFSSL_SUCCESS) { |
| Curl_ssl_session_destroy(scs); |
| scs = NULL; |
| infof(data, "cached session not accepted (%d), " |
| "removing from cache", ret); |
| } |
| else { |
| infof(data, "SSL reusing session with ALPN '%s'", |
| scs->alpn ? scs->alpn : "-"); |
| if(ssl_config->earlydata && |
| !cf->conn->bits.connect_only && |
| !strcmp("TLSv1.3", wolfSSL_get_version(wss->ssl))) { |
| bool do_early_data = FALSE; |
| if(sess_reuse_cb) { |
| result = sess_reuse_cb(cf, data, alpns, scs, &do_early_data); |
| if(result) { |
| wolfSSL_SESSION_free(session); |
| goto out; |
| } |
| } |
| #ifdef WOLFSSL_EARLY_DATA |
| if(do_early_data) { |
| unsigned int edmax = (scs->earlydata_max < UINT_MAX) ? |
| (unsigned int)scs->earlydata_max : UINT_MAX; |
| /* We only try the ALPN protocol the session used before, |
| * otherwise we might send early data for the wrong protocol */ |
| Curl_alpn_restrict_to(alpns, scs->alpn); |
| wolfSSL_set_max_early_data(wss->ssl, edmax); |
| } |
| #else |
| /* Should never enable when not supported */ |
| DEBUGASSERT(!do_early_data); |
| #endif |
| } |
| } |
| wolfSSL_SESSION_free(session); |
| } |
| else { |
| failf(data, "could not decode previous session"); |
| } |
| } |
| out: |
| Curl_ssl_scache_return(cf, data, ssl_peer_key, scs); |
| return result; |
| } |
| |
| static CURLcode wssl_populate_x509_store(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| WOLFSSL_X509_STORE *store, |
| struct wssl_ctx *wssl) |
| { |
| 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; |
| 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; |
| struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); |
| bool imported_native_ca = FALSE; |
| bool imported_ca_info_blob = FALSE; |
| |
| /* We do not want to do this again, no matter the outcome */ |
| wssl->x509_store_setup = TRUE; |
| |
| #ifndef NO_FILESYSTEM |
| /* load native CA certificates */ |
| if(ssl_config->native_ca_store) { |
| #ifdef WOLFSSL_SYS_CA_CERTS |
| if(wolfSSL_CTX_load_system_CA_certs(wssl->ssl_ctx) != WOLFSSL_SUCCESS) { |
| infof(data, "error importing native CA store, continuing anyway"); |
| } |
| else { |
| imported_native_ca = TRUE; |
| infof(data, "successfully imported native CA store"); |
| } |
| #else |
| infof(data, "ignoring native CA option because wolfSSL was built without " |
| "native CA support"); |
| #endif |
| } |
| #endif /* !NO_FILESYSTEM */ |
| |
| /* load certificate blob */ |
| if(ca_info_blob) { |
| if(wolfSSL_CTX_load_verify_buffer(wssl->ssl_ctx, ca_info_blob->data, |
| (long)ca_info_blob->len, |
| WOLFSSL_FILETYPE_PEM) != |
| WOLFSSL_SUCCESS) { |
| failf(data, "error importing CA certificate blob"); |
| return CURLE_SSL_CACERT_BADFILE; |
| } |
| else { |
| imported_ca_info_blob = TRUE; |
| infof(data, "successfully imported CA certificate blob"); |
| } |
| } |
| |
| #ifndef NO_FILESYSTEM |
| /* load trusted cacert from file if not blob */ |
| |
| CURL_TRC_CF(data, cf, "wssl_populate_x509_store, path=%s, blob=%d", |
| ssl_cafile ? ssl_cafile : "none", !!ca_info_blob); |
| if(!store) |
| return CURLE_OUT_OF_MEMORY; |
| |
| if(ssl_cafile || ssl_capath) { |
| int rc = |
| wolfSSL_CTX_load_verify_locations_ex(wssl->ssl_ctx, |
| ssl_cafile, |
| ssl_capath, |
| WOLFSSL_LOAD_FLAG_IGNORE_ERR); |
| if(WOLFSSL_SUCCESS != rc) { |
| if(conn_config->verifypeer && |
| !imported_native_ca && !imported_ca_info_blob) { |
| /* 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; |
| } |
| else { |
| /* 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_cafile ? ssl_cafile : "none"); |
| infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none"); |
| } |
| #endif |
| (void)store; |
| return CURLE_OK; |
| } |
| |
| /* key to use at `multi->proto_hash` */ |
| #define MPROTO_WSSL_X509_KEY "tls:wssl:x509:share" |
| |
| struct wssl_x509_share { |
| char *CAfile; /* CAfile path used to generate X509 store */ |
| WOLFSSL_X509_STORE *store; /* cached X509 store or NULL if none */ |
| struct curltime time; /* when the cached store was created */ |
| }; |
| |
| static void wssl_x509_share_free(void *key, size_t key_len, void *p) |
| { |
| struct wssl_x509_share *share = p; |
| DEBUGASSERT(key_len == (sizeof(MPROTO_WSSL_X509_KEY) - 1)); |
| DEBUGASSERT(!memcmp(MPROTO_WSSL_X509_KEY, key, key_len)); |
| (void)key; |
| (void)key_len; |
| if(share->store) { |
| wolfSSL_X509_STORE_free(share->store); |
| } |
| curlx_free(share->CAfile); |
| curlx_free(share); |
| } |
| |
| static bool wssl_cached_x509_store_expired(struct Curl_easy *data, |
| const struct wssl_x509_share *mb) |
| { |
| const struct ssl_general_config *cfg = &data->set.general_ssl; |
| timediff_t elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &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 wssl_cached_x509_store_different(struct Curl_cfilter *cf, |
| const struct wssl_x509_share *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 WOLFSSL_X509_STORE *wssl_get_cached_x509_store(struct Curl_cfilter *cf, |
| struct Curl_easy *data) |
| { |
| struct Curl_multi *multi = data->multi; |
| struct wssl_x509_share *share; |
| WOLFSSL_X509_STORE *store = NULL; |
| |
| DEBUGASSERT(multi); |
| share = multi ? Curl_hash_pick(&multi->proto_hash, |
| CURL_UNCONST(MPROTO_WSSL_X509_KEY), |
| sizeof(MPROTO_WSSL_X509_KEY) - 1) : NULL; |
| if(share && share->store && |
| !wssl_cached_x509_store_expired(data, share) && |
| !wssl_cached_x509_store_different(cf, share)) { |
| store = share->store; |
| } |
| |
| return store; |
| } |
| |
| static void wssl_set_cached_x509_store(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| WOLFSSL_X509_STORE *store) |
| { |
| struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); |
| struct Curl_multi *multi = data->multi; |
| struct wssl_x509_share *share; |
| |
| DEBUGASSERT(multi); |
| if(!multi) |
| return; |
| share = Curl_hash_pick(&multi->proto_hash, |
| CURL_UNCONST(MPROTO_WSSL_X509_KEY), |
| sizeof(MPROTO_WSSL_X509_KEY) - 1); |
| |
| if(!share) { |
| share = curlx_calloc(1, sizeof(*share)); |
| if(!share) |
| return; |
| if(!Curl_hash_add2(&multi->proto_hash, |
| CURL_UNCONST(MPROTO_WSSL_X509_KEY), |
| sizeof(MPROTO_WSSL_X509_KEY) - 1, |
| share, wssl_x509_share_free)) { |
| curlx_free(share); |
| return; |
| } |
| } |
| |
| if(wolfSSL_X509_STORE_up_ref(store)) { |
| char *CAfile = NULL; |
| |
| if(conn_config->CAfile) { |
| CAfile = curlx_strdup(conn_config->CAfile); |
| if(!CAfile) { |
| wolfSSL_X509_STORE_free(store); |
| return; |
| } |
| } |
| |
| if(share->store) { |
| wolfSSL_X509_STORE_free(share->store); |
| curlx_free(share->CAfile); |
| } |
| |
| share->time = *Curl_pgrs_now(data); |
| share->store = store; |
| share->CAfile = CAfile; |
| } |
| } |
| |
| CURLcode Curl_wssl_setup_x509_store(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct wssl_ctx *wssl) |
| { |
| 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; |
| WOLFSSL_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 wolfSSL'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 = cache_criteria_met ? wssl_get_cached_x509_store(cf, data) |
| : NULL; |
| if(cached_store && |
| wolfSSL_CTX_get_cert_store(wssl->ssl_ctx) == cached_store) { |
| /* The cached store is already in use, do nothing. */ |
| } |
| else if(cached_store && wolfSSL_X509_STORE_up_ref(cached_store)) { |
| wolfSSL_CTX_set_cert_store(wssl->ssl_ctx, cached_store); |
| } |
| else if(cache_criteria_met) { |
| /* wolfSSL's initial store in CTX is not shareable by default. |
| * Make a new one, suitable for adding to the cache. See #14278 */ |
| WOLFSSL_X509_STORE *store = wolfSSL_X509_STORE_new(); |
| if(!store) { |
| failf(data, "SSL: could not create a X509 store"); |
| return CURLE_OUT_OF_MEMORY; |
| } |
| wolfSSL_CTX_set_cert_store(wssl->ssl_ctx, store); |
| |
| result = wssl_populate_x509_store(cf, data, store, wssl); |
| if(!result) { |
| wssl_set_cached_x509_store(cf, data, store); |
| } |
| } |
| else { |
| /* We never share the CTX's store, use it. */ |
| WOLFSSL_X509_STORE *store = wolfSSL_CTX_get_cert_store(wssl->ssl_ctx); |
| result = wssl_populate_x509_store(cf, data, store, wssl); |
| } |
| |
| return result; |
| } |
| |
| #ifdef WOLFSSL_TLS13 |
| static CURLcode wssl_add_default_ciphers(bool tls13, struct dynbuf *buf) |
| { |
| int i; |
| char *str; |
| |
| for(i = 0; (str = wolfSSL_get_cipher_list(i)) != NULL; i++) { |
| size_t n; |
| if((strncmp(str, "TLS13", 5) == 0) != tls13) |
| continue; |
| |
| /* if there already is data in the string, add colon separator */ |
| if(curlx_dyn_len(buf)) { |
| CURLcode result = curlx_dyn_addn(buf, ":", 1); |
| if(result) |
| return result; |
| } |
| |
| n = strlen(str); |
| if(curlx_dyn_addn(buf, str, n)) |
| return CURLE_OUT_OF_MEMORY; |
| } |
| |
| return CURLE_OK; |
| } |
| #endif |
| |
| #ifndef OPENSSL_EXTRA |
| static int wssl_legacy_CTX_set_min_proto_version(WOLFSSL_CTX *ctx, int version) |
| { |
| int res; |
| switch(version) { |
| default: |
| case TLS1_VERSION: |
| res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1); |
| if(res == WOLFSSL_SUCCESS) |
| return res; |
| FALLTHROUGH(); |
| case TLS1_1_VERSION: |
| res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_1); |
| if(res == WOLFSSL_SUCCESS) |
| return res; |
| FALLTHROUGH(); |
| case TLS1_2_VERSION: |
| res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_2); |
| #ifdef WOLFSSL_TLS13 |
| if(res == WOLFSSL_SUCCESS) |
| return res; |
| FALLTHROUGH(); |
| case TLS1_3_VERSION: |
| res = wolfSSL_CTX_SetMinVersion(ctx, WOLFSSL_TLSV1_3); |
| #endif |
| } |
| return res; |
| } |
| |
| static int wssl_legacy_CTX_set_max_proto_version(WOLFSSL_CTX *ctx, int version) |
| { |
| (void)ctx, (void)version; |
| return WOLFSSL_NOT_IMPLEMENTED; |
| } |
| #define wolfSSL_CTX_set_min_proto_version wssl_legacy_CTX_set_min_proto_version |
| #define wolfSSL_CTX_set_max_proto_version wssl_legacy_CTX_set_max_proto_version |
| #endif /* OPENSSL_EXTRA */ |
| |
| static CURLcode wssl_client_cert(struct Curl_easy *data, |
| struct ssl_config_data *ssl_config, |
| struct wssl_ctx *wctx) |
| { |
| /* Load the client certificate, and private key */ |
| #ifndef NO_FILESYSTEM |
| if(ssl_config->primary.cert_blob || ssl_config->primary.clientcert) { |
| const char *cert_file = ssl_config->primary.clientcert; |
| const char *key_file = ssl_config->key; |
| const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; |
| const struct curl_blob *key_blob = ssl_config->key_blob; |
| int file_type = wssl_do_file_type(ssl_config->cert_type); |
| int rc; |
| |
| switch(file_type) { |
| case WOLFSSL_FILETYPE_PEM: |
| rc = cert_blob ? |
| wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, |
| cert_blob->data, |
| (long)cert_blob->len) : |
| wolfSSL_CTX_use_certificate_chain_file(wctx->ssl_ctx, cert_file); |
| break; |
| case WOLFSSL_FILETYPE_ASN1: |
| rc = cert_blob ? |
| wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, |
| (long)cert_blob->len, file_type) : |
| wolfSSL_CTX_use_certificate_file(wctx->ssl_ctx, cert_file, file_type); |
| break; |
| default: |
| failf(data, "unknown cert type"); |
| return CURLE_BAD_FUNCTION_ARGUMENT; |
| } |
| if(rc != 1) { |
| failf(data, "unable to use client certificate"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| if(!key_blob && !key_file) { |
| key_blob = cert_blob; |
| key_file = cert_file; |
| } |
| else |
| file_type = wssl_do_file_type(ssl_config->key_type); |
| |
| rc = key_blob ? |
| wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, |
| (long)key_blob->len, file_type) : |
| wolfSSL_CTX_use_PrivateKey_file(wctx->ssl_ctx, key_file, file_type); |
| if(rc != 1) { |
| failf(data, "unable to set private key"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| #else /* NO_FILESYSTEM */ |
| if(ssl_config->primary.cert_blob) { |
| const struct curl_blob *cert_blob = ssl_config->primary.cert_blob; |
| const struct curl_blob *key_blob = ssl_config->key_blob; |
| int file_type = wssl_do_file_type(ssl_config->cert_type); |
| int rc; |
| |
| switch(file_type) { |
| case WOLFSSL_FILETYPE_PEM: |
| rc = wolfSSL_CTX_use_certificate_chain_buffer(wctx->ssl_ctx, |
| cert_blob->data, |
| (long)cert_blob->len); |
| break; |
| case WOLFSSL_FILETYPE_ASN1: |
| rc = wolfSSL_CTX_use_certificate_buffer(wctx->ssl_ctx, cert_blob->data, |
| (long)cert_blob->len, file_type); |
| break; |
| default: |
| failf(data, "unknown cert type"); |
| return CURLE_BAD_FUNCTION_ARGUMENT; |
| } |
| if(rc != 1) { |
| failf(data, "unable to use client certificate"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| if(!key_blob) |
| key_blob = cert_blob; |
| else |
| file_type = wssl_do_file_type(ssl_config->key_type); |
| |
| if(wolfSSL_CTX_use_PrivateKey_buffer(wctx->ssl_ctx, key_blob->data, |
| (long)key_blob->len, |
| file_type) != 1) { |
| failf(data, "unable to set private key"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| #endif /* !NO_FILESYSTEM */ |
| return CURLE_OK; |
| } |
| |
| static CURLcode ssl_version(struct Curl_easy *data, |
| struct ssl_primary_config *conn_config, |
| struct wssl_ctx *wctx, |
| int *min_version, int *max_version) |
| { |
| int res; |
| *min_version = *max_version = 0; |
| DEBUGASSERT(conn_config->version != CURL_SSLVERSION_DEFAULT); |
| |
| switch(conn_config->version) { |
| case CURL_SSLVERSION_TLSv1: |
| case CURL_SSLVERSION_TLSv1_0: |
| *min_version = TLS1_VERSION; |
| break; |
| case CURL_SSLVERSION_TLSv1_1: |
| *min_version = TLS1_1_VERSION; |
| break; |
| case CURL_SSLVERSION_TLSv1_2: |
| *min_version = TLS1_2_VERSION; |
| break; |
| #ifdef WOLFSSL_TLS13 |
| case CURL_SSLVERSION_TLSv1_3: |
| *min_version = TLS1_3_VERSION; |
| break; |
| #endif |
| default: |
| failf(data, "wolfSSL: unsupported minimum TLS version value"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| switch(conn_config->version_max) { |
| #ifdef WOLFSSL_TLS13 |
| case CURL_SSLVERSION_MAX_TLSv1_3: |
| *max_version = TLS1_3_VERSION; |
| break; |
| #endif |
| case CURL_SSLVERSION_MAX_TLSv1_2: |
| *max_version = TLS1_2_VERSION; |
| break; |
| case CURL_SSLVERSION_MAX_TLSv1_1: |
| *max_version = TLS1_1_VERSION; |
| break; |
| case CURL_SSLVERSION_MAX_TLSv1_0: |
| *max_version = TLS1_VERSION; |
| break; |
| case CURL_SSLVERSION_MAX_DEFAULT: |
| case CURL_SSLVERSION_MAX_NONE: |
| break; |
| default: |
| failf(data, "wolfSSL: unsupported maximum TLS version value"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, *min_version); |
| if(res != WOLFSSL_SUCCESS) { |
| failf(data, "wolfSSL: failed set the minimum TLS version"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| |
| if(*max_version) { |
| res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, *max_version); |
| if(res != WOLFSSL_SUCCESS) { |
| failf(data, "wolfSSL: failed set the maximum TLS version"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| return CURLE_OK; |
| } |
| |
| #define QUIC_GROUPS "P-256:P-384:P-521" |
| #ifdef WOLFSSL_TLS13 |
| #define MAX_CIPHER_LEN 4096 |
| #endif |
| |
| static CURLcode wssl_init_ciphers(struct Curl_easy *data, |
| struct wssl_ctx *wctx, |
| struct ssl_primary_config *conn_config, |
| int tls_min, int tls_max) |
| { |
| #ifndef WOLFSSL_TLS13 |
| const char *ciphers = conn_config->cipher_list; |
| (void)tls_min; |
| (void)tls_max; |
| if(ciphers) { |
| if(!SSL_CTX_set_cipher_list(wctx->ssl_ctx, ciphers)) { |
| failf(data, "failed setting cipher list: %s", ciphers); |
| return CURLE_SSL_CIPHER; |
| } |
| infof(data, "Cipher selection: %s", ciphers); |
| } |
| return CURLE_OK; |
| #else |
| CURLcode result = CURLE_OK; |
| if(conn_config->cipher_list || conn_config->cipher_list13) { |
| const char *ciphers12 = conn_config->cipher_list; |
| const char *ciphers13 = conn_config->cipher_list13; |
| struct dynbuf c; |
| curlx_dyn_init(&c, MAX_CIPHER_LEN); |
| |
| if(!tls_max || (tls_max >= TLS1_3_VERSION)) { |
| if(ciphers13) |
| result = curlx_dyn_add(&c, ciphers13); |
| else |
| result = wssl_add_default_ciphers(TRUE, &c); |
| } |
| |
| if(!result && (tls_min < TLS1_3_VERSION)) { |
| if(ciphers12) { |
| if(curlx_dyn_len(&c)) |
| result = curlx_dyn_addn(&c, ":", 1); |
| if(!result) |
| result = curlx_dyn_add(&c, ciphers12); |
| } |
| else |
| result = wssl_add_default_ciphers(FALSE, &c); |
| } |
| if(!result) { |
| if(!wolfSSL_CTX_set_cipher_list(wctx->ssl_ctx, curlx_dyn_ptr(&c))) { |
| failf(data, "failed setting cipher list: %s", curlx_dyn_ptr(&c)); |
| result = CURLE_SSL_CIPHER; |
| } |
| else |
| infof(data, "Cipher selection: %s", curlx_dyn_ptr(&c)); |
| } |
| curlx_dyn_free(&c); |
| } |
| return result; |
| #endif |
| } |
| |
| static CURLcode wssl_init_curves(struct Curl_easy *data, |
| struct wssl_ctx *wctx, |
| struct ssl_primary_config *conn_config, |
| unsigned char transport |
| #ifdef WOLFSSL_HAVE_KYBER |
| , word16 *out_pqkem |
| #endif |
| ) |
| { |
| char *curves = conn_config->curves; |
| if(!curves && (transport == TRNSPRT_QUIC)) |
| curves = (char *)CURL_UNCONST(QUIC_GROUPS); |
| |
| if(curves) { |
| #ifdef WOLFSSL_HAVE_KYBER |
| size_t idx; |
| for(idx = 0; gnm[idx].name != NULL; idx++) { |
| if(!strncmp(curves, gnm[idx].name, strlen(gnm[idx].name))) { |
| *out_pqkem = gnm[idx].group; |
| break; |
| } |
| } |
| |
| if(*out_pqkem == 0) |
| #endif |
| { |
| if(!wolfSSL_CTX_set1_curves_list(wctx->ssl_ctx, curves)) { |
| failf(data, "failed setting curves list: '%s'", curves); |
| return CURLE_SSL_CIPHER; |
| } |
| } |
| } |
| return CURLE_OK; |
| } |
| |
| static CURLcode wssl_init_ssl_handle(struct wssl_ctx *wctx, |
| struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct ssl_peer *peer, |
| struct alpn_spec *alpns, |
| void *ssl_user_data, |
| unsigned char transport, |
| #ifdef WOLFSSL_HAVE_KYBER |
| word16 pqkem, |
| #endif |
| Curl_wssl_init_session_reuse_cb |
| *sess_reuse_cb) |
| { |
| /* Let's make an SSL structure */ |
| wctx->ssl = wolfSSL_new(wctx->ssl_ctx); |
| if(!wctx->ssl) { |
| failf(data, "SSL: could not create a handle"); |
| return CURLE_OUT_OF_MEMORY; |
| } |
| |
| #ifdef HAVE_EX_DATA |
| wolfSSL_set_app_data(wctx->ssl, ssl_user_data); |
| #else |
| (void)ssl_user_data; |
| #endif |
| #ifdef WOLFSSL_QUIC |
| if(transport == TRNSPRT_QUIC) |
| wolfSSL_set_quic_use_legacy_codepoint(wctx->ssl, 0); |
| #else |
| (void)transport; |
| #endif |
| |
| #ifdef WOLFSSL_HAVE_KYBER |
| if(pqkem) { |
| if(wolfSSL_UseKeyShare(wctx->ssl, pqkem) != |
| WOLFSSL_SUCCESS) { |
| failf(data, "unable to use PQ KEM"); |
| } |
| } |
| #endif |
| |
| /* Check if there is a cached ID we can/should use here! */ |
| if(Curl_ssl_scache_use(cf, data)) { |
| /* Set session from cache if there is one */ |
| (void)wssl_setup_session(cf, data, wctx, alpns, peer->scache_key, |
| sess_reuse_cb); |
| } |
| |
| #ifdef HAVE_ALPN |
| if(alpns->count) { |
| struct alpn_proto_buf proto; |
| memset(&proto, 0, sizeof(proto)); |
| Curl_alpn_to_proto_str(&proto, alpns); |
| |
| if(wolfSSL_UseALPN(wctx->ssl, (char *)proto.data, |
| (unsigned int)proto.len, |
| WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) |
| != WOLFSSL_SUCCESS) { |
| failf(data, "SSL: failed setting ALPN protocols"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| CURL_TRC_CF(data, cf, "set ALPN: %s", proto.data); |
| } |
| #endif /* HAVE_ALPN */ |
| |
| #ifdef OPENSSL_EXTRA |
| if(Curl_tls_keylog_enabled()) { |
| /* Ensure the Client Random is preserved. */ |
| wolfSSL_KeepArrays(wctx->ssl); |
| #if defined(HAVE_SECRET_CALLBACK) && defined(WOLFSSL_TLS13) |
| wolfSSL_set_tls13_secret_cb(wctx->ssl, wssl_tls13_secret_callback, NULL); |
| #endif |
| } |
| #endif /* OPENSSL_EXTRA */ |
| |
| #ifdef HAVE_SECURE_RENEGOTIATION |
| if(wolfSSL_UseSecureRenegotiation(wctx->ssl) != SSL_SUCCESS) { |
| failf(data, "SSL: failed setting secure renegotiation"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| #endif /* HAVE_SECURE_RENEGOTIATION */ |
| |
| return CURLE_OK; |
| } |
| |
| #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG |
| static CURLcode wssl_init_ech(struct wssl_ctx *wctx, |
| struct Curl_cfilter *cf, |
| struct Curl_easy *data) |
| { |
| int trying_ech_now = 0; |
| |
| if(data->set.str[STRING_ECH_PUBLIC]) { |
| infof(data, "ECH: outername not (yet) supported" |
| " with wolfSSL"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| if(data->set.tls_ech == CURLECH_GREASE) { |
| infof(data, "ECH: GREASE is done by default by" |
| " wolfSSL: no need to ask"); |
| } |
| if(data->set.tls_ech & CURLECH_CLA_CFG && |
| data->set.str[STRING_ECH_CONFIG]) { |
| char *b64val = data->set.str[STRING_ECH_CONFIG]; |
| word32 b64len = 0; |
| |
| b64len = (word32)strlen(b64val); |
| if(b64len && wolfSSL_SetEchConfigsBase64(wctx->ssl, b64val, |
| b64len) != WOLFSSL_SUCCESS) { |
| if(data->set.tls_ech & CURLECH_HARD) |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| else { |
| trying_ech_now = 1; |
| infof(data, "ECH: ECHConfig from command line"); |
| } |
| } |
| else { |
| const struct Curl_https_rrinfo *rinfo = |
| Curl_conn_dns_get_https(data, cf->sockindex); |
| |
| if(rinfo && rinfo->echconfiglist) { |
| const unsigned char *ecl = rinfo->echconfiglist; |
| size_t elen = rinfo->echconfiglist_len; |
| |
| infof(data, "ECH: ECHConfig from HTTPS RR"); |
| if(wolfSSL_SetEchConfigs(wctx->ssl, ecl, (word32)elen) != |
| WOLFSSL_SUCCESS) { |
| infof(data, "ECH: wolfSSL_SetEchConfigs failed"); |
| if(data->set.tls_ech & CURLECH_HARD) { |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| else { |
| trying_ech_now = 1; |
| infof(data, "ECH: imported ECHConfigList of length %zu", elen); |
| } |
| } |
| else { |
| infof(data, "ECH: requested but no ECHConfig available"); |
| if(data->set.tls_ech & CURLECH_HARD) { |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| } |
| |
| if(trying_ech_now && |
| wolfSSL_set_min_proto_version(wctx->ssl, TLS1_3_VERSION) != 1) { |
| infof(data, "ECH: cannot force TLSv1.3 [ERROR]"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| return CURLE_OK; |
| } |
| #endif /* HAVE_WOLFSSL_CTX_GENERATEECHCONFIG */ |
| |
| CURLcode Curl_wssl_ctx_init(struct wssl_ctx *wctx, |
| struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct ssl_peer *peer, |
| const struct alpn_spec *alpns_requested, |
| Curl_wssl_ctx_setup_cb *cb_setup, |
| void *cb_user_data, |
| void *ssl_user_data, |
| Curl_wssl_init_session_reuse_cb *sess_reuse_cb) |
| { |
| struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data); |
| struct ssl_primary_config *conn_config; |
| WOLFSSL_METHOD *req_method = NULL; |
| struct alpn_spec alpns; |
| #ifdef WOLFSSL_HAVE_KYBER |
| word16 pqkem = 0; |
| #endif |
| CURLcode result = CURLE_FAILED_INIT; |
| unsigned char transport; |
| int tls_min, tls_max; |
| |
| DEBUGASSERT(!wctx->ssl_ctx); |
| DEBUGASSERT(!wctx->ssl); |
| conn_config = Curl_ssl_cf_get_primary_config(cf); |
| if(!conn_config) { |
| result = CURLE_FAILED_INIT; |
| goto out; |
| } |
| Curl_alpn_copy(&alpns, alpns_requested); |
| DEBUGASSERT(cf->next); |
| transport = Curl_conn_cf_get_transport(cf->next, data); |
| |
| req_method = wolfTLS_client_method(); |
| if(!req_method) { |
| failf(data, "wolfSSL: could not create a client method"); |
| result = CURLE_OUT_OF_MEMORY; |
| goto out; |
| } |
| |
| if(wctx->ssl_ctx) |
| wolfSSL_CTX_free(wctx->ssl_ctx); |
| |
| wctx->ssl_ctx = wolfSSL_CTX_new(req_method); |
| if(!wctx->ssl_ctx) { |
| failf(data, "wolfSSL: could not create a context"); |
| result = CURLE_OUT_OF_MEMORY; |
| goto out; |
| } |
| |
| result = ssl_version(data, conn_config, wctx, &tls_min, &tls_max); |
| if(result) |
| goto out; |
| |
| result = wssl_init_ciphers(data, wctx, conn_config, tls_min, tls_max); |
| if(result) |
| goto out; |
| |
| result = wssl_init_curves(data, wctx, conn_config, transport |
| #ifdef WOLFSSL_HAVE_KYBER |
| , &pqkem |
| #endif |
| ); |
| if(result) |
| goto out; |
| |
| result = wssl_client_cert(data, ssl_config, wctx); |
| if(result) |
| goto out; |
| |
| /* 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. */ |
| wolfSSL_CTX_set_verify(wctx->ssl_ctx, conn_config->verifypeer ? |
| WOLFSSL_VERIFY_PEER : WOLFSSL_VERIFY_NONE, NULL); |
| |
| #ifdef HAVE_SNI |
| if(peer->sni) { |
| size_t sni_len = strlen(peer->sni); |
| if((sni_len < USHRT_MAX)) { |
| if(wolfSSL_CTX_UseSNI(wctx->ssl_ctx, WOLFSSL_SNI_HOST_NAME, |
| peer->sni, (unsigned short)sni_len) != 1) { |
| failf(data, "Failed to set SNI"); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto out; |
| } |
| CURL_TRC_CF(data, cf, "set SNI '%s'", peer->sni); |
| } |
| } |
| #endif |
| |
| #ifdef HAVE_EX_DATA |
| if(Curl_ssl_scache_use(cf, data) && (transport != TRNSPRT_QUIC)) { |
| /* Register to get notified when a new session is received */ |
| wolfSSL_CTX_sess_set_new_cb(wctx->ssl_ctx, wssl_vtls_new_session_cb); |
| } |
| #endif |
| |
| if(cb_setup) { |
| result = cb_setup(cf, data, cb_user_data); |
| if(result) |
| goto out; |
| } |
| |
| /* give application a chance to interfere with SSL set up. */ |
| if(data->set.ssl.fsslctx) { |
| if(!wctx->x509_store_setup) { |
| result = Curl_wssl_setup_x509_store(cf, data, wctx); |
| if(result) |
| goto out; |
| } |
| result = (*data->set.ssl.fsslctx)(data, wctx->ssl_ctx, |
| data->set.ssl.fsslctxp); |
| if(result) { |
| failf(data, "error signaled by ssl ctx callback"); |
| goto out; |
| } |
| } |
| #ifdef NO_FILESYSTEM |
| else if(conn_config->verifypeer) { |
| failf(data, "SSL: Certificates cannot be loaded because wolfSSL was built" |
| " with no file system. Either disable peer verification" |
| " (insecure) or if you are building an application with libcurl you" |
| " can load certificates via CURLOPT_SSL_CTX_FUNCTION."); |
| result = CURLE_SSL_CONNECT_ERROR; |
| goto out; |
| } |
| #endif |
| |
| result = wssl_init_ssl_handle(wctx, cf, data, peer, &alpns, ssl_user_data, |
| transport, |
| #ifdef WOLFSSL_HAVE_KYBER |
| pqkem, |
| #endif |
| sess_reuse_cb); |
| if(result) |
| goto out; |
| |
| #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG |
| if(CURLECH_ENABLED(data)) { |
| result = wssl_init_ech(wctx, cf, data); |
| if(result) |
| goto out; |
| } |
| #endif /* HAVE_WOLFSSL_CTX_GENERATEECHCONFIG */ |
| |
| result = CURLE_OK; |
| |
| out: |
| if(result && wctx->ssl) { |
| wolfSSL_free(wctx->ssl); |
| wctx->ssl = NULL; |
| } |
| if(result && wctx->ssl_ctx) { |
| wolfSSL_CTX_free(wctx->ssl_ctx); |
| wctx->ssl_ctx = NULL; |
| } |
| return result; |
| } |
| |
| bool Curl_wssl_need_httpsrr(struct Curl_easy *data) |
| { |
| #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG |
| if(!CURLECH_ENABLED(data)) |
| return FALSE; |
| if((data->set.tls_ech & CURLECH_GREASE) || |
| (data->set.tls_ech & CURLECH_CLA_CFG)) |
| return FALSE; |
| return TRUE; |
| #else |
| (void)data; |
| return FALSE; |
| #endif |
| } |
| |
| /* |
| * This function loads all the client/CA certificates and CRLs. Setup the TLS |
| * layer and do all necessary magic. |
| */ |
| static CURLcode wssl_connect_step1(struct Curl_cfilter *cf, |
| struct Curl_easy *data) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); |
| CURLcode result; |
| |
| DEBUGASSERT(wssl); |
| |
| if(connssl->state == ssl_connection_complete) |
| return CURLE_OK; |
| |
| result = Curl_wssl_ctx_init(wssl, cf, data, &connssl->peer, connssl->alpn, |
| NULL, NULL, cf, wssl_on_session_reuse); |
| if(result) |
| return result; |
| |
| #ifdef HAVE_ALPN |
| if(connssl->alpn && (connssl->state != ssl_connection_deferred)) { |
| struct alpn_proto_buf proto; |
| memset(&proto, 0, sizeof(proto)); |
| Curl_alpn_to_proto_str(&proto, connssl->alpn); |
| infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data); |
| } |
| #endif |
| |
| /* Enable RFC2818 checks on domain names. This cannot check |
| * IP addresses which we need to do extra after the handshake. */ |
| if(conn_config->verifyhost && connssl->peer.sni) { |
| if(wolfSSL_check_domain_name(wssl->ssl, connssl->peer.sni) != |
| WOLFSSL_SUCCESS) { |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| |
| #ifdef USE_BIO_CHAIN |
| { |
| WOLFSSL_BIO *bio; |
| |
| if(!wssl_bio_cf_method) |
| return CURLE_FAILED_INIT; |
| bio = wolfSSL_BIO_new(wssl_bio_cf_method); |
| if(!bio) |
| return CURLE_OUT_OF_MEMORY; |
| |
| wolfSSL_BIO_set_data(bio, cf); |
| wolfSSL_set_bio(wssl->ssl, bio, bio); |
| } |
| #else /* !USE_BIO_CHAIN */ |
| curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data); |
| if(sockfd > INT_MAX) { |
| failf(data, "SSL: socket value too large"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| /* pass the raw socket into the SSL layer */ |
| if(!wolfSSL_set_fd(wssl->ssl, (int)sockfd)) { |
| failf(data, "SSL: wolfSSL_set_fd failed"); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| #endif /* USE_BIO_CHAIN */ |
| |
| return CURLE_OK; |
| } |
| |
| static char *wssl_strerror(unsigned long error, char *buf, unsigned long size) |
| { |
| DEBUGASSERT(size > 40); |
| *buf = '\0'; |
| |
| wolfSSL_ERR_error_string_n(error, buf, size); |
| |
| if(!*buf) { |
| const char *msg = error ? "Unknown error" : "No error"; |
| curlx_strcopy(buf, size, msg, strlen(msg)); |
| } |
| |
| return buf; |
| } |
| |
| CURLcode Curl_wssl_verify_pinned(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| struct wssl_ctx *wssl) |
| { |
| WOLFSSL_X509 *x509 = NULL; |
| CURLcode result = CURLE_OK; |
| #ifndef CURL_DISABLE_PROXY |
| const char * const pinnedpubkey = Curl_ssl_cf_is_proxy(cf) ? |
| data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] : |
| data->set.str[STRING_SSL_PINNEDPUBLICKEY]; |
| #else |
| const char * const pinnedpubkey = data->set.str[STRING_SSL_PINNEDPUBLICKEY]; |
| (void)cf; |
| #endif |
| |
| if(pinnedpubkey) { |
| #ifdef KEEP_PEER_CERT |
| const char *x509_der; |
| int x509_der_len; |
| struct Curl_X509certificate x509_parsed; |
| struct Curl_asn1Element *pubkey; |
| |
| result = CURLE_SSL_PINNEDPUBKEYNOTMATCH; |
| |
| x509 = wolfSSL_get_peer_certificate(wssl->ssl); |
| if(!x509) { |
| failf(data, "SSL: failed retrieving server certificate"); |
| goto end; |
| } |
| |
| x509_der = (const char *)wolfSSL_X509_get_der(x509, &x509_der_len); |
| if(!x509_der) { |
| failf(data, "SSL: failed retrieving ASN.1 server certificate"); |
| goto end; |
| } |
| |
| memset(&x509_parsed, 0, sizeof(x509_parsed)); |
| if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len)) |
| goto end; |
| |
| pubkey = &x509_parsed.subjectPublicKeyInfo; |
| if(!pubkey->header || pubkey->end <= pubkey->header) { |
| failf(data, "SSL: failed retrieving public key from server certificate"); |
| goto end; |
| } |
| |
| 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"); |
| #else |
| failf(data, "Library lacks pinning support built-in"); |
| return CURLE_NOT_BUILT_IN; |
| #endif |
| } |
| end: |
| wolfSSL_FreeX509(x509); |
| return result; |
| } |
| |
| #ifdef WOLFSSL_EARLY_DATA |
| static CURLcode wssl_send_earlydata(struct Curl_cfilter *cf, |
| struct Curl_easy *data) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| const unsigned char *buf; |
| size_t blen; |
| |
| DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending); |
| wssl->io_result = CURLE_OK; |
| while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) { |
| int nwritten = 0, rc; |
| |
| wolfSSL_ERR_clear_error(); |
| rc = wolfSSL_write_early_data(wssl->ssl, buf, (int)blen, &nwritten); |
| CURL_TRC_CF(data, cf, "wolfSSL_write_early_data(len=%zu) -> %d, %d", |
| blen, rc, nwritten); |
| if(rc < 0) { |
| int err = wolfSSL_get_error(wssl->ssl, rc); |
| char error_buffer[256]; |
| switch(err) { |
| case WOLFSSL_ERROR_NONE: /* did not get anything */ |
| case WOLFSSL_ERROR_WANT_READ: |
| case WOLFSSL_ERROR_WANT_WRITE: |
| return CURLE_AGAIN; |
| } |
| CURL_TRC_CF(data, cf, "SSL send early data, error: '%s'(%d)", |
| wssl_strerror((unsigned long)err, error_buffer, |
| sizeof(error_buffer)), err); |
| return CURLE_SEND_ERROR; |
| } |
| |
| Curl_bufq_skip(&connssl->earlydata, (size_t)nwritten); |
| } |
| /* sent everything there was */ |
| connssl->earlydata_state = ssl_earlydata_sent; |
| if(!Curl_ssl_cf_is_proxy(cf)) |
| Curl_pgrsEarlyData(data, (curl_off_t)connssl->earlydata_skip); |
| infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip); |
| return CURLE_OK; |
| } |
| #endif /* WOLFSSL_EARLY_DATA */ |
| |
| static CURLcode wssl_handshake(struct Curl_cfilter *cf, struct Curl_easy *data) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); |
| int ret = -1, detail; |
| CURLcode result; |
| |
| DEBUGASSERT(wssl); |
| connssl->io_need = CURL_SSL_IO_NEED_NONE; |
| |
| #ifdef WOLFSSL_EARLY_DATA |
| if(connssl->earlydata_state == ssl_earlydata_sending) { |
| result = wssl_send_earlydata(cf, data); |
| if(result) |
| return result; |
| } |
| DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) || |
| (connssl->earlydata_state == ssl_earlydata_sent)); |
| #else |
| DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_none); |
| #endif /* WOLFSSL_EARLY_DATA */ |
| |
| wolfSSL_ERR_clear_error(); |
| ret = wolfSSL_connect(wssl->ssl); |
| |
| if(!wssl->x509_store_setup) { |
| /* After having send off the ClientHello, we prepare the x509 |
| * store to verify the coming certificate from the server */ |
| result = Curl_wssl_setup_x509_store(cf, data, wssl); |
| if(result) { |
| CURL_TRC_CF(data, cf, "Curl_wssl_setup_x509_store() -> %d", result); |
| return result; |
| } |
| } |
| |
| #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 == WOLFSSL_SUCCESS || |
| (!wolfSSL_want_read(wssl->ssl) && |
| !wolfSSL_want_write(wssl->ssl))) { |
| wssl_log_tls12_secret(wssl->ssl); |
| /* Client Random and master secrets are no longer needed, erase these. |
| * Ignored while the handshake is still in progress. */ |
| wolfSSL_FreeArrays(wssl->ssl); |
| } |
| } |
| #endif /* OPENSSL_EXTRA */ |
| |
| detail = wolfSSL_get_error(wssl->ssl, ret); |
| CURL_TRC_CF(data, cf, "wolfSSL_connect() -> %d, detail=%d", ret, detail); |
| |
| /* On a successful handshake with an IP address, do an extra check |
| * on the peer certificate */ |
| if(ret == WOLFSSL_SUCCESS && |
| conn_config->verifyhost && |
| !connssl->peer.sni) { |
| /* we have an IP address as hostname. */ |
| WOLFSSL_X509 *cert = wolfSSL_get_peer_certificate(wssl->ssl); |
| if(!cert) { |
| failf(data, "unable to get peer certificate"); |
| return CURLE_PEER_FAILED_VERIFICATION; |
| } |
| ret = wolfSSL_X509_check_ip_asc(cert, connssl->peer.hostname, 0); |
| CURL_TRC_CF(data, cf, "check peer certificate for IP match on %s -> %d", |
| connssl->peer.hostname, ret); |
| if(ret != WOLFSSL_SUCCESS) |
| detail = DOMAIN_NAME_MISMATCH; |
| wolfSSL_X509_free(cert); |
| } |
| |
| if(ret == WOLFSSL_SUCCESS) { |
| return CURLE_OK; |
| } |
| else { |
| if(WOLFSSL_ERROR_WANT_READ == detail) { |
| connssl->io_need = CURL_SSL_IO_NEED_RECV; |
| return CURLE_AGAIN; |
| } |
| else if(WOLFSSL_ERROR_WANT_WRITE == detail) { |
| connssl->io_need = CURL_SSL_IO_NEED_SEND; |
| return CURLE_AGAIN; |
| } |
| else if(DOMAIN_NAME_MISMATCH == detail) { |
| /* There is no easy way to override only the CN matching. |
| * This enables the override of both mismatching SubjectAltNames |
| * as also mismatching CN fields */ |
| failf(data, " subject alt name(s) or common name do not match \"%s\"", |
| connssl->peer.dispname); |
| return CURLE_PEER_FAILED_VERIFICATION; |
| } |
| else if(ASN_NO_SIGNER_E == detail) { |
| if(conn_config->verifypeer) { |
| failf(data, " CA signer not available for verification"); |
| return CURLE_SSL_CACERT_BADFILE; |
| } |
| /* Continue with a warning if no strict certificate |
| verification is required. */ |
| infof(data, "CA signer not available for verification, " |
| "continuing anyway"); |
| return CURLE_OK; |
| } |
| else if(ASN_AFTER_DATE_E == detail) { |
| failf(data, "server verification failed: certificate has expired."); |
| return CURLE_PEER_FAILED_VERIFICATION; |
| } |
| else if(ASN_BEFORE_DATE_E == detail) { |
| failf(data, "server verification failed: certificate not valid yet."); |
| return CURLE_PEER_FAILED_VERIFICATION; |
| } |
| else if(wssl->io_result) { |
| switch(wssl->io_result) { |
| case CURLE_SEND_ERROR: |
| case CURLE_RECV_ERROR: |
| return CURLE_SSL_CONNECT_ERROR; |
| default: |
| return wssl->io_result; |
| } |
| } |
| #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG |
| else if(detail == -1) { |
| /* try access a retry_config ECHConfigList for tracing */ |
| byte echConfigs[1000]; |
| word32 echConfigsLen = 1000; |
| int rv = 0; |
| |
| /* this currently does not produce the retry_configs */ |
| rv = wolfSSL_GetEchConfigs(wssl->ssl, echConfigs, &echConfigsLen); |
| if(rv != WOLFSSL_SUCCESS) { |
| infof(data, "Failed to get ECHConfigs"); |
| } |
| else { |
| char *b64str = NULL; |
| size_t blen = 0; |
| |
| result = curlx_base64_encode(echConfigs, echConfigsLen, |
| &b64str, &blen); |
| if(!result && b64str) |
| infof(data, "ECH: (not yet) retry_configs %s", b64str); |
| curlx_free(b64str); |
| } |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| #endif |
| else { |
| char error_buffer[256]; |
| failf(data, "SSL_connect failed with error %d: %s", detail, |
| wssl_strerror((unsigned long)detail, error_buffer, |
| sizeof(error_buffer))); |
| return CURLE_SSL_CONNECT_ERROR; |
| } |
| } |
| } |
| |
| static CURLcode wssl_send(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| const void *buf, size_t blen, |
| size_t *pnwritten) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| CURLcode result = CURLE_OK; |
| int nwritten; |
| |
| DEBUGASSERT(wssl); |
| *pnwritten = 0; |
| wolfSSL_ERR_clear_error(); |
| |
| if(blen) { |
| int memlen = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; |
| |
| nwritten = wolfSSL_write(wssl->ssl, buf, memlen); |
| |
| if(nwritten > 0) |
| *pnwritten += (size_t)nwritten; |
| else { |
| int err = wolfSSL_get_error(wssl->ssl, nwritten); |
| |
| switch(err) { |
| case WOLFSSL_ERROR_WANT_READ: |
| case WOLFSSL_ERROR_WANT_WRITE: |
| /* there is data pending, re-invoke wolfSSL_write() */ |
| if(*pnwritten) { |
| result = CURLE_OK; |
| goto out; |
| } |
| result = CURLE_AGAIN; |
| goto out; |
| |
| default: |
| if(wssl->io_result == CURLE_AGAIN) { |
| if(*pnwritten) { |
| result = CURLE_OK; |
| goto out; |
| } |
| result = CURLE_AGAIN; |
| goto out; |
| } |
| { |
| char error_buffer[256]; |
| failf(data, "SSL write: %s, errno %d", |
| wssl_strerror((unsigned long)err, error_buffer, |
| sizeof(error_buffer)), |
| SOCKERRNO); |
| } |
| result = CURLE_SEND_ERROR; |
| goto out; |
| } |
| } |
| } |
| |
| out: |
| CURL_TRC_CF(data, cf, "wssl_send(len=%zu) -> %d, %zu", |
| blen, result, *pnwritten); |
| return result; |
| } |
| |
| static CURLcode wssl_shutdown(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| bool send_shutdown, bool *done) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wctx = (struct wssl_ctx *)connssl->backend; |
| CURLcode result = CURLE_OK; |
| char buf[1024]; |
| int nread = -1, err; |
| size_t i; |
| VERBOSE(char error_buffer[256]); |
| |
| DEBUGASSERT(wctx); |
| if(!wctx->ssl || cf->shutdown) { |
| *done = TRUE; |
| goto out; |
| } |
| |
| wctx->shutting_down = TRUE; |
| connssl->io_need = CURL_SSL_IO_NEED_NONE; |
| *done = FALSE; |
| if(!(wolfSSL_get_shutdown(wctx->ssl) & WOLFSSL_SENT_SHUTDOWN)) { |
| /* We have not started the shutdown from our side yet. Check |
| * if the server already sent us one. */ |
| wolfSSL_ERR_clear_error(); |
| nread = wolfSSL_read(wctx->ssl, buf, (int)sizeof(buf)); |
| err = wolfSSL_get_error(wctx->ssl, nread); |
| CURL_TRC_CF(data, cf, "wolfSSL_read, nread=%d, err=%d", nread, err); |
| if(!nread && err == WOLFSSL_ERROR_ZERO_RETURN) { |
| bool input_pending; |
| /* Yes, it did. */ |
| if(!send_shutdown) { |
| CURL_TRC_CF(data, cf, "SSL shutdown received, not sending"); |
| *done = TRUE; |
| goto out; |
| } |
| else if(!cf->next->cft->is_alive(cf->next, data, &input_pending)) { |
| /* Server closed the connection after its closy notify. It |
| * seems not interested to see our close notify, so do not |
| * send it. We are done. */ |
| CURL_TRC_CF(data, cf, "peer closed connection"); |
| connssl->peer_closed = TRUE; |
| *done = TRUE; |
| goto out; |
| } |
| } |
| } |
| |
| /* wolfSSL should now have started the shutdown from our side. Since it |
| * was not complete, we are lacking the close notify from the server. */ |
| if(send_shutdown) { |
| wolfSSL_ERR_clear_error(); |
| nread = wolfSSL_shutdown(wctx->ssl); |
| if(nread == 1) { |
| CURL_TRC_CF(data, cf, "SSL shutdown finished"); |
| *done = TRUE; |
| goto out; |
| } |
| if(WOLFSSL_ERROR_WANT_WRITE == wolfSSL_get_error(wctx->ssl, nread)) { |
| CURL_TRC_CF(data, cf, "SSL shutdown still wants to send"); |
| connssl->io_need = CURL_SSL_IO_NEED_SEND; |
| goto out; |
| } |
| /* Having sent the close notify, we use wolfSSL_read() to get the |
| * missing close notify from the server. */ |
| } |
| |
| for(i = 0; i < 10; ++i) { |
| wolfSSL_ERR_clear_error(); |
| nread = wolfSSL_read(wctx->ssl, buf, (int)sizeof(buf)); |
| if(nread <= 0) |
| break; |
| } |
| err = wolfSSL_get_error(wctx->ssl, nread); |
| switch(err) { |
| case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ |
| CURL_TRC_CF(data, cf, "SSL shutdown received"); |
| *done = TRUE; |
| break; |
| case WOLFSSL_ERROR_NONE: /* did not get anything */ |
| case WOLFSSL_ERROR_WANT_READ: |
| /* wolfSSL has send its notify and now wants to read the reply |
| * from the server. We are not really interested in that. */ |
| CURL_TRC_CF(data, cf, "SSL shutdown sent, want receive"); |
| connssl->io_need = CURL_SSL_IO_NEED_RECV; |
| break; |
| case WOLFSSL_ERROR_WANT_WRITE: |
| CURL_TRC_CF(data, cf, "SSL shutdown send blocked"); |
| connssl->io_need = CURL_SSL_IO_NEED_SEND; |
| break; |
| default: |
| CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)", |
| wssl_strerror((unsigned long)err, error_buffer, |
| sizeof(error_buffer)), |
| err); |
| result = CURLE_RECV_ERROR; |
| break; |
| } |
| |
| out: |
| cf->shutdown = (result || *done); |
| return result; |
| } |
| |
| static void wssl_close(struct Curl_cfilter *cf, struct Curl_easy *data) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| |
| (void)data; |
| |
| DEBUGASSERT(wssl); |
| |
| if(wssl->ssl) { |
| wolfSSL_free(wssl->ssl); |
| wssl->ssl = NULL; |
| } |
| if(wssl->ssl_ctx) { |
| wolfSSL_CTX_free(wssl->ssl_ctx); |
| wssl->ssl_ctx = NULL; |
| } |
| } |
| |
| static CURLcode wssl_recv(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| char *buf, size_t blen, |
| size_t *pnread) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| int buffsize = (blen > (size_t)INT_MAX) ? INT_MAX : (int)blen; |
| int nread; |
| |
| DEBUGASSERT(wssl); |
| |
| *pnread = 0; |
| wolfSSL_ERR_clear_error(); |
| |
| nread = wolfSSL_read(wssl->ssl, buf, buffsize); |
| |
| if(nread > 0) |
| *pnread = (size_t)nread; |
| else { |
| int err = wolfSSL_get_error(wssl->ssl, nread); |
| |
| switch(err) { |
| case WOLFSSL_ERROR_ZERO_RETURN: /* no more data */ |
| CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen); |
| return CURLE_OK; |
| case WOLFSSL_ERROR_NONE: |
| case WOLFSSL_ERROR_WANT_READ: |
| case WOLFSSL_ERROR_WANT_WRITE: |
| if(!wssl->io_result && !connssl->peer_closed) { |
| /* there is data pending, re-invoke wolfSSL_read() */ |
| CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen); |
| return CURLE_AGAIN; |
| } |
| /* fall through to default error handling below */ |
| FALLTHROUGH(); |
| default: |
| if(wssl->io_result == CURLE_AGAIN) { |
| CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen); |
| return CURLE_AGAIN; |
| } |
| else if(!wssl->io_result && connssl->peer_closed) { |
| CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen); |
| failf(data, "Connection closed abruptly"); |
| } |
| else { |
| char error_buffer[256]; |
| failf(data, "SSL read: %s, errno %d", |
| wssl_strerror((unsigned long)err, error_buffer, |
| sizeof(error_buffer)), |
| SOCKERRNO); |
| } |
| return CURLE_RECV_ERROR; |
| } |
| } |
| |
| CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> 0, %zu", blen, *pnread); |
| return CURLE_OK; |
| } |
| |
| size_t Curl_wssl_version(char *buffer, size_t size) |
| { |
| return curl_msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version()); |
| } |
| |
| static int wssl_init(void) |
| { |
| int ret; |
| |
| #ifdef OPENSSL_EXTRA |
| Curl_tls_keylog_open(); |
| #endif |
| ret = (wolfSSL_Init() == WOLFSSL_SUCCESS); |
| if(ret) |
| ret = wssl_bio_cf_init_methods(); |
| return ret; |
| } |
| |
| static void wssl_cleanup(void) |
| { |
| wssl_bio_cf_free_methods(); |
| wolfSSL_Cleanup(); |
| #ifdef OPENSSL_EXTRA |
| Curl_tls_keylog_close(); |
| #endif |
| } |
| |
| static bool wssl_data_pending(struct Curl_cfilter *cf, |
| const struct Curl_easy *data) |
| { |
| struct ssl_connect_data *ctx = cf->ctx; |
| struct wssl_ctx *wssl; |
| |
| (void)data; |
| DEBUGASSERT(ctx && ctx->backend); |
| |
| wssl = (struct wssl_ctx *)ctx->backend; |
| if(wssl->ssl) /* wolfSSL is in use */ |
| return wolfSSL_pending(wssl->ssl); |
| else |
| return FALSE; |
| } |
| |
| void Curl_wssl_report_handshake(struct Curl_easy *data, struct wssl_ctx *wssl) |
| { |
| (void)wssl; |
| infof(data, "SSL connection using %s / %s", |
| wolfSSL_get_version(wssl->ssl), |
| wolfSSL_get_cipher_name(wssl->ssl)); |
| } |
| |
| static CURLcode wssl_connect(struct Curl_cfilter *cf, |
| struct Curl_easy *data, |
| bool *done) |
| { |
| struct ssl_connect_data *connssl = cf->ctx; |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| CURLcode result = CURLE_OK; |
| |
| /* check if the connection has already been established */ |
| if(ssl_connection_complete == connssl->state) { |
| *done = TRUE; |
| return CURLE_OK; |
| } |
| |
| *done = FALSE; |
| connssl->io_need = CURL_SSL_IO_NEED_NONE; |
| |
| if(ssl_connect_1 == connssl->connecting_state) { |
| #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG |
| /* if we do ECH and need the HTTPS-RR information for it, |
| * we delay the connect until it arrives or DNS resolve fails. */ |
| if(Curl_wssl_need_httpsrr(data) && |
| !Curl_conn_dns_resolved_https(data, cf->sockindex)) { |
| CURL_TRC_CF(data, cf, "need HTTPS-RR for ECH, delaying connect"); |
| return CURLE_OK; |
| } |
| #endif /* HAVE_WOLFSSL_CTX_GENERATEECHCONFIG */ |
| result = wssl_connect_step1(cf, data); |
| if(result) |
| return result; |
| connssl->connecting_state = ssl_connect_2; |
| } |
| |
| if(ssl_connect_2 == connssl->connecting_state) { |
| if(connssl->earlydata_state == ssl_earlydata_await) { |
| /* We defer the handshake until request data arrives. */ |
| DEBUGASSERT(connssl->state == ssl_connection_deferred); |
| goto out; |
| } |
| result = wssl_handshake(cf, data); |
| if(result == CURLE_AGAIN) |
| goto out; |
| wssl->hs_result = result; |
| connssl->connecting_state = ssl_connect_3; |
| } |
| |
| if(ssl_connect_3 == connssl->connecting_state) { |
| /* Once the handshake has errored, it stays in that state and |
| * errors again on every call. */ |
| if(wssl->hs_result) { |
| result = wssl->hs_result; |
| goto out; |
| } |
| result = Curl_wssl_verify_pinned(cf, data, wssl); |
| if(result) { |
| wssl->hs_result = result; |
| goto out; |
| } |
| /* handhshake was done without errors */ |
| #ifdef HAVE_ALPN |
| if(connssl->alpn) { |
| int rc; |
| char *protocol = NULL; |
| unsigned short protocol_len = 0; |
| |
| rc = wolfSSL_ALPN_GetProtocol(wssl->ssl, &protocol, &protocol_len); |
| |
| if(rc == WOLFSSL_SUCCESS) { |
| Curl_alpn_set_negotiated(cf, data, connssl, |
| (const unsigned char *)protocol, |
| protocol_len); |
| } |
| else if(rc == WOLFSSL_ALPN_NOT_FOUND) |
| Curl_alpn_set_negotiated(cf, data, connssl, NULL, 0); |
| else { |
| failf(data, "ALPN, failure getting protocol, error %d", rc); |
| wssl->hs_result = result = CURLE_SSL_CONNECT_ERROR; |
| goto out; |
| } |
| } |
| #endif /* HAVE_ALPN */ |
| |
| connssl->connecting_state = ssl_connect_done; |
| connssl->state = ssl_connection_complete; |
| Curl_wssl_report_handshake(data, wssl); |
| |
| #ifdef WOLFSSL_EARLY_DATA |
| if(connssl->earlydata_state > ssl_earlydata_none) { |
| /* We should be in this state by now */ |
| DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent); |
| connssl->earlydata_state = |
| (wolfSSL_get_early_data_status(wssl->ssl) == |
| WOLFSSL_EARLY_DATA_REJECTED) ? |
| ssl_earlydata_rejected : ssl_earlydata_accepted; |
| } |
| #endif /* WOLFSSL_EARLY_DATA */ |
| } |
| |
| if((connssl->connecting_state == ssl_connect_done) || |
| (connssl->state == ssl_connection_deferred)) { |
| *done = TRUE; |
| } |
| |
| out: |
| if(result) { |
| *done = FALSE; |
| if(result == CURLE_AGAIN) |
| return CURLE_OK; |
| } |
| else if((connssl->connecting_state == ssl_connect_done) || |
| (connssl->state == ssl_connection_deferred)) { |
| *done = TRUE; |
| } |
| return result; |
| } |
| |
| static CURLcode wssl_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 wssl_sha256sum(const unsigned char *tmp, /* input */ |
| size_t tmplen, |
| unsigned char *sha256sum /* output */, |
| size_t unused) |
| { |
| wc_Sha256 SHA256pw; |
| (void)unused; |
| if(wc_InitSha256(&SHA256pw)) |
| return CURLE_FAILED_INIT; |
| wc_Sha256Update(&SHA256pw, tmp, (word32)tmplen); |
| wc_Sha256Final(&SHA256pw, sha256sum); |
| return CURLE_OK; |
| } |
| |
| static void *wssl_get_internals(struct ssl_connect_data *connssl, |
| CURLINFO info) |
| { |
| struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend; |
| DEBUGASSERT(wssl); |
| return info == CURLINFO_TLS_SESSION ? |
| (void *)wssl->ssl_ctx : (void *)wssl->ssl; |
| } |
| |
| 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_CA_PATH | |
| SSLSUPP_CAINFO_BLOB | |
| #ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG |
| SSLSUPP_ECH | |
| #endif |
| SSLSUPP_SSL_CTX | |
| #ifdef WOLFSSL_TLS13 |
| SSLSUPP_TLS13_CIPHERSUITES | |
| #endif |
| SSLSUPP_CA_CACHE | |
| SSLSUPP_CIPHER_LIST | |
| SSLSUPP_SSL_EC_CURVES, |
| |
| sizeof(struct wssl_ctx), |
| |
| wssl_init, /* init */ |
| wssl_cleanup, /* cleanup */ |
| Curl_wssl_version, /* version */ |
| wssl_shutdown, /* shutdown */ |
| wssl_data_pending, /* data_pending */ |
| wssl_random, /* random */ |
| NULL, /* cert_status_request */ |
| wssl_connect, /* connect */ |
| Curl_ssl_adjust_pollset, /* adjust_pollset */ |
| wssl_get_internals, /* get_internals */ |
| wssl_close, /* close_one */ |
| NULL, /* close_all */ |
| NULL, /* set_engine */ |
| NULL, /* set_engine_default */ |
| NULL, /* engines_list */ |
| wssl_sha256sum, /* sha256sum */ |
| wssl_recv, /* recv decrypted data */ |
| wssl_send, /* send data to encrypt */ |
| NULL, /* get_channel_binding */ |
| }; |
| |
| #endif |