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 */
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) */
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;
}
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;
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)
{
if(ssl_config->primary.sessionid) {
bool incache;
- bool added = FALSE;
void *oldsession;
br_ssl_session_parameters *session;
&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;
}
}
-static void bearssl_session_free(void *ptr)
-{
- free(ptr);
-}
-
static CURLcode bearssl_sha256sum(const unsigned char *input,
size_t inputlen,
unsigned char *sha256sum,
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 */
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,
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);
}
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);
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;
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));
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 */
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)
{
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)
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;
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
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 */
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);
}
/*
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
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,
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 */
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,
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 */
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)
{
/* 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);
}
}
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);
}
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). */
/* 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;
}
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 */
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)
{
}
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;
- }
}
}
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");
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 */
/* 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);
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;
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;
- }
-
- if(added)
- *added = TRUE;
+ result = CURLE_OK;
- 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"));
+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;
+ }
+ 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;
}
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 */
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);
* 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 */
}
+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)
{
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;
}
}
}
}
-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
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 */
--- /dev/null
+#!/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}'
+
+
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,
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>',
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,
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)
}
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;
+}