vtls: TLS session storage overhaul

- add session with destructor callback
- remove vtls `session_free` method
- let `Curl_ssl_addsessionid()` take ownership
  of session object, freeing it also on failures
- change tls backend use
- test_17, add tests for SSL session resumption

Closes #13386
This commit is contained in:
Stefan Eissing 2024-04-26 10:11:51 +02:00 committed by Daniel Stenberg
parent 2d2c27e5a3
commit fb22459dc1
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
17 changed files with 470 additions and 229 deletions

View file

@ -345,6 +345,8 @@ struct ssl_general_config {
int ca_cache_timeout; /* Certificate store cache timeout (seconds) */
};
typedef void Curl_ssl_sessionid_dtor(void *sessionid, size_t idsize);
/* information stored about one single SSL session */
struct Curl_ssl_session {
char *name; /* host name for which this ID was used */
@ -352,6 +354,7 @@ struct Curl_ssl_session {
const char *scheme; /* protocol scheme used */
void *sessionid; /* as returned from the SSL layer */
size_t idsize; /* if known, otherwise 0 */
Curl_ssl_sessionid_dtor *sessionid_free; /* free `sessionid` callback */
long age; /* just a number, the higher the more recent */
int remote_port; /* remote port */
int conn_to_port; /* remote port for the connection (may be -1) */

View file

@ -1992,9 +1992,8 @@ static int quic_ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
ctx = cf? cf->ctx : NULL;
data = cf? CF_DATA_CURRENT(cf) : NULL;
if(cf && data && ctx) {
CURLcode result = Curl_ossl_add_session(cf, data, &ctx->peer,
ssl_sessionid);
return result? 0 : 1;
Curl_ossl_add_session(cf, data, &ctx->peer, ssl_sessionid);
return 1;
}
return 0;
}

View file

@ -830,7 +830,7 @@ static CURLcode bearssl_run_until(struct Curl_cfilter *cf,
CURL_TRC_CF(data, cf, "ssl_recv(len=%zu) -> %zd, %d", len, ret, result);
if(ret == 0) {
failf(data, "SSL: EOF without close notify");
return CURLE_READ_ERROR;
return CURLE_RECV_ERROR;
}
if(ret <= 0) {
return result;
@ -873,6 +873,12 @@ static CURLcode bearssl_connect_step2(struct Curl_cfilter *cf,
return ret;
}
static void bearssl_session_free(void *sessionid, size_t idsize)
{
(void)idsize;
free(sessionid);
}
static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@ -896,7 +902,6 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
if(ssl_config->primary.sessionid) {
bool incache;
bool added = FALSE;
void *oldsession;
br_ssl_session_parameters *session;
@ -909,13 +914,12 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
&oldsession, NULL));
if(incache)
Curl_ssl_delsessionid(data, oldsession);
ret = Curl_ssl_addsessionid(cf, data, &connssl->peer, session, 0, &added);
ret = Curl_ssl_addsessionid(cf, data, &connssl->peer, session, 0,
bearssl_session_free);
Curl_ssl_sessionid_unlock(data);
if(!added)
free(session);
if(ret) {
return CURLE_OUT_OF_MEMORY;
}
if(ret)
return ret;
}
connssl->connecting_state = ssl_connect_done;
@ -1174,11 +1178,6 @@ static void bearssl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
}
}
static void bearssl_session_free(void *ptr)
{
free(ptr);
}
static CURLcode bearssl_sha256sum(const unsigned char *input,
size_t inputlen,
unsigned char *sha256sum,
@ -1211,7 +1210,6 @@ const struct Curl_ssl Curl_ssl_bearssl = {
bearssl_get_internals, /* get_internals */
bearssl_close, /* close_one */
Curl_none_close_all, /* close_all */
bearssl_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -532,6 +532,88 @@ CURLcode Curl_gtls_client_trust_setup(struct Curl_cfilter *cf,
return CURLE_OK;
}
static void gtls_sessionid_free(void *sessionid, size_t idsize)
{
(void)idsize;
free(sessionid);
}
static CURLcode gtls_update_session_id(struct Curl_cfilter *cf,
struct Curl_easy *data,
gnutls_session_t session)
{
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
struct ssl_connect_data *connssl = cf->ctx;
CURLcode result = CURLE_OK;
if(ssl_config->primary.sessionid) {
/* we always unconditionally get the session id here, as even if we
already got it from the cache and asked to use it in the connection, it
might've been rejected and then a new one is in use now and we need to
detect that. */
void *connect_sessionid;
size_t connect_idsize = 0;
/* get the session ID data size */
gnutls_session_get_data(session, NULL, &connect_idsize);
connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
if(!connect_sessionid) {
return CURLE_OUT_OF_MEMORY;
}
else {
bool incache;
void *ssl_sessionid;
/* extract session ID to the allocated buffer */
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
DEBUGF(infof(data, "get session id (len=%zu) and store in cache",
connect_idsize));
Curl_ssl_sessionid_lock(data);
incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer,
&ssl_sessionid, NULL));
if(incache) {
/* there was one before in the cache, so instead of risking that the
previous one was rejected, we just kill that and store the new */
Curl_ssl_delsessionid(data, ssl_sessionid);
}
/* store this session id, takes ownership */
result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
connect_sessionid, connect_idsize,
gtls_sessionid_free);
Curl_ssl_sessionid_unlock(data);
}
}
return result;
}
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) { /* after message has been processed */
struct Curl_easy *data = CF_DATA_CURRENT(cf);
if(data) {
DEBUGF(infof(data, "handshake: %s message type %d",
incoming? "incoming" : "outgoing", htype));
switch(htype) {
case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: {
gtls_update_session_id(cf, data, session);
break;
}
default:
break;
}
}
}
return 0;
}
static CURLcode gtls_client_init(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct ssl_peer *peer,
@ -828,10 +910,13 @@ CURLcode Curl_gtls_ctx_init(struct gtls_ctx *gctx,
Curl_ssl_sessionid_lock(data);
if(!Curl_ssl_getsessionid(cf, data, peer, &ssl_sessionid, &ssl_idsize)) {
/* we got a session id, use it! */
gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize);
int rc;
/* Informational message */
infof(data, "SSL reusing session ID");
rc = gnutls_session_set_data(gctx->session, ssl_sessionid, ssl_idsize);
if(rc < 0)
infof(data, "SSL failed to set session ID");
else
infof(data, "SSL reusing session ID (size=%zu)", ssl_idsize);
}
Curl_ssl_sessionid_unlock(data);
}
@ -869,6 +954,9 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
if(result)
return result;
gnutls_handshake_set_hook_function(backend->gtls.session,
GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST,
gtls_handshake_cb);
/* register callback functions and handle to send and receive data. */
gnutls_transport_set_ptr(backend->gtls.session, cf);
@ -1403,49 +1491,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf,
Curl_alpn_set_negotiated(cf, data, NULL, 0);
}
if(ssl_config->primary.sessionid) {
/* we always unconditionally get the session id here, as even if we
already got it from the cache and asked to use it in the connection, it
might've been rejected and then a new one is in use now and we need to
detect that. */
void *connect_sessionid;
size_t connect_idsize = 0;
/* get the session ID data size */
gnutls_session_get_data(session, NULL, &connect_idsize);
connect_sessionid = malloc(connect_idsize); /* get a buffer for it */
if(connect_sessionid) {
bool incache;
bool added = FALSE;
void *ssl_sessionid;
/* extract session ID to the allocated buffer */
gnutls_session_get_data(session, connect_sessionid, &connect_idsize);
Curl_ssl_sessionid_lock(data);
incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer,
&ssl_sessionid, NULL));
if(incache) {
/* there was one before in the cache, so instead of risking that the
previous one was rejected, we just kill that and store the new */
Curl_ssl_delsessionid(data, ssl_sessionid);
}
/* store this session id */
result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
connect_sessionid, connect_idsize,
&added);
Curl_ssl_sessionid_unlock(data);
if(!added)
free(connect_sessionid);
if(result) {
result = CURLE_OUT_OF_MEMORY;
}
}
else
result = CURLE_OUT_OF_MEMORY;
}
/* Only on TLSv1.2 or lower do we have the session id now. For
* TLSv1.3 we get it via a SESSION_TICKET message that arrives later. */
if(gnutls_protocol_get_version(session) < GNUTLS_TLS1_3)
result = gtls_update_session_id(cf, data, session);
out:
return result;
@ -1734,11 +1783,6 @@ out:
return ret;
}
static void gtls_session_free(void *ptr)
{
free(ptr);
}
static size_t gtls_version(char *buffer, size_t size)
{
return msnprintf(buffer, size, "GnuTLS/%s", gnutls_check_version(NULL));
@ -1805,7 +1849,6 @@ const struct Curl_ssl Curl_ssl_gnutls = {
gtls_get_internals, /* get_internals */
gtls_close, /* close_one */
Curl_none_close_all, /* close_all */
gtls_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -1033,6 +1033,13 @@ pinnedpubkey_error:
return CURLE_OK;
}
static void mbedtls_session_free(void *sessionid, size_t idsize)
{
(void)idsize;
mbedtls_ssl_session_free(sessionid);
free(sessionid);
}
static CURLcode
mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
{
@ -1049,7 +1056,6 @@ mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
int ret;
mbedtls_ssl_session *our_ssl_sessionid;
void *old_ssl_sessionid = NULL;
bool added = FALSE;
our_ssl_sessionid = malloc(sizeof(mbedtls_ssl_session));
if(!our_ssl_sessionid)
@ -1073,16 +1079,11 @@ mbed_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
Curl_ssl_delsessionid(data, old_ssl_sessionid);
retcode = Curl_ssl_addsessionid(cf, data, &connssl->peer,
our_ssl_sessionid, 0, &added);
our_ssl_sessionid, 0,
mbedtls_session_free);
Curl_ssl_sessionid_unlock(data);
if(!added) {
mbedtls_ssl_session_free(our_ssl_sessionid);
free(our_ssl_sessionid);
}
if(retcode) {
failf(data, "failed to store ssl session");
if(retcode)
return retcode;
}
}
connssl->connecting_state = ssl_connect_done;
@ -1176,12 +1177,6 @@ static ssize_t mbed_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
return len;
}
static void mbedtls_session_free(void *ptr)
{
mbedtls_ssl_session_free(ptr);
free(ptr);
}
static size_t mbedtls_version(char *buffer, size_t size)
{
#ifdef MBEDTLS_VERSION_C
@ -1460,7 +1455,6 @@ const struct Curl_ssl Curl_ssl_mbedtls = {
mbedtls_get_internals, /* get_internals */
mbedtls_close, /* close_one */
mbedtls_close_all, /* close_all */
mbedtls_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -2074,10 +2074,11 @@ static int ossl_shutdown(struct Curl_cfilter *cf,
return retval;
}
static void ossl_session_free(void *ptr)
static void ossl_session_free(void *sessionid, size_t idsize)
{
/* free the ID */
SSL_SESSION_free(ptr);
(void)idsize;
SSL_SESSION_free(sessionid);
}
/*
@ -2935,52 +2936,45 @@ ossl_set_ssl_version_min_max_legacy(ctx_option_t *ctx_options,
CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
SSL_SESSION *ssl_sessionid)
SSL_SESSION *session)
{
const struct ssl_config_data *config;
bool isproxy;
CURLcode result = CURLE_WRITE_ERROR;
bool added = FALSE;
if(!cf || !data)
return result;
goto out;
isproxy = Curl_ssl_cf_is_proxy(cf);
config = Curl_ssl_cf_get_config(cf, data);
if(config->primary.sessionid) {
bool incache;
bool added = FALSE;
void *old_ssl_sessionid = NULL;
void *old_session = NULL;
Curl_ssl_sessionid_lock(data);
if(isproxy)
incache = FALSE;
else
incache = !(Curl_ssl_getsessionid(cf, data, peer,
&old_ssl_sessionid, NULL));
if(incache) {
if(old_ssl_sessionid != ssl_sessionid) {
infof(data, "old SSL session ID is stale, removing");
Curl_ssl_delsessionid(data, old_ssl_sessionid);
incache = FALSE;
}
&old_session, NULL));
if(incache && (old_session != session)) {
infof(data, "old SSL session ID is stale, removing");
Curl_ssl_delsessionid(data, old_session);
incache = FALSE;
}
if(!incache) {
if(!Curl_ssl_addsessionid(cf, data, peer, ssl_sessionid,
0 /* unknown size */, &added)) {
if(added) {
/* the session has been put into the session cache */
result = CURLE_OK;
}
}
else
failf(data, "failed to store ssl session");
added = TRUE;
Curl_ssl_addsessionid(cf, data, peer, session, 0, ossl_session_free);
}
Curl_ssl_sessionid_unlock(data);
}
return result;
out:
if(!added)
ossl_session_free(session, 0);
return CURLE_OK;
}
/* The "new session" callback must return zero if the session can be removed
@ -2991,13 +2985,12 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
struct Curl_cfilter *cf;
struct Curl_easy *data;
struct ssl_connect_data *connssl;
CURLcode result;
cf = (struct Curl_cfilter*) SSL_get_app_data(ssl);
connssl = cf? cf->ctx : NULL;
data = connssl? CF_DATA_CURRENT(cf) : NULL;
result = Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid);
return result? 0 : 1;
Curl_ossl_add_session(cf, data, &connssl->peer, ssl_sessionid);
return 1;
}
static CURLcode load_cacert_from_memory(X509_STORE *store,
@ -5291,7 +5284,6 @@ const struct Curl_ssl Curl_ssl_openssl = {
ossl_get_internals, /* get_internals */
ossl_close, /* close_one */
ossl_close_all, /* close_all */
ossl_session_free, /* session_free */
ossl_set_engine, /* set_engine */
ossl_set_engine_default, /* set_engine_default */
ossl_engines_list, /* engines_list */

View file

@ -100,7 +100,7 @@ CURLcode Curl_ossl_ctx_configure(struct Curl_cfilter *cf,
SSL_CTX *ssl_ctx);
/*
* Add a new session to the cache.
* Add a new session to the cache. Takes ownership of the session.
*/
CURLcode Curl_ossl_add_session(struct Curl_cfilter *cf,
struct Curl_easy *data,

View file

@ -742,7 +742,6 @@ const struct Curl_ssl Curl_ssl_rustls = {
cr_get_internals, /* get_internals */
cr_close, /* close_one */
Curl_none_close_all, /* close_all */
Curl_none_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -1675,6 +1675,28 @@ add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, bool reverse_order,
return args->result == CURLE_OK;
}
static void schannel_session_free(void *sessionid, size_t idsize)
{
/* this is expected to be called under sessionid lock */
struct Curl_schannel_cred *cred = sessionid;
(void)idsize;
if(cred) {
cred->refcount--;
if(cred->refcount == 0) {
s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
curlx_unicodefree(cred->sni_hostname);
#ifdef HAS_CLIENT_CERT_PATH
if(cred->client_cert_store) {
CertCloseStore(cred->client_cert_store, 0);
cred->client_cert_store = NULL;
}
#endif
Curl_safefree(cred);
}
}
}
static CURLcode
schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
{
@ -1752,7 +1774,6 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
/* save the current session data for possible reuse */
if(ssl_config->primary.sessionid) {
bool incache;
bool added = FALSE;
struct Curl_schannel_cred *old_cred = NULL;
Curl_ssl_sessionid_lock(data);
@ -1768,20 +1789,15 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
}
}
if(!incache) {
/* Up ref count since call takes ownership */
backend->cred->refcount++;
result = Curl_ssl_addsessionid(cf, data, &connssl->peer, backend->cred,
sizeof(struct Curl_schannel_cred),
&added);
schannel_session_free);
if(result) {
Curl_ssl_sessionid_unlock(data);
failf(data, "schannel: failed to store credential handle");
return result;
}
else if(added) {
/* this cred session is now also referenced by sessionid cache */
backend->cred->refcount++;
DEBUGF(infof(data,
"schannel: stored credential handle in session cache"));
}
}
Curl_ssl_sessionid_unlock(data);
}
@ -2456,27 +2472,6 @@ static bool schannel_data_pending(struct Curl_cfilter *cf,
return FALSE;
}
static void schannel_session_free(void *ptr)
{
/* this is expected to be called under sessionid lock */
struct Curl_schannel_cred *cred = ptr;
if(cred) {
cred->refcount--;
if(cred->refcount == 0) {
s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
curlx_unicodefree(cred->sni_hostname);
#ifdef HAS_CLIENT_CERT_PATH
if(cred->client_cert_store) {
CertCloseStore(cred->client_cert_store, 0);
cred->client_cert_store = NULL;
}
#endif
Curl_safefree(cred);
}
}
}
/* shut down the SSL connection and clean up related memory.
this function can be called multiple times on the same connection including
if the SSL connection failed (eg connection made but failed handshake). */
@ -2560,7 +2555,7 @@ static int schannel_shutdown(struct Curl_cfilter *cf,
/* free SSPI Schannel API credential handle */
if(backend->cred) {
Curl_ssl_sessionid_lock(data);
schannel_session_free(backend->cred);
schannel_session_free(backend->cred, 0);
Curl_ssl_sessionid_unlock(data);
backend->cred = NULL;
}
@ -2923,7 +2918,6 @@ const struct Curl_ssl Curl_ssl_schannel = {
schannel_get_internals, /* get_internals */
schannel_close, /* close_one */
Curl_none_close_all, /* close_all */
schannel_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -1636,6 +1636,18 @@ static CURLcode sectransp_set_selected_ciphers(struct Curl_easy *data,
return CURLE_OK;
}
static void sectransp_session_free(void *sessionid, size_t idsize)
{
/* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a
cached session ID inside the Security framework. There is a private
function that does this, but I don't want to have to explain to you why I
got your application rejected from the App Store due to the use of a
private API, so the best we can do is free up our own char array that we
created way back in sectransp_connect_step1... */
(void)idsize;
Curl_safefree(sessionid);
}
static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
@ -2078,12 +2090,11 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
}
result = Curl_ssl_addsessionid(cf, data, &connssl->peer, ssl_sessionid,
ssl_sessionid_len, NULL);
ssl_sessionid_len,
sectransp_session_free);
Curl_ssl_sessionid_unlock(data);
if(result) {
failf(data, "failed to store ssl session");
if(result)
return result;
}
}
}
@ -3225,17 +3236,6 @@ static int sectransp_shutdown(struct Curl_cfilter *cf,
return rc;
}
static void sectransp_session_free(void *ptr)
{
/* ST, as of iOS 5 and Mountain Lion, has no public method of deleting a
cached session ID inside the Security framework. There is a private
function that does this, but I don't want to have to explain to you why I
got your application rejected from the App Store due to the use of a
private API, so the best we can do is free up our own char array that we
created way back in sectransp_connect_step1... */
Curl_safefree(ptr);
}
static size_t sectransp_version(char *buffer, size_t size)
{
return msnprintf(buffer, size, "SecureTransport");
@ -3469,7 +3469,6 @@ const struct Curl_ssl Curl_ssl_sectransp = {
sectransp_get_internals, /* get_internals */
sectransp_close, /* close_one */
Curl_none_close_all, /* close_all */
sectransp_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -605,9 +605,10 @@ void Curl_ssl_kill_session(struct Curl_ssl_session *session)
/* defensive check */
/* free the ID the SSL-layer specific way */
Curl_ssl->session_free(session->sessionid);
session->sessionid_free(session->sessionid, session->idsize);
session->sessionid = NULL;
session->sessionid_free = NULL;
session->age = 0; /* fresh */
Curl_free_primary_ssl_config(&session->ssl_config);
@ -645,42 +646,41 @@ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
const struct ssl_peer *peer,
void *ssl_sessionid,
size_t idsize,
bool *added)
Curl_ssl_sessionid_dtor *sessionid_free_cb)
{
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
size_t i;
struct Curl_ssl_session *store;
long oldest_age;
char *clone_host;
char *clone_conn_to_host;
char *clone_host = NULL;
char *clone_conn_to_host = NULL;
int conn_to_port;
long *general_age;
CURLcode result = CURLE_OUT_OF_MEMORY;
if(added)
*added = FALSE;
DEBUGASSERT(ssl_sessionid);
DEBUGASSERT(sessionid_free_cb);
if(!data->state.session)
if(!data->state.session) {
sessionid_free_cb(ssl_sessionid, idsize);
return CURLE_OK;
}
store = &data->state.session[0];
oldest_age = data->state.session[0].age; /* zero if unused */
(void)ssl_config;
DEBUGASSERT(ssl_config->primary.sessionid);
(void)ssl_config;
clone_host = strdup(peer->hostname);
if(!clone_host)
return CURLE_OUT_OF_MEMORY; /* bail out */
goto out;
if(cf->conn->bits.conn_to_host) {
clone_conn_to_host = strdup(cf->conn->conn_to_host.name);
if(!clone_conn_to_host) {
free(clone_host);
return CURLE_OUT_OF_MEMORY; /* bail out */
}
if(!clone_conn_to_host)
goto out;
}
else
clone_conn_to_host = NULL;
if(cf->conn->bits.conn_to_port)
conn_to_port = cf->conn->conn_to_port;
@ -713,34 +713,43 @@ CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
store = &data->state.session[i]; /* use this slot */
/* now init the session struct wisely */
if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) {
Curl_free_primary_ssl_config(&store->ssl_config);
store->sessionid = NULL; /* let caller free sessionid */
goto out;
}
store->sessionid = ssl_sessionid;
store->idsize = idsize;
store->sessionid_free = sessionid_free_cb;
store->age = *general_age; /* set current age */
/* free it if there's one already present */
free(store->name);
free(store->conn_to_host);
store->name = clone_host; /* clone host name */
clone_host = NULL;
store->conn_to_host = clone_conn_to_host; /* clone connect to host name */
clone_conn_to_host = NULL;
store->conn_to_port = conn_to_port; /* connect to port number */
/* port number */
store->remote_port = peer->port;
store->scheme = cf->conn->handler->scheme;
store->transport = peer->transport;
if(!clone_ssl_primary_config(conn_config, &store->ssl_config)) {
Curl_free_primary_ssl_config(&store->ssl_config);
store->sessionid = NULL; /* let caller free sessionid */
free(clone_host);
free(clone_conn_to_host);
return CURLE_OUT_OF_MEMORY;
result = CURLE_OK;
out:
free(clone_host);
free(clone_conn_to_host);
if(result) {
failf(data, "Failed to add Session ID to cache for %s://%s:%d [%s]",
store->scheme, store->name, store->remote_port,
Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server");
sessionid_free_cb(ssl_sessionid, idsize);
return result;
}
if(added)
*added = TRUE;
DEBUGF(infof(data, "Added Session ID to cache for %s://%s:%d [%s]",
store->scheme, store->name, store->remote_port,
Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server"));
CURL_TRC_CF(data, cf, "Added Session ID to cache for %s://%s:%d [%s]",
store->scheme, store->name, store->remote_port,
Curl_ssl_cf_is_proxy(cf) ? "PROXY" : "server");
return CURLE_OK;
}
@ -1323,7 +1332,6 @@ static const struct Curl_ssl Curl_ssl_multi = {
multissl_get_internals, /* get_internals */
multissl_close, /* close_one */
Curl_none_close_all, /* close_all */
Curl_none_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -123,7 +123,6 @@ struct Curl_ssl {
void *(*get_internals)(struct ssl_connect_data *connssl, CURLINFO info);
void (*close)(struct Curl_cfilter *cf, struct Curl_easy *data);
void (*close_all)(struct Curl_easy *data);
void (*session_free)(void *ptr);
CURLcode (*set_engine)(struct Curl_easy *data, const char *engine);
CURLcode (*set_engine_default)(struct Curl_easy *data);
@ -186,13 +185,15 @@ bool Curl_ssl_getsessionid(struct Curl_cfilter *cf,
* Sessionid mutex must be locked (see Curl_ssl_sessionid_lock).
* Caller must ensure that it has properly shared ownership of this sessionid
* object with cache (e.g. incrementing refcount on success)
* Call takes ownership of `ssl_sessionid`, using `sessionid_free_cb`
* to destroy it in case of failure or later removal.
*/
CURLcode Curl_ssl_addsessionid(struct Curl_cfilter *cf,
struct Curl_easy *data,
const struct ssl_peer *peer,
void *ssl_sessionid,
size_t idsize,
bool *added);
Curl_ssl_sessionid_dtor *sessionid_free_cb);
#include "openssl.h" /* OpenSSL versions */
#include "gtls.h" /* GnuTLS versions */

View file

@ -1058,6 +1058,13 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
}
static void wolfssl_session_free(void *sessionid, size_t idsize)
{
(void)idsize;
wolfSSL_SESSION_free(sessionid);
}
static CURLcode
wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
{
@ -1071,42 +1078,27 @@ wolfssl_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
DEBUGASSERT(backend);
if(ssl_config->primary.sessionid) {
bool incache;
bool added = FALSE;
void *old_ssl_sessionid = NULL;
/* wolfSSL_get1_session allocates memory that has to be freed. */
WOLFSSL_SESSION *our_ssl_sessionid = wolfSSL_get1_session(backend->handle);
if(our_ssl_sessionid) {
void *old_ssl_sessionid = NULL;
bool incache;
Curl_ssl_sessionid_lock(data);
incache = !(Curl_ssl_getsessionid(cf, data, &connssl->peer,
&old_ssl_sessionid, NULL));
if(incache) {
if(old_ssl_sessionid != our_ssl_sessionid) {
infof(data, "old SSL session ID is stale, removing");
Curl_ssl_delsessionid(data, old_ssl_sessionid);
incache = FALSE;
}
Curl_ssl_delsessionid(data, old_ssl_sessionid);
}
if(!incache) {
result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
our_ssl_sessionid, 0, NULL);
if(result) {
Curl_ssl_sessionid_unlock(data);
wolfSSL_SESSION_free(our_ssl_sessionid);
failf(data, "failed to store ssl session");
return result;
}
else {
added = TRUE;
}
}
/* call takes ownership of `our_ssl_sessionid` */
result = Curl_ssl_addsessionid(cf, data, &connssl->peer,
our_ssl_sessionid, 0,
wolfssl_session_free);
Curl_ssl_sessionid_unlock(data);
if(!added) {
/* If the session info wasn't added to the cache, free our copy. */
wolfSSL_SESSION_free(our_ssl_sessionid);
if(result) {
failf(data, "failed to store ssl session");
return result;
}
}
}
@ -1240,12 +1232,6 @@ static ssize_t wolfssl_recv(struct Curl_cfilter *cf,
}
static void wolfssl_session_free(void *ptr)
{
wolfSSL_SESSION_free(ptr);
}
static size_t wolfssl_version(char *buffer, size_t size)
{
#if LIBWOLFSSL_VERSION_HEX >= 0x03006000
@ -1525,7 +1511,6 @@ const struct Curl_ssl Curl_ssl_wolfssl = {
wolfssl_get_internals, /* get_internals */
wolfssl_close, /* close_one */
Curl_none_close_all, /* close_all */
wolfssl_session_free, /* session_free */
Curl_none_set_engine, /* set_engine */
Curl_none_set_engine_default, /* set_engine_default */
Curl_none_engines_list, /* engines_list */

View file

@ -0,0 +1,104 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
#
import difflib
import filecmp
import json
import logging
import os
from datetime import timedelta
import pytest
from testenv import Env, CurlClient, LocalClient, ExecResult
log = logging.getLogger(__name__)
class TestSSLUse:
@pytest.fixture(autouse=True, scope='class')
def _class_scope(self, env, httpd, nghttpx):
if env.have_h3():
nghttpx.start_if_needed()
httpd.clear_extra_configs()
httpd.reload()
def test_17_01_sslinfo_plain(self, env: Env, httpd, nghttpx, repeat):
proto = 'http/1.1'
curl = CurlClient(env=env)
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
r = curl.http_get(url=url, alpn_proto=proto)
assert r.json['HTTPS'] == 'on', f'{r.json}'
assert 'SSL_SESSION_ID' in r.json, f'{r.json}'
assert 'SSL_SESSION_RESUMED' in r.json, f'{r.json}'
assert r.json['SSL_SESSION_RESUMED'] == 'Initial', f'{r.json}'
@pytest.mark.parametrize("tls_max", ['1.2', '1.3'])
def test_17_02_sslinfo_reconnect(self, env: Env, httpd, nghttpx, tls_max, repeat):
proto = 'http/1.1'
count = 3
exp_resumed = 'Resumed'
xargs = ['--sessionid', '--tls-max', tls_max, f'--tlsv{tls_max}']
if env.curl_uses_lib('gnutls'):
if tls_max == '1.3':
exp_resumed = 'Initial' # 1.2 works in gnutls, but 1.3 does not, TODO
if env.curl_uses_lib('libressl'):
if tls_max == '1.3':
exp_resumed = 'Initial' # 1.2 works in libressl, but 1.3 does not, TODO
if env.curl_uses_lib('wolfssl'):
xargs = ['--sessionid', f'--tlsv{tls_max}']
if tls_max == '1.3':
exp_resumed = 'Initial' # 1.2 works in wolfssl, but 1.3 does not, TODO
if env.curl_uses_lib('rustls-ffi'):
exp_resumed = 'Initial' # rustls does not support sessions, TODO
if env.curl_uses_lib('bearssl') and tls_max == '1.3':
pytest.skip('BearSSL does not support TLSv1.3')
if env.curl_uses_lib('mbedtls') and tls_max == '1.3':
pytest.skip('mbedtls does not support TLSv1.3')
curl = CurlClient(env=env)
# tell the server to close the connection after each request
urln = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo?'\
f'id=[0-{count-1}]&close'
r = curl.http_download(urls=[urln], alpn_proto=proto, with_stats=True,
extra_args=xargs)
r.check_response(count=count, http_status=200)
# should have used one connection for each request, sessions after
# first should have been resumed
assert r.total_connects == count, r.dump_logs()
for i in range(count):
dfile = curl.download_file(i)
assert os.path.exists(dfile)
with open(dfile) as f:
djson = json.load(f)
assert djson['HTTPS'] == 'on', f'{i}: {djson}'
if i == 0:
assert djson['SSL_SESSION_RESUMED'] == 'Initial', f'{i}: {djson}'
else:
assert djson['SSL_SESSION_RESUMED'] == exp_resumed, f'{i}: {djson}'

View file

@ -415,9 +415,15 @@ class CurlClient:
return xargs
def http_get(self, url: str, extra_args: Optional[List[str]] = None,
def_tracing: bool = True, with_profile: bool = False):
return self._raw(url, options=extra_args, with_stats=False,
def_tracing=def_tracing, with_profile=with_profile)
alpn_proto: Optional[str] = None,
def_tracing: bool = True,
with_stats: bool = False,
with_profile: bool = False):
return self._raw(url, options=extra_args,
with_stats=with_stats,
alpn_proto=alpn_proto,
def_tracing=def_tracing,
with_profile=with_profile)
def http_download(self, urls: List[str],
alpn_proto: Optional[str] = None,

View file

@ -397,6 +397,10 @@ class Httpd:
f' Redirect 302 /curltest/echo302 /curltest/echo',
f' Redirect 303 /curltest/echo303 /curltest/echo',
f' Redirect 307 /curltest/echo307 /curltest/echo',
f' <Location /curltest/sslinfo>',
f' SSLOptions StdEnvVars',
f' SetHandler curltest-sslinfo',
f' </Location>',
f' <Location /curltest/echo>',
f' SetHandler curltest-echo',
f' </Location>',

View file

@ -40,6 +40,7 @@ static int curltest_echo_handler(request_rec *r);
static int curltest_put_handler(request_rec *r);
static int curltest_tweak_handler(request_rec *r);
static int curltest_1_1_required(request_rec *r);
static int curltest_sslinfo_handler(request_rec *r);
AP_DECLARE_MODULE(curltest) = {
STANDARD20_MODULE_STUFF,
@ -88,6 +89,7 @@ static void curltest_hooks(apr_pool_t *pool)
ap_hook_handler(curltest_put_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(curltest_tweak_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(curltest_1_1_required, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(curltest_sslinfo_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
#define SECS_PER_HOUR (60*60)
@ -628,3 +630,113 @@ cleanup:
}
return DECLINED;
}
static int brigade_env_var(request_rec *r, apr_bucket_brigade *bb,
const char *name)
{
const char *s;
s = apr_table_get(r->subprocess_env, name);
if(s)
return apr_brigade_printf(bb, NULL, NULL, ",\n \"%s\": \"%s\"", name, s);
return 0;
}
static int curltest_sslinfo_handler(request_rec *r)
{
conn_rec *c = r->connection;
apr_bucket_brigade *bb;
apr_bucket *b;
apr_status_t rv;
apr_array_header_t *args = NULL;
const char *request_id = NULL;
int close_conn = 0;
long l;
int i;
if(strcmp(r->handler, "curltest-sslinfo")) {
return DECLINED;
}
if(r->method_number != M_GET) {
return DECLINED;
}
if(r->args) {
apr_array_header_t *args = apr_cstr_split(r->args, "&", 1, r->pool);
for(i = 0; i < args->nelts; ++i) {
char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*);
s = strchr(arg, '=');
if(s) {
*s = '\0';
val = s + 1;
if(!strcmp("id", arg)) {
/* just an id for repeated requests with curl's url globbing */
request_id = val;
continue;
}
}
else if(!strcmp("close", arg)) {
/* we are asked to close the connection */
close_conn = 1;
continue;
}
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "query parameter not "
"understood: '%s' in %s",
arg, r->args);
ap_die(HTTP_BAD_REQUEST, r);
return OK;
}
}
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "sslinfo: processing");
r->status = 200;
r->clength = -1;
r->chunked = 1;
apr_table_unset(r->headers_out, "Content-Length");
/* Discourage content-encodings */
apr_table_unset(r->headers_out, "Content-Encoding");
apr_table_setn(r->subprocess_env, "no-brotli", "1");
apr_table_setn(r->subprocess_env, "no-gzip", "1");
ap_set_content_type(r, "application/json");
bb = apr_brigade_create(r->pool, c->bucket_alloc);
apr_brigade_puts(bb, NULL, NULL, "{\n \"Name\": \"SSL-Information\"");
brigade_env_var(r, bb, "HTTPS");
brigade_env_var(r, bb, "SSL_PROTOCOL");
brigade_env_var(r, bb, "SSL_CIPHER");
brigade_env_var(r, bb, "SSL_SESSION_ID");
brigade_env_var(r, bb, "SSL_SESSION_RESUMED");
brigade_env_var(r, bb, "SSL_SRP_USER");
brigade_env_var(r, bb, "SSL_SRP_USERINFO");
apr_brigade_puts(bb, NULL, NULL, "}\n");
/* flush response */
b = apr_bucket_flush_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
rv = ap_pass_brigade(r->output_filters, bb);
if (APR_SUCCESS != rv) goto cleanup;
/* we are done */
b = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "1_1_handler: request read");
rv = ap_pass_brigade(r->output_filters, bb);
cleanup:
if(close_conn)
r->connection->keepalive = AP_CONN_CLOSE;
if(rv == APR_SUCCESS
|| r->status != HTTP_OK
|| c->aborted) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler: done");
return OK;
}
else {
/* no way to know what type of error occurred */
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "1_1_handler failed");
return AP_FILTER_ERROR;
}
return DECLINED;
}