gnutls: lazy init the trust settings

- delay loading of trust anchors and CRLs after the ClientHello
  has been sent off
- add tracing to IO operations
- on IO errors, return the CURLcode of the underlying filter

Closes #13339
This commit is contained in:
Stefan Eissing 2024-04-10 13:30:16 +02:00 committed by Daniel Stenberg
parent 61e6db87ac
commit 8cee4c9238
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
3 changed files with 151 additions and 95 deletions

View File

@ -286,6 +286,12 @@ CURLcode Curl_vquic_tls_before_recv(struct curl_tls_ctx *ctx,
return result; return result;
ctx->ossl.x509_store_setup = TRUE; ctx->ossl.x509_store_setup = TRUE;
} }
#elif defined(USE_GNUTLS)
if(!ctx->gtls.trust_setup) {
CURLcode result = Curl_gtls_client_trust_setup(cf, data, &ctx->gtls);
if(result)
return result;
}
#else #else
(void)ctx; (void)cf; (void)data; (void)ctx; (void)cf; (void)data;
#endif #endif

View File

@ -95,15 +95,18 @@ static ssize_t gtls_push(void *s, const void *buf, size_t blen)
{ {
struct Curl_cfilter *cf = s; struct Curl_cfilter *cf = s;
struct ssl_connect_data *connssl = cf->ctx; 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); struct Curl_easy *data = CF_DATA_CURRENT(cf);
ssize_t nwritten; ssize_t nwritten;
CURLcode result; CURLcode result;
DEBUGASSERT(data); DEBUGASSERT(data);
nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result); nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %zd, err=%d",
blen, nwritten, result);
backend->gtls.io_result = result;
if(nwritten < 0) { if(nwritten < 0) {
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
gnutls_transport_set_errno(backend->gtls.session, gnutls_transport_set_errno(backend->gtls.session,
(CURLE_AGAIN == result)? EAGAIN : EINVAL); (CURLE_AGAIN == result)? EAGAIN : EINVAL);
nwritten = -1; nwritten = -1;
@ -115,15 +118,27 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen)
{ {
struct Curl_cfilter *cf = s; struct Curl_cfilter *cf = s;
struct ssl_connect_data *connssl = cf->ctx; 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); struct Curl_easy *data = CF_DATA_CURRENT(cf);
ssize_t nread; ssize_t nread;
CURLcode result; CURLcode result;
DEBUGASSERT(data); DEBUGASSERT(data);
if(!backend->gtls.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;
}
}
nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result); nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
CURL_TRC_CF(data, cf, "glts_pull(len=%zu) -> %zd, err=%d",
blen, nread, result);
backend->gtls.io_result = result;
if(nread < 0) { if(nread < 0) {
struct gtls_ssl_backend_data *backend =
(struct gtls_ssl_backend_data *)connssl->backend;
gnutls_transport_set_errno(backend->gtls.session, gnutls_transport_set_errno(backend->gtls.session,
(CURLE_AGAIN == result)? EAGAIN : EINVAL); (CURLE_AGAIN == result)? EAGAIN : EINVAL);
nread = -1; nread = -1;
@ -279,8 +294,17 @@ static CURLcode handshake(struct Curl_cfilter *cf,
/* socket is readable or writable */ /* socket is readable or writable */
} }
backend->gtls.io_result = CURLE_OK;
rc = gnutls_handshake(session); rc = gnutls_handshake(session);
if(!backend->gtls.trust_setup) {
/* After having send off the ClientHello, we prepare the trust
* store to verify the coming certificate from the server */
CURLcode result = Curl_gtls_client_trust_setup(cf, data, &backend->gtls);
if(result)
return result;
}
if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) {
connssl->connecting_state = connssl->connecting_state =
gnutls_record_get_direction(session)? gnutls_record_get_direction(session)?
@ -301,6 +325,9 @@ static CURLcode handshake(struct Curl_cfilter *cf,
infof(data, "gnutls_handshake() warning: %s", strerr); infof(data, "gnutls_handshake() warning: %s", strerr);
continue; continue;
} }
else if((rc < 0) && backend->gtls.io_result) {
return backend->gtls.io_result;
}
else if(rc < 0) { else if(rc < 0) {
const char *strerr = NULL; const char *strerr = NULL;
@ -423,13 +450,95 @@ set_ssl_version_min_max(struct Curl_easy *data,
return CURLE_SSL_CONNECT_ERROR; return CURLE_SSL_CONNECT_ERROR;
} }
static CURLcode gtls_client_init(struct Curl_easy *data, CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf,
struct ssl_primary_config *config, struct Curl_easy *data,
struct ssl_config_data *ssl_config, struct gtls_ctx *gtls)
struct ssl_peer *peer,
struct gtls_ctx *gtls,
long *pverifyresult)
{ {
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);
int rc;
CURL_TRC_CF(data, cf, "setup trust anchors and CRLs");
if(config->verifypeer) {
bool imported_native_ca = false;
if(ssl_config->native_ca_store) {
rc = gnutls_certificate_set_x509_system_trust(gtls->cred);
if(rc < 0)
infof(data, "error reading native ca store (%s), continuing anyway",
gnutls_strerror(rc));
else {
infof(data, "found %d certificates in native ca store", rc);
if(rc > 0)
imported_native_ca = true;
}
}
if(config->CAfile) {
/* set the trusted CA cert bundle file */
gnutls_certificate_set_verify_flags(gtls->cred,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
rc = gnutls_certificate_set_x509_trust_file(gtls->cred,
config->CAfile,
GNUTLS_X509_FMT_PEM);
if(rc < 0) {
infof(data, "error reading ca cert file %s (%s)%s",
config->CAfile, gnutls_strerror(rc),
(imported_native_ca ? ", continuing anyway" : ""));
if(!imported_native_ca) {
ssl_config->certverifyresult = rc;
return CURLE_SSL_CACERT_BADFILE;
}
}
else
infof(data, "found %d certificates in %s", rc, config->CAfile);
}
if(config->CApath) {
/* set the trusted CA cert directory */
rc = gnutls_certificate_set_x509_trust_dir(gtls->cred,
config->CApath,
GNUTLS_X509_FMT_PEM);
if(rc < 0) {
infof(data, "error reading ca cert file %s (%s)%s",
config->CApath, gnutls_strerror(rc),
(imported_native_ca ? ", continuing anyway" : ""));
if(!imported_native_ca) {
ssl_config->certverifyresult = rc;
return CURLE_SSL_CACERT_BADFILE;
}
}
else
infof(data, "found %d certificates in %s", rc, config->CApath);
}
}
if(config->CRLfile) {
/* set the CRL list file */
rc = gnutls_certificate_set_x509_crl_file(gtls->cred,
config->CRLfile,
GNUTLS_X509_FMT_PEM);
if(rc < 0) {
failf(data, "error reading crl file %s (%s)",
config->CRLfile, gnutls_strerror(rc));
return CURLE_SSL_CRL_BADFILE;
}
else
infof(data, "found %d CRL in %s", rc, config->CRLfile);
}
gtls->trust_setup = TRUE;
return CURLE_OK;
}
static CURLcode gtls_client_init(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
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; unsigned int init_flags;
int rc; int rc;
bool sni = TRUE; /* default is SNI enabled */ bool sni = TRUE; /* default is SNI enabled */
@ -441,8 +550,6 @@ static CURLcode gtls_client_init(struct Curl_easy *data,
if(!gtls_inited) if(!gtls_inited)
gtls_init(); gtls_init();
*pverifyresult = 0;
if(config->version == CURL_SSLVERSION_SSLv2) { if(config->version == CURL_SSLVERSION_SSLv2) {
failf(data, "GnuTLS does not support SSLv2"); failf(data, "GnuTLS does not support SSLv2");
return CURLE_SSL_CONNECT_ERROR; return CURLE_SSL_CONNECT_ERROR;
@ -479,74 +586,7 @@ static CURLcode gtls_client_init(struct Curl_easy *data,
} }
#endif #endif
if(config->verifypeer) { ssl_config->certverifyresult = 0;
bool imported_native_ca = false;
if(ssl_config->native_ca_store) {
rc = gnutls_certificate_set_x509_system_trust(gtls->cred);
if(rc < 0)
infof(data, "error reading native ca store (%s), continuing anyway",
gnutls_strerror(rc));
else {
infof(data, "found %d certificates in native ca store", rc);
if(rc > 0)
imported_native_ca = true;
}
}
if(config->CAfile) {
/* set the trusted CA cert bundle file */
gnutls_certificate_set_verify_flags(gtls->cred,
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT);
rc = gnutls_certificate_set_x509_trust_file(gtls->cred,
config->CAfile,
GNUTLS_X509_FMT_PEM);
if(rc < 0) {
infof(data, "error reading ca cert file %s (%s)%s",
config->CAfile, gnutls_strerror(rc),
(imported_native_ca ? ", continuing anyway" : ""));
if(!imported_native_ca) {
*pverifyresult = rc;
return CURLE_SSL_CACERT_BADFILE;
}
}
else
infof(data, "found %d certificates in %s", rc, config->CAfile);
}
if(config->CApath) {
/* set the trusted CA cert directory */
rc = gnutls_certificate_set_x509_trust_dir(gtls->cred,
config->CApath,
GNUTLS_X509_FMT_PEM);
if(rc < 0) {
infof(data, "error reading ca cert file %s (%s)%s",
config->CApath, gnutls_strerror(rc),
(imported_native_ca ? ", continuing anyway" : ""));
if(!imported_native_ca) {
*pverifyresult = rc;
return CURLE_SSL_CACERT_BADFILE;
}
}
else
infof(data, "found %d certificates in %s", rc, config->CApath);
}
}
if(config->CRLfile) {
/* set the CRL list file */
rc = gnutls_certificate_set_x509_crl_file(gtls->cred,
config->CRLfile,
GNUTLS_X509_FMT_PEM);
if(rc < 0) {
failf(data, "error reading crl file %s (%s)",
config->CRLfile, gnutls_strerror(rc));
return CURLE_SSL_CRL_BADFILE;
}
else
infof(data, "found %d CRL in %s", rc, config->CRLfile);
}
/* Initialize TLS session as a client */ /* Initialize TLS session as a client */
init_flags = GNUTLS_CLIENT; init_flags = GNUTLS_CLIENT;
@ -634,6 +674,11 @@ static CURLcode gtls_client_init(struct Curl_easy *data,
} }
if(config->clientcert) { if(config->clientcert) {
if(!gtls->trust_setup) {
result = Curl_gtls_client_trust_setup(cf, data, gtls);
if(result)
return result;
}
if(ssl_config->key_passwd) { if(ssl_config->key_passwd) {
const unsigned int supported_key_encryption_algorithms = const unsigned int supported_key_encryption_algorithms =
GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR | GNUTLS_PKCS_USE_PKCS12_3DES | GNUTLS_PKCS_USE_PKCS12_ARCFOUR |
@ -725,14 +770,11 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
void *ssl_user_data) void *ssl_user_data)
{ {
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf); struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
long * const pverifyresult = &ssl_config->certverifyresult;
CURLcode result; CURLcode result;
DEBUGASSERT(gctx); DEBUGASSERT(gctx);
result = gtls_client_init(data, conn_config, ssl_config, peer, result = gtls_client_init(cf, data, peer, gctx);
gctx, pverifyresult);
if(result) if(result)
return result; return result;
@ -1515,12 +1557,13 @@ static ssize_t gtls_send(struct Curl_cfilter *cf,
(void)data; (void)data;
DEBUGASSERT(backend); DEBUGASSERT(backend);
backend->gtls.io_result = CURLE_OK;
rc = gnutls_record_send(backend->gtls.session, mem, len); rc = gnutls_record_send(backend->gtls.session, mem, len);
if(rc < 0) { if(rc < 0) {
*curlcode = (rc == GNUTLS_E_AGAIN) *curlcode = (rc == GNUTLS_E_AGAIN)?
? CURLE_AGAIN CURLE_AGAIN :
: CURLE_SEND_ERROR; (backend->gtls.io_result? backend->gtls.io_result : CURLE_SEND_ERROR);
rc = -1; rc = -1;
} }
@ -1656,6 +1699,7 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf,
(void)data; (void)data;
DEBUGASSERT(backend); DEBUGASSERT(backend);
backend->gtls.io_result = CURLE_OK;
ret = gnutls_record_recv(backend->gtls.session, buf, buffersize); ret = gnutls_record_recv(backend->gtls.session, buf, buffersize);
if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) { if((ret == GNUTLS_E_AGAIN) || (ret == GNUTLS_E_INTERRUPTED)) {
*curlcode = CURLE_AGAIN; *curlcode = CURLE_AGAIN;
@ -1680,7 +1724,8 @@ static ssize_t gtls_recv(struct Curl_cfilter *cf,
failf(data, "GnuTLS recv error (%d): %s", failf(data, "GnuTLS recv error (%d): %s",
(int)ret, gnutls_strerror((int)ret)); (int)ret, gnutls_strerror((int)ret));
*curlcode = CURLE_RECV_ERROR; *curlcode = backend->gtls.io_result?
backend->gtls.io_result : CURLE_RECV_ERROR;
ret = -1; ret = -1;
goto out; goto out;
} }

View File

@ -51,6 +51,8 @@ struct gtls_ctx {
#ifdef USE_GNUTLS_SRP #ifdef USE_GNUTLS_SRP
gnutls_srp_client_credentials_t srp_client_cred; gnutls_srp_client_credentials_t srp_client_cred;
#endif #endif
CURLcode io_result; /* result of last IO cfilter operation */
BIT(trust_setup); /* x509 anchors + CRLs have been set up */
}; };
typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf, typedef CURLcode Curl_gtls_ctx_setup_cb(struct Curl_cfilter *cf,
@ -66,13 +68,16 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
void *cb_user_data, void *cb_user_data,
void *ssl_user_data); void *ssl_user_data);
CURLcode CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf,
Curl_gtls_verifyserver(struct Curl_easy *data, struct Curl_easy *data,
gnutls_session_t session, struct gtls_ctx *gtls);
struct ssl_primary_config *config,
struct ssl_config_data *ssl_config, CURLcode Curl_gtls_verifyserver(struct Curl_easy *data,
struct ssl_peer *peer, gnutls_session_t session,
const char *pinned_key); struct ssl_primary_config *config,
struct ssl_config_data *ssl_config,
struct ssl_peer *peer,
const char *pinned_key);
extern const struct Curl_ssl Curl_ssl_gnutls; extern const struct Curl_ssl Curl_ssl_gnutls;