#include "../curl_setup.h"
#ifdef USE_GNUTLS
#include <gnutls/abstract.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/crypto.h>
#include <nettle/sha2.h>
#include "../urldata.h"
#include "../sendf.h"
#include "../curlx/inet_pton.h"
#include "keylog.h"
#include "gtls.h"
#include "vtls.h"
#include "vtls_int.h"
#include "vtls_scache.h"
#include "apple.h"
#include "../vauth/vauth.h"
#include "../parsedate.h"
#include "../connect.h"
#include "../progress.h"
#include "../select.h"
#include "../strdup.h"
#include "../curlx/fopen.h"
#include "../curlx/warnless.h"
#include "x509asn1.h"
#include "../multiif.h"
#include "../curl_memory.h"
#include "../memdebug.h"
#ifdef GTLSDEBUG
static void tls_log_func(int level, const char *str)
{
curl_mfprintf(stderr, "|<%d>| %s", level, str);
}
#endif
static bool gtls_inited = FALSE;
#if !defined(GNUTLS_VERSION_NUMBER) || (GNUTLS_VERSION_NUMBER < 0x03010a)
#error "too old GnuTLS version"
#endif
#undef CURL_GNUTLS_EARLY_DATA
#if GNUTLS_VERSION_NUMBER >= 0x03060d
#define CURL_GNUTLS_EARLY_DATA
#endif
#include <gnutls/ocsp.h>
struct gtls_ssl_backend_data {
struct gtls_ctx gtls;
};
static ssize_t gtls_push(void *s, const void *buf, size_t blen)
{
struct Curl_cfilter *cf = s;
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
size_t nwritten;
CURLcode result;
DEBUGASSERT(data);
result = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &nwritten);
CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %d, %zu",
blen, result, nwritten);
backend->gtls.io_result = result;
if(result) {
gnutls_transport_set_errno(backend->gtls.session,
(CURLE_AGAIN == result) ? EAGAIN : EINVAL);
return -1;
}
return (ssize_t)nwritten;
}
static ssize_t gtls_pull(void *s, void *buf, size_t blen)
{
struct Curl_cfilter *cf = s;
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
struct Curl_easy *data = CF_DATA_CURRENT(cf);
size_t nread;
CURLcode result;
DEBUGASSERT(data);
if(!backend->gtls.shared_creds->trust_setup) {
result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls);
if(result) {
gnutls_transport_set_errno(backend->gtls.session, EINVAL);
backend->gtls.io_result = result;
return -1;
}
}
result = Curl_conn_cf_recv(cf->next, data, buf, blen, &nread);
CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %d, %zd",
blen, result, nread);
backend->gtls.io_result = result;
if(result) {
gnutls_transport_set_errno(backend->gtls.session,
(CURLE_AGAIN == result) ? EAGAIN : EINVAL);
return -1;
}
else if(nread == 0)
connssl->peer_closed = TRUE;
return (ssize_t)nread;
}
static int gtls_init(void)
{
int ret = 1;
if(!gtls_inited) {
ret = gnutls_global_init() ? 0 : 1;
#ifdef GTLSDEBUG
gnutls_global_set_log_function(tls_log_func);
gnutls_global_set_log_level(2);
#endif
gtls_inited = TRUE;
}
return ret;
}
static void gtls_cleanup(void)
{
if(gtls_inited) {
gnutls_global_deinit();
gtls_inited = FALSE;
}
}
#ifndef CURL_DISABLE_VERBOSE_STRINGS
static void showtime(struct Curl_easy *data,
const char *text,
time_t stamp)
{
struct tm buffer;
const struct tm *tm = &buffer;
char str[96];
CURLcode result = Curl_gmtime(stamp, &buffer);
if(result)
return;
curl_msnprintf(str,
sizeof(str),
" %s: %s, %02d %s %4d %02d:%02d:%02d GMT",
text,
Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6],
tm->tm_mday,
Curl_month[tm->tm_mon],
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
infof(data, "%s", str);
}
#endif
static gnutls_datum_t load_file(const char *file)
{
FILE *f;
gnutls_datum_t loaded_file = { NULL, 0 };
long filelen;
void *ptr;
f = curlx_fopen(file, "rb");
if(!f)
return loaded_file;
if(fseek(f, 0, SEEK_END) != 0
|| (filelen = ftell(f)) < 0
|| fseek(f, 0, SEEK_SET) != 0
|| !(ptr = malloc((size_t)filelen)))
goto out;
if(fread(ptr, 1, (size_t)filelen, f) < (size_t)filelen) {
free(ptr);
goto out;
}
loaded_file.data = ptr;
loaded_file.size = (unsigned int)filelen;
out:
curlx_fclose(f);
return loaded_file;
}
static void unload_file(gnutls_datum_t data)
{
free(data.data);
}
static CURLcode cf_gtls_handshake(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
gnutls_session_t session;
int rc;
DEBUGASSERT(backend);
session = backend->gtls.session;
connssl->io_need = CURL_SSL_IO_NEED_NONE;
backend->gtls.io_result = CURLE_OK;
rc = gnutls_handshake(session);
if(!backend->gtls.shared_creds->trust_setup) {
CURLcode result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls);
if(result)
return result;
}
if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
connssl->io_need =
gnutls_record_get_direction(session) ?
CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV;
return CURLE_AGAIN;
}
else if((rc < 0) && !gnutls_error_is_fatal(rc)) {
const char *strerr = NULL;
if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) {
gnutls_alert_description_t alert = gnutls_alert_get(session);
strerr = gnutls_alert_get_name(alert);
}
if(!strerr)
strerr = gnutls_strerror(rc);
infof(data, "gnutls_handshake() warning: %s", strerr);
return CURLE_AGAIN;
}
else if((rc < 0) && backend->gtls.io_result) {
return backend->gtls.io_result;
}
else if(rc < 0) {
const char *strerr = NULL;
if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) {
gnutls_alert_description_t alert = gnutls_alert_get(session);
strerr = gnutls_alert_get_name(alert);
}
if(!strerr)
strerr = gnutls_strerror(rc);
failf(data, "GnuTLS, handshake failed: %s", strerr);
return CURLE_SSL_CONNECT_ERROR;
}
return CURLE_OK;
}
static gnutls_x509_crt_fmt_t gnutls_do_file_type(const char *type)
{
if(!type || !type[0])
return GNUTLS_X509_FMT_PEM;
if(curl_strequal(type, "PEM"))
return GNUTLS_X509_FMT_PEM;
if(curl_strequal(type, "DER"))
return GNUTLS_X509_FMT_DER;
return GNUTLS_X509_FMT_PEM;
}
#define GNUTLS_CIPHERS "NORMAL:-ARCFOUR-128:-CTYPE-ALL:+CTYPE-X509"
#define GNUTLS_SRP "+SRP"
#define QUIC_PRIORITY \
"NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
"+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
"+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
"%DISABLE_TLS13_COMPAT_MODE"
static CURLcode
gnutls_set_ssl_version_min_max(struct Curl_easy *data,
struct ssl_peer *peer,
struct ssl_primary_config *conn_config,
const char **prioritylist,
bool tls13support)
{
long ssl_version = conn_config->version;
long ssl_version_max = conn_config->version_max;
if((ssl_version == CURL_SSLVERSION_DEFAULT) ||
(ssl_version == CURL_SSLVERSION_TLSv1))
ssl_version = CURL_SSLVERSION_TLSv1_0;
if((ssl_version_max == CURL_SSLVERSION_MAX_NONE) ||
(ssl_version_max == CURL_SSLVERSION_MAX_DEFAULT))
ssl_version_max = tls13support ?
CURL_SSLVERSION_MAX_TLSv1_3 : CURL_SSLVERSION_MAX_TLSv1_2;
if(peer->transport == TRNSPRT_QUIC) {
if(ssl_version_max < CURL_SSLVERSION_MAX_TLSv1_3) {
failf(data, "QUIC needs at least TLS version 1.3");
return CURLE_SSL_CONNECT_ERROR;
}
*prioritylist = QUIC_PRIORITY;
return CURLE_OK;
}
switch(ssl_version | ssl_version_max) {
case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_0:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.0";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_1:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.1:+VERS-TLS1.0";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_2:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_1:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.1";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_2:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.2:+VERS-TLS1.1";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_2:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.2";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_3 | CURL_SSLVERSION_MAX_TLSv1_3:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.3";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_0 | CURL_SSLVERSION_MAX_TLSv1_3:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_1 | CURL_SSLVERSION_MAX_TLSv1_3:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.3:+VERS-TLS1.2:+VERS-TLS1.1";
return CURLE_OK;
case CURL_SSLVERSION_TLSv1_2 | CURL_SSLVERSION_MAX_TLSv1_3:
*prioritylist = GNUTLS_CIPHERS ":-VERS-SSL3.0:-VERS-TLS-ALL:"
"+VERS-TLS1.3:+VERS-TLS1.2";
return CURLE_OK;
}
failf(data, "GnuTLS: cannot set ssl protocol");
return CURLE_SSL_CONNECT_ERROR;
}
CURLcode Curl_gtls_shared_creds_create(struct Curl_easy *data,
struct gtls_shared_creds **pcreds)
{
struct gtls_shared_creds *shared;
int rc;
*pcreds = NULL;
shared = calloc(1, sizeof(*shared));
if(!shared)
return CURLE_OUT_OF_MEMORY;
rc = gnutls_certificate_allocate_credentials(&shared->creds);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc));
free(shared);
return CURLE_SSL_CONNECT_ERROR;
}
shared->refcount = 1;
shared->time = curlx_now();
*pcreds = shared;
return CURLE_OK;
}
CURLcode Curl_gtls_shared_creds_up_ref(struct gtls_shared_creds *creds)
{
DEBUGASSERT(creds);
if(creds->refcount < SIZE_MAX) {
++creds->refcount;
return CURLE_OK;
}
return CURLE_BAD_FUNCTION_ARGUMENT;
}
void Curl_gtls_shared_creds_free(struct gtls_shared_creds **pcreds)
{
struct gtls_shared_creds *shared = *pcreds;
*pcreds = NULL;
if(shared) {
--shared->refcount;
if(!shared->refcount) {
gnutls_certificate_free_credentials(shared->creds);
free(shared->CAfile);
free(shared);
}
}
}
static CURLcode gtls_populate_creds(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_certificate_credentials_t creds)
{
struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
bool creds_are_empty = TRUE;
int rc;
if(!config->verifypeer) {
infof(data, "SSL Trust: peer verification disabled");
return CURLE_OK;
}
infof(data, "SSL Trust Anchors:");
if(ssl_config->native_ca_store) {
#ifdef USE_APPLE_SECTRUST
infof(data, " Native: Apple SecTrust");
creds_are_empty = FALSE;
#else
rc = gnutls_certificate_set_x509_system_trust(creds);
if(rc < 0)
infof(data, "error reading native ca store (%s), continuing anyway",
gnutls_strerror(rc));
else {
infof(data, " Native: %d certificates from system trust", rc);
if(rc > 0)
creds_are_empty = FALSE;
}
#endif
}
if(config->CAfile) {
gnutls_certificate_set_verify_flags(creds,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
rc = gnutls_certificate_set_x509_trust_file(creds,
config->CAfile,
GNUTLS_X509_FMT_PEM);
creds_are_empty = creds_are_empty && (rc <= 0);
if(rc < 0) {
infof(data, "error reading ca cert file %s (%s)%s",
config->CAfile, gnutls_strerror(rc),
(creds_are_empty ? "" : ", continuing anyway"));
if(creds_are_empty) {
ssl_config->certverifyresult = rc;
return CURLE_SSL_CACERT_BADFILE;
}
}
else
infof(data, " CAfile: %d certificates in %s", rc, config->CAfile);
}
if(config->CApath) {
rc = gnutls_certificate_set_x509_trust_dir(creds, config->CApath,
GNUTLS_X509_FMT_PEM);
creds_are_empty = creds_are_empty && (rc <= 0);
if(rc < 0) {
infof(data, "error reading ca cert file %s (%s)%s",
config->CApath, gnutls_strerror(rc),
(creds_are_empty ? "" : ", continuing anyway"));
if(creds_are_empty) {
ssl_config->certverifyresult = rc;
return CURLE_SSL_CACERT_BADFILE;
}
}
else
infof(data, " CApath: %d certificates in %s", rc, config->CApath);
}
if(creds_are_empty)
infof(data, " no trust anchors configured");
if(config->CRLfile) {
rc = gnutls_certificate_set_x509_crl_file(creds, config->CRLfile,
GNUTLS_X509_FMT_PEM);
if(rc < 0) {
failf(data, "error reading crl file %s (%s)",
config->CRLfile, gnutls_strerror(rc));
return CURLE_SSL_CRL_BADFILE;
}
else
infof(data, " CRLfile: %d CRL in %s", rc, config->CRLfile);
}
return CURLE_OK;
}
#define MPROTO_GTLS_X509_KEY "tls:gtls:x509:share"
static bool gtls_shared_creds_expired(const struct Curl_easy *data,
const struct gtls_shared_creds *sc)
{
const struct ssl_general_config *cfg = &data->set.general_ssl;
struct curltime now = curlx_now();
timediff_t elapsed_ms = curlx_timediff(now, sc->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 gtls_shared_creds_different(struct Curl_cfilter *cf,
const struct gtls_shared_creds *sc)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
if(!sc->CAfile || !conn_config->CAfile)
return sc->CAfile != conn_config->CAfile;
return strcmp(sc->CAfile, conn_config->CAfile);
}
static struct gtls_shared_creds*
gtls_get_cached_creds(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct gtls_shared_creds *shared_creds;
if(data->multi) {
shared_creds = Curl_hash_pick(&data->multi->proto_hash,
CURL_UNCONST(MPROTO_GTLS_X509_KEY),
sizeof(MPROTO_GTLS_X509_KEY)-1);
if(shared_creds && shared_creds->creds &&
!gtls_shared_creds_expired(data, shared_creds) &&
!gtls_shared_creds_different(cf, shared_creds)) {
return shared_creds;
}
}
return NULL;
}
static void gtls_shared_creds_hash_free(void *key, size_t key_len, void *p)
{
struct gtls_shared_creds *sc = p;
DEBUGASSERT(key_len == (sizeof(MPROTO_GTLS_X509_KEY)-1));
DEBUGASSERT(!memcmp(MPROTO_GTLS_X509_KEY, key, key_len));
(void)key;
(void)key_len;
Curl_gtls_shared_creds_free(&sc);
}
static void gtls_set_cached_creds(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct gtls_shared_creds *sc)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
DEBUGASSERT(sc);
DEBUGASSERT(sc->creds);
DEBUGASSERT(!sc->CAfile);
DEBUGASSERT(sc->refcount == 1);
if(!data->multi)
return;
if(conn_config->CAfile) {
sc->CAfile = strdup(conn_config->CAfile);
if(!sc->CAfile)
return;
}
if(Curl_gtls_shared_creds_up_ref(sc))
return;
if(!Curl_hash_add2(&data->multi->proto_hash,
CURL_UNCONST(MPROTO_GTLS_X509_KEY),
sizeof(MPROTO_GTLS_X509_KEY)-1,
sc, gtls_shared_creds_hash_free)) {
Curl_gtls_shared_creds_free(&sc);
return;
}
}
CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct gtls_ctx *gtls)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
struct gtls_shared_creds *cached_creds = NULL;
bool cache_criteria_met;
CURLcode result;
int rc;
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 &&
!conn_config->clientcert;
if(cache_criteria_met)
cached_creds = gtls_get_cached_creds(cf, data);
if(cached_creds && !Curl_gtls_shared_creds_up_ref(cached_creds)) {
CURL_TRC_CF(data, cf, "using shared trust anchors and CRLs");
Curl_gtls_shared_creds_free(>ls->shared_creds);
gtls->shared_creds = cached_creds;
rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE,
gtls->shared_creds->creds);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
return CURLE_SSL_CONNECT_ERROR;
}
}
else {
CURL_TRC_CF(data, cf, "loading trust anchors and CRLs");
result = gtls_populate_creds(cf, data, gtls->shared_creds->creds);
if(result)
return result;
gtls->shared_creds->trust_setup = TRUE;
if(cache_criteria_met)
gtls_set_cached_creds(cf, data, gtls->shared_creds);
}
return CURLE_OK;
}
#ifdef CURL_GNUTLS_EARLY_DATA
CURLcode Curl_gtls_cache_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const char *ssl_peer_key,
gnutls_session_t session,
curl_off_t valid_until,
const char *alpn,
unsigned char *quic_tp,
size_t quic_tp_len)
{
struct Curl_ssl_session *sc_session;
unsigned char *sdata, *qtp_clone = NULL;
size_t sdata_len = 0;
size_t earlydata_max = 0;
CURLcode result = CURLE_OK;
if(!Curl_ssl_scache_use(cf, data))
return CURLE_OK;
gnutls_session_get_data(session, NULL, &sdata_len);
if(!sdata_len)
return CURLE_OK;
sdata = malloc(sdata_len);
if(!sdata)
return CURLE_OUT_OF_MEMORY;
gnutls_session_get_data(session, sdata, &sdata_len);
earlydata_max = gnutls_record_get_max_early_data_size(session);
CURL_TRC_CF(data, cf, "get session id (len=%zu, alpn=%s, earlymax=%zu) "
"and store in cache", sdata_len, alpn ? alpn : "-",
earlydata_max);
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;
}
}
result = Curl_ssl_session_create2(sdata, sdata_len,
Curl_glts_get_ietf_proto(session),
alpn, valid_until, earlydata_max,
qtp_clone, quic_tp_len,
&sc_session);
if(!result) {
result = Curl_ssl_scache_put(cf, data, ssl_peer_key, sc_session);
}
return result;
}
#endif
int Curl_glts_get_ietf_proto(gnutls_session_t session)
{
switch(gnutls_protocol_get_version(session)) {
case GNUTLS_SSL3:
return CURL_IETF_PROTO_SSL3;
case GNUTLS_TLS1_0:
return CURL_IETF_PROTO_TLS1;
case GNUTLS_TLS1_1:
return CURL_IETF_PROTO_TLS1_1;
case GNUTLS_TLS1_2:
return CURL_IETF_PROTO_TLS1_2;
#if GNUTLS_VERSION_NUMBER >= 0x030603
case GNUTLS_TLS1_3:
return CURL_IETF_PROTO_TLS1_3;
#endif
default:
return CURL_IETF_PROTO_UNKNOWN;
}
}
#ifdef CURL_GNUTLS_EARLY_DATA
static CURLcode cf_gtls_update_session_id(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_session_t session)
{
struct ssl_connect_data *connssl = cf->ctx;
return Curl_gtls_cache_session(cf, data, connssl->peer.scache_key,
session, 0, connssl->negotiated.alpn,
NULL, 0);
}
static int gtls_handshake_cb(gnutls_session_t session, unsigned int htype,
unsigned when, unsigned int incoming,
const gnutls_datum_t *msg)
{
struct Curl_cfilter *cf = gnutls_session_get_ptr(session);
(void)msg;
(void)incoming;
if(when) {
struct Curl_easy *data = CF_DATA_CURRENT(cf);
if(data) {
CURL_TRC_CF(data, cf, "handshake: %s message type %d",
incoming ? "incoming" : "outgoing", htype);
switch(htype) {
case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
cf_gtls_update_session_id(cf, data, session);
break;
}
default:
break;
}
}
}
return 0;
}
#endif
static CURLcode gtls_set_priority(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct gtls_ctx *gtls,
const char *priority)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct dynbuf buf;
const char *err = NULL;
CURLcode result = CURLE_OK;
int rc;
curlx_dyn_init(&buf, 4096);
#ifdef USE_GNUTLS_SRP
if(conn_config->username) {
result = curlx_dyn_add(&buf, priority);
if(!result)
result = curlx_dyn_add(&buf, ":" GNUTLS_SRP);
if(result)
goto out;
priority = curlx_dyn_ptr(&buf);
}
#endif
if(conn_config->cipher_list) {
if((conn_config->cipher_list[0] == '+') ||
(conn_config->cipher_list[0] == '-') ||
(conn_config->cipher_list[0] == '!')) {
if(!curlx_dyn_len(&buf)) {
result = curlx_dyn_add(&buf, priority);
if(result)
goto out;
}
result = curlx_dyn_addf(&buf, ":%s", conn_config->cipher_list);
if(result)
goto out;
priority = curlx_dyn_ptr(&buf);
}
else
priority = conn_config->cipher_list;
}
infof(data, "GnuTLS priority: %s", priority);
rc = gnutls_priority_set_direct(gtls->session, priority, &err);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "Error %d setting GnuTLS priority: %s", rc, err);
result = CURLE_SSL_CONNECT_ERROR;
}
out:
curlx_dyn_free(&buf);
return result;
}
static CURLcode gtls_client_init(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
size_t earlydata_max,
struct gtls_ctx *gtls)
{
struct ssl_primary_config *config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
unsigned int init_flags;
int rc;
bool sni = TRUE;
const char *prioritylist;
bool tls13support;
CURLcode result;
if(!gtls_inited)
gtls_init();
if(config->version == CURL_SSLVERSION_SSLv2) {
failf(data, "GnuTLS does not support SSLv2");
return CURLE_SSL_CONNECT_ERROR;
}
else if(config->version == CURL_SSLVERSION_SSLv3)
sni = FALSE;
result = Curl_gtls_shared_creds_create(data, >ls->shared_creds);
if(result)
return result;
#ifdef USE_GNUTLS_SRP
if(config->username && Curl_auth_allowed_to_host(data)) {
infof(data, "Using TLS-SRP username: %s", config->username);
rc = gnutls_srp_allocate_client_credentials(>ls->srp_client_cred);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_srp_allocate_client_cred() failed: %s",
gnutls_strerror(rc));
return CURLE_OUT_OF_MEMORY;
}
rc = gnutls_srp_set_client_credentials(gtls->srp_client_cred,
config->username,
config->password);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_srp_set_client_cred() failed: %s",
gnutls_strerror(rc));
return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
#endif
ssl_config->certverifyresult = 0;
init_flags = GNUTLS_CLIENT;
#ifdef CURL_GNUTLS_EARLY_DATA
if(peer->transport == TRNSPRT_QUIC && earlydata_max > 0)
init_flags |= GNUTLS_ENABLE_EARLY_DATA | GNUTLS_NO_END_OF_EARLY_DATA;
else if(earlydata_max > 0 && earlydata_max != 0xFFFFFFFFUL)
init_flags |= GNUTLS_ENABLE_EARLY_DATA;
#endif
#ifdef GNUTLS_FORCE_CLIENT_CERT
init_flags |= GNUTLS_FORCE_CLIENT_CERT;
#endif
#ifdef GNUTLS_NO_TICKETS_TLS12
init_flags |= GNUTLS_NO_TICKETS_TLS12;
#endif
#ifdef GNUTLS_NO_STATUS_REQUEST
if(!config->verifystatus)
init_flags |= GNUTLS_NO_STATUS_REQUEST;
#endif
CURL_TRC_CF(data, cf, "gnutls_init(flags=%x), earlydata=%zu",
init_flags, earlydata_max);
rc = gnutls_init(>ls->session, init_flags);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_init() failed: %d", rc);
return CURLE_SSL_CONNECT_ERROR;
}
if(sni && peer->sni) {
if(gnutls_server_name_set(gtls->session, GNUTLS_NAME_DNS,
peer->sni, strlen(peer->sni)) < 0) {
failf(data, "Failed to set SNI");
return CURLE_SSL_CONNECT_ERROR;
}
}
rc = gnutls_set_default_priority(gtls->session);
if(rc != GNUTLS_E_SUCCESS)
return CURLE_SSL_CONNECT_ERROR;
tls13support = !!gnutls_check_version("3.6.5");
if(config->version == CURL_SSLVERSION_SSLv2 ||
config->version == CURL_SSLVERSION_SSLv3) {
failf(data, "GnuTLS does not support SSLv2 or SSLv3");
return CURLE_SSL_CONNECT_ERROR;
}
if(config->version == CURL_SSLVERSION_TLSv1_3) {
if(!tls13support) {
failf(data, "This GnuTLS installation does not support TLS 1.3");
return CURLE_SSL_CONNECT_ERROR;
}
}
result = gnutls_set_ssl_version_min_max(data, peer,
config, &prioritylist, tls13support);
if(result)
return result;
result = gtls_set_priority(cf, data, gtls, prioritylist);
if(result)
return result;
if(config->clientcert) {
if(!gtls->shared_creds->trust_setup) {
result = Curl_gtls_client_trust_setup(cf, data, gtls);
if(result)
return result;
}
if(ssl_config->cert_type && curl_strequal(ssl_config->cert_type, "P12")) {
rc = gnutls_certificate_set_x509_simple_pkcs12_file(
gtls->shared_creds->creds, config->clientcert, GNUTLS_X509_FMT_DER,
ssl_config->key_passwd ? ssl_config->key_passwd : "");
if(rc != GNUTLS_E_SUCCESS) {
failf(data,
"error reading X.509 potentially-encrypted key or certificate "
"file: %s",
gnutls_strerror(rc));
return CURLE_SSL_CONNECT_ERROR;
}
}
else {
const unsigned int supported_key_encryption_algorithms =
GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR |
GNUTLS_PKCS_USE_PKCS12_RC2_40 | GNUTLS_PKCS_USE_PBES2_3DES |
GNUTLS_PKCS_USE_PBES2_AES_128 | GNUTLS_PKCS_USE_PBES2_AES_192 |
GNUTLS_PKCS_USE_PBES2_AES_256;
rc = gnutls_certificate_set_x509_key_file2(
gtls->shared_creds->creds,
config->clientcert,
ssl_config->key ? ssl_config->key : config->clientcert,
gnutls_do_file_type(ssl_config->cert_type),
ssl_config->key_passwd,
supported_key_encryption_algorithms);
if(rc != GNUTLS_E_SUCCESS) {
failf(data,
"error reading X.509 %skey file: %s",
ssl_config->key_passwd ? "potentially-encrypted " : "",
gnutls_strerror(rc));
return CURLE_SSL_CONNECT_ERROR;
}
}
}
#ifdef USE_GNUTLS_SRP
if(config->username) {
rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP,
gtls->srp_client_cred);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
return CURLE_SSL_CONNECT_ERROR;
}
}
else
#endif
{
rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_CERTIFICATE,
gtls->shared_creds->creds);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_credentials_set() failed: %s", gnutls_strerror(rc));
return CURLE_SSL_CONNECT_ERROR;
}
}
if(config->verifystatus) {
rc = gnutls_ocsp_status_request_enable_client(gtls->session,
NULL, 0, NULL);
if(rc != GNUTLS_E_SUCCESS) {
failf(data, "gnutls_ocsp_status_request_enable_client() failed: %d", rc);
return CURLE_SSL_CONNECT_ERROR;
}
}
return CURLE_OK;
}
#ifdef CURL_GNUTLS_EARLY_DATA
static int keylog_callback(gnutls_session_t session, const char *label,
const gnutls_datum_t *secret)
{
gnutls_datum_t crandom;
gnutls_datum_t srandom;
gnutls_session_get_random(session, &crandom, &srandom);
if(crandom.size != 32) {
return -1;
}
Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size);
return 0;
}
static CURLcode gtls_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 gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
CURLcode result = CURLE_OK;
*do_early_data = FALSE;
connssl->earlydata_max =
gnutls_record_get_max_early_data_size(backend->gtls.session);
if((!connssl->earlydata_max || connssl->earlydata_max == 0xFFFFFFFFUL)) {
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;
}
#endif
CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
const struct alpn_spec *alpns_requested,
Curl_gtls_ctx_setup_cb *cb_setup,
void *cb_user_data,
void *ssl_user_data,
Curl_gtls_init_session_reuse_cb *sess_reuse_cb)
{
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
struct Curl_ssl_session *scs = NULL;
gnutls_datum_t gtls_alpns[ALPN_ENTRIES_MAX];
size_t gtls_alpns_count = 0;
bool gtls_session_setup = FALSE;
struct alpn_spec alpns;
CURLcode result = CURLE_OK;
int rc;
DEBUGASSERT(gctx);
Curl_alpn_copy(&alpns, alpns_requested);
if(conn_config->cache_session) {
result = Curl_ssl_scache_take(cf, data, peer->scache_key, &scs);
if(result)
goto out;
if(scs && scs->sdata && scs->sdata_len &&
(!scs->alpn || Curl_alpn_contains_proto(&alpns, scs->alpn))) {
result = gtls_client_init(cf, data, peer, scs->earlydata_max, gctx);
if(result)
goto out;
gtls_session_setup = TRUE;
rc = gnutls_session_set_data(gctx->session, scs->sdata, scs->sdata_len);
if(rc < 0)
infof(data, "SSL session not accepted by GnuTLS, continuing without");
else {
infof(data, "SSL reusing session with ALPN '%s'",
scs->alpn ? scs->alpn : "-");
if(ssl_config->earlydata && scs->alpn && !cf->conn->connect_only) {
bool do_early_data = FALSE;
if(sess_reuse_cb) {
result = sess_reuse_cb(cf, data, &alpns, scs, &do_early_data);
if(result)
goto out;
}
if(do_early_data) {
Curl_alpn_restrict_to(&alpns, scs->alpn);
}
}
}
}
}
if(!gtls_session_setup) {
result = gtls_client_init(cf, data, peer, 0, gctx);
if(result)
goto out;
}
gnutls_session_set_ptr(gctx->session, ssl_user_data);
if(cb_setup) {
result = cb_setup(cf, data, cb_user_data);
if(result)
goto out;
}
#ifdef CURL_GNUTLS_EARLY_DATA
Curl_tls_keylog_open();
if(Curl_tls_keylog_enabled()) {
gnutls_session_set_keylog_function(gctx->session, keylog_callback);
}
#endif
if(!gtls_alpns_count && alpns.count) {
size_t i;
DEBUGASSERT(CURL_ARRAYSIZE(gtls_alpns) >= alpns.count);
for(i = 0; i < alpns.count; ++i) {
gtls_alpns[i].data = (unsigned char *)alpns.entries[i];
gtls_alpns[i].size = (unsigned int)strlen(alpns.entries[i]);
}
gtls_alpns_count = alpns.count;
}
if(gtls_alpns_count &&
gnutls_alpn_set_protocols(gctx->session,
gtls_alpns, (unsigned int)gtls_alpns_count,
GNUTLS_ALPN_MANDATORY)) {
failf(data, "failed setting ALPN");
result = CURLE_SSL_CONNECT_ERROR;
}
out:
Curl_ssl_scache_return(cf, data, peer->scache_key, scs);
return result;
}
static CURLcode
gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
CURLcode result;
DEBUGASSERT(backend);
if(connssl->state == ssl_connection_complete)
return CURLE_OK;
result = Curl_gtls_ctx_init(&backend->gtls, cf, data, &connssl->peer,
connssl->alpn, NULL, NULL, cf,
#ifdef CURL_GNUTLS_EARLY_DATA
gtls_on_session_reuse
#else
NULL
#endif
);
if(result)
return result;
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);
}
#ifdef CURL_GNUTLS_EARLY_DATA
gnutls_handshake_set_hook_function(backend->gtls.session,
GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST,
gtls_handshake_cb);
#endif
gnutls_transport_set_ptr(backend->gtls.session, cf);
gnutls_transport_set_push_function(backend->gtls.session, gtls_push);
gnutls_transport_set_pull_function(backend->gtls.session, gtls_pull);
return CURLE_OK;
}
static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data,
gnutls_x509_crt_t cert,
const char *pinnedpubkey)
{
size_t len1 = 0, len2 = 0;
unsigned char *buff1 = NULL;
gnutls_pubkey_t key = NULL;
CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
if(!pinnedpubkey)
return CURLE_OK;
if(!cert)
return result;
do {
int ret;
ret = gnutls_pubkey_init(&key);
if(ret < 0)
break;
ret = gnutls_pubkey_import_x509(key, cert, 0);
if(ret < 0)
break;
ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, NULL, &len1);
if(ret != GNUTLS_E_SHORT_MEMORY_BUFFER || len1 == 0)
break;
buff1 = malloc(len1);
if(!buff1)
break;
len2 = len1;
ret = gnutls_pubkey_export(key, GNUTLS_X509_FMT_DER, buff1, &len2);
if(ret < 0 || len1 != len2)
break;
result = Curl_pin_peer_pubkey(data, pinnedpubkey, buff1, len1);
} while(0);
if(key)
gnutls_pubkey_deinit(key);
Curl_safefree(buff1);
return result;
}
void Curl_gtls_report_handshake(struct Curl_easy *data,
struct gtls_ctx *gctx)
{
#ifndef CURL_DISABLE_VERBOSE_STRINGS
if(Curl_trc_is_verbose(data)) {
const char *ptr;
gnutls_protocol_t version = gnutls_protocol_get_version(gctx->session);
ptr = gnutls_cipher_suite_get_name(gnutls_kx_get(gctx->session),
gnutls_cipher_get(gctx->session),
gnutls_mac_get(gctx->session));
infof(data, "SSL connection using %s / %s",
gnutls_protocol_get_name(version), ptr);
}
#else
(void)data;
(void)gctx;
#endif
}
static void gtls_msg_verify_result(struct Curl_easy *data,
struct ssl_peer *peer,
gnutls_x509_crt_t x509_cert,
bool was_verified,
bool needs_verified)
{
char certname[65] = "";
size_t size = sizeof(certname);
int rc;
rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME,
0,
FALSE, certname, &size);
if(rc) {
infof(data, "error fetching CN from cert:%s", gnutls_strerror(rc));
certname[0] = 0;
}
if(!was_verified) {
if(needs_verified) {
failf(data, "SSL: certificate subject name (%s) does not match "
"target hostname '%s'", certname, peer->dispname);
}
else
infof(data, " common name: %s (does not match '%s')",
certname, peer->dispname);
}
else
infof(data, " common name: %s (matched)", certname);
}
static void gtls_infof_cert(struct Curl_easy *data,
gnutls_x509_crt_t x509_cert)
{
#ifndef CURL_DISABLE_VERBOSE_STRINGS
if(Curl_trc_is_verbose(data)) {
gnutls_datum_t certfields;
int rc, algo;
time_t tstamp;
unsigned int bits;
algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits);
infof(data, " certificate public key: %s",
gnutls_pk_algorithm_get_name((gnutls_pk_algorithm_t)algo));
infof(data, " certificate version: #%d",
gnutls_x509_crt_get_version(x509_cert));
rc = gnutls_x509_crt_get_dn2(x509_cert, &certfields);
if(rc)
infof(data, "Failed to get certificate name");
else {
infof(data, " subject: %s", certfields.data);
tstamp = gnutls_x509_crt_get_activation_time(x509_cert);
showtime(data, "start date", tstamp);
tstamp = gnutls_x509_crt_get_expiration_time(x509_cert);
showtime(data, "expire date", tstamp);
gnutls_free(certfields.data);
}
rc = gnutls_x509_crt_get_issuer_dn2(x509_cert, &certfields);
if(rc)
infof(data, "Failed to get certificate issuer");
else {
infof(data, " issuer: %s", certfields.data);
gnutls_free(certfields.data);
}
}
#else
(void)data;
(void)x509_cert;
#endif
}
static CURLcode gtls_verify_ocsp_status(struct Curl_easy *data,
gnutls_session_t session)
{
gnutls_ocsp_resp_t ocsp_resp = NULL;
gnutls_datum_t status_request;
gnutls_ocsp_cert_status_t status;
gnutls_x509_crl_reason_t reason;
CURLcode result = CURLE_OK;
int rc;
rc = gnutls_ocsp_status_request_get(session, &status_request);
if(rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
failf(data, "No OCSP response received");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto out;
}
else if(rc < 0) {
failf(data, "Invalid OCSP response received");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto out;
}
gnutls_ocsp_resp_init(&ocsp_resp);
rc = gnutls_ocsp_resp_import(ocsp_resp, &status_request);
if(rc < 0) {
failf(data, "Invalid OCSP response received");
result = CURLE_SSL_INVALIDCERTSTATUS;
goto out;
}
(void)gnutls_ocsp_resp_get_single(ocsp_resp, 0, NULL, NULL, NULL, NULL,
&status, NULL, NULL, NULL, &reason);
switch(status) {
case GNUTLS_OCSP_CERT_GOOD:
break;
case GNUTLS_OCSP_CERT_REVOKED: {
const char *crl_reason;
switch(reason) {
default:
case GNUTLS_X509_CRLREASON_UNSPECIFIED:
crl_reason = "unspecified reason";
break;
case GNUTLS_X509_CRLREASON_KEYCOMPROMISE:
crl_reason = "private key compromised";
break;
case GNUTLS_X509_CRLREASON_CACOMPROMISE:
crl_reason = "CA compromised";
break;
case GNUTLS_X509_CRLREASON_AFFILIATIONCHANGED:
crl_reason = "affiliation has changed";
break;
case GNUTLS_X509_CRLREASON_SUPERSEDED:
crl_reason = "certificate superseded";
break;
case GNUTLS_X509_CRLREASON_CESSATIONOFOPERATION:
crl_reason = "operation has ceased";
break;
case GNUTLS_X509_CRLREASON_CERTIFICATEHOLD:
crl_reason = "certificate is on hold";
break;
case GNUTLS_X509_CRLREASON_REMOVEFROMCRL:
crl_reason = "will be removed from delta CRL";
break;
case GNUTLS_X509_CRLREASON_PRIVILEGEWITHDRAWN:
crl_reason = "privilege withdrawn";
break;
case GNUTLS_X509_CRLREASON_AACOMPROMISE:
crl_reason = "AA compromised";
break;
}
failf(data, "Server certificate was revoked: %s", crl_reason);
break;
}
default:
case GNUTLS_OCSP_CERT_UNKNOWN:
failf(data, "Server certificate status is unknown");
break;
}
result = (status != GNUTLS_OCSP_CERT_GOOD) ?
CURLE_SSL_INVALIDCERTSTATUS : CURLE_OK;
out:
if(ocsp_resp)
gnutls_ocsp_resp_deinit(ocsp_resp);
return result;
}
struct gtls_cert_chain {
const gnutls_datum_t *certs;
unsigned int num_certs;
};
#ifdef USE_APPLE_SECTRUST
static CURLcode gtls_chain_get_der(struct Curl_cfilter *cf,
struct Curl_easy *data,
void *user_data,
size_t i,
unsigned char **pder,
size_t *pder_len)
{
struct gtls_cert_chain *chain = user_data;
(void)cf;
(void)data;
*pder_len = 0;
*pder = NULL;
if(i >= chain->num_certs)
return CURLE_TOO_LARGE;
*pder = chain->certs[i].data;
*pder_len = (size_t)chain->certs[i].size;
return CURLE_OK;
}
static CURLcode glts_apple_verify(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
struct gtls_cert_chain *chain,
bool *pverified)
{
CURLcode result;
result = Curl_vtls_apple_verify(cf, data, peer, chain->num_certs,
gtls_chain_get_der, chain,
NULL, 0);
*pverified = !result;
if(*pverified)
infof(data, " SSL certificate verified by Apple SecTrust.");
return result;
}
#endif
CURLcode
Curl_gtls_verifyserver(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_session_t session,
struct ssl_primary_config *config,
struct ssl_config_data *ssl_config,
struct ssl_peer *peer,
const char *pinned_key)
{
struct gtls_cert_chain chain;
gnutls_x509_crt_t x509_cert = NULL, x509_issuer = NULL;
time_t certclock;
int rc;
CURLcode result = CURLE_OK;
long * const certverifyresult = &ssl_config->certverifyresult;
(void)cf;
chain.certs = gnutls_certificate_get_peers(session, &chain.num_certs);
if(!chain.certs) {
if(config->verifypeer ||
config->verifyhost ||
config->issuercert) {
#ifdef USE_GNUTLS_SRP
if(ssl_config->primary.username && !config->verifypeer &&
gnutls_cipher_get(session)) {
}
else {
#endif
failf(data, "failed to get server cert");
*certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND;
result = CURLE_PEER_FAILED_VERIFICATION;
goto out;
#ifdef USE_GNUTLS_SRP
}
#endif
}
infof(data, " common name: WARNING could not obtain");
}
if(data->set.ssl.certinfo && chain.certs) {
unsigned int i;
result = Curl_ssl_init_certinfo(data, (int)chain.num_certs);
if(result)
goto out;
for(i = 0; i < chain.num_certs; i++) {
const char *beg = (const char *) chain.certs[i].data;
const char *end = beg + chain.certs[i].size;
result = Curl_extract_certinfo(data, (int)i, beg, end);
if(result)
goto out;
}
}
if(config->verifypeer) {
bool verified = FALSE;
unsigned int verify_status = 0;
rc = gnutls_certificate_verify_peers2(session, &verify_status);
if(rc < 0) {
failf(data, "server cert verify failed: %d", rc);
*certverifyresult = rc;
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
*certverifyresult = verify_status;
verified = !(verify_status & GNUTLS_CERT_INVALID);
if(verified)
infof(data, " SSL certificate verified by GnuTLS");
#ifdef USE_APPLE_SECTRUST
if(!verified && ssl_config->native_ca_store &&
(verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND)) {
result = glts_apple_verify(cf, data, peer, &chain, &verified);
if(result && (result != CURLE_PEER_FAILED_VERIFICATION))
goto out;
if(verified) {
infof(data, "SSL certificate verified via Apple SecTrust.");
*certverifyresult = 0;
}
}
#endif
if(!verified) {
const char *cause = "certificate error, no details available";
if(verify_status & GNUTLS_CERT_EXPIRED)
cause = "certificate has expired";
else if(verify_status & GNUTLS_CERT_SIGNER_NOT_FOUND)
cause = "certificate signer not trusted";
else if(verify_status & GNUTLS_CERT_INSECURE_ALGORITHM)
cause = "certificate uses insecure algorithm";
else if(verify_status & GNUTLS_CERT_INVALID_OCSP_STATUS)
cause = "attached OCSP status response is invalid";
failf(data, "SSL certificate verification failed: %s. (CAfile: %s "
"CRLfile: %s)", cause,
config->CAfile ? config->CAfile : "none",
ssl_config->primary.CRLfile ?
ssl_config->primary.CRLfile : "none");
result = CURLE_PEER_FAILED_VERIFICATION;
goto out;
}
}
else
infof(data, " SSL certificate verification SKIPPED");
if(gnutls_x509_crt_init(&x509_cert)) {
failf(data, "failed to init gnutls x509_crt");
*certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND;
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
if(chain.certs) {
rc = gnutls_x509_crt_import(x509_cert, chain.certs, GNUTLS_X509_FMT_DER);
if(rc) {
failf(data, "error parsing server's certificate chain");
*certverifyresult = GNUTLS_E_NO_CERTIFICATE_FOUND;
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
}
certclock = gnutls_x509_crt_get_expiration_time(x509_cert);
if(certclock == (time_t)-1) {
if(config->verifypeer) {
failf(data, "server cert expiration date verify failed");
*certverifyresult = GNUTLS_CERT_EXPIRED;
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
else
infof(data, " SSL certificate expiration date verify FAILED");
}
else {
if(certclock < time(NULL)) {
if(config->verifypeer) {
failf(data, "server certificate expiration date has passed.");
*certverifyresult = GNUTLS_CERT_EXPIRED;
result = CURLE_PEER_FAILED_VERIFICATION;
goto out;
}
else
infof(data, " SSL certificate expiration date FAILED");
}
else
infof(data, " SSL certificate expiration date OK");
}
certclock = gnutls_x509_crt_get_activation_time(x509_cert);
if(certclock == (time_t)-1) {
if(config->verifypeer) {
failf(data, "server cert activation date verify failed");
*certverifyresult = GNUTLS_CERT_NOT_ACTIVATED;
result = CURLE_SSL_CONNECT_ERROR;
goto out;
}
else
infof(data, " SSL certificate activation date verify FAILED");
}
else {
if(certclock > time(NULL)) {
if(config->verifypeer) {
failf(data, "server certificate not activated yet.");
*certverifyresult = GNUTLS_CERT_NOT_ACTIVATED;
result = CURLE_PEER_FAILED_VERIFICATION;
goto out;
}
else
infof(data, " SSL certificate activation date FAILED");
}
else
infof(data, " SSL certificate activation date OK");
}
if(config->verifystatus) {
result = gtls_verify_ocsp_status(data, session);
if(result)
goto out;
}
else
infof(data, " SSL certificate status verification SKIPPED");
if(config->issuercert) {
gnutls_datum_t issuerp;
if(gnutls_x509_crt_init(&x509_issuer)) {
failf(data, "failed to init gnutls x509_crt for issuer");
result = CURLE_SSL_ISSUER_ERROR;
goto out;
}
issuerp = load_file(config->issuercert);
rc = gnutls_x509_crt_import(x509_issuer, &issuerp, GNUTLS_X509_FMT_PEM);
if(!rc)
rc = (int)gnutls_x509_crt_check_issuer(x509_cert, x509_issuer);
unload_file(issuerp);
if(rc <= 0) {
failf(data, "server certificate issuer check failed (IssuerCert: %s)",
config->issuercert ? config->issuercert : "none");
result = CURLE_SSL_ISSUER_ERROR;
goto out;
}
infof(data, " SSL certificate issuer check OK (Issuer Cert: %s)",
config->issuercert ? config->issuercert : "none");
}
rc = (int)gnutls_x509_crt_check_hostname(x509_cert,
peer->sni ? peer->sni :
peer->hostname);
#if GNUTLS_VERSION_NUMBER < 0x030306
if(!rc) {
#ifdef USE_IPV6
#define use_addr in6_addr
#else
#define use_addr in_addr
#endif
unsigned char addrbuf[sizeof(struct use_addr)];
size_t addrlen = 0;
if(curlx_inet_pton(AF_INET, peer->hostname, addrbuf) > 0)
addrlen = 4;
#ifdef USE_IPV6
else if(curlx_inet_pton(AF_INET6, peer->hostname, addrbuf) > 0)
addrlen = 16;
#endif
if(addrlen) {
unsigned char certaddr[sizeof(struct use_addr)];
int i;
for(i = 0; ; i++) {
size_t certaddrlen = sizeof(certaddr);
int ret = gnutls_x509_crt_get_subject_alt_name(x509_cert, i, certaddr,
&certaddrlen, NULL);
if(ret == GNUTLS_E_SHORT_MEMORY_BUFFER)
continue;
if(ret < 0)
break;
if(ret != GNUTLS_SAN_IPADDRESS)
continue;
if(certaddrlen == addrlen && !memcmp(addrbuf, certaddr, addrlen)) {
rc = 1;
break;
}
}
}
}
#endif
result = (!rc && config->verifyhost) ?
CURLE_PEER_FAILED_VERIFICATION : CURLE_OK;
gtls_msg_verify_result(data, peer, x509_cert, rc, config->verifyhost);
if(result)
goto out;
if(pinned_key) {
result = pkp_pin_peer_pubkey(data, x509_cert, pinned_key);
if(result != CURLE_OK) {
failf(data, "SSL: public key does not match pinned public key");
goto out;
}
}
gtls_infof_cert(data, x509_cert);
out:
if(x509_issuer)
gnutls_x509_crt_deinit(x509_issuer);
if(x509_cert)
gnutls_x509_crt_deinit(x509_cert);
return result;
}
static CURLcode gtls_verifyserver(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_session_t session)
{
struct ssl_connect_data *connssl = cf->ctx;
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
#ifndef CURL_DISABLE_PROXY
const char *pinned_key = Curl_ssl_cf_is_proxy(cf) ?
data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
data->set.str[STRING_SSL_PINNEDPUBLICKEY];
#else
const char *pinned_key = data->set.str[STRING_SSL_PINNEDPUBLICKEY];
#endif
CURLcode result;
result = Curl_gtls_verifyserver(cf, data, session, conn_config, ssl_config,
&connssl->peer, pinned_key);
if(result)
goto out;
#ifdef CURL_GNUTLS_EARLY_DATA
if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3)
result = cf_gtls_update_session_id(cf, data, session);
#endif
out:
return result;
}
#ifdef CURL_GNUTLS_EARLY_DATA
static CURLcode gtls_send_earlydata(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
CURLcode result = CURLE_OK;
const unsigned char *buf;
size_t blen;
ssize_t n;
DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sending);
backend->gtls.io_result = CURLE_OK;
while(Curl_bufq_peek(&connssl->earlydata, &buf, &blen)) {
n = gnutls_record_send_early_data(backend->gtls.session, buf, blen);
CURL_TRC_CF(data, cf, "gtls_send_earlydata(len=%zu) -> %zd",
blen, n);
if(n < 0) {
if(n == GNUTLS_E_AGAIN)
result = CURLE_AGAIN;
else
result = backend->gtls.io_result ?
backend->gtls.io_result : CURLE_SEND_ERROR;
goto out;
}
else if(!n) {
n = (ssize_t)blen;
}
Curl_bufq_skip(&connssl->earlydata, (size_t)n);
}
infof(data, "SSL sending %zu bytes of early data", connssl->earlydata_skip);
out:
return result;
}
#endif
static CURLcode gtls_connect_common(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done) {
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
CURLcode result = CURLE_OK;
DEBUGASSERT(backend);
if(ssl_connection_complete == connssl->state) {
*done = TRUE;
return CURLE_OK;
}
*done = FALSE;
if(connssl->connecting_state == ssl_connect_1) {
result = gtls_connect_step1(cf, data);
if(result)
goto out;
connssl->connecting_state = ssl_connect_2;
}
if(connssl->connecting_state == ssl_connect_2) {
#ifdef CURL_GNUTLS_EARLY_DATA
if(connssl->earlydata_state == ssl_earlydata_await) {
goto out;
}
else if(connssl->earlydata_state == ssl_earlydata_sending) {
result = gtls_send_earlydata(cf, data);
if(result)
goto out;
connssl->earlydata_state = ssl_earlydata_sent;
}
DEBUGASSERT((connssl->earlydata_state == ssl_earlydata_none) ||
(connssl->earlydata_state == ssl_earlydata_sent));
#endif
result = cf_gtls_handshake(cf, data);
if(result)
goto out;
connssl->connecting_state = ssl_connect_3;
}
if(connssl->connecting_state == ssl_connect_3) {
gnutls_datum_t proto;
int rc;
Curl_gtls_report_handshake(data, &backend->gtls);
result = gtls_verifyserver(cf, data, backend->gtls.session);
if(result)
goto out;
connssl->state = ssl_connection_complete;
rc = gnutls_alpn_get_selected_protocol(backend->gtls.session, &proto);
if(rc) {
proto.data = NULL;
proto.size = 0;
}
result = Curl_alpn_set_negotiated(cf, data, connssl,
proto.data, proto.size);
if(result)
goto out;
#ifdef CURL_GNUTLS_EARLY_DATA
if(connssl->earlydata_state > ssl_earlydata_none) {
DEBUGASSERT(connssl->earlydata_state == ssl_earlydata_sent);
connssl->earlydata_state =
(gnutls_session_get_flags(backend->gtls.session) &
GNUTLS_SFLAGS_EARLY_DATA) ?
ssl_earlydata_accepted : ssl_earlydata_rejected;
}
#endif
connssl->connecting_state = ssl_connect_done;
}
if(connssl->connecting_state == ssl_connect_done)
DEBUGASSERT(connssl->state == ssl_connection_complete);
out:
if(result == CURLE_AGAIN) {
*done = FALSE;
return CURLE_OK;
}
*done = ((connssl->state == ssl_connection_complete) ||
(connssl->state == ssl_connection_deferred));
CURL_TRC_CF(data, cf, "gtls_connect_common() -> %d, done=%d", result, *done);
return result;
}
static CURLcode gtls_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool *done)
{
#ifdef CURL_GNUTLS_EARLY_DATA
struct ssl_connect_data *connssl = cf->ctx;
if((connssl->state == ssl_connection_deferred) &&
(connssl->earlydata_state == ssl_earlydata_await)) {
*done = TRUE;
return CURLE_OK;
}
#endif
return gtls_connect_common(cf, data, done);
}
static bool gtls_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
struct ssl_connect_data *ctx = cf->ctx;
struct gtls_ssl_backend_data *backend;
(void)data;
DEBUGASSERT(ctx && ctx->backend);
backend = (struct gtls_ssl_backend_data *)ctx->backend;
if(backend->gtls.session &&
gnutls_record_check_pending(backend->gtls.session) != 0)
return TRUE;
return FALSE;
}
static CURLcode gtls_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 gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
CURLcode result = CURLE_OK;
ssize_t nwritten;
size_t remain = blen;
(void)data;
DEBUGASSERT(backend);
*pnwritten = 0;
while(remain) {
backend->gtls.io_result = CURLE_OK;
nwritten = gnutls_record_send(backend->gtls.session, buf, remain);
if(nwritten >= 0) {
*pnwritten += (size_t)nwritten;
DEBUGASSERT((size_t)nwritten <= remain);
buf = (char *)CURL_UNCONST(buf) + (size_t)nwritten;
remain -= (size_t)nwritten;
}
else {
if(*pnwritten && (nwritten == GNUTLS_E_AGAIN)) {
result = CURLE_OK;
goto out;
}
result = (nwritten == GNUTLS_E_AGAIN) ?
CURLE_AGAIN :
(backend->gtls.io_result ? backend->gtls.io_result : CURLE_SEND_ERROR);
goto out;
}
}
out:
CURL_TRC_CF(data, cf, "gtls_send(len=%zu) -> %d, %zu",
blen, result, *pnwritten);
return result;
}
static CURLcode gtls_shutdown(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool send_shutdown, bool *done)
{
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
char buf[1024];
CURLcode result = CURLE_OK;
ssize_t nread = 0;
size_t i;
DEBUGASSERT(backend);
if(!backend->gtls.session || cf->shutdown ||
connssl->state != ssl_connection_complete) {
*done = TRUE;
goto out;
}
connssl->io_need = CURL_SSL_IO_NEED_NONE;
*done = FALSE;
if(!backend->gtls.sent_shutdown) {
backend->gtls.sent_shutdown = TRUE;
if(send_shutdown) {
int ret = gnutls_bye(backend->gtls.session, GNUTLS_SHUT_RDWR);
if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) {
CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye EAGAIN");
connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ?
CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV;
backend->gtls.sent_shutdown = FALSE;
result = CURLE_OK;
goto out;
}
if(ret != GNUTLS_E_SUCCESS) {
CURL_TRC_CF(data, cf, "SSL shutdown, gnutls_bye error: '%s'(%d)",
gnutls_strerror((int)ret), (int)ret);
result = CURLE_RECV_ERROR;
goto out;
}
}
}
for(i = 0; i < 10; ++i) {
nread = gnutls_record_recv(backend->gtls.session, buf, sizeof(buf));
if(nread <= 0)
break;
}
if(nread > 0) {
}
else if(nread == 0) {
*done = TRUE;
}
else if((nread == GNUTLS_E_AGAIN) || (nread == GNUTLS_E_INTERRUPTED)) {
connssl->io_need = gnutls_record_get_direction(backend->gtls.session) ?
CURL_SSL_IO_NEED_SEND : CURL_SSL_IO_NEED_RECV;
}
else {
CURL_TRC_CF(data, cf, "SSL shutdown, error: '%s'(%d)",
gnutls_strerror((int)nread), (int)nread);
result = CURLE_RECV_ERROR;
}
out:
cf->shutdown = (result || *done);
return result;
}
static void gtls_close(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct ssl_connect_data *connssl = cf->ctx;
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
(void)data;
DEBUGASSERT(backend);
CURL_TRC_CF(data, cf, "close");
if(backend->gtls.session) {
gnutls_deinit(backend->gtls.session);
backend->gtls.session = NULL;
}
if(backend->gtls.shared_creds) {
Curl_gtls_shared_creds_free(&backend->gtls.shared_creds);
}
#ifdef USE_GNUTLS_SRP
if(backend->gtls.srp_client_cred) {
gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred);
backend->gtls.srp_client_cred = NULL;
}
#endif
}
static CURLcode gtls_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 gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
CURLcode result = CURLE_OK;
ssize_t nread;
(void)data;
DEBUGASSERT(backend);
nread = gnutls_record_recv(backend->gtls.session, buf, blen);
if(nread >= 0)
*pnread = (size_t)nread;
else {
if((nread == GNUTLS_E_AGAIN) || (nread == GNUTLS_E_INTERRUPTED)) {
result = CURLE_AGAIN;
goto out;
}
else if(nread == GNUTLS_E_REHANDSHAKE) {
result = cf_gtls_handshake(cf, data);
if(!result)
result = CURLE_AGAIN;
goto out;
}
else {
failf(data, "GnuTLS recv error (%d): %s",
(int)nread, gnutls_strerror((int)nread));
result = backend->gtls.io_result ?
backend->gtls.io_result : CURLE_RECV_ERROR;
goto out;
}
}
out:
CURL_TRC_CF(data, cf, "gtls_recv(len=%zu) -> 0, %zd", blen, nread);
return result;
}
size_t Curl_gtls_version(char *buffer, size_t size)
{
return curl_msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL));
}
static CURLcode gtls_random(struct Curl_easy *data,
unsigned char *entropy, size_t length)
{
int rc;
(void)data;
rc = gnutls_rnd(GNUTLS_RND_RANDOM, entropy, length);
return rc ? CURLE_FAILED_INIT : CURLE_OK;
}
static CURLcode gtls_sha256sum(const unsigned char *tmp,
size_t tmplen,
unsigned char *sha256sum,
size_t sha256len)
{
struct sha256_ctx SHA256pw;
sha256_init(&SHA256pw);
sha256_update(&SHA256pw, (unsigned int)tmplen, tmp);
sha256_digest(&SHA256pw, (unsigned int)sha256len, sha256sum);
return CURLE_OK;
}
static bool gtls_cert_status_request(void)
{
return TRUE;
}
static void *gtls_get_internals(struct ssl_connect_data *connssl,
CURLINFO info)
{
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
(void)info;
DEBUGASSERT(backend);
return backend->gtls.session;
}
const struct Curl_ssl Curl_ssl_gnutls = {
{ CURLSSLBACKEND_GNUTLS, "gnutls" },
SSLSUPP_CA_PATH |
SSLSUPP_CERTINFO |
SSLSUPP_PINNEDPUBKEY |
SSLSUPP_HTTPS_PROXY |
SSLSUPP_CIPHER_LIST |
SSLSUPP_CA_CACHE,
sizeof(struct gtls_ssl_backend_data),
gtls_init,
gtls_cleanup,
Curl_gtls_version,
gtls_shutdown,
gtls_data_pending,
gtls_random,
gtls_cert_status_request,
gtls_connect,
Curl_ssl_adjust_pollset,
gtls_get_internals,
gtls_close,
NULL,
NULL,
NULL,
NULL,
gtls_sha256sum,
gtls_recv,
gtls_send,
NULL,
};
#endif