Instead of receive and lifetime, keep only the eppch seconds when a session expires. Closes #15861
905 lines
26 KiB
C
905 lines
26 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* 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
|
|
*
|
|
***************************************************************************/
|
|
|
|
/* This file is for implementing all "generic" SSL functions that all libcurl
|
|
internals should use. It is then responsible for calling the proper
|
|
"backend" function.
|
|
|
|
SSL-functions in libcurl should call functions in this source file, and not
|
|
to any specific SSL-layer.
|
|
|
|
Curl_ssl_ - prefix for generic ones
|
|
|
|
Note that this source code uses the functions of the configured SSL
|
|
backend via the global Curl_ssl instance.
|
|
|
|
"SSL/TLS Strong Encryption: An Introduction"
|
|
https://httpd.apache.org/docs/2.0/ssl/ssl_intro.html
|
|
*/
|
|
|
|
#include "curl_setup.h"
|
|
|
|
#ifdef USE_SSL
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#include "urldata.h"
|
|
#include "cfilters.h"
|
|
|
|
#include "vtls.h" /* generic SSL protos etc */
|
|
#include "vtls_int.h"
|
|
#include "vtls_scache.h"
|
|
|
|
#include "strcase.h"
|
|
#include "url.h"
|
|
#include "llist.h"
|
|
#include "share.h"
|
|
#include "curl_trc.h"
|
|
#include "curl_sha256.h"
|
|
#include "warnless.h"
|
|
#include "curl_printf.h"
|
|
#include "strdup.h"
|
|
|
|
/* The last #include files should be: */
|
|
#include "curl_memory.h"
|
|
#include "memdebug.h"
|
|
|
|
/* a peer+tls-config we cache sessions for */
|
|
struct Curl_ssl_scache_peer {
|
|
char *ssl_peer_key; /* id for peer + relevant TLS configuration */
|
|
char *clientcert;
|
|
char *srp_username;
|
|
char *srp_password;
|
|
struct Curl_llist sessions;
|
|
void *sobj; /* object instance or NULL */
|
|
Curl_ssl_scache_obj_dtor *sobj_free; /* free `sobj` callback */
|
|
unsigned char key_salt[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
|
|
unsigned char key_hmac[CURL_SHA256_DIGEST_LENGTH]; /* for entry export */
|
|
size_t max_sessions;
|
|
long age; /* just a number, the higher the more recent */
|
|
BIT(hmac_set); /* if key_salt and key_hmac are present */
|
|
};
|
|
|
|
struct Curl_ssl_scache {
|
|
struct Curl_ssl_scache_peer *peers;
|
|
size_t peer_count;
|
|
int default_lifetime_secs;
|
|
long age;
|
|
};
|
|
|
|
static void cf_ssl_scache_clear_session(struct Curl_ssl_session *s)
|
|
{
|
|
if(s->sdata) {
|
|
free((void *)s->sdata);
|
|
s->sdata = NULL;
|
|
}
|
|
s->sdata_len = 0;
|
|
if(s->quic_tp) {
|
|
free((void *)s->quic_tp);
|
|
s->quic_tp = NULL;
|
|
}
|
|
s->quic_tp_len = 0;
|
|
s->ietf_tls_id = 0;
|
|
s->valid_until = 0;
|
|
Curl_safefree(s->alpn);
|
|
}
|
|
|
|
static void cf_ssl_scache_sesssion_ldestroy(void *udata, void *s)
|
|
{
|
|
(void)udata;
|
|
cf_ssl_scache_clear_session(s);
|
|
free(s);
|
|
}
|
|
|
|
CURLcode
|
|
Curl_ssl_session_create(unsigned char *sdata, size_t sdata_len,
|
|
int ietf_tls_id, const char *alpn,
|
|
curl_off_t valid_until, size_t earlydata_max,
|
|
struct Curl_ssl_session **psession)
|
|
{
|
|
return Curl_ssl_session_create2(sdata, sdata_len, ietf_tls_id, alpn,
|
|
valid_until, earlydata_max,
|
|
NULL, 0, psession);
|
|
}
|
|
|
|
CURLcode
|
|
Curl_ssl_session_create2(unsigned char *sdata, size_t sdata_len,
|
|
int ietf_tls_id, const char *alpn,
|
|
curl_off_t valid_until, size_t earlydata_max,
|
|
unsigned char *quic_tp, size_t quic_tp_len,
|
|
struct Curl_ssl_session **psession)
|
|
{
|
|
struct Curl_ssl_session *s;
|
|
|
|
if(!sdata || !sdata_len) {
|
|
free(sdata);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
*psession = NULL;
|
|
s = calloc(1, sizeof(*s));
|
|
if(!s) {
|
|
free(sdata);
|
|
free(quic_tp);
|
|
return CURLE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
s->ietf_tls_id = ietf_tls_id;
|
|
s->valid_until = valid_until;
|
|
s->earlydata_max = earlydata_max;
|
|
s->sdata = sdata;
|
|
s->sdata_len = sdata_len;
|
|
s->quic_tp = quic_tp;
|
|
s->quic_tp_len = quic_tp_len;
|
|
if(alpn) {
|
|
s->alpn = strdup(alpn);
|
|
if(!s->alpn) {
|
|
cf_ssl_scache_sesssion_ldestroy(NULL, s);
|
|
return CURLE_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
*psession = s;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
void Curl_ssl_session_destroy(struct Curl_ssl_session *s)
|
|
{
|
|
if(s) {
|
|
/* if in the list, the list destructor takes care of it */
|
|
if(Curl_node_llist(&s->list))
|
|
Curl_node_remove(&s->list);
|
|
else {
|
|
cf_ssl_scache_sesssion_ldestroy(NULL, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cf_ssl_scache_clear_peer(struct Curl_ssl_scache_peer *peer)
|
|
{
|
|
Curl_llist_destroy(&peer->sessions, NULL);
|
|
if(peer->sobj) {
|
|
DEBUGASSERT(peer->sobj_free);
|
|
if(peer->sobj_free)
|
|
peer->sobj_free(peer->sobj);
|
|
peer->sobj = NULL;
|
|
}
|
|
peer->sobj_free = NULL;
|
|
Curl_safefree(peer->clientcert);
|
|
#ifdef USE_TLS_SRP
|
|
Curl_safefree(peer->srp_username);
|
|
Curl_safefree(peer->srp_password);
|
|
#endif
|
|
Curl_safefree(peer->ssl_peer_key);
|
|
peer->age = 0;
|
|
peer->hmac_set = FALSE;
|
|
}
|
|
|
|
static void cf_ssl_scache_peer_set_obj(struct Curl_ssl_scache_peer *peer,
|
|
void *sobj,
|
|
Curl_ssl_scache_obj_dtor *sobj_free)
|
|
{
|
|
DEBUGASSERT(peer);
|
|
if(peer->sobj_free) {
|
|
peer->sobj_free(peer->sobj);
|
|
}
|
|
peer->sobj = sobj;
|
|
peer->sobj_free = sobj_free;
|
|
}
|
|
|
|
static CURLcode cf_ssl_scache_peer_init(struct Curl_ssl_scache_peer *peer,
|
|
const char *ssl_peer_key,
|
|
const char *clientcert,
|
|
const char *srp_username,
|
|
const char *srp_password)
|
|
{
|
|
CURLcode result = CURLE_OUT_OF_MEMORY;
|
|
|
|
DEBUGASSERT(!peer->ssl_peer_key);
|
|
peer->ssl_peer_key = strdup(ssl_peer_key);
|
|
if(!peer->ssl_peer_key)
|
|
goto out;
|
|
if(clientcert) {
|
|
peer->clientcert = strdup(clientcert);
|
|
if(!peer->clientcert)
|
|
goto out;
|
|
}
|
|
if(srp_username) {
|
|
peer->srp_username = strdup(srp_username);
|
|
if(!peer->srp_username)
|
|
goto out;
|
|
}
|
|
if(srp_password) {
|
|
peer->srp_password = strdup(srp_password);
|
|
if(!peer->srp_password)
|
|
goto out;
|
|
}
|
|
result = CURLE_OK;
|
|
out:
|
|
if(result)
|
|
cf_ssl_scache_clear_peer(peer);
|
|
return result;
|
|
}
|
|
|
|
static void cf_scache_session_remove(struct Curl_ssl_scache_peer *peer,
|
|
struct Curl_ssl_session *s)
|
|
{
|
|
(void)peer;
|
|
DEBUGASSERT(Curl_node_llist(&s->list) == &peer->sessions);
|
|
Curl_ssl_session_destroy(s);
|
|
}
|
|
|
|
static bool cf_scache_session_expired(struct Curl_ssl_session *s,
|
|
curl_off_t now)
|
|
{
|
|
return (s->valid_until > 0) && (s->valid_until < now);
|
|
}
|
|
|
|
static void cf_scache_peer_remove_expired(struct Curl_ssl_scache_peer *peer,
|
|
curl_off_t now)
|
|
{
|
|
struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
|
|
while(n) {
|
|
struct Curl_ssl_session *s = Curl_node_elem(n);
|
|
n = Curl_node_next(n);
|
|
if(cf_scache_session_expired(s, now))
|
|
cf_scache_session_remove(peer, s);
|
|
}
|
|
}
|
|
|
|
static void cf_scache_peer_remove_non13(struct Curl_ssl_scache_peer *peer)
|
|
{
|
|
struct Curl_llist_node *n = Curl_llist_head(&peer->sessions);
|
|
while(n) {
|
|
struct Curl_ssl_session *s = Curl_node_elem(n);
|
|
n = Curl_node_next(n);
|
|
if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3)
|
|
cf_scache_session_remove(peer, s);
|
|
}
|
|
}
|
|
|
|
CURLcode Curl_ssl_scache_create(size_t max_peers,
|
|
size_t max_sessions_per_peer,
|
|
struct Curl_ssl_scache **pscache)
|
|
{
|
|
struct Curl_ssl_scache *scache;
|
|
struct Curl_ssl_scache_peer *peers;
|
|
size_t i;
|
|
|
|
*pscache = NULL;
|
|
peers = calloc(max_peers, sizeof(*peers));
|
|
if(!peers)
|
|
return CURLE_OUT_OF_MEMORY;
|
|
|
|
scache = calloc(1, sizeof(*scache));
|
|
if(!scache) {
|
|
free(peers);
|
|
return CURLE_OUT_OF_MEMORY;
|
|
}
|
|
|
|
scache->default_lifetime_secs = (24*60*60); /* 1 day */
|
|
scache->peer_count = max_peers;
|
|
scache->peers = peers;
|
|
scache->age = 1;
|
|
for(i = 0; i < scache->peer_count; ++i) {
|
|
scache->peers[i].max_sessions = max_sessions_per_peer;
|
|
Curl_llist_init(&scache->peers[i].sessions,
|
|
cf_ssl_scache_sesssion_ldestroy);
|
|
}
|
|
|
|
*pscache = scache;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
void Curl_ssl_scache_destroy(struct Curl_ssl_scache *scache)
|
|
{
|
|
if(scache) {
|
|
size_t i;
|
|
for(i = 0; i < scache->peer_count; ++i) {
|
|
cf_ssl_scache_clear_peer(&scache->peers[i]);
|
|
}
|
|
free(scache->peers);
|
|
free(scache);
|
|
}
|
|
}
|
|
|
|
/* Lock shared SSL session data */
|
|
void Curl_ssl_scache_lock(struct Curl_easy *data)
|
|
{
|
|
if(CURL_SHARE_ssl_scache(data))
|
|
Curl_share_lock(data, CURL_LOCK_DATA_SSL_SESSION, CURL_LOCK_ACCESS_SINGLE);
|
|
}
|
|
|
|
/* Unlock shared SSL session data */
|
|
void Curl_ssl_scache_unlock(struct Curl_easy *data)
|
|
{
|
|
if(CURL_SHARE_ssl_scache(data))
|
|
Curl_share_unlock(data, CURL_LOCK_DATA_SSL_SESSION);
|
|
}
|
|
|
|
static CURLcode cf_ssl_peer_key_add_path(struct dynbuf *buf,
|
|
const char *name,
|
|
char *path)
|
|
{
|
|
if(path && path[0]) {
|
|
/* We try to add absolute paths, so that the session key can stay
|
|
* valid when used in another process with different CWD. However,
|
|
* when a path does not exist, this does not work. Then, we add
|
|
* the path as is. */
|
|
#ifdef _WIN32
|
|
char abspath[_MAX_PATH];
|
|
if(_fullpath(abspath, path, _MAX_PATH))
|
|
return Curl_dyn_addf(buf, ":%s-%s", name, abspath);
|
|
#else
|
|
if(path[0] != '/') {
|
|
char *abspath = realpath(path, NULL);
|
|
if(abspath) {
|
|
CURLcode r = Curl_dyn_addf(buf, ":%s-%s", name, abspath);
|
|
(free)(abspath); /* allocated by libc, free without memdebug */
|
|
return r;
|
|
}
|
|
}
|
|
#endif
|
|
return Curl_dyn_addf(buf, ":%s-%s", name, path);
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static CURLcode cf_ssl_peer_key_add_hash(struct dynbuf *buf,
|
|
const char *name,
|
|
struct curl_blob *blob)
|
|
{
|
|
CURLcode r = CURLE_OK;
|
|
if(blob && blob->len) {
|
|
unsigned char hash[CURL_SHA256_DIGEST_LENGTH];
|
|
size_t i;
|
|
|
|
r = Curl_dyn_addf(buf, ":%s-", name);
|
|
if(r)
|
|
goto out;
|
|
r = Curl_sha256it(hash, blob->data, blob->len);
|
|
if(r)
|
|
goto out;
|
|
for(i = 0; i < CURL_SHA256_DIGEST_LENGTH; ++i) {
|
|
r = Curl_dyn_addf(buf, "%02x", hash[i]);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
return r;
|
|
}
|
|
|
|
CURLcode Curl_ssl_peer_key_make(struct Curl_cfilter *cf,
|
|
const struct ssl_peer *peer,
|
|
const char *tls_id,
|
|
char **ppeer_key)
|
|
{
|
|
struct ssl_primary_config *ssl = Curl_ssl_cf_get_primary_config(cf);
|
|
struct dynbuf buf;
|
|
size_t key_len;
|
|
CURLcode r;
|
|
|
|
*ppeer_key = NULL;
|
|
Curl_dyn_init(&buf, 10 * 1024);
|
|
|
|
r = Curl_dyn_addf(&buf, "%s:%d", peer->hostname, peer->port);
|
|
if(r)
|
|
goto out;
|
|
|
|
switch(peer->transport) {
|
|
case TRNSPRT_TCP:
|
|
break;
|
|
case TRNSPRT_UDP:
|
|
r = Curl_dyn_add(&buf, ":UDP");
|
|
break;
|
|
case TRNSPRT_QUIC:
|
|
r = Curl_dyn_add(&buf, ":QUIC");
|
|
break;
|
|
case TRNSPRT_UNIX:
|
|
r = Curl_dyn_add(&buf, ":UNIX");
|
|
break;
|
|
default:
|
|
r = Curl_dyn_addf(&buf, ":TRNSPRT-%d", peer->transport);
|
|
break;
|
|
}
|
|
if(r)
|
|
goto out;
|
|
|
|
if(!ssl->verifypeer) {
|
|
r = Curl_dyn_add(&buf, ":NO-VRFY-PEER");
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(!ssl->verifyhost) {
|
|
r = Curl_dyn_add(&buf, ":NO-VRFY-HOST");
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->verifystatus) {
|
|
r = Curl_dyn_add(&buf, ":VRFY-STATUS");
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(!ssl->verifypeer || !ssl->verifyhost) {
|
|
if(cf->conn->bits.conn_to_host) {
|
|
r = Curl_dyn_addf(&buf, ":CHOST-%s", cf->conn->conn_to_host.name);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(cf->conn->bits.conn_to_port) {
|
|
r = Curl_dyn_addf(&buf, ":CPORT-%d", cf->conn->conn_to_port);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if(ssl->version || ssl->version_max) {
|
|
r = Curl_dyn_addf(&buf, ":TLSVER-%d-%d", ssl->version,
|
|
(ssl->version_max >> 16));
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->ssl_options) {
|
|
r = Curl_dyn_addf(&buf, ":TLSOPT-%x", ssl->ssl_options);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->cipher_list) {
|
|
r = Curl_dyn_addf(&buf, ":CIPHER-%s", ssl->cipher_list);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->cipher_list13) {
|
|
r = Curl_dyn_addf(&buf, ":CIPHER13-%s", ssl->cipher_list13);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->curves) {
|
|
r = Curl_dyn_addf(&buf, ":CURVES-%s", ssl->curves);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->verifypeer) {
|
|
r = cf_ssl_peer_key_add_path(&buf, "CA", ssl->CAfile);
|
|
if(r)
|
|
goto out;
|
|
r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath);
|
|
if(r)
|
|
goto out;
|
|
r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile);
|
|
if(r)
|
|
goto out;
|
|
r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert);
|
|
if(r)
|
|
goto out;
|
|
if(ssl->cert_blob) {
|
|
r = cf_ssl_peer_key_add_hash(&buf, "CertBlob", ssl->cert_blob);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->ca_info_blob) {
|
|
r = cf_ssl_peer_key_add_hash(&buf, "CAInfoBlob", ssl->ca_info_blob);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(ssl->issuercert_blob) {
|
|
r = cf_ssl_peer_key_add_hash(&buf, "IssuerBlob", ssl->issuercert_blob);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
}
|
|
if(ssl->pinned_key && ssl->pinned_key[0]) {
|
|
r = Curl_dyn_addf(&buf, ":Pinned-%s", ssl->pinned_key);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
|
|
if(ssl->clientcert && ssl->clientcert[0]) {
|
|
r = Curl_dyn_add(&buf, ":CCERT");
|
|
if(r)
|
|
goto out;
|
|
}
|
|
#ifdef USE_TLS_SRP
|
|
if(ssl->username || ssl->password) {
|
|
r = Curl_dyn_add(&buf, ":SRP-AUTH");
|
|
if(r)
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
if(!tls_id || !tls_id[0]) {
|
|
r = CURLE_FAILED_INIT;
|
|
goto out;
|
|
}
|
|
r = Curl_dyn_addf(&buf, ":IMPL-%s", tls_id);
|
|
if(r)
|
|
goto out;
|
|
|
|
*ppeer_key = Curl_dyn_take(&buf, &key_len);
|
|
/* we just added printable char, and dynbuf always 0 terminates,
|
|
* no need to track length */
|
|
|
|
|
|
out:
|
|
Curl_dyn_free(&buf);
|
|
return r;
|
|
}
|
|
|
|
static bool cf_ssl_scache_match_auth(struct Curl_ssl_scache_peer *peer,
|
|
struct ssl_primary_config *conn_config)
|
|
{
|
|
if(!Curl_safecmp(peer->clientcert, conn_config->clientcert))
|
|
return FALSE;
|
|
#ifdef USE_TLS_SRP
|
|
if(Curl_timestrcmp(peer->srp_username, conn_config->username) ||
|
|
Curl_timestrcmp(peer->srp_password, conn_config->password))
|
|
return FALSE;
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
|
|
static CURLcode cf_ssl_find_peer(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct Curl_ssl_scache *scache,
|
|
const char *ssl_peer_key,
|
|
struct Curl_ssl_scache_peer **ppeer)
|
|
{
|
|
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);
|
|
size_t i, peer_key_len = 0;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
*ppeer = NULL;
|
|
if(!ssl_config || !ssl_config->primary.cache_session)
|
|
goto out;
|
|
|
|
/* check for entries with known peer_key */
|
|
for(i = 0; scache && i < scache->peer_count; i++) {
|
|
if(scache->peers[i].ssl_peer_key &&
|
|
strcasecompare(ssl_peer_key, scache->peers[i].ssl_peer_key) &&
|
|
cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
|
|
/* yes, we have a cached session for this! */
|
|
*ppeer = &scache->peers[i];
|
|
goto out;
|
|
}
|
|
}
|
|
/* check for entries with HMAC set but no known peer_key */
|
|
for(i = 0; scache && i < scache->peer_count; i++) {
|
|
if(!scache->peers[i].ssl_peer_key &&
|
|
scache->peers[i].hmac_set &&
|
|
cf_ssl_scache_match_auth(&scache->peers[i], conn_config)) {
|
|
/* possible entry with unknown peer_key, check hmac */
|
|
unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
|
|
if(!peer_key_len) /* we are lazy */
|
|
peer_key_len = strlen(ssl_peer_key);
|
|
result = Curl_hmacit(&Curl_HMAC_SHA256,
|
|
scache->peers[i].key_salt,
|
|
sizeof(scache->peers[i].key_salt),
|
|
(const unsigned char *)ssl_peer_key,
|
|
peer_key_len,
|
|
my_hmac);
|
|
if(result)
|
|
goto out;
|
|
if(!memcmp(scache->peers[i].key_hmac, my_hmac, sizeof(my_hmac))) {
|
|
/* remember peer_key for future lookups */
|
|
scache->peers[i].ssl_peer_key = strdup(ssl_peer_key);
|
|
if(!scache->peers[i].ssl_peer_key) {
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
goto out;
|
|
}
|
|
*ppeer = &scache->peers[i];
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
if(result)
|
|
CURL_TRC_CF(data, cf, "[SACHE] failure finding scache peer: %d", result);
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_ssl_add_peer(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct Curl_ssl_scache *scache,
|
|
const char *ssl_peer_key,
|
|
struct Curl_ssl_scache_peer **ppeer)
|
|
{
|
|
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
size_t i;
|
|
CURLcode result;
|
|
|
|
*ppeer = NULL;
|
|
result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
|
|
if(result || !scache->peer_count)
|
|
return result;
|
|
|
|
if(peer) {
|
|
*ppeer = peer;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/* not there, find empty or oldest peer */
|
|
for(i = 0; i < scache->peer_count; ++i) {
|
|
/* free peer entry? */
|
|
if(!scache->peers[i].ssl_peer_key && !scache->peers[i].hmac_set) {
|
|
peer = &scache->peers[i];
|
|
break;
|
|
}
|
|
/* peer without sessions and obj */
|
|
if(!scache->peers[i].sobj &&
|
|
!Curl_llist_count(&scache->peers[i].sessions)) {
|
|
peer = &scache->peers[i];
|
|
break;
|
|
}
|
|
/* remember "oldest" peer */
|
|
if(!peer || (scache->peers[i].age < peer->age)) {
|
|
peer = &scache->peers[i];
|
|
}
|
|
}
|
|
DEBUGASSERT(peer);
|
|
if(!peer)
|
|
return CURLE_OK;
|
|
/* clear previous peer and reinit */
|
|
cf_ssl_scache_clear_peer(peer);
|
|
result = cf_ssl_scache_peer_init(peer, ssl_peer_key,
|
|
conn_config->clientcert,
|
|
#ifdef USE_TLS_SRP
|
|
conn_config->username,
|
|
conn_config->password);
|
|
#else
|
|
NULL, NULL);
|
|
#endif
|
|
if(result)
|
|
goto out;
|
|
/* all ready */
|
|
*ppeer = peer;
|
|
result = CURLE_OK;
|
|
|
|
out:
|
|
if(result) {
|
|
cf_ssl_scache_clear_peer(peer);
|
|
CURL_TRC_CF(data, cf, "[SACHE] failure adding peer: %d", result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_scache_peer_add_session(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct Curl_ssl_scache *scache,
|
|
const char *ssl_peer_key,
|
|
struct Curl_ssl_session *s)
|
|
{
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result = CURLE_OUT_OF_MEMORY;
|
|
curl_off_t now = (curl_off_t)time(NULL);
|
|
curl_off_t max_lifetime;
|
|
|
|
if(!scache || !scache->peer_count) {
|
|
Curl_ssl_session_destroy(s);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
if(s->valid_until <= 0)
|
|
s->valid_until = now + scache->default_lifetime_secs;
|
|
|
|
max_lifetime = (s->ietf_tls_id == CURL_IETF_PROTO_TLS1_3) ?
|
|
CURL_SCACHE_MAX_13_LIFETIME_SEC :
|
|
CURL_SCACHE_MAX_12_LIFETIME_SEC;
|
|
if(s->valid_until > (now + max_lifetime))
|
|
s->valid_until = now + max_lifetime;
|
|
|
|
if(cf_scache_session_expired(s, now)) {
|
|
CURL_TRC_CF(data, cf, "[SCACHE] add, session already expired");
|
|
Curl_ssl_session_destroy(s);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer);
|
|
if(result || !peer) {
|
|
CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result);
|
|
Curl_ssl_session_destroy(s);
|
|
goto out;
|
|
}
|
|
|
|
/* A session not from TLSv1.3 replaces all other. */
|
|
if(s->ietf_tls_id != CURL_IETF_PROTO_TLS1_3) {
|
|
Curl_llist_destroy(&peer->sessions, NULL);
|
|
Curl_llist_append(&peer->sessions, s, &s->list);
|
|
}
|
|
else {
|
|
/* Expire existing, append, trim from head to obey max_sessions */
|
|
cf_scache_peer_remove_expired(peer, now);
|
|
cf_scache_peer_remove_non13(peer);
|
|
Curl_llist_append(&peer->sessions, s, &s->list);
|
|
while(Curl_llist_count(&peer->sessions) > peer->max_sessions) {
|
|
Curl_node_remove(Curl_llist_head(&peer->sessions));
|
|
}
|
|
}
|
|
|
|
out:
|
|
if(result) {
|
|
failf(data, "[SCACHE] failed to add session for %s, error=%d",
|
|
ssl_peer_key, result);
|
|
}
|
|
else
|
|
CURL_TRC_CF(data, cf, "[SCACHE] added session for %s [proto=0x%x, "
|
|
"valid_secs=%" FMT_OFF_T ", alpn=%s, earlydata=%zu, "
|
|
"quic_tp=%s], peer has %zu sessions now",
|
|
ssl_peer_key, s->ietf_tls_id, s->valid_until - now, s->alpn,
|
|
s->earlydata_max, s->quic_tp ? "yes" : "no",
|
|
Curl_llist_count(&peer->sessions));
|
|
return result;
|
|
}
|
|
|
|
CURLcode Curl_ssl_scache_put(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const char *ssl_peer_key,
|
|
struct Curl_ssl_session *s)
|
|
{
|
|
struct Curl_ssl_scache *scache = data->state.ssl_scache;
|
|
CURLcode result;
|
|
|
|
Curl_ssl_scache_lock(data);
|
|
result = cf_scache_peer_add_session(cf, data, scache, ssl_peer_key, s);
|
|
Curl_ssl_scache_unlock(data);
|
|
return result;
|
|
}
|
|
|
|
void Curl_ssl_scache_return(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const char *ssl_peer_key,
|
|
struct Curl_ssl_session *s)
|
|
{
|
|
/* See RFC 8446 C.4:
|
|
* "Clients SHOULD NOT reuse a ticket for multiple connections." */
|
|
if(s && s->ietf_tls_id < 0x304)
|
|
(void)Curl_ssl_scache_put(cf, data, ssl_peer_key, s);
|
|
else
|
|
Curl_ssl_session_destroy(s);
|
|
}
|
|
|
|
CURLcode Curl_ssl_scache_take(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const char *ssl_peer_key,
|
|
struct Curl_ssl_session **ps)
|
|
{
|
|
struct Curl_ssl_scache *scache = data->state.ssl_scache;
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
struct Curl_llist_node *n;
|
|
struct Curl_ssl_session *s = NULL;
|
|
CURLcode result;
|
|
|
|
*ps = NULL;
|
|
if(!scache)
|
|
return CURLE_OK;
|
|
|
|
Curl_ssl_scache_lock(data);
|
|
result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
|
|
if(!result && peer) {
|
|
cf_scache_peer_remove_expired(peer, (curl_off_t)time(NULL));
|
|
n = Curl_llist_head(&peer->sessions);
|
|
if(n) {
|
|
s = Curl_node_take_elem(n);
|
|
(scache->age)++; /* increase general age */
|
|
peer->age = scache->age; /* set this as used in this age */
|
|
}
|
|
}
|
|
Curl_ssl_scache_unlock(data);
|
|
if(s) {
|
|
*ps = s;
|
|
CURL_TRC_CF(data, cf, "[SCACHE] took session for %s [proto=0x%x, "
|
|
"alpn=%s, earlydata=%zu, quic_tp=%s], %zu sessions remain",
|
|
ssl_peer_key, s->ietf_tls_id, s->alpn,
|
|
s->earlydata_max, s->quic_tp ? "yes" : "no",
|
|
Curl_llist_count(&peer->sessions));
|
|
}
|
|
else {
|
|
CURL_TRC_CF(data, cf, "[SCACHE] no cached session for %s", ssl_peer_key);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CURLcode Curl_ssl_scache_add_obj(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const char *ssl_peer_key,
|
|
void *sobj,
|
|
Curl_ssl_scache_obj_dtor *sobj_free)
|
|
{
|
|
struct Curl_ssl_scache *scache = data->state.ssl_scache;
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result;
|
|
|
|
DEBUGASSERT(sobj);
|
|
DEBUGASSERT(sobj_free);
|
|
|
|
result = cf_ssl_add_peer(cf, data, scache, ssl_peer_key, &peer);
|
|
if(result || !peer) {
|
|
CURL_TRC_CF(data, cf, "[SCACHE] unable to add scache peer: %d", result);
|
|
goto out;
|
|
}
|
|
|
|
cf_ssl_scache_peer_set_obj(peer, sobj, sobj_free);
|
|
sobj = NULL; /* peer took ownership */
|
|
|
|
out:
|
|
if(sobj && sobj_free)
|
|
sobj_free(sobj);
|
|
return result;
|
|
}
|
|
|
|
bool Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const char *ssl_peer_key,
|
|
void **sobj)
|
|
{
|
|
struct Curl_ssl_scache *scache = data->state.ssl_scache;
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result;
|
|
|
|
*sobj = NULL;
|
|
if(!scache)
|
|
return FALSE;
|
|
|
|
result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
|
|
if(result)
|
|
return FALSE;
|
|
|
|
if(peer)
|
|
*sobj = peer->sobj;
|
|
|
|
CURL_TRC_CF(data, cf, "[SACHE] %s cached session for '%s'",
|
|
*sobj ? "Found" : "No", ssl_peer_key);
|
|
return !!*sobj;
|
|
}
|
|
|
|
void Curl_ssl_scache_remove_all(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const char *ssl_peer_key)
|
|
{
|
|
struct Curl_ssl_scache *scache = data->state.ssl_scache;
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result;
|
|
|
|
(void)cf;
|
|
if(!scache)
|
|
return;
|
|
|
|
Curl_ssl_scache_lock(data);
|
|
result = cf_ssl_find_peer(cf, data, scache, ssl_peer_key, &peer);
|
|
if(!result && peer)
|
|
cf_ssl_scache_clear_peer(peer);
|
|
Curl_ssl_scache_unlock(data);
|
|
}
|
|
|
|
#endif /* USE_SSL */
|