820 lines
25 KiB
C
820 lines
25 KiB
C
/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/queue.h>
|
|
#include <time.h>
|
|
|
|
#ifndef WIN32
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <sys/socket.h>
|
|
#endif
|
|
|
|
#include <openssl/aead.h>
|
|
#include <openssl/hkdf.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/ssl.h>
|
|
|
|
#include "lsquic.h"
|
|
#include "lsquic_int_types.h"
|
|
#include "lsquic_sizes.h"
|
|
#include "lsquic_types.h"
|
|
#include "lsquic_packet_common.h"
|
|
#include "lsquic_packet_in.h"
|
|
#include "lsquic_tokgen.h"
|
|
#include "lsquic_trans_params.h"
|
|
#include "lsquic_util.h"
|
|
#include "lsquic_mm.h"
|
|
#include "lsquic_engine_public.h"
|
|
|
|
#define LSQUIC_LOGGER_MODULE LSQLM_TOKGEN
|
|
#include "lsquic_logger.h"
|
|
|
|
#define STRINGIFY(x) #x
|
|
#define TOSTRING(x) STRINGIFY(x)
|
|
|
|
#define TOKGEN_VERSION 2
|
|
|
|
#define CRYPTER_KEY_SIZE 16
|
|
#define SRST_MAX_PRK_SIZE EVP_MAX_MD_SIZE
|
|
|
|
#define TOKGEN_SHM_KEY "TOKGEN" TOSTRING(TOKGEN_VERSION)
|
|
#define TOKGEN_SHM_KEY_SIZE (sizeof(TOKGEN_SHM_KEY) - 1)
|
|
|
|
#define TOKGEN_SHM_MAGIC_TOP "Feliz"
|
|
#define TOKGEN_SHM_MAGIC_BOTTOM "Navidad"
|
|
|
|
struct tokgen_shm_state
|
|
{
|
|
uint8_t tgss_version;
|
|
uint8_t tgss_magic_top[sizeof(TOKGEN_SHM_MAGIC_TOP) - 1];
|
|
uint8_t tgss_crypter_key[N_TOKEN_TYPES][CRYPTER_KEY_SIZE];
|
|
uint8_t tgss_srst_prk_size;
|
|
uint8_t tgss_srst_prk[SRST_MAX_PRK_SIZE];
|
|
uint8_t tgss_magic_bottom[sizeof(TOKGEN_SHM_MAGIC_BOTTOM) - 1];
|
|
};
|
|
|
|
|
|
/* The various salt values below were obtained by reading from /dev/random
|
|
* when the code was first written.
|
|
*/
|
|
|
|
static const uint64_t salts[N_TOKEN_TYPES] =
|
|
{
|
|
[TOKEN_RETRY] = 0xa49c3ef763a6243f,
|
|
[TOKEN_RESUME] = 0x0b3664549086b8ca,
|
|
};
|
|
|
|
static const uint8_t srst_salt[8] = "\x28\x6e\x81\x02\x40\x5b\x2c\x2b";
|
|
|
|
struct crypter
|
|
{
|
|
EVP_AEAD_CTX ctx;
|
|
unsigned long nonce_counter;
|
|
size_t nonce_prk_sz;
|
|
uint8_t nonce_prk_buf[EVP_MAX_MD_SIZE];
|
|
};
|
|
|
|
|
|
/* Bloom filter of Resume tokens. See below. */
|
|
struct resumed_token_page
|
|
{
|
|
TAILQ_ENTRY(resumed_token_page) next;
|
|
time_t begin, /* Oldest entry */
|
|
end; /* Newest entry */
|
|
unsigned count; /* Number of entries */
|
|
uintptr_t masks[];
|
|
};
|
|
|
|
|
|
struct token_generator
|
|
{
|
|
/* We encrypt different token types using different keys. */
|
|
struct crypter tg_crypters[N_TOKEN_TYPES];
|
|
|
|
/* Stateless reset token is generated using HKDF with CID as the
|
|
* `info' parameter to HKDF-Expand.
|
|
*/
|
|
size_t tg_srst_prk_sz;
|
|
uint8_t tg_srst_prk_buf[SRST_MAX_PRK_SIZE];
|
|
unsigned tg_retry_token_duration;
|
|
TAILQ_HEAD(resumed_token_pages_head, resumed_token_page)
|
|
tg_resume_token_pages;
|
|
};
|
|
|
|
|
|
static int
|
|
setup_nonce_prk (unsigned char *nonce_prk_buf, size_t *nonce_prk_sz,
|
|
unsigned i, time_t now)
|
|
{
|
|
struct {
|
|
time_t now;
|
|
enum token_type tt;
|
|
uint8_t buf[16];
|
|
} ikm;
|
|
|
|
ikm.now = now;
|
|
ikm.tt = i;
|
|
RAND_bytes(ikm.buf, sizeof(ikm.buf));
|
|
if (HKDF_extract(nonce_prk_buf, nonce_prk_sz,
|
|
EVP_sha256(), (uint8_t *) &ikm, sizeof(ikm),
|
|
(void *) &salts[i], sizeof(salts[i])))
|
|
return 0;
|
|
else
|
|
{
|
|
LSQ_ERROR("HKDF_extract failed");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
get_or_generate_state (struct lsquic_engine_public *enpub, time_t now,
|
|
struct tokgen_shm_state *shm_state)
|
|
{
|
|
const struct lsquic_shared_hash_if *const shi = enpub->enp_shi;
|
|
void *const ctx = enpub->enp_shi_ctx;
|
|
void *data, *copy;
|
|
char key_copy[TOKGEN_SHM_KEY_SIZE];
|
|
int s;
|
|
unsigned sz;
|
|
size_t bufsz;
|
|
struct {
|
|
time_t now;
|
|
unsigned char buf[24];
|
|
}
|
|
#if __GNUC__
|
|
/* This is more of a documentation note: this struct should already
|
|
* have a multiple-of-eight size.
|
|
*/
|
|
__attribute__((packed))
|
|
#endif
|
|
srst_ikm;
|
|
|
|
data = shm_state;
|
|
sz = sizeof(*shm_state);
|
|
s = shi->shi_lookup(ctx, TOKGEN_SHM_KEY, TOKGEN_SHM_KEY_SIZE, &data, &sz);
|
|
|
|
if (s == 1)
|
|
{
|
|
if (sz != sizeof(*shm_state))
|
|
{
|
|
LSQ_WARN("found SHM data has non-matching size %u", sz);
|
|
return -1;
|
|
}
|
|
if (data != (void *) shm_state)
|
|
memcpy(shm_state, data, sizeof(*shm_state));
|
|
if (shm_state->tgss_version != TOKGEN_VERSION)
|
|
{
|
|
LSQ_DEBUG("found SHM data has non-matching version %u",
|
|
shm_state->tgss_version);
|
|
return -1;
|
|
}
|
|
LSQ_DEBUG("found SHM data: size %u; version %u", sz,
|
|
shm_state->tgss_version);
|
|
return 0;
|
|
}
|
|
|
|
if (s != 0)
|
|
{
|
|
if (s != -1)
|
|
LSQ_WARN("SHM lookup returned unexpected value %d", s);
|
|
LSQ_DEBUG("SHM lookup returned an error: generate");
|
|
goto generate;
|
|
}
|
|
|
|
assert(s == 0);
|
|
LSQ_DEBUG("%s does not exist: generate", TOKGEN_SHM_KEY);
|
|
generate:
|
|
now = time(NULL);
|
|
memset(shm_state, 0, sizeof(*shm_state));
|
|
shm_state->tgss_version = TOKGEN_VERSION;
|
|
memcpy(shm_state->tgss_magic_top, TOKGEN_SHM_MAGIC_TOP,
|
|
sizeof(TOKGEN_SHM_MAGIC_TOP) - 1);
|
|
if (getenv("LSQUIC_NULL_TOKGEN"))
|
|
{
|
|
LSQ_NOTICE("using NULL tokgen");
|
|
memset(shm_state->tgss_crypter_key, 0,
|
|
sizeof(shm_state->tgss_crypter_key));
|
|
memset(&srst_ikm, 0, sizeof(srst_ikm));
|
|
}
|
|
else
|
|
{
|
|
RAND_bytes((void *) shm_state->tgss_crypter_key,
|
|
sizeof(shm_state->tgss_crypter_key));
|
|
srst_ikm.now = now;
|
|
RAND_bytes(srst_ikm.buf, sizeof(srst_ikm.buf));
|
|
}
|
|
if (!HKDF_extract(shm_state->tgss_srst_prk, &bufsz,
|
|
EVP_sha256(), (uint8_t *) &srst_ikm, sizeof(srst_ikm),
|
|
srst_salt, sizeof(srst_salt)))
|
|
{
|
|
LSQ_ERROR("HKDF_extract failed");
|
|
return -1;
|
|
}
|
|
shm_state->tgss_srst_prk_size = (uint8_t) bufsz;
|
|
memcpy(shm_state->tgss_magic_bottom, TOKGEN_SHM_MAGIC_BOTTOM,
|
|
sizeof(TOKGEN_SHM_MAGIC_BOTTOM) - 1);
|
|
|
|
data = shm_state;
|
|
memcpy(key_copy, TOKGEN_SHM_KEY, TOKGEN_SHM_KEY_SIZE);
|
|
s = shi->shi_insert(ctx, key_copy, TOKGEN_SHM_KEY_SIZE, data,
|
|
sizeof(*shm_state), 0);
|
|
if (s != 0)
|
|
{
|
|
LSQ_ERROR("cannot insert into SHM");
|
|
return -1;
|
|
}
|
|
sz = sizeof(*shm_state);
|
|
s = shi->shi_lookup(ctx, TOKGEN_SHM_KEY, TOKGEN_SHM_KEY_SIZE, ©, &sz);
|
|
if (s != 1 || sz != sizeof(*shm_state))
|
|
{
|
|
LSQ_ERROR("cannot lookup after insert: s=%d; sz=%u", s, sz);
|
|
return -1;
|
|
}
|
|
if (copy != data)
|
|
memcpy(shm_state, copy, sizeof(*shm_state));
|
|
LSQ_INFO("inserted %s of size %u", TOKGEN_SHM_KEY, sz);
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct token_generator *
|
|
lsquic_tg_new (struct lsquic_engine_public *enpub)
|
|
{
|
|
struct token_generator *tokgen;
|
|
time_t now;
|
|
struct tokgen_shm_state shm_state;
|
|
|
|
tokgen = calloc(1, sizeof(*tokgen));
|
|
if (!tokgen)
|
|
goto err;
|
|
|
|
now = time(NULL);
|
|
if (0 != get_or_generate_state(enpub, now, &shm_state))
|
|
goto err;
|
|
|
|
TAILQ_INIT(&tokgen->tg_resume_token_pages);
|
|
unsigned i;
|
|
for (i = 0; i < sizeof(tokgen->tg_crypters)
|
|
/ sizeof(tokgen->tg_crypters[0]); ++i)
|
|
{
|
|
struct crypter *crypter;
|
|
crypter = tokgen->tg_crypters + i;
|
|
if (0 != setup_nonce_prk(crypter->nonce_prk_buf,
|
|
&crypter->nonce_prk_sz, i, now))
|
|
goto err;
|
|
if (1 != EVP_AEAD_CTX_init(&crypter->ctx, EVP_aead_aes_128_gcm(),
|
|
shm_state.tgss_crypter_key[i],
|
|
sizeof(shm_state.tgss_crypter_key[i]), RETRY_TAG_LEN, 0))
|
|
goto err;
|
|
}
|
|
|
|
tokgen->tg_retry_token_duration
|
|
= enpub->enp_settings.es_retry_token_duration;
|
|
if (tokgen->tg_retry_token_duration == 0)
|
|
tokgen->tg_retry_token_duration = LSQUIC_DF_RETRY_TOKEN_DURATION;
|
|
|
|
tokgen->tg_srst_prk_sz = shm_state.tgss_srst_prk_size;
|
|
if (tokgen->tg_srst_prk_sz > sizeof(tokgen->tg_srst_prk_buf))
|
|
{
|
|
LSQ_WARN("bad stateless reset key size");
|
|
goto err;
|
|
}
|
|
memcpy(tokgen->tg_srst_prk_buf, shm_state.tgss_srst_prk,
|
|
tokgen->tg_srst_prk_sz);
|
|
|
|
LSQ_DEBUG("initialized");
|
|
return tokgen;
|
|
|
|
err:
|
|
LSQ_ERROR("error initializing");
|
|
free(tokgen);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
lsquic_tg_destroy (struct token_generator *tokgen)
|
|
{
|
|
struct resumed_token_page *page;
|
|
struct crypter *crypter;
|
|
unsigned i;
|
|
|
|
while ((page = TAILQ_FIRST(&tokgen->tg_resume_token_pages)))
|
|
{
|
|
TAILQ_REMOVE(&tokgen->tg_resume_token_pages, page, next);
|
|
free(page);
|
|
}
|
|
for (i = 0; i < sizeof(tokgen->tg_crypters)
|
|
/ sizeof(tokgen->tg_crypters[0]); ++i)
|
|
{
|
|
crypter = tokgen->tg_crypters + i;
|
|
EVP_AEAD_CTX_cleanup(&crypter->ctx);
|
|
}
|
|
free(tokgen);
|
|
LSQ_DEBUG("destroyed");
|
|
}
|
|
|
|
|
|
/* To limit reuse of Resume tokens, used Resume tokens are inserted into a
|
|
* list of Bloom filters with very low false positive rate. Before a Resume
|
|
* token is used, we check in the Bloom filter. If this token has already
|
|
* been used, it fails validation.
|
|
*
|
|
* There are three ways when this check will fail:
|
|
* 1. Bloom filter false positive. In this case, Resume token fails
|
|
* validation, which may cause the server may issue a Retry. This
|
|
* should happen very infrequently (see below).
|
|
* 2. Server restart. Because the Bloom filter is stored in process
|
|
* memory, this will result in false negative and a Resume token can be
|
|
* reused.
|
|
* 3. Different working process. Similar to (2).
|
|
*
|
|
* Bloom filters are on a linked list. Each filter is used up to MAX_PER_PAGE
|
|
* values or RESUME_MAX_SECS seconds, after which a new Bloom filter is inserted.
|
|
* Bloom filters are removed once the most recent element is older than
|
|
* RESUME_MAX_HOURS hours.
|
|
*/
|
|
|
|
#define RESUME_MAX_SECS (24 * 3600)
|
|
|
|
#define N_BLOOM_FUNCS 10
|
|
|
|
/* We need 30 bytes to generate 10 24-bit Bloom filter values */
|
|
typedef char enough_blooms[MIN_RESUME_TOKEN_LEN >= 3 * N_BLOOM_FUNCS ? 1 : -1];
|
|
|
|
typedef uint32_t bloom_vals_t[N_BLOOM_FUNCS];
|
|
|
|
#define RESUME_TOKEN_PAGE_SIZE (1u << 21)
|
|
|
|
/* For memory efficiency, we allocate 2MB chunks of memory,
|
|
* not 2MB + 28 bytes. Thus, we can't use the whole 24-bit range.
|
|
*/
|
|
#define MAX_BLOOM_VALUE ((RESUME_TOKEN_PAGE_SIZE - \
|
|
sizeof(struct resumed_token_page)) * 8 - 1)
|
|
|
|
#define MAX_PER_PAGE 500000
|
|
/* This works out to 0.00012924% false positive rate:
|
|
* perl -E '$k=10;$m=1<<24;$n=500000;printf("%.10lf",(1-exp(1)**-($k*$n/$m))**$k)'
|
|
*/
|
|
|
|
static int
|
|
tokgen_seen_resumed_token (struct token_generator *tokgen,
|
|
const unsigned char *token, size_t token_sz, bloom_vals_t bloom_vals)
|
|
{
|
|
const struct resumed_token_page *page;
|
|
unsigned n, idx;
|
|
uintptr_t slot;
|
|
|
|
if (1 + N_BLOOM_FUNCS * 3 > token_sz)
|
|
return 0;
|
|
|
|
++token;
|
|
for (n = 0; n < N_BLOOM_FUNCS; ++n)
|
|
{
|
|
bloom_vals[n] = *token++;
|
|
bloom_vals[n] |= *token++ << 8;
|
|
bloom_vals[n] |= *token++ << 16;
|
|
if (bloom_vals[n] > MAX_BLOOM_VALUE)
|
|
bloom_vals[n] = MAX_BLOOM_VALUE;
|
|
}
|
|
|
|
page = TAILQ_FIRST(&tokgen->tg_resume_token_pages);
|
|
while (page)
|
|
{
|
|
for (n = 0; n < N_BLOOM_FUNCS; ++n)
|
|
{
|
|
idx = bloom_vals[n] / (sizeof(page->masks[0]) * 8);
|
|
slot = 1;
|
|
slot <<= bloom_vals[n] % (sizeof(page->masks[0]) * 8);
|
|
if (!(page->masks[idx] & slot))
|
|
goto next_page;
|
|
}
|
|
return 1;
|
|
next_page:
|
|
page = TAILQ_NEXT(page, next);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void /* void: if it fails, there is nothing to do */
|
|
tokgen_record_resumed_token (struct token_generator *tokgen, time_t now,
|
|
bloom_vals_t bloom_vals)
|
|
{
|
|
struct resumed_token_page *page;
|
|
unsigned n, idx;
|
|
uintptr_t slot;
|
|
|
|
/* Expunge old pages at insertion time only to save on time() syscall */
|
|
while ((page = TAILQ_FIRST(&tokgen->tg_resume_token_pages)))
|
|
if (page->end + RESUME_MAX_SECS < now)
|
|
{
|
|
LSQ_DEBUG("drop resumed cache page");
|
|
TAILQ_REMOVE(&tokgen->tg_resume_token_pages, page, next);
|
|
free(page);
|
|
}
|
|
else
|
|
break;
|
|
|
|
page = TAILQ_LAST(&tokgen->tg_resume_token_pages, resumed_token_pages_head);
|
|
if (!(page && page->count < MAX_PER_PAGE && now
|
|
< page->begin + RESUME_MAX_SECS))
|
|
{
|
|
page = calloc(1, RESUME_TOKEN_PAGE_SIZE);
|
|
if (!page)
|
|
{
|
|
LSQ_WARN("cannot allocate resumed cache page");
|
|
return;
|
|
}
|
|
LSQ_DEBUG("allocate resumed cache page");
|
|
TAILQ_INSERT_TAIL(&tokgen->tg_resume_token_pages, page, next);
|
|
page->begin = now;
|
|
page->end = now;
|
|
page->count = 0;
|
|
}
|
|
|
|
page->end = now;
|
|
++page->count;
|
|
|
|
for (n = 0; n < N_BLOOM_FUNCS; ++n)
|
|
{
|
|
idx = bloom_vals[n] / (sizeof(page->masks[0]) * 8);
|
|
slot = 1;
|
|
slot <<= bloom_vals[n] % (sizeof(page->masks[0]) * 8);
|
|
page->masks[idx] |= slot;
|
|
}
|
|
}
|
|
|
|
|
|
static const char *const tt2str[N_TOKEN_TYPES] = {
|
|
[TOKEN_RESUME] = "resume",
|
|
[TOKEN_RETRY] = "retry",
|
|
};
|
|
|
|
|
|
int
|
|
lsquic_tg_validate_token (struct token_generator *tokgen,
|
|
const struct lsquic_packet_in *packet_in, const struct sockaddr *sa_peer,
|
|
lsquic_cid_t *odcid)
|
|
{
|
|
size_t decr_token_len, encr_token_len, ad_len;
|
|
const unsigned char *nonce, *encr_token, *p, *end, *ad;
|
|
struct crypter *crypter;
|
|
enum token_type token_type;
|
|
time_t issued_at, ttl, now;
|
|
int is_ipv6;
|
|
unsigned version;
|
|
bloom_vals_t bloom_vals;
|
|
unsigned char decr_token[MAX_RETRY_TOKEN_LEN - RETRY_TAG_LEN
|
|
- RETRY_NONCE_LEN];
|
|
char token_str[MAX_RETRY_TOKEN_LEN * 2 + 1];
|
|
char addr_str[2][INET6_ADDRSTRLEN];
|
|
|
|
if (!(packet_in->pi_token && packet_in->pi_token_size))
|
|
{
|
|
LSQ_DEBUGC("packet for connection %"CID_FMT" has no token: "
|
|
"validation failed", CID_BITS(&packet_in->pi_dcid));
|
|
return -1;
|
|
}
|
|
|
|
if (packet_in->pi_token_size < RETRY_TAG_LEN + RETRY_NONCE_LEN)
|
|
{
|
|
LSQ_DEBUGC("packet for connection %"CID_FMT" has too-short token "
|
|
"(%hu bytes): validation failed", CID_BITS(&packet_in->pi_dcid),
|
|
packet_in->pi_token_size);
|
|
return -1;
|
|
}
|
|
|
|
token_type = packet_in->pi_data[packet_in->pi_token];
|
|
switch (token_type)
|
|
{
|
|
case TOKEN_RETRY:
|
|
ttl = tokgen->tg_retry_token_duration;
|
|
ad = packet_in->pi_dcid.idbuf;
|
|
ad_len = packet_in->pi_dcid.len;
|
|
break;
|
|
case TOKEN_RESUME:
|
|
if (tokgen_seen_resumed_token(tokgen, packet_in->pi_data
|
|
+ packet_in->pi_token, packet_in->pi_token_size, bloom_vals))
|
|
{
|
|
LSQ_DEBUGC("%s token for connection %"CID_FMT" has already "
|
|
"been used: validation failed", tt2str[token_type],
|
|
CID_BITS(&packet_in->pi_dcid));
|
|
return -1;
|
|
}
|
|
ttl = RESUME_MAX_SECS;
|
|
ad = NULL;
|
|
ad_len = 0;
|
|
break;
|
|
default:
|
|
LSQ_DEBUGC("packet for connection %"CID_FMT" has unknown token "
|
|
"type (%u): validation failed", CID_BITS(&packet_in->pi_dcid),
|
|
token_type);
|
|
return -1;
|
|
}
|
|
crypter = &tokgen->tg_crypters[ token_type ];
|
|
|
|
nonce = packet_in->pi_data + packet_in->pi_token;
|
|
encr_token = nonce + RETRY_NONCE_LEN;
|
|
encr_token_len = packet_in->pi_token_size - RETRY_NONCE_LEN;
|
|
decr_token_len = sizeof(decr_token);
|
|
if (!EVP_AEAD_CTX_open(&crypter->ctx, decr_token, &decr_token_len,
|
|
decr_token_len, nonce, RETRY_NONCE_LEN,
|
|
encr_token, encr_token_len, ad, ad_len))
|
|
{
|
|
LSQ_DEBUGC("packet for connection %"CID_FMT" has undecryptable %s "
|
|
"token %s: validation failed", CID_BITS(&packet_in->pi_dcid),
|
|
tt2str[token_type],
|
|
HEXSTR(packet_in->pi_data + packet_in->pi_token,
|
|
packet_in->pi_token_size, token_str));
|
|
return -1;
|
|
}
|
|
|
|
/* From here on, we begin to warn: this is because we were able to
|
|
* decrypt it, so this is our token. We should be able to parse it.
|
|
*/
|
|
|
|
p = decr_token;
|
|
end = p + decr_token_len;
|
|
|
|
if (p + 1 > end)
|
|
goto too_short;
|
|
version = *p++;
|
|
if (version != TOKGEN_VERSION)
|
|
{
|
|
LSQ_DEBUGC("packet for connection %"CID_FMT" has %s token with "
|
|
"wrong version %u (expected %u): validation failed",
|
|
CID_BITS(&packet_in->pi_dcid), tt2str[token_type],
|
|
version, TOKGEN_VERSION);
|
|
return -1;
|
|
}
|
|
|
|
if (p + sizeof(issued_at) > end)
|
|
goto too_short;
|
|
memcpy(&issued_at, p, sizeof(issued_at));
|
|
now = time(NULL);
|
|
if (issued_at + ttl < now)
|
|
{
|
|
LSQ_DEBUGC("%s token for connection %"CID_FMT" expired %lu "
|
|
"seconds ago", tt2str[token_type], CID_BITS(&packet_in->pi_dcid),
|
|
(unsigned long) (now - issued_at - ttl));
|
|
return -1;
|
|
}
|
|
p += sizeof(issued_at);
|
|
|
|
if (p + 1 > end)
|
|
goto too_short;
|
|
is_ipv6 = *p++;
|
|
if (is_ipv6)
|
|
{
|
|
if (p + 16 > end)
|
|
goto too_short;
|
|
if (!(AF_INET6 == sa_peer->sa_family &&
|
|
0 == memcmp(p, &((struct sockaddr_in6 *) sa_peer)->sin6_addr, 16)))
|
|
goto ip_mismatch;
|
|
p += 16;
|
|
}
|
|
else
|
|
{
|
|
if (p + 4 > end)
|
|
goto too_short;
|
|
if (!(AF_INET == sa_peer->sa_family &&
|
|
0 == memcmp(p, &((struct sockaddr_in *)
|
|
sa_peer)->sin_addr.s_addr, 4)))
|
|
goto ip_mismatch;
|
|
p += 4;
|
|
}
|
|
|
|
if (TOKEN_RETRY == token_type)
|
|
{
|
|
if (p + 2 >= end)
|
|
goto too_short;
|
|
if (AF_INET == sa_peer->sa_family)
|
|
{
|
|
if (memcmp(p, &((struct sockaddr_in *) sa_peer)->sin_port, 2))
|
|
goto port_mismatch;
|
|
}
|
|
else if (memcmp(p, &((struct sockaddr_in6 *) sa_peer)->sin6_port, 2))
|
|
goto port_mismatch;
|
|
if (0 && LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
|
|
{
|
|
uint16_t port;
|
|
memcpy(&port, p, sizeof(port));
|
|
port = ntohs(port);
|
|
LSQ_DEBUG("port %hu in Retry token matches", port);
|
|
}
|
|
p += 2;
|
|
if (end - p > MAX_CID_LEN)
|
|
goto too_long;
|
|
if (odcid)
|
|
{
|
|
memcpy(odcid->idbuf, p, end - p);
|
|
odcid->len = end - p;
|
|
LSQ_DEBUGC("ODCID: %"CID_FMT, CID_BITS(odcid));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (p != end)
|
|
{
|
|
assert(p < end);
|
|
goto too_long;
|
|
}
|
|
tokgen_record_resumed_token(tokgen, now, bloom_vals);
|
|
}
|
|
|
|
LSQ_DEBUGC("validated %lu-second-old %s token %s for connection "
|
|
"%"CID_FMT, (unsigned long) (now - issued_at), tt2str[token_type],
|
|
HEXSTR(packet_in->pi_data + packet_in->pi_token,
|
|
packet_in->pi_token_size, token_str),
|
|
CID_BITS(&packet_in->pi_dcid));
|
|
return 0;
|
|
|
|
too_short:
|
|
LSQ_INFOC("decrypted %s token for connection %"CID_FMT" is too short "
|
|
"(%zu bytes): validation failed", tt2str[token_type],
|
|
CID_BITS(&packet_in->pi_dcid), decr_token_len);
|
|
return -1;
|
|
|
|
ip_mismatch:
|
|
addr_str[0][0] = '\0';
|
|
addr_str[1][0] = '\0';
|
|
(void) inet_ntop(is_ipv6 ? AF_INET6 : AF_INET, p, addr_str[0],
|
|
sizeof(addr_str[0]));
|
|
if (AF_INET6 == sa_peer->sa_family)
|
|
(void) inet_ntop(AF_INET6, &((struct sockaddr_in6 *) sa_peer
|
|
)->sin6_addr, addr_str[1], sizeof(addr_str[1]));
|
|
else
|
|
(void) inet_ntop(AF_INET, &((struct sockaddr_in *) sa_peer
|
|
)->sin_addr.s_addr, addr_str[1], sizeof(addr_str[1]));
|
|
LSQ_INFOC("IP address %s in %s token for connection %"CID_FMT" does not "
|
|
"match peer IP address %s: validation failed", addr_str[0],
|
|
tt2str[token_type], CID_BITS(&packet_in->pi_dcid), addr_str[1]);
|
|
return -1;
|
|
|
|
too_long:
|
|
LSQ_INFOC("decrypted %s token for connection %"CID_FMT" is too long "
|
|
"(%zu bytes): validation failed", tt2str[token_type],
|
|
CID_BITS(&packet_in->pi_dcid), decr_token_len);
|
|
return -1;
|
|
|
|
port_mismatch:
|
|
{
|
|
uint16_t ports[2];
|
|
ports[0] = AF_INET6 == sa_peer->sa_family
|
|
? ((struct sockaddr_in6 *) sa_peer)->sin6_port
|
|
: ((struct sockaddr_in *) sa_peer)->sin_port;
|
|
ports[0] = ntohs(ports[0]);
|
|
memcpy(&ports[1], p, sizeof(ports[1]));
|
|
ports[1] = ntohs(ports[1]);
|
|
LSQ_INFOC("port %hu in %s token for connection %"CID_FMT" does not "
|
|
"match peer port %hu: validation failed", ports[1], tt2str[token_type],
|
|
CID_BITS(&packet_in->pi_dcid), ports[0]);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
#define LABEL_PREFIX_SZ 8
|
|
|
|
static const uint8_t *labels[N_TOKEN_TYPES] =
|
|
{
|
|
[TOKEN_RETRY] = (uint8_t *) "retry me",
|
|
[TOKEN_RESUME] = (uint8_t *) "resume m",
|
|
};
|
|
|
|
|
|
static ssize_t
|
|
tokgen_generate_token (struct token_generator *tokgen,
|
|
enum token_type token_type, unsigned char *buf, size_t bufsz,
|
|
const unsigned char *ad_buf, size_t ad_len,
|
|
const struct sockaddr *sa_peer, const lsquic_cid_t *odcid)
|
|
{
|
|
struct crypter *crypter;
|
|
unsigned char *p, *in;
|
|
time_t now;
|
|
size_t len, in_len;
|
|
unsigned char label[ LABEL_PREFIX_SZ + sizeof(crypter->nonce_counter) ];
|
|
char in_str[(MAX_RETRY_TOKEN_LEN - RETRY_NONCE_LEN
|
|
- RETRY_TAG_LEN) * 2 + 1],
|
|
ad_str[ad_len * 2 + 1],
|
|
token_str[MAX_RETRY_TOKEN_LEN * 2 + 1];
|
|
|
|
if (bufsz < MAX_RETRY_TOKEN_LEN)
|
|
return -1;
|
|
|
|
crypter = &tokgen->tg_crypters[ token_type ];
|
|
p = buf;
|
|
|
|
*p = token_type;
|
|
memcpy(label, labels[token_type], LABEL_PREFIX_SZ);
|
|
memcpy(label + LABEL_PREFIX_SZ, &crypter->nonce_counter,
|
|
sizeof(crypter->nonce_counter));
|
|
(void) HKDF_expand(p + 1, RETRY_NONCE_LEN - 1, EVP_sha256(),
|
|
crypter->nonce_prk_buf, crypter->nonce_prk_sz, label, sizeof(label));
|
|
p += RETRY_NONCE_LEN;
|
|
*p++ = TOKGEN_VERSION;
|
|
now = time(NULL);
|
|
memcpy(p, &now, sizeof(now));
|
|
p += sizeof(now);
|
|
|
|
if (AF_INET == sa_peer->sa_family)
|
|
{
|
|
*p++ = 0;
|
|
memcpy(p, &((struct sockaddr_in *) sa_peer)->sin_addr.s_addr, 4);
|
|
p += 4;
|
|
}
|
|
else
|
|
{
|
|
*p++ = 1;
|
|
memcpy(p, &((struct sockaddr_in6 *) sa_peer)->sin6_addr, 16);
|
|
p += 16;
|
|
}
|
|
|
|
if (token_type == TOKEN_RETRY)
|
|
{
|
|
if (AF_INET == sa_peer->sa_family)
|
|
memcpy(p, &((struct sockaddr_in *) sa_peer)->sin_port, 2);
|
|
else
|
|
memcpy(p, &((struct sockaddr_in6 *) sa_peer)->sin6_port, 2);
|
|
p += 2;
|
|
}
|
|
|
|
if (odcid)
|
|
{
|
|
assert(odcid->len <= MAX_CID_LEN);
|
|
memcpy(p, odcid->idbuf, odcid->len);
|
|
p += odcid->len;
|
|
}
|
|
|
|
len = bufsz - RETRY_NONCE_LEN;
|
|
in = buf + RETRY_NONCE_LEN;
|
|
in_len = p - buf - RETRY_NONCE_LEN;
|
|
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
|
|
lsquic_hexstr(in, in_len, in_str, sizeof(in_str));
|
|
if (EVP_AEAD_CTX_seal(&crypter->ctx, in, &len, len,
|
|
buf, RETRY_NONCE_LEN, in, in_len, ad_buf, ad_len))
|
|
{
|
|
++crypter->nonce_counter;
|
|
LSQ_DEBUG("in: %s, ad: %s -> %s token: %s (%zu bytes)",
|
|
in_str,
|
|
HEXSTR(ad_buf, ad_len, ad_str),
|
|
tt2str[token_type],
|
|
HEXSTR(buf, RETRY_NONCE_LEN + len, token_str),
|
|
RETRY_NONCE_LEN + len);
|
|
return RETRY_NONCE_LEN + len;
|
|
}
|
|
else
|
|
{
|
|
LSQ_WARN("could not seal retry token");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
ssize_t
|
|
lsquic_tg_generate_retry (struct token_generator *tokgen,
|
|
unsigned char *buf, size_t bufsz, const unsigned char *scid_buf,
|
|
size_t scid_len, const struct sockaddr *sa_peer,
|
|
const lsquic_cid_t *odcid)
|
|
{
|
|
return tokgen_generate_token(tokgen, TOKEN_RETRY, buf, bufsz, scid_buf,
|
|
scid_len, sa_peer, odcid);
|
|
}
|
|
|
|
|
|
ssize_t
|
|
lsquic_tg_generate_resume (struct token_generator *tokgen,
|
|
unsigned char *buf, size_t bufsz, const struct sockaddr *sa_peer)
|
|
{
|
|
return tokgen_generate_token(tokgen, TOKEN_RESUME, buf, bufsz, NULL,
|
|
0, sa_peer, NULL);
|
|
}
|
|
|
|
|
|
size_t
|
|
lsquic_tg_token_size (const struct token_generator *tokgen,
|
|
enum token_type token_type, const struct sockaddr *sa_peer)
|
|
{
|
|
return MAX_RETRY_TOKEN_LEN - 16
|
|
+ (AF_INET == sa_peer->sa_family ? 4 : 16);
|
|
}
|
|
|
|
|
|
void
|
|
lsquic_tg_generate_sreset (struct token_generator *tokgen,
|
|
const struct lsquic_cid *cid, unsigned char *reset_token)
|
|
{
|
|
char str[IQUIC_SRESET_TOKEN_SZ * 2 + 1];
|
|
|
|
(void) HKDF_expand(reset_token, IQUIC_SRESET_TOKEN_SZ, EVP_sha256(),
|
|
tokgen->tg_srst_prk_buf, tokgen->tg_srst_prk_sz, cid->idbuf, cid->len);
|
|
LSQ_DEBUGC("generated stateless reset token %s for CID %"CID_FMT,
|
|
HEXSTR(reset_token, IQUIC_SRESET_TOKEN_SZ, str), CID_BITS(cid));
|
|
}
|