1241 lines
35 KiB
C
1241 lines
35 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
|
|
*
|
|
***************************************************************************/
|
|
|
|
#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 "vtls_spack.h"
|
|
|
|
#include "strcase.h"
|
|
#include "url.h"
|
|
#include "llist.h"
|
|
#include "share.h"
|
|
#include "curl_trc.h"
|
|
#include "curl_sha256.h"
|
|
#include "rand.h"
|
|
#include "warnless.h"
|
|
#include "curl_printf.h"
|
|
#include "strdup.h"
|
|
|
|
/* The last #include files should be: */
|
|
#include "curl_memory.h"
|
|
#include "memdebug.h"
|
|
|
|
|
|
static bool cf_ssl_peer_key_is_global(const char *peer_key);
|
|
|
|
/* 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 */
|
|
BIT(exportable); /* sessions for this peer can be exported */
|
|
};
|
|
|
|
#define CURL_SCACHE_MAGIC 0x000e1551
|
|
|
|
#define GOOD_SCACHE(x) ((x) && (x)->magic == CURL_SCACHE_MAGIC)
|
|
|
|
struct Curl_ssl_scache {
|
|
unsigned int magic;
|
|
struct Curl_ssl_scache_peer *peers;
|
|
size_t peer_count;
|
|
int default_lifetime_secs;
|
|
long age;
|
|
};
|
|
|
|
static struct Curl_ssl_scache *cf_ssl_scache_get(struct Curl_easy *data)
|
|
{
|
|
struct Curl_ssl_scache *scache = NULL;
|
|
/* If a share is present, its ssl_scache has preference over the multi */
|
|
if(data->share && data->share->ssl_scache)
|
|
scache = data->share->ssl_scache;
|
|
else if(data->multi && data->multi->ssl_scache)
|
|
scache = data->multi->ssl_scache;
|
|
if(scache && !GOOD_SCACHE(scache)) {
|
|
failf(data, "transfer would use an invalid scache at %p, denied",
|
|
(void *)scache);
|
|
DEBUGASSERT(0);
|
|
return NULL;
|
|
}
|
|
return scache;
|
|
}
|
|
|
|
static void cf_ssl_scache_sesssion_ldestroy(void *udata, void *obj)
|
|
{
|
|
struct Curl_ssl_session *s = obj;
|
|
(void)udata;
|
|
free(CURL_UNCONST(s->sdata));
|
|
free(CURL_UNCONST(s->quic_tp));
|
|
free((void *)s->alpn);
|
|
free(s);
|
|
}
|
|
|
|
CURLcode
|
|
Curl_ssl_session_create(void *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(void *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 void cf_ssl_cache_peer_update(struct Curl_ssl_scache_peer *peer)
|
|
{
|
|
/* The sessions of this peer are exportable if
|
|
* - it has no confidential information
|
|
* - its peer key is not yet known, because sessions were
|
|
* imported using only the salt+hmac
|
|
* - the peer key is global, e.g. carrying no relative paths */
|
|
peer->exportable = (!peer->clientcert && !peer->srp_username &&
|
|
!peer->srp_password &&
|
|
(!peer->ssl_peer_key ||
|
|
cf_ssl_peer_key_is_global(peer->ssl_peer_key)));
|
|
}
|
|
|
|
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,
|
|
const unsigned char *salt,
|
|
const unsigned char *hmac)
|
|
{
|
|
CURLcode result = CURLE_OUT_OF_MEMORY;
|
|
|
|
DEBUGASSERT(!peer->ssl_peer_key);
|
|
if(ssl_peer_key) {
|
|
peer->ssl_peer_key = strdup(ssl_peer_key);
|
|
if(!peer->ssl_peer_key)
|
|
goto out;
|
|
peer->hmac_set = FALSE;
|
|
}
|
|
else if(salt && hmac) {
|
|
memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
|
|
memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
|
|
peer->hmac_set = TRUE;
|
|
}
|
|
else {
|
|
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
|
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;
|
|
}
|
|
|
|
cf_ssl_cache_peer_update(peer);
|
|
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->magic = CURL_SCACHE_MAGIC;
|
|
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 && GOOD_SCACHE(scache)) {
|
|
size_t i;
|
|
scache->magic = 0;
|
|
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,
|
|
bool *is_local)
|
|
{
|
|
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 UNDER_CE
|
|
(void)is_local;
|
|
return Curl_dyn_addf(buf, ":%s-%s", name, path);
|
|
#elif defined(_WIN32)
|
|
char abspath[_MAX_PATH];
|
|
if(_fullpath(abspath, path, _MAX_PATH))
|
|
return Curl_dyn_addf(buf, ":%s-%s", name, abspath);
|
|
*is_local = TRUE;
|
|
#elif defined(HAVE_REALPATH)
|
|
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;
|
|
}
|
|
*is_local = TRUE;
|
|
}
|
|
#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;
|
|
}
|
|
|
|
#define CURL_SSLS_LOCAL_SUFFIX ":L"
|
|
#define CURL_SSLS_GLOBAL_SUFFIX ":G"
|
|
|
|
static bool cf_ssl_peer_key_is_global(const char *peer_key)
|
|
{
|
|
size_t len = peer_key ? strlen(peer_key) : 0;
|
|
return (len > 2) &&
|
|
(peer_key[len - 1] == 'G') &&
|
|
(peer_key[len - 2] == ':');
|
|
}
|
|
|
|
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;
|
|
bool is_local = FALSE;
|
|
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, &is_local);
|
|
if(r)
|
|
goto out;
|
|
r = cf_ssl_peer_key_add_path(&buf, "CApath", ssl->CApath, &is_local);
|
|
if(r)
|
|
goto out;
|
|
r = cf_ssl_peer_key_add_path(&buf, "CRL", ssl->CRLfile, &is_local);
|
|
if(r)
|
|
goto out;
|
|
r = cf_ssl_peer_key_add_path(&buf, "Issuer", ssl->issuercert, &is_local);
|
|
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;
|
|
|
|
r = Curl_dyn_addf(&buf, is_local ?
|
|
CURL_SSLS_LOCAL_SUFFIX : CURL_SSLS_GLOBAL_SUFFIX);
|
|
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(!conn_config) {
|
|
if(peer->clientcert)
|
|
return FALSE;
|
|
#ifdef USE_TLS_SRP
|
|
if(peer->srp_username || peer->srp_password)
|
|
return FALSE;
|
|
#endif
|
|
return TRUE;
|
|
}
|
|
else 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_by_key(struct Curl_easy *data,
|
|
struct Curl_ssl_scache *scache,
|
|
const char *ssl_peer_key,
|
|
struct ssl_primary_config *conn_config,
|
|
struct Curl_ssl_scache_peer **ppeer)
|
|
{
|
|
size_t i, peer_key_len = 0;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
*ppeer = NULL;
|
|
if(!GOOD_SCACHE(scache)) {
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
CURL_TRC_SSLS(data, "find peer slot for %s among %zu slots",
|
|
ssl_peer_key, scache->peer_count);
|
|
|
|
/* 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 */
|
|
CURL_TRC_SSLS(data, "peer entry %zu key recovered: %s",
|
|
i, ssl_peer_key);
|
|
scache->peers[i].ssl_peer_key = strdup(ssl_peer_key);
|
|
if(!scache->peers[i].ssl_peer_key) {
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
goto out;
|
|
}
|
|
cf_ssl_cache_peer_update(&scache->peers[i]);
|
|
*ppeer = &scache->peers[i];
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
CURL_TRC_SSLS(data, "peer not found for %s", ssl_peer_key);
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
static struct Curl_ssl_scache_peer *
|
|
cf_ssl_get_free_peer(struct Curl_ssl_scache *scache)
|
|
{
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
size_t i;
|
|
|
|
/* 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)
|
|
cf_ssl_scache_clear_peer(peer);
|
|
return peer;
|
|
}
|
|
|
|
static CURLcode
|
|
cf_ssl_add_peer(struct Curl_easy *data,
|
|
struct Curl_ssl_scache *scache,
|
|
const char *ssl_peer_key,
|
|
struct ssl_primary_config *conn_config,
|
|
struct Curl_ssl_scache_peer **ppeer)
|
|
{
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
*ppeer = NULL;
|
|
if(ssl_peer_key) {
|
|
result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
|
|
&peer);
|
|
if(result || !scache->peer_count)
|
|
return result;
|
|
}
|
|
|
|
if(peer) {
|
|
*ppeer = peer;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
peer = cf_ssl_get_free_peer(scache);
|
|
if(peer) {
|
|
const char *ccert = conn_config ? conn_config->clientcert : NULL;
|
|
const char *username = NULL, *password = NULL;
|
|
#ifdef USE_TLS_SRP
|
|
username = conn_config ? conn_config->username : NULL;
|
|
password = conn_config ? conn_config->password : NULL;
|
|
#endif
|
|
result = cf_ssl_scache_peer_init(peer, ssl_peer_key, ccert,
|
|
username, password, NULL, NULL);
|
|
if(result)
|
|
goto out;
|
|
/* all ready */
|
|
*ppeer = peer;
|
|
result = CURLE_OK;
|
|
}
|
|
|
|
out:
|
|
if(result) {
|
|
cf_ssl_scache_clear_peer(peer);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void cf_scache_peer_add_session(struct Curl_ssl_scache_peer *peer,
|
|
struct Curl_ssl_session *s,
|
|
curl_off_t now)
|
|
{
|
|
/* 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
static CURLcode cf_scache_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;
|
|
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
|
|
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_SSLS(data, "add, session already expired");
|
|
Curl_ssl_session_destroy(s);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
|
|
if(result || !peer) {
|
|
CURL_TRC_SSLS(data, "unable to add scache peer: %d", result);
|
|
Curl_ssl_session_destroy(s);
|
|
goto out;
|
|
}
|
|
|
|
cf_scache_peer_add_session(peer, s, now);
|
|
|
|
out:
|
|
if(result) {
|
|
failf(data, "[SCACHE] failed to add session for %s, error=%d",
|
|
ssl_peer_key, result);
|
|
}
|
|
else
|
|
CURL_TRC_SSLS(data, "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",
|
|
peer ? Curl_llist_count(&peer->sessions) : 0);
|
|
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 = cf_ssl_scache_get(data);
|
|
struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
|
|
CURLcode result;
|
|
DEBUGASSERT(ssl_config);
|
|
|
|
if(!scache || !ssl_config->primary.cache_session) {
|
|
Curl_ssl_session_destroy(s);
|
|
return CURLE_OK;
|
|
}
|
|
if(!GOOD_SCACHE(scache)) {
|
|
Curl_ssl_session_destroy(s);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
Curl_ssl_scache_lock(data);
|
|
result = cf_scache_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 = cf_ssl_scache_get(data);
|
|
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
|
|
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_by_key(data, scache, ssl_peer_key, conn_config,
|
|
&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_SSLS(data, "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_SSLS(data, "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 = cf_ssl_scache_get(data);
|
|
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result;
|
|
|
|
DEBUGASSERT(sobj);
|
|
DEBUGASSERT(sobj_free);
|
|
|
|
if(!scache) {
|
|
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
|
goto out;
|
|
}
|
|
|
|
result = cf_ssl_add_peer(data, scache, ssl_peer_key, conn_config, &peer);
|
|
if(result || !peer) {
|
|
CURL_TRC_SSLS(data, "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;
|
|
}
|
|
|
|
void *Curl_ssl_scache_get_obj(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
const char *ssl_peer_key)
|
|
{
|
|
struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
|
|
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result;
|
|
void *sobj;
|
|
|
|
if(!scache)
|
|
return NULL;
|
|
|
|
result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
|
|
&peer);
|
|
if(result)
|
|
return NULL;
|
|
|
|
sobj = peer ? peer->sobj : NULL;
|
|
|
|
CURL_TRC_SSLS(data, "%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 = cf_ssl_scache_get(data);
|
|
struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
CURLcode result;
|
|
|
|
(void)cf;
|
|
if(!scache)
|
|
return;
|
|
|
|
Curl_ssl_scache_lock(data);
|
|
result = cf_ssl_find_peer_by_key(data, scache, ssl_peer_key, conn_config,
|
|
&peer);
|
|
if(!result && peer)
|
|
cf_ssl_scache_clear_peer(peer);
|
|
Curl_ssl_scache_unlock(data);
|
|
}
|
|
|
|
#ifdef USE_SSLS_EXPORT
|
|
|
|
#define CURL_SSL_TICKET_MAX (16*1024)
|
|
|
|
static CURLcode cf_ssl_scache_peer_set_hmac(struct Curl_ssl_scache_peer *peer)
|
|
{
|
|
CURLcode result;
|
|
|
|
DEBUGASSERT(peer);
|
|
if(!peer->ssl_peer_key)
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
|
|
result = Curl_rand(NULL, peer->key_salt, sizeof(peer->key_salt));
|
|
if(result)
|
|
return result;
|
|
|
|
result = Curl_hmacit(&Curl_HMAC_SHA256,
|
|
peer->key_salt, sizeof(peer->key_salt),
|
|
(const unsigned char *)peer->ssl_peer_key,
|
|
strlen(peer->ssl_peer_key),
|
|
peer->key_hmac);
|
|
if(!result)
|
|
peer->hmac_set = TRUE;
|
|
return result;
|
|
}
|
|
|
|
static CURLcode
|
|
cf_ssl_find_peer_by_hmac(struct Curl_ssl_scache *scache,
|
|
const unsigned char *salt,
|
|
const unsigned char *hmac,
|
|
struct Curl_ssl_scache_peer **ppeer)
|
|
{
|
|
size_t i;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
*ppeer = NULL;
|
|
if(!GOOD_SCACHE(scache))
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
|
|
/* look for an entry that matches salt+hmac exactly or has a known
|
|
* ssl_peer_key which salt+hmac's to the same. */
|
|
for(i = 0; scache && i < scache->peer_count; i++) {
|
|
struct Curl_ssl_scache_peer *peer = &scache->peers[i];
|
|
if(!cf_ssl_scache_match_auth(peer, NULL))
|
|
continue;
|
|
if(scache->peers[i].hmac_set &&
|
|
!memcmp(peer->key_salt, salt, sizeof(peer->key_salt)) &&
|
|
!memcmp(peer->key_hmac, hmac, sizeof(peer->key_hmac))) {
|
|
/* found exact match, return */
|
|
*ppeer = peer;
|
|
goto out;
|
|
}
|
|
else if(peer->ssl_peer_key) {
|
|
unsigned char my_hmac[CURL_SHA256_DIGEST_LENGTH];
|
|
/* compute hmac for the passed salt */
|
|
result = Curl_hmacit(&Curl_HMAC_SHA256,
|
|
salt, sizeof(peer->key_salt),
|
|
(const unsigned char *)peer->ssl_peer_key,
|
|
strlen(peer->ssl_peer_key),
|
|
my_hmac);
|
|
if(result)
|
|
goto out;
|
|
if(!memcmp(my_hmac, hmac, sizeof(my_hmac))) {
|
|
/* cryptohash match, take over salt+hmac if no set and return */
|
|
if(!peer->hmac_set) {
|
|
memcpy(peer->key_salt, salt, sizeof(peer->key_salt));
|
|
memcpy(peer->key_hmac, hmac, sizeof(peer->key_hmac));
|
|
peer->hmac_set = TRUE;
|
|
}
|
|
*ppeer = peer;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
CURLcode Curl_ssl_session_import(struct Curl_easy *data,
|
|
const char *ssl_peer_key,
|
|
const unsigned char *shmac, size_t shmac_len,
|
|
const void *sdata, size_t sdata_len)
|
|
{
|
|
struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
|
|
struct Curl_ssl_scache_peer *peer = NULL;
|
|
struct Curl_ssl_session *s = NULL;
|
|
bool locked = FALSE;
|
|
CURLcode r;
|
|
|
|
if(!scache) {
|
|
r = CURLE_BAD_FUNCTION_ARGUMENT;
|
|
goto out;
|
|
}
|
|
if(!ssl_peer_key && (!shmac || !shmac_len)) {
|
|
r = CURLE_BAD_FUNCTION_ARGUMENT;
|
|
goto out;
|
|
}
|
|
|
|
r = Curl_ssl_session_unpack(data, sdata, sdata_len, &s);
|
|
if(r)
|
|
goto out;
|
|
|
|
Curl_ssl_scache_lock(data);
|
|
locked = TRUE;
|
|
|
|
if(ssl_peer_key) {
|
|
r = cf_ssl_add_peer(data, scache, ssl_peer_key, NULL, &peer);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
else if(shmac_len != (sizeof(peer->key_salt) + sizeof(peer->key_hmac))) {
|
|
/* Either salt+hmac was garbled by caller or is from a curl version
|
|
* that does things differently */
|
|
r = CURLE_BAD_FUNCTION_ARGUMENT;
|
|
goto out;
|
|
}
|
|
else {
|
|
const unsigned char *salt = shmac;
|
|
const unsigned char *hmac = shmac + sizeof(peer->key_salt);
|
|
|
|
r = cf_ssl_find_peer_by_hmac(scache, salt, hmac, &peer);
|
|
if(r)
|
|
goto out;
|
|
if(!peer) {
|
|
peer = cf_ssl_get_free_peer(scache);
|
|
if(peer) {
|
|
r = cf_ssl_scache_peer_init(peer, ssl_peer_key, NULL,
|
|
NULL, NULL, salt, hmac);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(peer) {
|
|
cf_scache_peer_add_session(peer, s, time(NULL));
|
|
s = NULL; /* peer is now owner */
|
|
CURL_TRC_SSLS(data, "successfully imported ticket for peer %s, now "
|
|
"with %zu tickets",
|
|
peer->ssl_peer_key ? peer->ssl_peer_key : "without key",
|
|
Curl_llist_count(&peer->sessions));
|
|
}
|
|
|
|
out:
|
|
if(locked)
|
|
Curl_ssl_scache_unlock(data);
|
|
Curl_ssl_session_destroy(s);
|
|
return r;
|
|
}
|
|
|
|
CURLcode Curl_ssl_session_export(struct Curl_easy *data,
|
|
curl_ssls_export_cb *export_fn,
|
|
void *userptr)
|
|
{
|
|
struct Curl_ssl_scache *scache = cf_ssl_scache_get(data);
|
|
struct Curl_ssl_scache_peer *peer;
|
|
struct dynbuf sbuf, hbuf;
|
|
struct Curl_llist_node *n;
|
|
size_t i, npeers = 0, ntickets = 0;
|
|
curl_off_t now = time(NULL);
|
|
CURLcode r = CURLE_OK;
|
|
|
|
if(!export_fn)
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
if(!scache)
|
|
return CURLE_OK;
|
|
|
|
Curl_ssl_scache_lock(data);
|
|
|
|
Curl_dyn_init(&hbuf, (CURL_SHA256_DIGEST_LENGTH * 2) + 1);
|
|
Curl_dyn_init(&sbuf, CURL_SSL_TICKET_MAX);
|
|
|
|
for(i = 0; scache && i < scache->peer_count; i++) {
|
|
peer = &scache->peers[i];
|
|
if(!peer->ssl_peer_key && !peer->hmac_set)
|
|
continue; /* skip free entry */
|
|
if(!peer->exportable)
|
|
continue;
|
|
|
|
Curl_dyn_reset(&hbuf);
|
|
cf_scache_peer_remove_expired(peer, now);
|
|
n = Curl_llist_head(&peer->sessions);
|
|
if(n)
|
|
++npeers;
|
|
while(n) {
|
|
struct Curl_ssl_session *s = Curl_node_elem(n);
|
|
if(!peer->hmac_set) {
|
|
r = cf_ssl_scache_peer_set_hmac(peer);
|
|
if(r)
|
|
goto out;
|
|
}
|
|
if(!Curl_dyn_len(&hbuf)) {
|
|
r = Curl_dyn_addn(&hbuf, peer->key_salt, sizeof(peer->key_salt));
|
|
if(r)
|
|
goto out;
|
|
r = Curl_dyn_addn(&hbuf, peer->key_hmac, sizeof(peer->key_hmac));
|
|
if(r)
|
|
goto out;
|
|
}
|
|
Curl_dyn_reset(&sbuf);
|
|
r = Curl_ssl_session_pack(data, s, &sbuf);
|
|
if(r)
|
|
goto out;
|
|
|
|
r = export_fn(data, userptr, peer->ssl_peer_key,
|
|
Curl_dyn_uptr(&hbuf), Curl_dyn_len(&hbuf),
|
|
Curl_dyn_uptr(&sbuf), Curl_dyn_len(&sbuf),
|
|
s->valid_until, s->ietf_tls_id,
|
|
s->alpn, s->earlydata_max);
|
|
if(r)
|
|
goto out;
|
|
++ntickets;
|
|
n = Curl_node_next(n);
|
|
}
|
|
|
|
}
|
|
r = CURLE_OK;
|
|
CURL_TRC_SSLS(data, "exported %zu session tickets for %zu peers",
|
|
ntickets, npeers);
|
|
|
|
out:
|
|
Curl_ssl_scache_unlock(data);
|
|
Curl_dyn_free(&hbuf);
|
|
Curl_dyn_free(&sbuf);
|
|
return r;
|
|
}
|
|
|
|
#endif /* USE_SSLS_EXPORT */
|
|
|
|
#endif /* USE_SSL */
|