#include "../curl_setup.h"
#ifdef USE_WOLFSSL
#define WOLFSSL_OPTIONS_IGNORE_SYS
#include <wolfssl/options.h>
#include <wolfssl/version.h>
#if LIBWOLFSSL_VERSION_HEX < 0x03004006
#error "wolfSSL version should be at least 3.4.6"
#endif
#ifndef HAVE_ALPN
#ifdef HAVE_WOLFSSL_USEALPN
#define HAVE_ALPN
#endif
#endif
#include <limits.h>
#include "../urldata.h"
#include "../sendf.h"
#include "../curlx/inet_pton.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "keylog.h"
#include "../parsedate.h"
#include "../connect.h"
#include "../progress.h"
#include "../select.h"
#include "../strdup.h"
#include "x509asn1.h"
#include "../multiif.h"
#include <wolfssl/ssl.h>
#include <wolfssl/error-ssl.h>
#include "wolfssl.h"
#include "../curl_memory.h"
#include "../memdebug.h"
#ifdef HAVE_WOLFSSL_CTX_GENERATEECHCONFIG
#define USE_ECH_WOLFSSL
#endif
#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
#undef USE_FULL_BIO
#endif
#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
#else
#undef USE_BIO_CHAIN
#endif
static CURLcode wssl_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done);
#ifdef OPENSSL_EXTRA
#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) {
return 0;
}
Curl_tls_keylog_write(label, client_random, secret, secretSz);
return 0;
}
#endif
static void wssl_log_tls12_secret(WOLFSSL *ssl)
{
unsigned char *ms, *sr, *cr;
unsigned int msLen, srLen, crLen, i, x = 0;
#if LIBWOLFSSL_VERSION_HEX >= 0x0300d000
switch(wolfSSL_GetVersion(ssl)) {
case WOLFSSL_SSLV3:
case WOLFSSL_TLSV1:
case WOLFSSL_TLSV1_1:
case WOLFSSL_TLSV1_2:
break;
default:
return;
}
#endif
if(wolfSSL_get_keys(ssl, &ms, &msLen, &sr, &srLen, &cr, &crLen) !=
WOLFSSL_SUCCESS) {
return;
}
for(i = 0; i < msLen; i++) {
x |= ms[i];
}
if(x == 0) {
return;
}
Curl_tls_keylog_write("CLIENT_RANDOM", cr, ms, msLen);
}
#endif
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:
ret = 1;
break;
case WOLFSSL_BIO_CTRL_DUP:
ret = 1;
break;
#ifdef WOLFSSL_BIO_CTRL_EOF
case WOLFSSL_BIO_CTRL_EOF: {
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)) {
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, 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(CURLE_AGAIN == result) {
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) {
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(CURLE_AGAIN == result)
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 void wssl_bio_cf_init_methods(void)
{
wssl_bio_cf_method = wolfSSL_BIO_meth_new(WOLFSSL_BIO_MEMORY,
"wolfSSL CF BIO");
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);
}
static void wssl_bio_cf_free_methods(void)
{
wolfSSL_BIO_meth_free(wssl_bio_cf_method);
}
#else
#define wssl_bio_cf_init_methods() Curl_nop_stmt
#define wssl_bio_cf_free_methods() Curl_nop_stmt
#endif
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, *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 = 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;
}
sdata_len = wolfSSL_i2d_SSL_SESSION(session, &sdata);
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 = Curl_memdup0((char *)quic_tp, quic_tp_len);
if(!qtp_clone) {
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;
if(!result) {
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
}
out:
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;
}
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;
struct wssl_ctx *wssl = (struct wssl_ctx *)connssl->backend;
CURLcode result = CURLE_OK;
*do_early_data = FALSE;
#ifdef WOLFSSL_EARLY_DATA
connssl->earlydata_max = wolfSSL_SESSION_get_max_early_data(
wolfSSL_get_session(wssl->ssl));
#else
(void)wssl;
connssl->earlydata_max = 0;
#endif
if(!connssl->earlydata_max) {
CURL_TRC_CF(data, cf, "SSL session does not allow earlydata");
}
else if(!Curl_alpn_contains_proto(alpns, scs->alpn)) {
CURL_TRC_CF(data, cf, "SSL session has different ALPN, no early data");
}
else {
infof(data, "SSL session allows %zu bytes of early data, "
"reusing ALPN '%s'", connssl->earlydata_max, scs->alpn);
connssl->earlydata_state = ssl_earlydata_await;
connssl->state = ssl_connection_deferred;
result = Curl_alpn_set_negotiated(cf, data, connssl,
(const unsigned char *)scs->alpn,
scs->alpn ? strlen(scs->alpn) : 0);
*do_early_data = !result;
}
return result;
}
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;
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->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)
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;
Curl_alpn_restrict_to(alpns, scs->alpn);
wolfSSL_set_max_early_data(wss->ssl, edmax);
}
#else
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 =
(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;
wssl->x509_store_setup = TRUE;
#ifndef NO_FILESYSTEM
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
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
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) {
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 {
infof(data, "error setting certificate verify locations,"
" continuing anyway:");
}
}
else {
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;
}
#define MPROTO_WSSL_X509_KEY "tls:wssl:x509:share"
struct wssl_x509_share {
char *CAfile;
WOLFSSL_X509_STORE *store;
struct curltime time;
};
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);
}
free(share->CAfile);
free(share);
}
static bool
wssl_cached_x509_store_expired(const struct Curl_easy *data,
const struct wssl_x509_share *mb)
{
const struct ssl_general_config *cfg = &data->set.general_ssl;
struct curltime now = curlx_now();
timediff_t elapsed_ms = curlx_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
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,
const 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,
const 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 = 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)) {
free(share);
return;
}
}
if(wolfSSL_X509_STORE_up_ref(store)) {
char *CAfile = NULL;
if(conn_config->CAfile) {
CAfile = strdup(conn_config->CAfile);
if(!CAfile) {
wolfSSL_X509_STORE_free(store);
return;
}
}
if(share->store) {
wolfSSL_X509_STORE_free(share->store);
free(share->CAfile);
}
share->time = curlx_now();
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;
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) {
}
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_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 {
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(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
#if LIBWOLFSSL_VERSION_HEX < 0x04002000 || !defined(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
static CURLcode client_certificate(struct Curl_easy *data,
struct ssl_config_data *ssl_config,
struct wssl_ctx *wctx)
{
#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
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
return CURLE_OK;
}
static CURLcode ssl_version(struct Curl_easy *data,
struct ssl_primary_config *conn_config,
struct wssl_ctx *wctx)
{
int res;
switch(conn_config->version) {
case CURL_SSLVERSION_DEFAULT:
case CURL_SSLVERSION_TLSv1:
case CURL_SSLVERSION_TLSv1_0:
res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_VERSION);
break;
case CURL_SSLVERSION_TLSv1_1:
res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_1_VERSION);
break;
case CURL_SSLVERSION_TLSv1_2:
res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_2_VERSION);
break;
#ifdef WOLFSSL_TLS13
case CURL_SSLVERSION_TLSv1_3:
res = wolfSSL_CTX_set_min_proto_version(wctx->ssl_ctx, TLS1_3_VERSION);
break;
#endif
default:
failf(data, "wolfSSL: unsupported minimum TLS version value");
return CURLE_SSL_CONNECT_ERROR;
}
if(res != WOLFSSL_SUCCESS) {
failf(data, "wolfSSL: failed set the minimum TLS version");
return CURLE_SSL_CONNECT_ERROR;
}
switch(conn_config->version_max) {
#ifdef WOLFSSL_TLS13
case CURL_SSLVERSION_MAX_TLSv1_3:
res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_3_VERSION);
break;
#endif
case CURL_SSLVERSION_MAX_TLSv1_2:
res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_2_VERSION);
break;
case CURL_SSLVERSION_MAX_TLSv1_1:
res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_1_VERSION);
break;
case CURL_SSLVERSION_MAX_TLSv1_0:
res = wolfSSL_CTX_set_max_proto_version(wctx->ssl_ctx, TLS1_VERSION);
break;
case CURL_SSLVERSION_MAX_DEFAULT:
case CURL_SSLVERSION_MAX_NONE:
res = WOLFSSL_SUCCESS;
break;
default:
failf(data, "wolfSSL: unsupported maximum TLS version value");
return CURLE_SSL_CONNECT_ERROR;
}
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"
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;
char *curves;
#ifdef WOLFSSL_HAVE_KYBER
word16 pqkem = 0;
size_t idx = 0;
#endif
CURLcode result = CURLE_FAILED_INIT;
unsigned char transport;
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);
#if LIBWOLFSSL_VERSION_HEX < 0x04002000
req_method = wolfSSLv23_client_method();
#else
req_method = wolfTLS_client_method();
#endif
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);
if(result)
goto out;
#ifndef WOLFSSL_TLS13
{
char *ciphers = conn_config->cipher_list;
if(ciphers) {
if(!SSL_CTX_set_cipher_list(wctx->ssl_ctx, ciphers)) {
failf(data, "failed setting cipher list: %s", ciphers);
result = CURLE_SSL_CIPHER;
goto out;
}
infof(data, "Cipher selection: %s", ciphers);
}
}
#else
#define MAX_CIPHER_LEN 4096
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(ciphers13)
result = curlx_dyn_add(&c, ciphers13);
else
result = wssl_add_default_ciphers(TRUE, &c);
if(!result) {
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)
goto out;
if(!wolfSSL_CTX_set_cipher_list(wctx->ssl_ctx, curlx_dyn_ptr(&c))) {
failf(data, "failed setting cipher list: %s", curlx_dyn_ptr(&c));
curlx_dyn_free(&c);
result = CURLE_SSL_CIPHER;
goto out;
}
infof(data, "Cipher selection: %s", curlx_dyn_ptr(&c));
curlx_dyn_free(&c);
}
#endif
curves = conn_config->curves;
if(!curves && (transport == TRNSPRT_QUIC))
curves = (char *)CURL_UNCONST(QUIC_GROUPS);
if(curves) {
#ifdef WOLFSSL_HAVE_KYBER
for(idx = 0; gnm[idx].name != NULL; idx++) {
if(strncmp(curves, gnm[idx].name, strlen(gnm[idx].name)) == 0) {
pqkem = gnm[idx].group;
break;
}
}
if(pqkem == 0)
#endif
{
if(!wolfSSL_CTX_set1_curves_list(wctx->ssl_ctx, curves)) {
failf(data, "failed setting curves list: '%s'", curves);
result = CURLE_SSL_CIPHER;
goto out;
}
}
}
result = client_certificate(data, ssl_config, wctx);
if(result)
goto out;
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
if(Curl_ssl_scache_use(cf, data) && (transport != TRNSPRT_QUIC)) {
wolfSSL_CTX_sess_set_new_cb(wctx->ssl_ctx, wssl_vtls_new_session_cb);
}
if(cb_setup) {
result = cb_setup(cf, data, cb_user_data);
if(result)
goto out;
}
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
wctx->ssl = wolfSSL_new(wctx->ssl_ctx);
if(!wctx->ssl) {
failf(data, "SSL: could not create a handle");
result = CURLE_OUT_OF_MEMORY;
goto out;
}
wolfSSL_set_app_data(wctx->ssl, ssl_user_data);
#ifdef WOLFSSL_QUIC
if(transport == TRNSPRT_QUIC)
wolfSSL_set_quic_use_legacy_codepoint(wctx->ssl, 0);
#endif
#ifdef WOLFSSL_HAVE_KYBER
if(pqkem) {
if(wolfSSL_UseKeyShare(wctx->ssl, pqkem) != WOLFSSL_SUCCESS) {
failf(data, "unable to use PQ KEM");
}
}
#endif
if(Curl_ssl_scache_use(cf, data)) {
(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");
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
CURL_TRC_CF(data, cf, "set ALPN: %s", proto.data);
}
#endif
#ifdef OPENSSL_EXTRA
if(Curl_tls_keylog_enabled()) {
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
#ifdef HAVE_SECURE_RENEGOTIATION
if(wolfSSL_UseSecureRenegotiation(wctx->ssl) != SSL_SUCCESS) {
failf(data, "SSL: failed setting secure renegotiation");
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
#endif
#ifdef USE_ECH_WOLFSSL
if(ECH_ENABLED(data)) {
int trying_ech_now = 0;
if(data->set.str[STRING_ECH_PUBLIC]) {
infof(data, "ECH: outername not (yet) supported with wolfSSL");
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
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) {
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
}
else {
trying_ech_now = 1;
infof(data, "ECH: ECHConfig from command line");
}
}
else {
struct ssl_connect_data *connssl = cf->ctx;
struct Curl_dns_entry *dns = NULL;
dns = Curl_dnscache_get(data, connssl->peer.hostname, connssl->peer.port,
cf->conn->ip_version);
if(!dns) {
infof(data, "ECH: requested but no DNS info available");
if(data->set.tls_ech & CURLECH_HARD) {
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
}
else {
struct Curl_https_rrinfo *rinfo = NULL;
rinfo = dns->hinfo;
if(rinfo && rinfo->echconfiglist) {
unsigned char *ecl = rinfo->echconfiglist;
size_t elen = rinfo->echconfiglist_len;
infof(data, "ECH: ECHConfig from DoH 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) {
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
}
else {
trying_ech_now = 1;
infof(data, "ECH: imported ECHConfigList of length %ld", elen);
}
}
else {
infof(data, "ECH: requested but no ECHConfig available");
if(data->set.tls_ech & CURLECH_HARD) {
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
}
Curl_resolv_unlink(data, &dns);
}
}
if(trying_ech_now && wolfSSL_set_min_proto_version(wctx->ssl,
TLS1_3_VERSION) != 1) {
infof(data, "ECH: cannot force TLSv1.3 [ERROR]");
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
}
#endif
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;
}
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
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;
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
if(!wolfSSL_set_fd(wssl->ssl,
(int)Curl_conn_cf_get_socket(cf, data))) {
failf(data, "SSL: wolfSSL_set_fd failed");
return CURLE_SSL_CONNECT_ERROR;
}
#endif
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";
strcpy(buf, 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;
CURLcode result = CURLE_OK;
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);
switch(err) {
case WOLFSSL_ERROR_NONE:
case WOLFSSL_ERROR_WANT_READ:
case WOLFSSL_ERROR_WANT_WRITE:
result = CURLE_AGAIN;
break;
default: {
char error_buffer[256];
CURL_TRC_CF(data, cf, "SSL send early data, error: '%s'(%d)",
wssl_strerror((unsigned long)err, error_buffer,
sizeof(error_buffer)),
err);
result = CURLE_SEND_ERROR;
break;
}
}
goto out;
}
Curl_bufq_skip(&connssl->earlydata, (size_t)nwritten);
}
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);
out:
return result;
}
#endif
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_ERR_clear_error();
ret = wolfSSL_connect(wssl->ssl);
if(!wssl->x509_store_setup) {
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(ret == WOLFSSL_SUCCESS ||
(!wolfSSL_want_read(wssl->ssl) &&
!wolfSSL_want_write(wssl->ssl))) {
wssl_log_tls12_secret(wssl->ssl);
wolfSSL_FreeArrays(wssl->ssl);
}
}
#endif
detail = wolfSSL_get_error(wssl->ssl, ret);
CURL_TRC_CF(data, cf, "wolfSSL_connect() -> %d, detail=%d", ret, detail);
if(ret == WOLFSSL_SUCCESS &&
conn_config->verifyhost &&
!connssl->peer.sni) {
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) {
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;
}
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 USE_ECH_WOLFSSL
else if(detail == -1) {
byte echConfigs[1000];
word32 echConfigsLen = 1000;
int rv = 0;
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((const char *)echConfigs, echConfigsLen,
&b64str, &blen);
if(!result && b64str)
infof(data, "ECH: (not yet) retry_configs %s", b64str);
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:
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];
char error_buffer[256];
int nread = -1, err;
size_t i;
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)) {
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;
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)) {
CURL_TRC_CF(data, cf, "peer closed connection");
connssl->peer_closed = TRUE;
*done = TRUE;
goto out;
}
}
}
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;
}
}
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:
CURL_TRC_CF(data, cf, "SSL shutdown received");
*done = TRUE;
break;
case WOLFSSL_ERROR_NONE:
case WOLFSSL_ERROR_WANT_READ:
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:
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) {
CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> CLOSED", blen);
return CURLE_OK;
}
CURL_TRC_CF(data, cf, "wssl_recv(len=%zu) -> AGAIN", blen);
return CURLE_AGAIN;
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)
{
#if LIBWOLFSSL_VERSION_HEX >= 0x03006000
return curl_msnprintf(buffer, size, "wolfSSL/%s", wolfSSL_lib_version());
#elif defined(WOLFSSL_VERSION)
return curl_msnprintf(buffer, size, "wolfSSL/%s", WOLFSSL_VERSION);
#endif
}
static int wssl_init(void)
{
int ret;
#ifdef OPENSSL_EXTRA
Curl_tls_keylog_open();
#endif
ret = (wolfSSL_Init() == WOLFSSL_SUCCESS);
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)
return wolfSSL_pending(wssl->ssl);
else
return FALSE;
}
void Curl_wssl_report_handshake(struct Curl_easy *data,
struct wssl_ctx *wssl)
{
#if (LIBWOLFSSL_VERSION_HEX >= 0x03009010)
infof(data, "SSL connection using %s / %s",
wolfSSL_get_version(wssl->ssl),
wolfSSL_get_cipher_name(wssl->ssl));
#else
infof(data, "SSL connected");
#endif
}
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;
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) {
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) {
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) {
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;
}
#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
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) {
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
}
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,
size_t tmplen,
unsigned char *sha256sum ,
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" },
#ifdef KEEP_PEER_CERT
SSLSUPP_PINNEDPUBKEY |
#endif
#ifdef USE_BIO_CHAIN
SSLSUPP_HTTPS_PROXY |
#endif
SSLSUPP_CA_PATH |
SSLSUPP_CAINFO_BLOB |
#ifdef USE_ECH_WOLFSSL
SSLSUPP_ECH |
#endif
SSLSUPP_SSL_CTX |
#ifdef WOLFSSL_TLS13
SSLSUPP_TLS13_CIPHERSUITES |
#endif
SSLSUPP_CA_CACHE |
SSLSUPP_CIPHER_LIST,
sizeof(struct wssl_ctx),
wssl_init,
wssl_cleanup,
Curl_wssl_version,
wssl_shutdown,
wssl_data_pending,
wssl_random,
NULL,
wssl_connect,
Curl_ssl_adjust_pollset,
wssl_get_internals,
wssl_close,
NULL,
NULL,
NULL,
NULL,
wssl_sha256sum,
wssl_recv,
wssl_send,
NULL,
};
#endif