3495 lines
112 KiB
C
3495 lines
112 KiB
C
/* Copyright (c) 2017 - 2021 LiteSpeed Technologies Inc. See LICENSE. */
|
|
/*
|
|
* lsquic_enc_sess_ietf.c -- Crypto session for IETF QUIC
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/queue.h>
|
|
#if LSQUIC_PREFERRED_ADDR
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
|
|
#include <openssl/chacha.h>
|
|
#include <openssl/hkdf.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/ssl.h>
|
|
|
|
#include "fiu-local.h"
|
|
|
|
#include "lsquic_types.h"
|
|
#include "lsquic_hkdf.h"
|
|
#include "lsquic.h"
|
|
#include "lsquic_int_types.h"
|
|
#include "lsquic_sizes.h"
|
|
#include "lsquic_hash.h"
|
|
#include "lsquic_conn.h"
|
|
#include "lsquic_enc_sess.h"
|
|
#include "lsquic_parse.h"
|
|
#include "lsquic_mm.h"
|
|
#include "lsquic_engine_public.h"
|
|
#include "lsquic_packet_common.h"
|
|
#include "lsquic_packet_out.h"
|
|
#include "lsquic_packet_ietf.h"
|
|
#include "lsquic_packet_in.h"
|
|
#include "lsquic_util.h"
|
|
#include "lsquic_byteswap.h"
|
|
#include "lsquic_ev_log.h"
|
|
#include "lsquic_trans_params.h"
|
|
#include "lsquic_version.h"
|
|
#include "lsquic_ver_neg.h"
|
|
#include "lsquic_frab_list.h"
|
|
#include "lsquic_tokgen.h"
|
|
#include "lsquic_ietf.h"
|
|
#include "lsquic_alarmset.h"
|
|
|
|
#if __GNUC__
|
|
# define UNLIKELY(cond) __builtin_expect(cond, 0)
|
|
#else
|
|
# define UNLIKELY(cond) cond
|
|
#endif
|
|
|
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
|
|
|
#define LSQUIC_LOGGER_MODULE LSQLM_HANDSHAKE
|
|
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid(enc_sess->esi_conn)
|
|
#include "lsquic_logger.h"
|
|
|
|
#define KEY_LABEL "quic key"
|
|
#define KEY_LABEL_SZ (sizeof(KEY_LABEL) - 1)
|
|
#define IV_LABEL "quic iv"
|
|
#define IV_LABEL_SZ (sizeof(IV_LABEL) - 1)
|
|
#define PN_LABEL "quic hp"
|
|
#define PN_LABEL_SZ (sizeof(PN_LABEL) - 1)
|
|
|
|
#define N_HSK_PAIRS (N_ENC_LEVS - 1)
|
|
|
|
static const struct alpn_map {
|
|
enum lsquic_version version;
|
|
const unsigned char *alpn;
|
|
} s_h3_alpns[] = {
|
|
{ LSQVER_ID27, (unsigned char *) "\x05h3-27", },
|
|
{ LSQVER_ID29, (unsigned char *) "\x05h3-29", },
|
|
{ LSQVER_ID34, (unsigned char *) "\x05h3-34", },
|
|
{ LSQVER_I001, (unsigned char *) "\x02h3", },
|
|
{ LSQVER_VERNEG, (unsigned char *) "\x05h3-34", },
|
|
};
|
|
|
|
struct enc_sess_iquic;
|
|
struct crypto_ctx;
|
|
struct crypto_ctx_pair;
|
|
struct header_prot;
|
|
|
|
static const int s_log_seal_and_open;
|
|
static char s_str[0x1000];
|
|
|
|
static const SSL_QUIC_METHOD cry_quic_method;
|
|
|
|
static int s_idx = -1;
|
|
|
|
static int
|
|
setup_handshake_keys (struct enc_sess_iquic *, const lsquic_cid_t *);
|
|
|
|
static void
|
|
free_handshake_keys (struct enc_sess_iquic *);
|
|
|
|
static struct stack_st_X509 *
|
|
iquic_esf_get_server_cert_chain (enc_session_t *);
|
|
|
|
static void
|
|
maybe_drop_SSL (struct enc_sess_iquic *);
|
|
|
|
static void
|
|
no_sess_ticket (enum alarm_id alarm_id, void *ctx,
|
|
lsquic_time_t expiry, lsquic_time_t now);
|
|
|
|
static int
|
|
iquic_new_session_cb (SSL *, SSL_SESSION *);
|
|
|
|
static enum ssl_verify_result_t
|
|
verify_server_cert_callback (SSL *, uint8_t *out_alert);
|
|
|
|
static void
|
|
iquic_esfi_destroy (enc_session_t *);
|
|
|
|
#define SAMPLE_SZ 16
|
|
|
|
typedef void (*gen_hp_mask_f)(struct enc_sess_iquic *,
|
|
struct header_prot *, unsigned rw,
|
|
const unsigned char *sample, unsigned char *mask, size_t sz);
|
|
|
|
#define CHACHA20_KEY_LENGTH 32
|
|
|
|
struct header_prot
|
|
{
|
|
gen_hp_mask_f hp_gen_mask;
|
|
enum enc_level hp_enc_level;
|
|
enum {
|
|
HP_CAN_READ = 1 << 0,
|
|
HP_CAN_WRITE = 1 << 1,
|
|
} hp_flags;
|
|
union {
|
|
EVP_CIPHER_CTX cipher_ctx[2]; /* AES */
|
|
unsigned char buf[2][CHACHA20_KEY_LENGTH]; /* ChaCha */
|
|
} hp_u;
|
|
};
|
|
|
|
#define header_prot_inited(hp_, rw_) ((hp_)->hp_flags & (1 << (rw_)))
|
|
|
|
|
|
struct crypto_ctx
|
|
{
|
|
enum {
|
|
YK_INITED = 1 << 0,
|
|
} yk_flags;
|
|
EVP_AEAD_CTX yk_aead_ctx;
|
|
unsigned yk_key_sz;
|
|
unsigned yk_iv_sz;
|
|
unsigned char yk_key_buf[EVP_MAX_KEY_LENGTH];
|
|
unsigned char yk_iv_buf[EVP_MAX_IV_LENGTH];
|
|
};
|
|
|
|
|
|
struct crypto_ctx_pair
|
|
{
|
|
lsquic_packno_t ykp_thresh;
|
|
struct crypto_ctx ykp_ctx[2]; /* client, server */
|
|
};
|
|
|
|
|
|
/* [draft-ietf-quic-tls-12] Section 5.3.6 */
|
|
static int
|
|
init_crypto_ctx (struct crypto_ctx *crypto_ctx, const EVP_MD *md,
|
|
const EVP_AEAD *aead, const unsigned char *secret,
|
|
size_t secret_sz, enum evp_aead_direction_t dir)
|
|
{
|
|
crypto_ctx->yk_key_sz = EVP_AEAD_key_length(aead);
|
|
crypto_ctx->yk_iv_sz = EVP_AEAD_nonce_length(aead);
|
|
|
|
if (crypto_ctx->yk_key_sz > sizeof(crypto_ctx->yk_key_buf)
|
|
|| crypto_ctx->yk_iv_sz > sizeof(crypto_ctx->yk_iv_buf))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
lsquic_qhkdf_expand(md, secret, secret_sz, KEY_LABEL, KEY_LABEL_SZ,
|
|
crypto_ctx->yk_key_buf, crypto_ctx->yk_key_sz);
|
|
lsquic_qhkdf_expand(md, secret, secret_sz, IV_LABEL, IV_LABEL_SZ,
|
|
crypto_ctx->yk_iv_buf, crypto_ctx->yk_iv_sz);
|
|
if (!EVP_AEAD_CTX_init_with_direction(&crypto_ctx->yk_aead_ctx, aead,
|
|
crypto_ctx->yk_key_buf, crypto_ctx->yk_key_sz, IQUIC_TAG_LEN, dir))
|
|
return -1;
|
|
|
|
crypto_ctx->yk_flags |= YK_INITED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
cleanup_crypto_ctx (struct crypto_ctx *crypto_ctx)
|
|
{
|
|
if (crypto_ctx->yk_flags & YK_INITED)
|
|
{
|
|
EVP_AEAD_CTX_cleanup(&crypto_ctx->yk_aead_ctx);
|
|
crypto_ctx->yk_flags &= ~YK_INITED;
|
|
}
|
|
}
|
|
|
|
|
|
#define HP_BATCH_SIZE 8
|
|
|
|
struct enc_sess_iquic
|
|
{
|
|
struct lsquic_engine_public
|
|
*esi_enpub;
|
|
struct lsquic_conn *esi_conn;
|
|
void **esi_streams;
|
|
const struct crypto_stream_if *esi_cryst_if;
|
|
const struct ver_neg
|
|
*esi_ver_neg;
|
|
SSL *esi_ssl;
|
|
|
|
/* These are used for forward encryption key phase 0 and 1 */
|
|
struct header_prot esi_hp;
|
|
struct crypto_ctx_pair
|
|
esi_pairs[2];
|
|
/* These are used during handshake. There are three of them.
|
|
* esi_hsk_pairs and esi_hsk_hps are allocated and freed
|
|
* together.
|
|
*/
|
|
struct crypto_ctx_pair *
|
|
esi_hsk_pairs;
|
|
struct header_prot *esi_hsk_hps;
|
|
lsquic_packno_t esi_max_packno[N_PNS];
|
|
lsquic_cid_t esi_odcid;
|
|
lsquic_cid_t esi_rscid; /* Retry SCID */
|
|
lsquic_cid_t esi_iscid; /* Initial SCID */
|
|
unsigned esi_key_phase;
|
|
enum {
|
|
ESI_UNUSED0 = 1 << 0,
|
|
ESI_LOG_SECRETS = 1 << 1,
|
|
ESI_HANDSHAKE_OK = 1 << 2,
|
|
ESI_ODCID = 1 << 3,
|
|
ESI_ON_WRITE = 1 << 4,
|
|
ESI_SERVER = 1 << 5,
|
|
ESI_USE_SSL_TICKET = 1 << 6,
|
|
ESI_HAVE_PEER_TP = 1 << 7,
|
|
ESI_ALPN_CHECKED = 1 << 8,
|
|
ESI_CACHED_INFO = 1 << 9,
|
|
ESI_HSK_CONFIRMED= 1 << 10,
|
|
ESI_WANT_TICKET = 1 << 11,
|
|
ESI_RECV_QL_BITS = 1 << 12,
|
|
ESI_SEND_QL_BITS = 1 << 13,
|
|
ESI_RSCID = 1 << 14,
|
|
ESI_ISCID = 1 << 15,
|
|
ESI_RETRY = 1 << 16, /* Connection was retried */
|
|
ESI_MAX_PACKNO_INIT = 1 << 17,
|
|
ESI_MAX_PACKNO_HSK = ESI_MAX_PACKNO_INIT << PNS_HSK,
|
|
ESI_MAX_PACKNO_APP = ESI_MAX_PACKNO_INIT << PNS_APP,
|
|
ESI_HAVE_0RTT_TP = 1 << 20,
|
|
} esi_flags;
|
|
enum enc_level esi_last_w;
|
|
unsigned esi_trasec_sz;
|
|
#ifndef NDEBUG
|
|
char *esi_sni_bypass;
|
|
#endif
|
|
const unsigned char *esi_alpn;
|
|
/* Need MD and AEAD for key rotation */
|
|
const EVP_MD *esi_md;
|
|
const EVP_AEAD *esi_aead;
|
|
struct {
|
|
const char *cipher_name;
|
|
int alg_bits;
|
|
} esi_cached_info;
|
|
/* Secrets are kept for key rotation */
|
|
unsigned char esi_traffic_secrets[2][EVP_MAX_KEY_LENGTH];
|
|
/* We never use the first two levels, so it seems we could reduce the
|
|
* memory requirement here at the cost of adding some code.
|
|
*/
|
|
struct frab_list esi_frals[N_ENC_LEVS];
|
|
struct transport_params
|
|
esi_peer_tp;
|
|
struct lsquic_alarmset
|
|
*esi_alset;
|
|
unsigned esi_max_streams_uni;
|
|
unsigned esi_hp_batch_idx;
|
|
unsigned esi_hp_batch_packno_len[HP_BATCH_SIZE];
|
|
unsigned esi_hp_batch_packno_off[HP_BATCH_SIZE];
|
|
struct lsquic_packet_out *
|
|
esi_hp_batch_packets[HP_BATCH_SIZE];
|
|
unsigned char esi_hp_batch_samples[HP_BATCH_SIZE][SAMPLE_SZ];
|
|
unsigned char esi_grease;
|
|
signed char esi_have_forw;
|
|
};
|
|
|
|
|
|
static void
|
|
gen_hp_mask_aes (struct enc_sess_iquic *enc_sess,
|
|
struct header_prot *hp, unsigned rw,
|
|
const unsigned char *sample, unsigned char *mask, size_t sz)
|
|
{
|
|
int out_len;
|
|
|
|
if (EVP_EncryptUpdate(&hp->hp_u.cipher_ctx[rw], mask, &out_len, sample, sz))
|
|
assert(out_len >= (int) sz);
|
|
else
|
|
{
|
|
LSQ_WARN("cannot generate hp mask, error code: %"PRIu32,
|
|
ERR_get_error());
|
|
enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn,
|
|
"cannot generate hp mask, error code: %"PRIu32, ERR_get_error());
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gen_hp_mask_chacha20 (struct enc_sess_iquic *enc_sess,
|
|
struct header_prot *hp, unsigned rw,
|
|
const unsigned char *sample, unsigned char *mask, size_t sz)
|
|
{
|
|
const uint8_t *nonce;
|
|
uint32_t counter;
|
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
memcpy(&counter, sample, sizeof(counter));
|
|
#else
|
|
#error TODO: support non-little-endian machines
|
|
#endif
|
|
nonce = sample + sizeof(counter);
|
|
CRYPTO_chacha_20(mask, (unsigned char [5]) { 0, 0, 0, 0, 0, }, 5,
|
|
hp->hp_u.buf[rw], nonce, counter);
|
|
}
|
|
|
|
|
|
static void
|
|
apply_hp (struct enc_sess_iquic *enc_sess, struct header_prot *hp,
|
|
unsigned char *dst, const unsigned char *mask,
|
|
unsigned packno_off, unsigned packno_len)
|
|
{
|
|
char mask_str[5 * 2 + 1];
|
|
|
|
LSQ_DEBUG("apply header protection using mask %s",
|
|
HEXSTR(mask, 5, mask_str));
|
|
if (enc_sess->esi_flags & ESI_SEND_QL_BITS)
|
|
dst[0] ^= (0x7 | ((dst[0] >> 7) << 3)) & mask[0];
|
|
else
|
|
dst[0] ^= (0xF | (((dst[0] & 0x80) == 0) << 4)) & mask[0];
|
|
switch (packno_len)
|
|
{
|
|
case 4:
|
|
dst[packno_off + 3] ^= mask[4];
|
|
/* fall-through */
|
|
case 3:
|
|
dst[packno_off + 2] ^= mask[3];
|
|
/* fall-through */
|
|
case 2:
|
|
dst[packno_off + 1] ^= mask[2];
|
|
/* fall-through */
|
|
default:
|
|
dst[packno_off + 0] ^= mask[1];
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
apply_hp_immediately (struct enc_sess_iquic *enc_sess,
|
|
struct header_prot *hp, struct lsquic_packet_out *packet_out,
|
|
unsigned packno_off, unsigned packno_len)
|
|
{
|
|
unsigned char mask[SAMPLE_SZ];
|
|
|
|
hp->hp_gen_mask(enc_sess, hp, 1,
|
|
packet_out->po_enc_data + packno_off + 4, mask, SAMPLE_SZ);
|
|
apply_hp(enc_sess, hp, packet_out->po_enc_data, mask, packno_off,
|
|
packno_len);
|
|
#ifndef NDEBUG
|
|
packet_out->po_lflags |= POL_HEADER_PROT;
|
|
#endif
|
|
}
|
|
|
|
|
|
static void
|
|
flush_hp_batch (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
unsigned i;
|
|
unsigned char mask[HP_BATCH_SIZE][SAMPLE_SZ];
|
|
|
|
enc_sess->esi_hp.hp_gen_mask(enc_sess, &enc_sess->esi_hp, 1,
|
|
(unsigned char *) enc_sess->esi_hp_batch_samples,
|
|
(unsigned char *) mask,
|
|
enc_sess->esi_hp_batch_idx * SAMPLE_SZ);
|
|
for (i = 0; i < enc_sess->esi_hp_batch_idx; ++i)
|
|
{
|
|
apply_hp(enc_sess, &enc_sess->esi_hp,
|
|
enc_sess->esi_hp_batch_packets[i]->po_enc_data,
|
|
mask[i],
|
|
enc_sess->esi_hp_batch_packno_off[i],
|
|
enc_sess->esi_hp_batch_packno_len[i]);
|
|
#ifndef NDEBUG
|
|
enc_sess->esi_hp_batch_packets[i]->po_lflags |= POL_HEADER_PROT;
|
|
#endif
|
|
}
|
|
enc_sess->esi_hp_batch_idx = 0;
|
|
}
|
|
|
|
|
|
static void
|
|
apply_hp_batch (struct enc_sess_iquic *enc_sess,
|
|
struct header_prot *hp, struct lsquic_packet_out *packet_out,
|
|
unsigned packno_off, unsigned packno_len)
|
|
{
|
|
memcpy(enc_sess->esi_hp_batch_samples[enc_sess->esi_hp_batch_idx],
|
|
packet_out->po_enc_data + packno_off + 4, SAMPLE_SZ);
|
|
enc_sess->esi_hp_batch_packno_off[enc_sess->esi_hp_batch_idx] = packno_off;
|
|
enc_sess->esi_hp_batch_packno_len[enc_sess->esi_hp_batch_idx] = packno_len;
|
|
enc_sess->esi_hp_batch_packets[enc_sess->esi_hp_batch_idx] = packet_out;
|
|
++enc_sess->esi_hp_batch_idx;
|
|
if (enc_sess->esi_hp_batch_idx == HP_BATCH_SIZE)
|
|
flush_hp_batch(enc_sess);
|
|
}
|
|
|
|
|
|
static lsquic_packno_t
|
|
decode_packno (lsquic_packno_t max_packno, lsquic_packno_t packno,
|
|
unsigned shift)
|
|
{
|
|
lsquic_packno_t candidates[3], epoch_delta;
|
|
int64_t diffs[3];
|
|
unsigned min;;
|
|
|
|
epoch_delta = 1ULL << shift;
|
|
candidates[1] = (max_packno & ~(epoch_delta - 1)) + packno;
|
|
candidates[0] = candidates[1] - epoch_delta;
|
|
candidates[2] = candidates[1] + epoch_delta;
|
|
|
|
diffs[0] = llabs((int64_t) candidates[0] - (int64_t) max_packno);
|
|
diffs[1] = llabs((int64_t) candidates[1] - (int64_t) max_packno);
|
|
diffs[2] = llabs((int64_t) candidates[2] - (int64_t) max_packno);
|
|
|
|
min = diffs[1] < diffs[0];
|
|
if (diffs[2] < diffs[min])
|
|
min = 2;
|
|
|
|
return candidates[min];
|
|
}
|
|
|
|
|
|
static lsquic_packno_t
|
|
strip_hp (struct enc_sess_iquic *enc_sess,
|
|
struct header_prot *hp,
|
|
const unsigned char *iv, unsigned char *dst, unsigned packno_off,
|
|
unsigned *packno_len)
|
|
{
|
|
enum packnum_space pns;
|
|
lsquic_packno_t packno;
|
|
unsigned shift;
|
|
unsigned char mask[SAMPLE_SZ];
|
|
char mask_str[5 * 2 + 1];
|
|
|
|
hp->hp_gen_mask(enc_sess, hp, 0, iv, mask, SAMPLE_SZ);
|
|
LSQ_DEBUG("strip header protection using mask %s",
|
|
HEXSTR(mask, 5, mask_str));
|
|
if (enc_sess->esi_flags & ESI_RECV_QL_BITS)
|
|
dst[0] ^= (0x7 | ((dst[0] >> 7) << 3)) & mask[0];
|
|
else
|
|
dst[0] ^= (0xF | (((dst[0] & 0x80) == 0) << 4)) & mask[0];
|
|
packno = 0;
|
|
shift = 0;
|
|
*packno_len = 1 + (dst[0] & 3);
|
|
switch (*packno_len)
|
|
{
|
|
case 4:
|
|
dst[packno_off + 3] ^= mask[4];
|
|
packno |= dst[packno_off + 3];
|
|
shift += 8;
|
|
/* fall-through */
|
|
case 3:
|
|
dst[packno_off + 2] ^= mask[3];
|
|
packno |= (unsigned) dst[packno_off + 2] << shift;
|
|
shift += 8;
|
|
/* fall-through */
|
|
case 2:
|
|
dst[packno_off + 1] ^= mask[2];
|
|
packno |= (unsigned) dst[packno_off + 1] << shift;
|
|
shift += 8;
|
|
/* fall-through */
|
|
default:
|
|
dst[packno_off + 0] ^= mask[1];
|
|
packno |= (unsigned) dst[packno_off + 0] << shift;
|
|
shift += 8;
|
|
}
|
|
pns = lsquic_enclev2pns[hp->hp_enc_level];
|
|
if (enc_sess->esi_flags & (ESI_MAX_PACKNO_INIT << pns))
|
|
{
|
|
LSQ_DEBUG("pre-decode packno: %"PRIu64, packno);
|
|
return decode_packno(enc_sess->esi_max_packno[pns], packno, shift);
|
|
}
|
|
else
|
|
{
|
|
LSQ_DEBUG("first packet in %s, packno: %"PRIu64, lsquic_pns2str[pns],
|
|
packno);
|
|
return packno;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
gen_trans_params (struct enc_sess_iquic *enc_sess, unsigned char *buf,
|
|
size_t bufsz)
|
|
{
|
|
const struct lsquic_engine_settings *const settings =
|
|
&enc_sess->esi_enpub->enp_settings;
|
|
struct transport_params params;
|
|
const enum lsquic_version version = enc_sess->esi_conn->cn_version;
|
|
int len;
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
if (version > LSQVER_ID27)
|
|
{
|
|
params.tp_initial_source_cid = *CN_SCID(enc_sess->esi_conn);
|
|
params.tp_set |= 1 << TPI_INITIAL_SOURCE_CID;
|
|
}
|
|
if (enc_sess->esi_flags & ESI_SERVER)
|
|
{
|
|
const struct lsquic_conn *const lconn = enc_sess->esi_conn;
|
|
|
|
params.tp_set |= 1 << TPI_STATELESS_RESET_TOKEN;
|
|
lsquic_tg_generate_sreset(enc_sess->esi_enpub->enp_tokgen,
|
|
CN_SCID(lconn), params.tp_stateless_reset_token);
|
|
|
|
if (enc_sess->esi_flags & ESI_ODCID)
|
|
{
|
|
params.tp_original_dest_cid = enc_sess->esi_odcid;
|
|
params.tp_set |= 1 << TPI_ORIGINAL_DEST_CID;
|
|
}
|
|
#if LSQUIC_PREFERRED_ADDR
|
|
char addr_buf[INET6_ADDRSTRLEN + 6 /* port */ + 1];
|
|
const char *s, *colon;
|
|
struct lsquic_conn *conn;
|
|
struct conn_cid_elem *cce;
|
|
unsigned seqno;
|
|
s = getenv("LSQUIC_PREFERRED_ADDR4");
|
|
if (s && strlen(s) < sizeof(addr_buf) && (colon = strchr(s, ':')))
|
|
{
|
|
strncpy(addr_buf, s, colon - s);
|
|
addr_buf[colon - s] = '\0';
|
|
inet_pton(AF_INET, addr_buf, params.tp_preferred_address.ipv4_addr);
|
|
params.tp_preferred_address.ipv4_port = atoi(colon + 1);
|
|
params.tp_set |= 1 << TPI_PREFERRED_ADDRESS;
|
|
}
|
|
s = getenv("LSQUIC_PREFERRED_ADDR6");
|
|
if (s && strlen(s) < sizeof(addr_buf) && (colon = strrchr(s, ':')))
|
|
{
|
|
strncpy(addr_buf, s, colon - s);
|
|
addr_buf[colon - s] = '\0';
|
|
inet_pton(AF_INET6, addr_buf,
|
|
params.tp_preferred_address.ipv6_addr);
|
|
params.tp_preferred_address.ipv6_port = atoi(colon + 1);
|
|
params.tp_set |= 1 << TPI_PREFERRED_ADDRESS;
|
|
}
|
|
conn = enc_sess->esi_conn;
|
|
if ((params.tp_set & (1 << TPI_PREFERRED_ADDRESS))
|
|
&& (1 << conn->cn_n_cces) - 1 != conn->cn_cces_mask)
|
|
{
|
|
seqno = 0;
|
|
for (cce = lconn->cn_cces; cce < END_OF_CCES(lconn); ++cce)
|
|
{
|
|
if (lconn->cn_cces_mask & (1 << (cce - lconn->cn_cces)))
|
|
{
|
|
if ((cce->cce_flags & CCE_SEQNO) && cce->cce_seqno > seqno)
|
|
seqno = cce->cce_seqno;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
if (cce == END_OF_CCES(lconn))
|
|
{
|
|
goto cant_use_prefaddr;
|
|
}
|
|
cce->cce_seqno = seqno + 1;
|
|
cce->cce_flags = CCE_SEQNO;
|
|
|
|
enc_sess->esi_enpub->enp_generate_scid(enc_sess->esi_conn,
|
|
&cce->cce_cid, enc_sess->esi_enpub->enp_settings.es_scid_len);
|
|
|
|
/* Don't add to hash: migration must not start until *after*
|
|
* handshake is complete.
|
|
*/
|
|
conn->cn_cces_mask |= 1 << (cce - conn->cn_cces);
|
|
params.tp_preferred_address.cid = cce->cce_cid;
|
|
lsquic_tg_generate_sreset(enc_sess->esi_enpub->enp_tokgen,
|
|
¶ms.tp_preferred_address.cid,
|
|
params.tp_preferred_address.srst);
|
|
}
|
|
else
|
|
{
|
|
cant_use_prefaddr:
|
|
params.tp_set &= ~(1 << TPI_PREFERRED_ADDRESS);
|
|
}
|
|
#endif
|
|
}
|
|
#if LSQUIC_TEST_QUANTUM_READINESS
|
|
{
|
|
const char *s = getenv("LSQUIC_TEST_QUANTUM_READINESS");
|
|
if (s && atoi(s))
|
|
params.tp_set |= 1 << TPI_QUANTUM_READINESS;
|
|
}
|
|
#endif
|
|
params.tp_init_max_data = settings->es_init_max_data;
|
|
params.tp_init_max_stream_data_bidi_local
|
|
= settings->es_init_max_stream_data_bidi_local;
|
|
params.tp_init_max_stream_data_bidi_remote
|
|
= settings->es_init_max_stream_data_bidi_remote;
|
|
params.tp_init_max_stream_data_uni
|
|
= settings->es_init_max_stream_data_uni;
|
|
params.tp_init_max_streams_uni
|
|
= enc_sess->esi_max_streams_uni;
|
|
params.tp_init_max_streams_bidi
|
|
= settings->es_init_max_streams_bidi;
|
|
params.tp_ack_delay_exponent
|
|
= TP_DEF_ACK_DELAY_EXP;
|
|
params.tp_max_idle_timeout = settings->es_idle_timeout * 1000;
|
|
params.tp_max_ack_delay = TP_DEF_MAX_ACK_DELAY;
|
|
params.tp_active_connection_id_limit = MAX_IETF_CONN_DCIDS;
|
|
params.tp_set |= (1 << TPI_INIT_MAX_DATA)
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL)
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE)
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_UNI)
|
|
| (1 << TPI_INIT_MAX_STREAMS_UNI)
|
|
| (1 << TPI_INIT_MAX_STREAMS_BIDI)
|
|
| (1 << TPI_ACK_DELAY_EXPONENT)
|
|
| (1 << TPI_MAX_IDLE_TIMEOUT)
|
|
| (1 << TPI_MAX_ACK_DELAY)
|
|
| (1 << TPI_ACTIVE_CONNECTION_ID_LIMIT)
|
|
;
|
|
if (settings->es_max_udp_payload_size_rx)
|
|
{
|
|
params.tp_max_udp_payload_size = settings->es_max_udp_payload_size_rx;
|
|
params.tp_set |= 1 << TPI_MAX_UDP_PAYLOAD_SIZE;
|
|
}
|
|
if (!settings->es_allow_migration)
|
|
params.tp_set |= 1 << TPI_DISABLE_ACTIVE_MIGRATION;
|
|
if (settings->es_ql_bits)
|
|
{
|
|
params.tp_loss_bits = settings->es_ql_bits - 1;
|
|
params.tp_set |= 1 << TPI_LOSS_BITS;
|
|
}
|
|
if (settings->es_delayed_acks)
|
|
{
|
|
params.tp_numerics[TPI_MIN_ACK_DELAY] = TP_MIN_ACK_DELAY;
|
|
params.tp_set |= 1 << TPI_MIN_ACK_DELAY;
|
|
params.tp_numerics[TPI_MIN_ACK_DELAY_02] = TP_MIN_ACK_DELAY;
|
|
params.tp_set |= 1 << TPI_MIN_ACK_DELAY_02;
|
|
}
|
|
if (settings->es_timestamps)
|
|
{
|
|
params.tp_numerics[TPI_TIMESTAMPS] = TS_GENERATE_THEM;
|
|
params.tp_set |= 1 << TPI_TIMESTAMPS;
|
|
}
|
|
if (settings->es_datagrams)
|
|
{
|
|
if (params.tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE))
|
|
params.tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE]
|
|
= params.tp_max_udp_payload_size;
|
|
else
|
|
params.tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE]
|
|
= TP_DEF_MAX_UDP_PAYLOAD_SIZE;
|
|
params.tp_set |= 1 << TPI_MAX_DATAGRAM_FRAME_SIZE;
|
|
}
|
|
|
|
len = (version == LSQVER_ID27 ? lsquic_tp_encode_27 : lsquic_tp_encode)(
|
|
¶ms, enc_sess->esi_flags & ESI_SERVER, buf, bufsz);
|
|
if (len >= 0)
|
|
{
|
|
char str[MAX_TP_STR_SZ];
|
|
LSQ_DEBUG("generated transport parameters buffer of %d bytes", len);
|
|
LSQ_DEBUG("%s", ((version == LSQVER_ID27 ? lsquic_tp_to_str_27
|
|
: lsquic_tp_to_str)(¶ms, str, sizeof(str)), str));
|
|
}
|
|
else
|
|
LSQ_WARN("cannot generate transport parameters: %d", errno);
|
|
return len;
|
|
}
|
|
|
|
|
|
/*
|
|
* Format:
|
|
* uint32_t lsquic_ver_tag_t
|
|
* uint32_t encoder version
|
|
* uint32_t ticket_size
|
|
* uint8_t ticket_buf[ ticket_size ]
|
|
* uint32_t trapa_size
|
|
* uint8_t trapa_buf[ trapa_size ]
|
|
*/
|
|
|
|
#define SESS_RESUME_VERSION 1
|
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
#define READ_NUM(var_, ptr_) do { \
|
|
memcpy(&var_, ptr_, sizeof(var_)); \
|
|
var_ = bswap_32(var_); \
|
|
ptr_ += sizeof(var_); \
|
|
} while (0)
|
|
#else
|
|
#define READ_NUM(var_, ptr_) do { \
|
|
memcpy(&var_, ptr_, sizeof(var_)); \
|
|
ptr_ += sizeof(var_); \
|
|
} while (0)
|
|
#endif
|
|
|
|
static SSL_SESSION *
|
|
maybe_create_SSL_SESSION (struct enc_sess_iquic *enc_sess,
|
|
const SSL_CTX *ssl_ctx, const unsigned char *sess_resume,
|
|
size_t sess_resume_sz)
|
|
{
|
|
SSL_SESSION *ssl_session;
|
|
lsquic_ver_tag_t ver_tag;
|
|
enum lsquic_version quic_ver;
|
|
uint32_t rtt_ver, ticket_sz, trapa_sz;
|
|
const unsigned char *ticket_buf, *trapa_buf, *p;
|
|
const unsigned char *const end = sess_resume + sess_resume_sz;
|
|
|
|
if (sess_resume_sz < sizeof(ver_tag) + sizeof(rtt_ver) + sizeof(ticket_sz))
|
|
{
|
|
LSQ_DEBUG("rtt buf too short");
|
|
return NULL;
|
|
}
|
|
|
|
p = sess_resume;
|
|
memcpy(&ver_tag, p, sizeof(ver_tag));
|
|
p += sizeof(ver_tag);
|
|
quic_ver = lsquic_tag2ver(ver_tag);
|
|
if (quic_ver != enc_sess->esi_ver_neg->vn_ver)
|
|
{
|
|
LSQ_DEBUG("negotiated version %s does not match that in the session "
|
|
"resumption nfo buffer",
|
|
lsquic_ver2str[enc_sess->esi_ver_neg->vn_ver]);
|
|
return NULL;
|
|
}
|
|
|
|
READ_NUM(rtt_ver, p);
|
|
if (rtt_ver != SESS_RESUME_VERSION)
|
|
{
|
|
LSQ_DEBUG("cannot use session resumption buffer: encoded using "
|
|
"%"PRIu32", while current version is %u",
|
|
rtt_ver, SESS_RESUME_VERSION);
|
|
return NULL;
|
|
}
|
|
|
|
READ_NUM(ticket_sz, p);
|
|
if (p + ticket_sz > end)
|
|
{
|
|
LSQ_WARN("truncated ticket buffer");
|
|
return NULL;
|
|
}
|
|
|
|
ticket_buf = p;
|
|
p += ticket_sz;
|
|
|
|
if (p + sizeof(trapa_sz) > end)
|
|
{
|
|
LSQ_WARN("too short to read trapa size");
|
|
return NULL;
|
|
}
|
|
|
|
READ_NUM(trapa_sz, p);
|
|
if (p + trapa_sz > end)
|
|
{
|
|
LSQ_WARN("truncated trapa buffer");
|
|
return NULL;
|
|
}
|
|
trapa_buf = p;
|
|
p += trapa_sz;
|
|
assert(p == end);
|
|
|
|
ssl_session = SSL_SESSION_from_bytes(ticket_buf, ticket_sz, ssl_ctx);
|
|
if (!ssl_session)
|
|
{
|
|
LSQ_WARN("SSL_SESSION could not be parsed out");
|
|
return NULL;
|
|
}
|
|
|
|
if (SSL_SESSION_early_data_capable(ssl_session))
|
|
{
|
|
if (0 > (quic_ver == LSQVER_ID27 ? lsquic_tp_decode_27
|
|
: lsquic_tp_decode)(trapa_buf, trapa_sz, 1,
|
|
&enc_sess->esi_peer_tp))
|
|
{
|
|
SSL_SESSION_free(ssl_session);
|
|
LSQ_WARN("cannot parse stored transport parameters");
|
|
return NULL;
|
|
}
|
|
LSQ_DEBUG("early data capable, will try 0-RTT");
|
|
enc_sess->esi_flags |= ESI_HAVE_0RTT_TP;
|
|
}
|
|
else
|
|
LSQ_DEBUG("early data not capable -- not trying 0-RTT");
|
|
|
|
LSQ_INFO("instantiated SSL_SESSION from serialized buffer");
|
|
return ssl_session;
|
|
}
|
|
|
|
|
|
static void
|
|
init_frals (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
struct frab_list *fral;
|
|
|
|
for (fral = enc_sess->esi_frals; fral < enc_sess->esi_frals
|
|
+ sizeof(enc_sess->esi_frals) / sizeof(enc_sess->esi_frals[0]);
|
|
++fral)
|
|
lsquic_frab_list_init(fral, 0x100, NULL, NULL, NULL);
|
|
}
|
|
|
|
|
|
static enc_session_t *
|
|
iquic_esfi_create_client (const char *hostname,
|
|
struct lsquic_engine_public *enpub, struct lsquic_conn *lconn,
|
|
const lsquic_cid_t *dcid, const struct ver_neg *ver_neg,
|
|
void *crypto_streams[4], const struct crypto_stream_if *cryst_if,
|
|
const unsigned char *sess_resume, size_t sess_resume_sz,
|
|
struct lsquic_alarmset *alset, unsigned max_streams_uni,
|
|
void* peer_ctx)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
SSL_CTX *ssl_ctx = NULL;
|
|
int set_app_ctx = 0;
|
|
SSL_SESSION *ssl_session;
|
|
const struct alpn_map *am;
|
|
int transpa_len;
|
|
char errbuf[ERR_ERROR_STRING_BUF_LEN];
|
|
unsigned char trans_params[0x80
|
|
#if LSQUIC_TEST_QUANTUM_READINESS
|
|
+ 4 + lsquic_tp_get_quantum_sz()
|
|
#endif
|
|
];
|
|
|
|
fiu_return_on("enc_sess_ietf/create_client", NULL);
|
|
|
|
enc_sess = calloc(1, sizeof(*enc_sess));
|
|
if (!enc_sess)
|
|
return NULL;
|
|
|
|
enc_sess->esi_enpub = enpub;
|
|
enc_sess->esi_streams = crypto_streams;
|
|
enc_sess->esi_cryst_if = cryst_if;
|
|
enc_sess->esi_conn = lconn;
|
|
enc_sess->esi_ver_neg = ver_neg;
|
|
|
|
enc_sess->esi_odcid = *dcid;
|
|
enc_sess->esi_flags |= ESI_ODCID;
|
|
enc_sess->esi_grease = 0xFF;
|
|
|
|
LSQ_DEBUGC("created client, DCID: %"CID_FMT, CID_BITS(dcid));
|
|
{
|
|
const char *log;
|
|
log = getenv("LSQUIC_LOG_SECRETS");
|
|
if (log)
|
|
{
|
|
if (atoi(log))
|
|
enc_sess->esi_flags |= ESI_LOG_SECRETS;
|
|
LSQ_DEBUG("will %slog secrets", atoi(log) ? "" : "not ");
|
|
}
|
|
}
|
|
|
|
init_frals(enc_sess);
|
|
|
|
if (0 != setup_handshake_keys(enc_sess, dcid))
|
|
{
|
|
free(enc_sess);
|
|
return NULL;
|
|
}
|
|
|
|
enc_sess->esi_max_streams_uni = max_streams_uni;
|
|
|
|
if (enc_sess->esi_enpub->enp_alpn)
|
|
enc_sess->esi_alpn = enc_sess->esi_enpub->enp_alpn;
|
|
else if (enc_sess->esi_enpub->enp_flags & ENPUB_HTTP)
|
|
{
|
|
for (am = s_h3_alpns; am < s_h3_alpns + sizeof(s_h3_alpns)
|
|
/ sizeof(s_h3_alpns[0]); ++am)
|
|
if (am->version == enc_sess->esi_ver_neg->vn_ver)
|
|
goto alpn_selected;
|
|
LSQ_ERROR("version %s has no matching ALPN",
|
|
lsquic_ver2str[enc_sess->esi_ver_neg->vn_ver]);
|
|
goto err;
|
|
alpn_selected:
|
|
enc_sess->esi_alpn = am->alpn;
|
|
LSQ_DEBUG("for QUIC version %s, ALPN is %s",
|
|
lsquic_ver2str[am->version], (char *) am->alpn + 1);
|
|
}
|
|
|
|
if (enc_sess->esi_enpub->enp_get_ssl_ctx)
|
|
{
|
|
struct network_path *const path =
|
|
enc_sess->esi_conn->cn_if->ci_get_path(enc_sess->esi_conn, NULL);
|
|
ssl_ctx = enc_sess->esi_enpub->enp_get_ssl_ctx(peer_ctx,
|
|
NP_LOCAL_SA(path));
|
|
if (ssl_ctx)
|
|
set_app_ctx = 1;
|
|
else
|
|
goto create_new_ssl_ctx;
|
|
}
|
|
else
|
|
{
|
|
create_new_ssl_ctx:
|
|
LSQ_DEBUG("Create new SSL_CTX");
|
|
ssl_ctx = SSL_CTX_new(TLS_method());
|
|
if (!ssl_ctx)
|
|
{
|
|
LSQ_ERROR("cannot create SSL context: %s",
|
|
ERR_error_string(ERR_get_error(), errbuf));
|
|
goto err;
|
|
}
|
|
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
|
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
|
|
SSL_CTX_set_default_verify_paths(ssl_ctx);
|
|
SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_CLIENT);
|
|
if (enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info)
|
|
SSL_CTX_sess_set_new_cb(ssl_ctx, iquic_new_session_cb);
|
|
if (enc_sess->esi_enpub->enp_verify_cert
|
|
|| LSQ_LOG_ENABLED_EXT(LSQ_LOG_DEBUG, LSQLM_EVENT)
|
|
|| LSQ_LOG_ENABLED_EXT(LSQ_LOG_DEBUG, LSQLM_QLOG))
|
|
SSL_CTX_set_custom_verify(ssl_ctx, SSL_VERIFY_PEER,
|
|
verify_server_cert_callback);
|
|
SSL_CTX_set_early_data_enabled(ssl_ctx, 1);
|
|
}
|
|
|
|
enc_sess->esi_ssl = SSL_new(ssl_ctx);
|
|
if (!enc_sess->esi_ssl)
|
|
{
|
|
LSQ_ERROR("cannot create SSL object: %s",
|
|
ERR_error_string(ERR_get_error(), errbuf));
|
|
goto err;
|
|
}
|
|
#if BORINGSSL_API_VERSION >= 13
|
|
SSL_set_quic_use_legacy_codepoint(enc_sess->esi_ssl,
|
|
enc_sess->esi_ver_neg->vn_ver < LSQVER_ID34);
|
|
#endif
|
|
|
|
transpa_len = gen_trans_params(enc_sess, trans_params,
|
|
sizeof(trans_params));
|
|
if (transpa_len < 0)
|
|
{
|
|
goto err;
|
|
}
|
|
if (1 != SSL_set_quic_transport_params(enc_sess->esi_ssl, trans_params,
|
|
transpa_len))
|
|
{
|
|
LSQ_ERROR("cannot set QUIC transport params: %s",
|
|
ERR_error_string(ERR_get_error(), errbuf));
|
|
goto err;
|
|
}
|
|
|
|
if (!(SSL_set_quic_method(enc_sess->esi_ssl, &cry_quic_method)))
|
|
{
|
|
LSQ_INFO("could not set stream method");
|
|
goto err;
|
|
}
|
|
|
|
if (enc_sess->esi_alpn &&
|
|
0 != SSL_set_alpn_protos(enc_sess->esi_ssl, enc_sess->esi_alpn,
|
|
enc_sess->esi_alpn[0] + 1))
|
|
{
|
|
LSQ_ERROR("cannot set ALPN: %s",
|
|
ERR_error_string(ERR_get_error(), errbuf));
|
|
goto err;
|
|
}
|
|
if (1 != SSL_set_tlsext_host_name(enc_sess->esi_ssl, hostname))
|
|
{
|
|
LSQ_ERROR("cannot set hostname: %s",
|
|
ERR_error_string(ERR_get_error(), errbuf));
|
|
goto err;
|
|
}
|
|
|
|
if (sess_resume && sess_resume_sz)
|
|
{
|
|
ssl_session = maybe_create_SSL_SESSION(enc_sess, ssl_ctx,
|
|
sess_resume, sess_resume_sz);
|
|
if (ssl_session)
|
|
{
|
|
(void) /* This only ever returns 1: */
|
|
SSL_set_session(enc_sess->esi_ssl, ssl_session);
|
|
SSL_SESSION_free(ssl_session);
|
|
ssl_session = NULL;
|
|
enc_sess->esi_flags |= ESI_USE_SSL_TICKET;
|
|
}
|
|
}
|
|
|
|
SSL_set_ex_data(enc_sess->esi_ssl, s_idx, enc_sess);
|
|
SSL_set_connect_state(enc_sess->esi_ssl);
|
|
|
|
if (SSL_CTX_sess_get_new_cb(ssl_ctx))
|
|
enc_sess->esi_flags |= ESI_WANT_TICKET;
|
|
enc_sess->esi_alset = alset;
|
|
lsquic_alarmset_init_alarm(enc_sess->esi_alset, AL_SESS_TICKET,
|
|
no_sess_ticket, enc_sess);
|
|
|
|
if( !set_app_ctx )
|
|
SSL_CTX_free(ssl_ctx);
|
|
return enc_sess;
|
|
|
|
err:
|
|
if (enc_sess)
|
|
iquic_esfi_destroy(enc_sess);
|
|
if (!set_app_ctx && ssl_ctx)
|
|
SSL_CTX_free(ssl_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
iquic_esfi_set_streams (enc_session_t *enc_session_p,
|
|
void *(crypto_streams)[4], const struct crypto_stream_if *cryst_if)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
enc_sess->esi_streams = crypto_streams;
|
|
enc_sess->esi_cryst_if = cryst_if;
|
|
}
|
|
|
|
|
|
static enc_session_t *
|
|
iquic_esfi_create_server (struct lsquic_engine_public *enpub,
|
|
struct lsquic_conn *lconn, const lsquic_cid_t *first_dcid,
|
|
void *(crypto_streams)[4],
|
|
const struct crypto_stream_if *cryst_if,
|
|
const struct lsquic_cid *odcid,
|
|
const struct lsquic_cid *iscid)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
|
|
enc_sess = calloc(1, sizeof(*enc_sess));
|
|
if (!enc_sess)
|
|
return NULL;
|
|
|
|
#ifndef NDEBUG
|
|
enc_sess->esi_sni_bypass = getenv("LSQUIC_SNI_BYPASS");
|
|
#endif
|
|
|
|
enc_sess->esi_flags = ESI_SERVER;
|
|
enc_sess->esi_streams = crypto_streams;
|
|
enc_sess->esi_cryst_if = cryst_if;
|
|
enc_sess->esi_enpub = enpub;
|
|
enc_sess->esi_conn = lconn;
|
|
enc_sess->esi_grease = 0xFF;
|
|
|
|
if (odcid)
|
|
{
|
|
enc_sess->esi_odcid = *odcid;
|
|
enc_sess->esi_flags |= ESI_ODCID;
|
|
}
|
|
enc_sess->esi_iscid = *iscid;
|
|
enc_sess->esi_flags |= ESI_ISCID;
|
|
|
|
init_frals(enc_sess);
|
|
|
|
{
|
|
const char *log;
|
|
log = getenv("LSQUIC_LOG_SECRETS");
|
|
if (log)
|
|
{
|
|
if (atoi(log))
|
|
enc_sess->esi_flags |= ESI_LOG_SECRETS;
|
|
LSQ_DEBUG("will %slog secrets", atoi(log) ? "" : "not ");
|
|
}
|
|
}
|
|
|
|
if (0 != setup_handshake_keys(enc_sess, first_dcid))
|
|
{
|
|
free(enc_sess);
|
|
return NULL;
|
|
}
|
|
|
|
enc_sess->esi_max_streams_uni
|
|
= enpub->enp_settings.es_init_max_streams_uni;
|
|
|
|
return enc_sess;
|
|
}
|
|
|
|
|
|
static const char *const rw2str[] = { "read", "write", };
|
|
|
|
typedef char evp_aead_enum_has_expected_values[
|
|
(int) evp_aead_open == 0 && (int) evp_aead_seal == 1 ? 1 : -1];
|
|
#define rw2dir(rw_) ((enum evp_aead_direction_t) (rw_))
|
|
|
|
|
|
static void
|
|
log_crypto_ctx (const struct enc_sess_iquic *enc_sess,
|
|
const struct crypto_ctx *ctx, const char *name, int rw)
|
|
{
|
|
char hexbuf[EVP_MAX_MD_SIZE * 2 + 1];
|
|
LSQ_DEBUG("%s %s key: %s", name, rw2str[rw],
|
|
HEXSTR(ctx->yk_key_buf, ctx->yk_key_sz, hexbuf));
|
|
LSQ_DEBUG("%s %s iv: %s", name, rw2str[rw],
|
|
HEXSTR(ctx->yk_iv_buf, ctx->yk_iv_sz, hexbuf));
|
|
}
|
|
|
|
|
|
static void
|
|
log_crypto_pair (const struct enc_sess_iquic *enc_sess,
|
|
const struct crypto_ctx_pair *pair, const char *name)
|
|
{
|
|
log_crypto_ctx(enc_sess, &pair->ykp_ctx[0], name, 0);
|
|
log_crypto_ctx(enc_sess, &pair->ykp_ctx[1], name, 1);
|
|
}
|
|
|
|
|
|
/* [draft-ietf-quic-tls-12] Section 5.3.2 */
|
|
static int
|
|
setup_handshake_keys (struct enc_sess_iquic *enc_sess, const lsquic_cid_t *cid)
|
|
{
|
|
const EVP_MD *const md = EVP_sha256();
|
|
const EVP_AEAD *const aead = EVP_aead_aes_128_gcm();
|
|
/* [draft-ietf-quic-tls-12] Section 5.6.1: AEAD_AES_128_GCM implies
|
|
* 128-bit AES-CTR.
|
|
*/
|
|
const EVP_CIPHER *const cipher = EVP_aes_128_ecb();
|
|
struct crypto_ctx_pair *pair;
|
|
struct header_prot *hp;
|
|
size_t hsk_secret_sz, key_len;
|
|
unsigned cliser, i;
|
|
const unsigned char *salt;
|
|
unsigned char hsk_secret[EVP_MAX_MD_SIZE];
|
|
unsigned char secret[2][SHA256_DIGEST_LENGTH]; /* client, server */
|
|
unsigned char key[2][EVP_MAX_KEY_LENGTH];
|
|
char hexbuf[EVP_MAX_MD_SIZE * 2 + 1];
|
|
|
|
if (!enc_sess->esi_hsk_pairs)
|
|
{
|
|
enc_sess->esi_hsk_pairs = calloc(N_HSK_PAIRS,
|
|
sizeof(enc_sess->esi_hsk_pairs[0]));
|
|
enc_sess->esi_hsk_hps = calloc(N_HSK_PAIRS,
|
|
sizeof(enc_sess->esi_hsk_hps[0]));
|
|
if (!(enc_sess->esi_hsk_pairs && enc_sess->esi_hsk_hps))
|
|
{
|
|
free(enc_sess->esi_hsk_pairs);
|
|
free(enc_sess->esi_hsk_hps);
|
|
return -1;
|
|
}
|
|
}
|
|
pair = &enc_sess->esi_hsk_pairs[ENC_LEV_CLEAR];
|
|
pair->ykp_thresh = IQUIC_INVALID_PACKNO;
|
|
hp = &enc_sess->esi_hsk_hps[ENC_LEV_CLEAR];
|
|
|
|
if (enc_sess->esi_conn->cn_version < LSQVER_ID29)
|
|
salt = HSK_SALT_PRE29;
|
|
else if (enc_sess->esi_conn->cn_version < LSQVER_ID34)
|
|
salt = HSK_SALT_PRE33;
|
|
else
|
|
salt = HSK_SALT;
|
|
HKDF_extract(hsk_secret, &hsk_secret_sz, md, cid->idbuf, cid->len,
|
|
salt, HSK_SALT_SZ);
|
|
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
|
|
{
|
|
LSQ_DEBUG("handshake salt: %s", HEXSTR(salt, HSK_SALT_SZ, hexbuf));
|
|
LSQ_DEBUG("handshake secret: %s", HEXSTR(hsk_secret, hsk_secret_sz,
|
|
hexbuf));
|
|
}
|
|
|
|
lsquic_qhkdf_expand(md, hsk_secret, hsk_secret_sz, CLIENT_LABEL,
|
|
CLIENT_LABEL_SZ, secret[0], sizeof(secret[0]));
|
|
lsquic_qhkdf_expand(md, hsk_secret, hsk_secret_sz, SERVER_LABEL,
|
|
SERVER_LABEL_SZ, secret[1], sizeof(secret[1]));
|
|
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
|
|
{
|
|
LSQ_DEBUG("client handshake secret: %s",
|
|
HEXSTR(secret[0], sizeof(secret[0]), hexbuf));
|
|
LSQ_DEBUG("server handshake secret: %s",
|
|
HEXSTR(secret[1], sizeof(secret[1]), hexbuf));
|
|
}
|
|
|
|
cliser = !!(enc_sess->esi_flags & ESI_SERVER);
|
|
if (0 != init_crypto_ctx(&pair->ykp_ctx[!cliser], md, aead, secret[0],
|
|
sizeof(secret[0]), rw2dir(!cliser)))
|
|
goto err;
|
|
if (0 != init_crypto_ctx(&pair->ykp_ctx[cliser], md, aead, secret[1],
|
|
sizeof(secret[1]), rw2dir(cliser)))
|
|
goto err;
|
|
|
|
hp->hp_gen_mask = gen_hp_mask_aes;
|
|
hp->hp_enc_level = ENC_LEV_CLEAR;
|
|
key_len = EVP_AEAD_key_length(aead);
|
|
lsquic_qhkdf_expand(md, secret[!cliser], sizeof(secret[0]), PN_LABEL,
|
|
PN_LABEL_SZ, key[0], key_len);
|
|
lsquic_qhkdf_expand(md, secret[cliser], sizeof(secret[0]), PN_LABEL,
|
|
PN_LABEL_SZ, key[1], key_len);
|
|
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
|
|
{
|
|
log_crypto_pair(enc_sess, pair, "handshake");
|
|
LSQ_DEBUG("read handshake hp: %s", HEXSTR(key[0], key_len, hexbuf));
|
|
LSQ_DEBUG("write handshake hp: %s", HEXSTR(key[1], key_len, hexbuf));
|
|
}
|
|
for (i = 0; i < 2; ++i)
|
|
{
|
|
EVP_CIPHER_CTX_init(&hp->hp_u.cipher_ctx[i]);
|
|
if (EVP_EncryptInit_ex(&hp->hp_u.cipher_ctx[i], cipher, NULL, key[i], 0))
|
|
hp->hp_flags |= 1 << i;
|
|
else
|
|
{
|
|
LSQ_ERROR("%s: cannot initialize cipher %u", __func__, i);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[0]);
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[1]);
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void
|
|
cleanup_hp (struct header_prot *hp)
|
|
{
|
|
unsigned rw;
|
|
|
|
if (hp->hp_gen_mask == gen_hp_mask_aes)
|
|
for (rw = 0; rw < 2; ++rw)
|
|
if (hp->hp_flags & (1 << rw))
|
|
(void) EVP_CIPHER_CTX_cleanup(&hp->hp_u.cipher_ctx[rw]);
|
|
}
|
|
|
|
|
|
static void
|
|
free_handshake_keys (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
struct crypto_ctx_pair *pair;
|
|
unsigned i;
|
|
|
|
if (enc_sess->esi_hsk_pairs)
|
|
{
|
|
assert(enc_sess->esi_hsk_hps);
|
|
for (pair = enc_sess->esi_hsk_pairs; pair <
|
|
enc_sess->esi_hsk_pairs + N_HSK_PAIRS; ++pair)
|
|
{
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[0]);
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[1]);
|
|
}
|
|
free(enc_sess->esi_hsk_pairs);
|
|
enc_sess->esi_hsk_pairs = NULL;
|
|
for (i = 0; i < N_HSK_PAIRS; ++i)
|
|
cleanup_hp(&enc_sess->esi_hsk_hps[i]);
|
|
free(enc_sess->esi_hsk_hps);
|
|
enc_sess->esi_hsk_hps = NULL;
|
|
}
|
|
else
|
|
assert(!enc_sess->esi_hsk_hps);
|
|
}
|
|
|
|
|
|
static enum ssl_verify_result_t
|
|
verify_server_cert_callback (SSL *ssl, uint8_t *out_alert)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
struct stack_st_X509 *chain;
|
|
int s;
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
chain = SSL_get_peer_cert_chain(ssl);
|
|
if (!chain)
|
|
{
|
|
LSQ_ERROR("cannot get peer chain");
|
|
return ssl_verify_invalid;
|
|
}
|
|
|
|
EV_LOG_CERT_CHAIN(LSQUIC_LOG_CONN_ID, chain);
|
|
if (enc_sess->esi_enpub->enp_verify_cert)
|
|
{
|
|
s = enc_sess->esi_enpub->enp_verify_cert(
|
|
enc_sess->esi_enpub->enp_verify_ctx, chain);
|
|
return s == 0 ? ssl_verify_ok : ssl_verify_invalid;
|
|
}
|
|
else
|
|
return ssl_verify_ok;
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_lookup_cert (SSL *ssl, void *arg)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = arg;
|
|
const struct network_path *path;
|
|
const char *server_name;
|
|
SSL_CTX *ssl_ctx;
|
|
|
|
server_name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
|
#ifndef NDEBUG
|
|
if (!server_name)
|
|
server_name = enc_sess->esi_sni_bypass;
|
|
#endif
|
|
if (!server_name)
|
|
{
|
|
if (enc_sess->esi_enpub->enp_flags & ENPUB_HTTP)
|
|
{
|
|
LSQ_DEBUG("SNI is not set, but is required in HTTP/3: "
|
|
"fail certificate lookup");
|
|
return 0;
|
|
}
|
|
else
|
|
LSQ_DEBUG("cert lookup: server name is not set");
|
|
}
|
|
|
|
path = enc_sess->esi_conn->cn_if->ci_get_path(enc_sess->esi_conn, NULL);
|
|
ssl_ctx = enc_sess->esi_enpub->enp_lookup_cert(
|
|
enc_sess->esi_enpub->enp_cert_lu_ctx, NP_LOCAL_SA(path),
|
|
server_name);
|
|
|
|
|
|
if (ssl_ctx)
|
|
{
|
|
if (SSL_set_SSL_CTX(enc_sess->esi_ssl, ssl_ctx))
|
|
{
|
|
LSQ_DEBUG("looked up cert for %s", server_name
|
|
? server_name : "<no SNI>");
|
|
SSL_set_verify(enc_sess->esi_ssl,
|
|
SSL_CTX_get_verify_mode(ssl_ctx), NULL);
|
|
SSL_set_verify_depth(enc_sess->esi_ssl,
|
|
SSL_CTX_get_verify_depth(ssl_ctx));
|
|
SSL_clear_options(enc_sess->esi_ssl,
|
|
SSL_get_options(enc_sess->esi_ssl));
|
|
SSL_set_options(enc_sess->esi_ssl,
|
|
SSL_CTX_get_options(ssl_ctx) & ~SSL_OP_NO_TLSv1_3);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
LSQ_WARN("cannot set SSL_CTX");
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LSQ_DEBUG("could not look up cert for %s", server_name
|
|
? server_name : "<no SNI>");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iquic_esf_set_conn (enc_session_t *enc_session_p, struct lsquic_conn *lconn)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
enc_sess->esi_conn = lconn;
|
|
LSQ_DEBUG("updated conn reference");
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esfi_init_server (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
struct network_path *path;
|
|
const struct alpn_map *am;
|
|
unsigned quic_ctx_idx;
|
|
int transpa_len;
|
|
SSL_CTX *ssl_ctx = NULL;
|
|
union {
|
|
char errbuf[ERR_ERROR_STRING_BUF_LEN];
|
|
unsigned char trans_params[sizeof(struct transport_params)
|
|
#if LSQUIC_TEST_QUANTUM_READINESS
|
|
+ 4 + lsquic_tp_get_quantum_sz()
|
|
#endif
|
|
];
|
|
} u;
|
|
|
|
if (enc_sess->esi_enpub->enp_alpn)
|
|
enc_sess->esi_alpn = enc_sess->esi_enpub->enp_alpn;
|
|
else if (enc_sess->esi_enpub->enp_flags & ENPUB_HTTP)
|
|
{
|
|
for (am = s_h3_alpns; am < s_h3_alpns + sizeof(s_h3_alpns)
|
|
/ sizeof(s_h3_alpns[0]); ++am)
|
|
if (am->version == enc_sess->esi_conn->cn_version)
|
|
goto ok;
|
|
LSQ_ERROR("version %s has no matching ALPN",
|
|
lsquic_ver2str[enc_sess->esi_conn->cn_version]);
|
|
return -1;
|
|
ok: enc_sess->esi_alpn = am->alpn;
|
|
LSQ_DEBUG("for QUIC version %s, ALPN is %s",
|
|
lsquic_ver2str[am->version], (char *) am->alpn + 1);
|
|
}
|
|
|
|
path = enc_sess->esi_conn->cn_if->ci_get_path(enc_sess->esi_conn, NULL);
|
|
ssl_ctx = enc_sess->esi_enpub->enp_get_ssl_ctx(path->np_peer_ctx,
|
|
NP_LOCAL_SA(path));
|
|
if (!ssl_ctx)
|
|
{
|
|
LSQ_ERROR("fetching SSL context associated with peer context failed");
|
|
return -1;
|
|
}
|
|
|
|
enc_sess->esi_ssl = SSL_new(ssl_ctx);
|
|
if (!enc_sess->esi_ssl)
|
|
{
|
|
LSQ_ERROR("cannot create SSL object: %s",
|
|
ERR_error_string(ERR_get_error(), u.errbuf));
|
|
return -1;
|
|
}
|
|
#if BORINGSSL_API_VERSION >= 13
|
|
SSL_set_quic_use_legacy_codepoint(enc_sess->esi_ssl,
|
|
enc_sess->esi_conn->cn_version < LSQVER_ID34);
|
|
#endif
|
|
if (!(SSL_set_quic_method(enc_sess->esi_ssl, &cry_quic_method)))
|
|
{
|
|
LSQ_INFO("could not set stream method");
|
|
return -1;
|
|
}
|
|
quic_ctx_idx = enc_sess->esi_conn->cn_version == LSQVER_ID27 ? 0 : 1;
|
|
if (!SSL_set_quic_early_data_context(enc_sess->esi_ssl,
|
|
enc_sess->esi_enpub->enp_quic_ctx_buf[quic_ctx_idx],
|
|
enc_sess->esi_enpub->enp_quic_ctx_sz[quic_ctx_idx]))
|
|
{
|
|
LSQ_INFO("could not set early data context");
|
|
return -1;
|
|
}
|
|
|
|
transpa_len = gen_trans_params(enc_sess, u.trans_params,
|
|
sizeof(u.trans_params));
|
|
if (transpa_len < 0)
|
|
return -1;
|
|
|
|
if (1 != SSL_set_quic_transport_params(enc_sess->esi_ssl, u.trans_params,
|
|
transpa_len))
|
|
{
|
|
LSQ_ERROR("cannot set QUIC transport params: %s",
|
|
ERR_error_string(ERR_get_error(), u.errbuf));
|
|
return -1;
|
|
}
|
|
|
|
SSL_clear_options(enc_sess->esi_ssl, SSL_OP_NO_TLSv1_3);
|
|
if (enc_sess->esi_enpub->enp_lookup_cert)
|
|
SSL_set_cert_cb(enc_sess->esi_ssl, iquic_lookup_cert, enc_sess);
|
|
SSL_set_ex_data(enc_sess->esi_ssl, s_idx, enc_sess);
|
|
SSL_set_accept_state(enc_sess->esi_ssl);
|
|
LSQ_DEBUG("initialized server enc session");
|
|
return 0;
|
|
}
|
|
|
|
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
#define WRITE_NUM(var_, val_, ptr_) do { \
|
|
var_ = (val_); \
|
|
var_ = bswap_32(var_); \
|
|
memcpy((ptr_), &var_, sizeof(var_)); \
|
|
ptr_ += sizeof(var_); \
|
|
} while (0)
|
|
#else
|
|
#define WRITE_NUM(var_, val_, ptr_) do { \
|
|
var_ = (val_); \
|
|
memcpy((ptr_), &var_, sizeof(var_)); \
|
|
ptr_ += sizeof(var_); \
|
|
} while (0)
|
|
#endif
|
|
|
|
|
|
/* Return 0 on success, in which case *buf is newly allocated memory and should
|
|
* be freed by the caller.
|
|
*/
|
|
static int
|
|
iquic_ssl_sess_to_resume_info (struct enc_sess_iquic *enc_sess, SSL *ssl,
|
|
SSL_SESSION *session, unsigned char **bufp, size_t *buf_szp)
|
|
{
|
|
uint32_t num;
|
|
unsigned char *p, *buf;
|
|
uint8_t *ticket_buf;
|
|
size_t ticket_sz;
|
|
lsquic_ver_tag_t tag;
|
|
const uint8_t *trapa_buf;
|
|
size_t trapa_sz, buf_sz;
|
|
|
|
SSL_get_peer_quic_transport_params(ssl, &trapa_buf, &trapa_sz);
|
|
if (!(trapa_buf + trapa_sz))
|
|
{
|
|
LSQ_WARN("no transport parameters: cannot generate session "
|
|
"resumption info");
|
|
return -1;
|
|
}
|
|
if (trapa_sz > UINT32_MAX)
|
|
{
|
|
LSQ_WARN("trapa size too large: %zu", trapa_sz);
|
|
return -1;
|
|
}
|
|
|
|
if (!SSL_SESSION_to_bytes(session, &ticket_buf, &ticket_sz))
|
|
{
|
|
LSQ_INFO("could not serialize new session");
|
|
return -1;
|
|
}
|
|
if (ticket_sz > UINT32_MAX)
|
|
{
|
|
LSQ_WARN("ticket size too large: %zu", ticket_sz);
|
|
OPENSSL_free(ticket_buf);
|
|
return -1;
|
|
}
|
|
|
|
buf_sz = sizeof(tag) + sizeof(uint32_t) + sizeof(uint32_t)
|
|
+ ticket_sz + sizeof(uint32_t) + trapa_sz;
|
|
buf = malloc(buf_sz);
|
|
if (!buf)
|
|
{
|
|
OPENSSL_free(ticket_buf);
|
|
LSQ_INFO("%s: malloc failed", __func__);
|
|
return -1;
|
|
}
|
|
|
|
p = buf;
|
|
tag = lsquic_ver2tag(enc_sess->esi_conn->cn_version);
|
|
memcpy(p, &tag, sizeof(tag));
|
|
p += sizeof(tag);
|
|
|
|
WRITE_NUM(num, SESS_RESUME_VERSION, p);
|
|
WRITE_NUM(num, ticket_sz, p);
|
|
memcpy(p, ticket_buf, ticket_sz);
|
|
p += ticket_sz;
|
|
WRITE_NUM(num, trapa_sz, p);
|
|
memcpy(p, trapa_buf, trapa_sz);
|
|
p += trapa_sz;
|
|
|
|
assert(buf + buf_sz == p);
|
|
OPENSSL_free(ticket_buf);
|
|
|
|
LSQ_DEBUG("generated %zu bytes of session resumption buffer", buf_sz);
|
|
|
|
*bufp = buf;
|
|
*buf_szp = buf_sz;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_new_session_cb (SSL *ssl, SSL_SESSION *session)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
unsigned char *buf;
|
|
size_t buf_sz;
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
assert(enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info);
|
|
|
|
if (0 == iquic_ssl_sess_to_resume_info(enc_sess, ssl, session, &buf,
|
|
&buf_sz))
|
|
enc_sess->esi_enpub->enp_stream_if->on_sess_resume_info(
|
|
enc_sess->esi_conn, buf, buf_sz);
|
|
free(buf);
|
|
enc_sess->esi_flags &= ~ESI_WANT_TICKET;
|
|
lsquic_alarmset_unset(enc_sess->esi_alset, AL_SESS_TICKET);
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct crypto_params
|
|
{
|
|
const EVP_AEAD *aead;
|
|
const EVP_MD *md;
|
|
const EVP_CIPHER *hp;
|
|
gen_hp_mask_f gen_hp_mask;
|
|
};
|
|
|
|
|
|
static int
|
|
get_crypto_params (const struct enc_sess_iquic *enc_sess,
|
|
const SSL_CIPHER *cipher, struct crypto_params *params)
|
|
{
|
|
unsigned key_sz, iv_sz;
|
|
uint32_t id;
|
|
|
|
id = SSL_CIPHER_get_id(cipher);
|
|
|
|
LSQ_DEBUG("Negotiated cipher ID is 0x%"PRIX32, id);
|
|
|
|
/* RFC 8446, Appendix B.4 */
|
|
switch (id)
|
|
{
|
|
case 0x03000000 | 0x1301: /* TLS_AES_128_GCM_SHA256 */
|
|
params->md = EVP_sha256();
|
|
params->aead = EVP_aead_aes_128_gcm();
|
|
params->hp = EVP_aes_128_ecb();
|
|
params->gen_hp_mask = gen_hp_mask_aes;
|
|
break;
|
|
case 0x03000000 | 0x1302: /* TLS_AES_256_GCM_SHA384 */
|
|
params->md = EVP_sha384();
|
|
params->aead = EVP_aead_aes_256_gcm();
|
|
params->hp = EVP_aes_256_ecb();
|
|
params->gen_hp_mask = gen_hp_mask_aes;
|
|
break;
|
|
case 0x03000000 | 0x1303: /* TLS_CHACHA20_POLY1305_SHA256 */
|
|
params->md = EVP_sha256();
|
|
params->aead = EVP_aead_chacha20_poly1305();
|
|
params->hp = NULL;
|
|
params->gen_hp_mask = gen_hp_mask_chacha20;
|
|
break;
|
|
default:
|
|
/* TLS_AES_128_CCM_SHA256 and TLS_AES_128_CCM_8_SHA256 are not
|
|
* supported by BoringSSL (grep for \b0x130[45]\b).
|
|
*/
|
|
LSQ_DEBUG("unsupported cipher 0x%"PRIX32, id);
|
|
return -1;
|
|
}
|
|
|
|
key_sz = EVP_AEAD_key_length(params->aead);
|
|
if (key_sz > EVP_MAX_KEY_LENGTH)
|
|
{
|
|
LSQ_DEBUG("key size %u is too large", key_sz);
|
|
return -1;
|
|
}
|
|
|
|
iv_sz = EVP_AEAD_nonce_length(params->aead);
|
|
if (iv_sz < 8)
|
|
iv_sz = 8; /* [draft-ietf-quic-tls-11], Section 5.3 */
|
|
if (iv_sz > EVP_MAX_IV_LENGTH)
|
|
{
|
|
LSQ_DEBUG("iv size %u is too large", iv_sz);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* [draft-ietf-quic-transport-31] Section 7.4.1:
|
|
" If 0-RTT data is accepted by the server, the server MUST NOT reduce
|
|
" any limits or alter any values that might be violated by the client
|
|
" with its 0-RTT data. In particular, a server that accepts 0-RTT data
|
|
" MUST NOT set values for the following parameters (Section 18.2) that
|
|
" are smaller than the remembered value of the parameters.
|
|
"
|
|
" * active_connection_id_limit
|
|
"
|
|
" * initial_max_data
|
|
"
|
|
" * initial_max_stream_data_bidi_local
|
|
"
|
|
" * initial_max_stream_data_bidi_remote
|
|
"
|
|
" * initial_max_stream_data_uni
|
|
"
|
|
" * initial_max_streams_bidi
|
|
"
|
|
" * initial_max_streams_uni
|
|
*/
|
|
#define REDUCTION_PROHIBITED_TPS (0 \
|
|
| (1 << TPI_ACTIVE_CONNECTION_ID_LIMIT) \
|
|
| (1 << TPI_INIT_MAX_DATA) \
|
|
| (1 << TPI_INIT_MAX_STREAMS_UNI) \
|
|
| (1 << TPI_INIT_MAX_STREAMS_BIDI) \
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL) \
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE) \
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_UNI) \
|
|
)
|
|
|
|
|
|
static int
|
|
check_server_tps_for_violations (const struct enc_sess_iquic *enc_sess,
|
|
const struct transport_params *params_0rtt,
|
|
const struct transport_params *new_params)
|
|
{
|
|
enum transport_param_id tpi;
|
|
|
|
for (tpi = 0; tpi <= MAX_NUMERIC_TPI; ++tpi)
|
|
if ((1 << tpi) & REDUCTION_PROHIBITED_TPS)
|
|
if (new_params->tp_numerics[tpi] > params_0rtt->tp_numerics[tpi])
|
|
{
|
|
LSQ_INFO("server's new TP %s increased in value from %"PRIu64
|
|
" to %"PRIu64, lsquic_tpi2str[tpi],
|
|
params_0rtt->tp_numerics[tpi],
|
|
new_params->tp_numerics[tpi]);
|
|
return -1;
|
|
}
|
|
|
|
LSQ_DEBUG("server's new transport parameters do not violate save 0-RTT "
|
|
"parameters");
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
get_peer_transport_params (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
struct transport_params *const trans_params = &enc_sess->esi_peer_tp;
|
|
struct transport_params params_0rtt;
|
|
const uint8_t *params_buf;
|
|
size_t bufsz;
|
|
char *params_str;
|
|
const enum lsquic_version version = enc_sess->esi_conn->cn_version;
|
|
int have_0rtt_tp;
|
|
|
|
SSL_get_peer_quic_transport_params(enc_sess->esi_ssl, ¶ms_buf, &bufsz);
|
|
if (!params_buf)
|
|
{
|
|
LSQ_DEBUG("no peer transport parameters");
|
|
return -1;
|
|
}
|
|
|
|
have_0rtt_tp = !!(enc_sess->esi_flags & ESI_HAVE_0RTT_TP);
|
|
if (have_0rtt_tp)
|
|
{
|
|
params_0rtt = enc_sess->esi_peer_tp;
|
|
enc_sess->esi_flags &= ~ESI_HAVE_0RTT_TP;
|
|
}
|
|
|
|
LSQ_DEBUG("have peer transport parameters (%zu bytes)", bufsz);
|
|
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
|
|
{
|
|
params_str = lsquic_mm_get_4k(&enc_sess->esi_enpub->enp_mm);
|
|
if (params_str)
|
|
{
|
|
lsquic_hexdump(params_buf, bufsz, params_str, 0x1000);
|
|
LSQ_DEBUG("transport parameters (%zd bytes):\n%s", bufsz,
|
|
params_str);
|
|
lsquic_mm_put_4k(&enc_sess->esi_enpub->enp_mm, params_str);
|
|
}
|
|
}
|
|
if (0 > (version == LSQVER_ID27 ? lsquic_tp_decode_27
|
|
: lsquic_tp_decode)(params_buf, bufsz,
|
|
!(enc_sess->esi_flags & ESI_SERVER),
|
|
trans_params))
|
|
{
|
|
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
|
|
{
|
|
params_str = lsquic_mm_get_4k(&enc_sess->esi_enpub->enp_mm);
|
|
if (params_str)
|
|
{
|
|
lsquic_hexdump(params_buf, bufsz, params_str, 0x1000);
|
|
LSQ_DEBUG("could not parse peer transport parameters "
|
|
"(%zd bytes):\n%s", bufsz, params_str);
|
|
lsquic_mm_put_4k(&enc_sess->esi_enpub->enp_mm, params_str);
|
|
}
|
|
else
|
|
LSQ_DEBUG("could not parse peer transport parameters "
|
|
"(%zd bytes)", bufsz);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (have_0rtt_tp && 0 != check_server_tps_for_violations(enc_sess,
|
|
¶ms_0rtt, trans_params))
|
|
return -1;
|
|
|
|
const lsquic_cid_t *const cids[LAST_TPI + 1] = {
|
|
[TP_CID_IDX(TPI_ORIGINAL_DEST_CID)] = enc_sess->esi_flags & ESI_ODCID ? &enc_sess->esi_odcid : NULL,
|
|
[TP_CID_IDX(TPI_RETRY_SOURCE_CID)] = enc_sess->esi_flags & ESI_RSCID ? &enc_sess->esi_rscid : NULL,
|
|
[TP_CID_IDX(TPI_INITIAL_SOURCE_CID)] = enc_sess->esi_flags & ESI_ISCID ? &enc_sess->esi_iscid : NULL,
|
|
};
|
|
|
|
unsigned must_have, must_not_have = 0;
|
|
if (version > LSQVER_ID27)
|
|
{
|
|
must_have = 1 << TPI_INITIAL_SOURCE_CID;
|
|
if (enc_sess->esi_flags & ESI_SERVER)
|
|
must_not_have |= 1 << TPI_ORIGINAL_DEST_CID;
|
|
else
|
|
must_have |= 1 << TPI_ORIGINAL_DEST_CID;
|
|
if ((enc_sess->esi_flags & (ESI_RETRY|ESI_SERVER)) == ESI_RETRY)
|
|
must_have |= 1 << TPI_RETRY_SOURCE_CID;
|
|
else
|
|
must_not_have |= 1 << TPI_RETRY_SOURCE_CID;
|
|
}
|
|
else if ((enc_sess->esi_flags & (ESI_RETRY|ESI_SERVER)) == ESI_RETRY)
|
|
must_have = 1 << TPI_ORIGINAL_DEST_CID;
|
|
else
|
|
must_have = 0;
|
|
|
|
enum transport_param_id tpi;
|
|
for (tpi = FIRST_TP_CID; tpi <= LAST_TP_CID; ++tpi)
|
|
{
|
|
if (!(must_have & (1 << tpi)))
|
|
continue;
|
|
if (!(trans_params->tp_set & (1 << tpi)))
|
|
{
|
|
LSQ_DEBUG("server did not produce %s", lsquic_tpi2str[tpi]);
|
|
return -1;
|
|
}
|
|
if (!cids[TP_CID_IDX(tpi)])
|
|
{
|
|
LSQ_WARN("do not have CID %s for checking",
|
|
lsquic_tpi2str[tpi]);
|
|
return -1;
|
|
}
|
|
if (LSQUIC_CIDS_EQ(cids[TP_CID_IDX(tpi)],
|
|
&trans_params->tp_cids[TP_CID_IDX(tpi)]))
|
|
LSQ_DEBUG("%s values match", lsquic_tpi2str[tpi]);
|
|
else
|
|
{
|
|
if (LSQ_LOG_ENABLED(LSQ_LOG_DEBUG))
|
|
{
|
|
char cidbuf[2][MAX_CID_LEN * 2 + 1];
|
|
LSQ_DEBUG("server provided %s %"CID_FMT" that does not "
|
|
"match ours %"CID_FMT, lsquic_tpi2str[tpi],
|
|
CID_BITS_B(&trans_params->tp_cids[TP_CID_IDX(tpi)],
|
|
cidbuf[0]),
|
|
CID_BITS_B(cids[TP_CID_IDX(tpi)], cidbuf[1]));
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (tpi = FIRST_TP_CID; tpi <= LAST_TP_CID; ++tpi)
|
|
if (must_not_have & (1 << tpi) & trans_params->tp_set)
|
|
{
|
|
LSQ_DEBUG("server transport parameters unexpectedly contain %s",
|
|
lsquic_tpi2str[tpi]);
|
|
return -1;
|
|
}
|
|
|
|
if ((trans_params->tp_set & (1 << TPI_LOSS_BITS))
|
|
&& enc_sess->esi_enpub->enp_settings.es_ql_bits)
|
|
{
|
|
const unsigned our_loss_bits
|
|
= enc_sess->esi_enpub->enp_settings.es_ql_bits - 1;
|
|
switch ((our_loss_bits << 1) | trans_params->tp_loss_bits)
|
|
{
|
|
case (0 << 1) | 0:
|
|
LSQ_DEBUG("both sides only tolerate QL bits: don't enable them");
|
|
break;
|
|
case (0 << 1) | 1:
|
|
LSQ_DEBUG("peer sends QL bits, we receive them");
|
|
enc_sess->esi_flags |= ESI_RECV_QL_BITS;
|
|
break;
|
|
case (1 << 1) | 0:
|
|
LSQ_DEBUG("we send QL bits, peer receives them");
|
|
enc_sess->esi_flags |= ESI_SEND_QL_BITS;
|
|
break;
|
|
default/*1 << 1) | 1*/:
|
|
LSQ_DEBUG("enable sending and receiving QL bits");
|
|
enc_sess->esi_flags |= ESI_RECV_QL_BITS;
|
|
enc_sess->esi_flags |= ESI_SEND_QL_BITS;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
LSQ_DEBUG("no QL bits");
|
|
|
|
if (trans_params->tp_set & (1 << TPI_GREASE_QUIC_BIT))
|
|
{
|
|
if (enc_sess->esi_enpub->enp_settings.es_grease_quic_bit)
|
|
{
|
|
LSQ_DEBUG("will grease the QUIC bit");
|
|
enc_sess->esi_grease = ~QUIC_BIT;
|
|
}
|
|
else
|
|
LSQ_DEBUG("greasing turned off: won't grease the QUIC bit");
|
|
}
|
|
|
|
if (enc_sess->esi_enpub->enp_settings.es_check_tp_sanity
|
|
/* We only care (and know) about HTTP/3. Other protocols may have
|
|
* their own limitations. The most generic way to do this would be
|
|
* to factor out transport parameter sanity check into a callback.
|
|
*/
|
|
&& enc_sess->esi_alpn && enc_sess->esi_alpn[0] >= 2
|
|
&& enc_sess->esi_alpn[1] == 'h'
|
|
&& enc_sess->esi_alpn[2] == '3')
|
|
{
|
|
const enum transport_param_id stream_data = enc_sess->esi_flags
|
|
& ESI_SERVER ? TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL
|
|
: TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE;
|
|
if (!((trans_params->tp_set & (1 << stream_data))
|
|
&& trans_params->tp_numerics[stream_data] >= 0x1000))
|
|
{
|
|
LSQ_INFO("peer transport parameters: %s=%"PRIu64" does not pass "
|
|
"sanity check", lsquic_tpi2str[stream_data],
|
|
trans_params->tp_numerics[stream_data]);
|
|
return -1;
|
|
}
|
|
if (!((trans_params->tp_set & (1 << TPI_INIT_MAX_DATA))
|
|
&& trans_params->tp_numerics[TPI_INIT_MAX_DATA] >= 0x1000))
|
|
{
|
|
LSQ_INFO("peer transport parameters: %s=%"PRIu64" does not pass "
|
|
"sanity check", lsquic_tpi2str[TPI_INIT_MAX_DATA],
|
|
trans_params->tp_numerics[TPI_INIT_MAX_DATA]);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
maybe_get_peer_transport_params (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
int s;
|
|
|
|
if (enc_sess->esi_flags & ESI_HAVE_PEER_TP)
|
|
return 0;
|
|
|
|
s = get_peer_transport_params(enc_sess);
|
|
if (s == 0)
|
|
enc_sess->esi_flags |= ESI_HAVE_PEER_TP;
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
enum iquic_handshake_status {
|
|
IHS_WANT_READ,
|
|
IHS_WANT_WRITE,
|
|
IHS_WANT_RW,
|
|
IHS_STOP,
|
|
};
|
|
|
|
|
|
static enum iquic_handshake_status
|
|
iquic_esfi_handshake (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
int s, err;
|
|
enum lsquic_hsk_status hsk_status;
|
|
char errbuf[ERR_ERROR_STRING_BUF_LEN];
|
|
|
|
s = SSL_do_handshake(enc_sess->esi_ssl);
|
|
if (s <= 0)
|
|
{
|
|
err = SSL_get_error(enc_sess->esi_ssl, s);
|
|
switch (err)
|
|
{
|
|
case SSL_ERROR_WANT_READ:
|
|
LSQ_DEBUG("retry read");
|
|
return IHS_WANT_READ;
|
|
case SSL_ERROR_WANT_WRITE:
|
|
LSQ_DEBUG("retry write");
|
|
return IHS_WANT_WRITE;
|
|
case SSL_ERROR_EARLY_DATA_REJECTED:
|
|
LSQ_DEBUG("early data rejected: reset");
|
|
SSL_reset_early_data_reject(enc_sess->esi_ssl);
|
|
if (enc_sess->esi_conn->cn_if->ci_early_data_failed)
|
|
enc_sess->esi_conn->cn_if->ci_early_data_failed(
|
|
enc_sess->esi_conn);
|
|
return IHS_WANT_RW;
|
|
/* fall through */
|
|
default:
|
|
LSQ_DEBUG("handshake: %s", ERR_error_string(err, errbuf));
|
|
hsk_status = LSQ_HSK_FAIL;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
|
|
if (SSL_in_early_data(enc_sess->esi_ssl))
|
|
{
|
|
LSQ_DEBUG("in early data");
|
|
if (enc_sess->esi_flags & ESI_SERVER)
|
|
LSQ_DEBUG("TODO");
|
|
else
|
|
return IHS_WANT_READ;
|
|
}
|
|
|
|
hsk_status = LSQ_HSK_OK;
|
|
LSQ_DEBUG("handshake reported complete");
|
|
EV_LOG_HSK_COMPLETED(LSQUIC_LOG_CONN_ID);
|
|
/* The ESI_USE_SSL_TICKET flag indicates if the client attempted session
|
|
* resumption. If the handshake is complete, and the client attempted
|
|
* session resumption, it must have succeeded.
|
|
*/
|
|
if (enc_sess->esi_flags & ESI_USE_SSL_TICKET)
|
|
{
|
|
hsk_status = LSQ_HSK_RESUMED_OK;
|
|
EV_LOG_SESSION_RESUMPTION(LSQUIC_LOG_CONN_ID);
|
|
}
|
|
|
|
if (0 != maybe_get_peer_transport_params(enc_sess))
|
|
{
|
|
hsk_status = LSQ_HSK_FAIL;
|
|
goto err;
|
|
}
|
|
|
|
enc_sess->esi_flags |= ESI_HANDSHAKE_OK;
|
|
enc_sess->esi_conn->cn_if->ci_hsk_done(enc_sess->esi_conn, hsk_status);
|
|
|
|
return IHS_STOP; /* XXX: what else can come on the crypto stream? */
|
|
|
|
err:
|
|
LSQ_DEBUG("handshake failed");
|
|
enc_sess->esi_conn->cn_if->ci_hsk_done(enc_sess->esi_conn, hsk_status);
|
|
return IHS_STOP;
|
|
}
|
|
|
|
|
|
static enum iquic_handshake_status
|
|
iquic_esfi_post_handshake (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
int s;
|
|
|
|
s = SSL_process_quic_post_handshake(enc_sess->esi_ssl);
|
|
LSQ_DEBUG("SSL_process_quic_post_handshake() returned %d", s);
|
|
if (s == 1)
|
|
return IHS_WANT_READ;
|
|
else
|
|
{
|
|
enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn,
|
|
"post-handshake error, code %d", s);
|
|
return IHS_STOP;
|
|
}
|
|
}
|
|
|
|
|
|
static struct transport_params *
|
|
iquic_esfi_get_peer_transport_params (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
|
|
if (enc_sess->esi_flags & ESI_HAVE_0RTT_TP)
|
|
return &enc_sess->esi_peer_tp;
|
|
else if (0 == maybe_get_peer_transport_params(enc_sess))
|
|
return &enc_sess->esi_peer_tp;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void
|
|
iquic_esfi_destroy (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
struct frab_list *fral;
|
|
LSQ_DEBUG("iquic_esfi_destroy");
|
|
|
|
for (fral = enc_sess->esi_frals; fral < enc_sess->esi_frals
|
|
+ sizeof(enc_sess->esi_frals) / sizeof(enc_sess->esi_frals[0]);
|
|
++fral)
|
|
lsquic_frab_list_cleanup(fral);
|
|
if (enc_sess->esi_ssl)
|
|
SSL_free(enc_sess->esi_ssl);
|
|
|
|
free_handshake_keys(enc_sess);
|
|
cleanup_hp(&enc_sess->esi_hp);
|
|
|
|
free(enc_sess);
|
|
}
|
|
|
|
|
|
/* See [draft-ietf-quic-tls-14], Section 4 */
|
|
static const enum enc_level hety2el[] =
|
|
{
|
|
[HETY_NOT_SET] = ENC_LEV_FORW,
|
|
[HETY_VERNEG] = 0,
|
|
[HETY_INITIAL] = ENC_LEV_CLEAR,
|
|
[HETY_RETRY] = 0,
|
|
[HETY_HANDSHAKE] = ENC_LEV_INIT,
|
|
[HETY_0RTT] = ENC_LEV_EARLY,
|
|
};
|
|
|
|
|
|
static const enum enc_level pns2enc_level[2][N_PNS] =
|
|
{
|
|
[0] = {
|
|
[PNS_INIT] = ENC_LEV_CLEAR,
|
|
[PNS_HSK] = ENC_LEV_INIT,
|
|
[PNS_APP] = ENC_LEV_EARLY,
|
|
},
|
|
[1] = {
|
|
[PNS_INIT] = ENC_LEV_CLEAR,
|
|
[PNS_HSK] = ENC_LEV_INIT,
|
|
[PNS_APP] = ENC_LEV_FORW,
|
|
},
|
|
};
|
|
|
|
|
|
static enum enc_packout
|
|
iquic_esf_encrypt_packet (enc_session_t *enc_session_p,
|
|
const struct lsquic_engine_public *enpub, struct lsquic_conn *lconn_UNUSED,
|
|
struct lsquic_packet_out *packet_out)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
struct lsquic_conn *const lconn = enc_sess->esi_conn;
|
|
unsigned char *dst;
|
|
const struct crypto_ctx_pair *pair;
|
|
const struct crypto_ctx *crypto_ctx;
|
|
struct header_prot *hp;
|
|
enum enc_level enc_level;
|
|
unsigned char nonce_buf[ sizeof(crypto_ctx->yk_iv_buf) + 8 ];
|
|
unsigned char *nonce, *begin_xor;
|
|
lsquic_packno_t packno;
|
|
size_t out_sz, dst_sz;
|
|
int header_sz;
|
|
int ipv6;
|
|
unsigned packno_off, packno_len;
|
|
enum packnum_space pns;
|
|
char errbuf[ERR_ERROR_STRING_BUF_LEN];
|
|
|
|
pns = lsquic_packet_out_pns(packet_out);
|
|
enc_level = pns2enc_level[ enc_sess->esi_have_forw ][ pns ];
|
|
|
|
if (enc_level == ENC_LEV_FORW)
|
|
{
|
|
pair = &enc_sess->esi_pairs[ enc_sess->esi_key_phase ];
|
|
crypto_ctx = &pair->ykp_ctx[ 1 ];
|
|
hp = &enc_sess->esi_hp;
|
|
}
|
|
else if (enc_sess->esi_hsk_pairs)
|
|
{
|
|
pair = &enc_sess->esi_hsk_pairs[ enc_level ];
|
|
crypto_ctx = &pair->ykp_ctx[ 1 ];
|
|
hp = &enc_sess->esi_hsk_hps[ enc_level ];
|
|
}
|
|
else
|
|
{
|
|
LSQ_WARN("no keys for encryption level %s",
|
|
lsquic_enclev2str[enc_level]);
|
|
return ENCPA_BADCRYPT;
|
|
}
|
|
|
|
if (UNLIKELY(0 == (crypto_ctx->yk_flags & YK_INITED)))
|
|
{
|
|
LSQ_WARN("encrypt crypto context at level %s not initialized",
|
|
lsquic_enclev2str[enc_level]);
|
|
return ENCPA_BADCRYPT;
|
|
}
|
|
|
|
if (packet_out->po_data_sz < 3)
|
|
{
|
|
/* [draft-ietf-quic-tls-20] Section 5.4.2 */
|
|
enum packno_bits bits = lsquic_packet_out_packno_bits(packet_out);
|
|
unsigned len = iquic_packno_bits2len(bits);
|
|
if (packet_out->po_data_sz + len < 4)
|
|
{
|
|
len = 4 - packet_out->po_data_sz - len;
|
|
memset(packet_out->po_data + packet_out->po_data_sz, 0, len);
|
|
packet_out->po_data_sz += len;
|
|
packet_out->po_frame_types |= QUIC_FTBIT_PADDING;
|
|
LSQ_DEBUG("padded packet %"PRIu64" with %u bytes of PADDING",
|
|
packet_out->po_packno, len);
|
|
}
|
|
}
|
|
|
|
dst_sz = lconn->cn_pf->pf_packout_size(lconn, packet_out);
|
|
ipv6 = NP_IS_IPv6(packet_out->po_path);
|
|
dst = enpub->enp_pmi->pmi_allocate(enpub->enp_pmi_ctx,
|
|
packet_out->po_path->np_peer_ctx, lconn->cn_conn_ctx, dst_sz, ipv6);
|
|
if (!dst)
|
|
{
|
|
LSQ_DEBUG("could not allocate memory for outgoing packet of size %zd",
|
|
dst_sz);
|
|
return ENCPA_NOMEM;
|
|
}
|
|
|
|
/* Align nonce so we can perform XOR safely in one shot: */
|
|
begin_xor = nonce_buf + sizeof(nonce_buf) - 8;
|
|
begin_xor = (unsigned char *) ((uintptr_t) begin_xor & ~0x7);
|
|
nonce = begin_xor - crypto_ctx->yk_iv_sz + 8;
|
|
memcpy(nonce, crypto_ctx->yk_iv_buf, crypto_ctx->yk_iv_sz);
|
|
packno = packet_out->po_packno;
|
|
if (s_log_seal_and_open)
|
|
LSQ_DEBUG("seal: iv: %s; packno: 0x%"PRIX64,
|
|
HEXSTR(crypto_ctx->yk_iv_buf, crypto_ctx->yk_iv_sz, s_str), packno);
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
packno = bswap_64(packno);
|
|
#endif
|
|
*((uint64_t *) begin_xor) ^= packno;
|
|
|
|
header_sz = lconn->cn_pf->pf_gen_reg_pkt_header(lconn, packet_out, dst,
|
|
dst_sz, &packno_off, &packno_len);
|
|
if (header_sz < 0)
|
|
goto err;
|
|
if (enc_level == ENC_LEV_FORW)
|
|
dst[0] |= enc_sess->esi_key_phase << 2;
|
|
dst[0] &= enc_sess->esi_grease | packet_out->po_path->np_dcid.idbuf[0];
|
|
|
|
if (s_log_seal_and_open)
|
|
{
|
|
LSQ_DEBUG("seal: nonce (%u bytes): %s", crypto_ctx->yk_iv_sz,
|
|
HEXSTR(nonce, crypto_ctx->yk_iv_sz, s_str));
|
|
LSQ_DEBUG("seal: ad (%u bytes): %s", header_sz,
|
|
HEXSTR(dst, header_sz, s_str));
|
|
LSQ_DEBUG("seal: in (%u bytes): %s", packet_out->po_data_sz,
|
|
HEXSTR(packet_out->po_data, packet_out->po_data_sz, s_str));
|
|
}
|
|
if (!EVP_AEAD_CTX_seal(&crypto_ctx->yk_aead_ctx, dst + header_sz, &out_sz,
|
|
dst_sz - header_sz, nonce, crypto_ctx->yk_iv_sz, packet_out->po_data,
|
|
packet_out->po_data_sz, dst, header_sz))
|
|
{
|
|
LSQ_WARN("cannot seal packet #%"PRIu64": %s", packet_out->po_packno,
|
|
ERR_error_string(ERR_get_error(), errbuf));
|
|
goto err;
|
|
}
|
|
assert(out_sz == dst_sz - header_sz);
|
|
|
|
#ifndef NDEBUG
|
|
const unsigned sample_off = packno_off + 4;
|
|
assert(sample_off + IQUIC_TAG_LEN <= dst_sz);
|
|
#endif
|
|
|
|
packet_out->po_enc_data = dst;
|
|
packet_out->po_enc_data_sz = dst_sz;
|
|
packet_out->po_sent_sz = dst_sz;
|
|
packet_out->po_flags &= ~PO_IPv6;
|
|
packet_out->po_flags |= PO_ENCRYPTED|PO_SENT_SZ|(ipv6 << POIPv6_SHIFT);
|
|
packet_out->po_dcid_len = packet_out->po_path->np_dcid.len;
|
|
lsquic_packet_out_set_enc_level(packet_out, enc_level);
|
|
lsquic_packet_out_set_kp(packet_out, enc_sess->esi_key_phase);
|
|
|
|
if (enc_level == ENC_LEV_FORW && hp->hp_gen_mask != gen_hp_mask_chacha20)
|
|
apply_hp_batch(enc_sess, hp, packet_out, packno_off, packno_len);
|
|
else
|
|
apply_hp_immediately(enc_sess, hp, packet_out, packno_off, packno_len);
|
|
|
|
return ENCPA_OK;
|
|
|
|
err:
|
|
enpub->enp_pmi->pmi_return(enpub->enp_pmi_ctx,
|
|
packet_out->po_path->np_peer_ctx, dst, ipv6);
|
|
return ENCPA_BADCRYPT;
|
|
}
|
|
|
|
|
|
static void
|
|
iquic_esf_flush_encryption (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
|
|
if (enc_sess->esi_hp_batch_idx)
|
|
{
|
|
LSQ_DEBUG("flush header protection application, count: %u",
|
|
enc_sess->esi_hp_batch_idx);
|
|
flush_hp_batch(enc_sess);
|
|
}
|
|
}
|
|
|
|
|
|
static struct ku_label
|
|
{
|
|
const char *str;
|
|
uint8_t len;
|
|
}
|
|
|
|
|
|
select_ku_label (const struct enc_sess_iquic *enc_sess)
|
|
{
|
|
return (struct ku_label) { "quic ku", 7, };
|
|
}
|
|
|
|
|
|
static enum dec_packin
|
|
iquic_esf_decrypt_packet (enc_session_t *enc_session_p,
|
|
struct lsquic_engine_public *enpub, const struct lsquic_conn *lconn,
|
|
struct lsquic_packet_in *packet_in)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
unsigned char *dst;
|
|
struct crypto_ctx_pair *pair;
|
|
struct header_prot *hp;
|
|
struct crypto_ctx *crypto_ctx = NULL;
|
|
unsigned char nonce_buf[ sizeof(crypto_ctx->yk_iv_buf) + 8 ];
|
|
unsigned char *nonce, *begin_xor;
|
|
unsigned sample_off, packno_len, key_phase;
|
|
enum enc_level enc_level;
|
|
enum packnum_space pns;
|
|
lsquic_packno_t packno;
|
|
size_t out_sz;
|
|
enum dec_packin dec_packin;
|
|
int s;
|
|
const size_t dst_sz = packet_in->pi_data_sz;
|
|
unsigned char new_secret[EVP_MAX_KEY_LENGTH];
|
|
struct crypto_ctx crypto_ctx_buf;
|
|
char secret_str[EVP_MAX_KEY_LENGTH * 2 + 1];
|
|
char errbuf[ERR_ERROR_STRING_BUF_LEN];
|
|
|
|
dst = lsquic_mm_get_packet_in_buf(&enpub->enp_mm, dst_sz);
|
|
if (!dst)
|
|
{
|
|
LSQ_WARN("cannot allocate memory to copy incoming packet data");
|
|
dec_packin = DECPI_NOMEM;
|
|
goto err;
|
|
}
|
|
|
|
enc_level = hety2el[packet_in->pi_header_type];
|
|
if (enc_level == ENC_LEV_FORW)
|
|
hp = &enc_sess->esi_hp;
|
|
else if (enc_sess->esi_hsk_pairs)
|
|
hp = &enc_sess->esi_hsk_hps[ enc_level ];
|
|
else
|
|
hp = NULL;
|
|
|
|
if (UNLIKELY(!(hp && header_prot_inited(hp, 0))))
|
|
{
|
|
LSQ_DEBUG("header protection for level %u not initialized yet",
|
|
enc_level);
|
|
dec_packin = DECPI_NOT_YET;
|
|
goto err;
|
|
}
|
|
|
|
/* Decrypt packet number. After this operation, packet_in is adjusted:
|
|
* the packet number becomes part of the header.
|
|
*/
|
|
sample_off = packet_in->pi_header_sz + 4;
|
|
if (sample_off + IQUIC_TAG_LEN > packet_in->pi_data_sz)
|
|
{
|
|
LSQ_INFO("packet data is too short: %hu bytes",
|
|
packet_in->pi_data_sz);
|
|
dec_packin = DECPI_TOO_SHORT;
|
|
goto err;
|
|
}
|
|
memcpy(dst, packet_in->pi_data, sample_off);
|
|
packet_in->pi_packno =
|
|
packno = strip_hp(enc_sess, hp,
|
|
packet_in->pi_data + sample_off,
|
|
dst, packet_in->pi_header_sz, &packno_len);
|
|
|
|
if (enc_level == ENC_LEV_FORW)
|
|
{
|
|
key_phase = (dst[0] & 0x04) > 0;
|
|
pair = &enc_sess->esi_pairs[ key_phase ];
|
|
if (key_phase == enc_sess->esi_key_phase)
|
|
{
|
|
crypto_ctx = &pair->ykp_ctx[ 0 ];
|
|
/* Checked by header_prot_inited() above */
|
|
assert(crypto_ctx->yk_flags & YK_INITED);
|
|
}
|
|
else if (!is_valid_packno(
|
|
enc_sess->esi_pairs[enc_sess->esi_key_phase].ykp_thresh)
|
|
|| packet_in->pi_packno
|
|
> enc_sess->esi_pairs[enc_sess->esi_key_phase].ykp_thresh)
|
|
{
|
|
const struct ku_label kl = select_ku_label(enc_sess);
|
|
lsquic_qhkdf_expand(enc_sess->esi_md,
|
|
enc_sess->esi_traffic_secrets[0], enc_sess->esi_trasec_sz,
|
|
kl.str, kl.len, new_secret, enc_sess->esi_trasec_sz);
|
|
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
|
|
LSQ_DEBUG("key phase changed to %u, will try decrypting using "
|
|
"new secret %s", key_phase, HEXSTR(new_secret,
|
|
enc_sess->esi_trasec_sz, secret_str));
|
|
else
|
|
LSQ_DEBUG("key phase changed to %u, will try decrypting using "
|
|
"new secret", key_phase);
|
|
crypto_ctx = &crypto_ctx_buf;
|
|
crypto_ctx->yk_flags = 0;
|
|
s = init_crypto_ctx(crypto_ctx, enc_sess->esi_md,
|
|
enc_sess->esi_aead, new_secret, enc_sess->esi_trasec_sz,
|
|
evp_aead_open);
|
|
if (s != 0)
|
|
{
|
|
LSQ_ERROR("could not init open crypto ctx (key phase)");
|
|
dec_packin = DECPI_BADCRYPT;
|
|
goto err;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
crypto_ctx = &pair->ykp_ctx[ 0 ];
|
|
if (UNLIKELY(0 == (crypto_ctx->yk_flags & YK_INITED)))
|
|
{
|
|
LSQ_DEBUG("supposedly older context is not initialized (key "
|
|
"phase: %u)", key_phase);
|
|
dec_packin = DECPI_BADCRYPT;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
key_phase = 0;
|
|
assert(enc_sess->esi_hsk_pairs);
|
|
pair = &enc_sess->esi_hsk_pairs[ enc_level ];
|
|
crypto_ctx = &pair->ykp_ctx[ 0 ];
|
|
if (UNLIKELY(0 == (crypto_ctx->yk_flags & YK_INITED)))
|
|
{
|
|
LSQ_WARN("decrypt crypto context at level %s not initialized",
|
|
lsquic_enclev2str[enc_level]);
|
|
dec_packin = DECPI_BADCRYPT;
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
if (s_log_seal_and_open)
|
|
LSQ_DEBUG("open: iv: %s; packno: 0x%"PRIX64,
|
|
HEXSTR(crypto_ctx->yk_iv_buf, crypto_ctx->yk_iv_sz, s_str), packno);
|
|
/* Align nonce so we can perform XOR safely in one shot: */
|
|
begin_xor = nonce_buf + sizeof(nonce_buf) - 8;
|
|
begin_xor = (unsigned char *) ((uintptr_t) begin_xor & ~0x7);
|
|
nonce = begin_xor - crypto_ctx->yk_iv_sz + 8;
|
|
memcpy(nonce, crypto_ctx->yk_iv_buf, crypto_ctx->yk_iv_sz);
|
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
|
packno = bswap_64(packno);
|
|
#endif
|
|
*((uint64_t *) begin_xor) ^= packno;
|
|
|
|
packet_in->pi_header_sz += packno_len;
|
|
|
|
if (s_log_seal_and_open)
|
|
{
|
|
LSQ_DEBUG("open: nonce (%u bytes): %s", crypto_ctx->yk_iv_sz,
|
|
HEXSTR(nonce, crypto_ctx->yk_iv_sz, s_str));
|
|
LSQ_DEBUG("open: ad (%u bytes): %s", packet_in->pi_header_sz,
|
|
HEXSTR(dst, packet_in->pi_header_sz, s_str));
|
|
LSQ_DEBUG("open: in (%u bytes): %s", packet_in->pi_data_sz
|
|
- packet_in->pi_header_sz, HEXSTR(packet_in->pi_data
|
|
+ packet_in->pi_header_sz, packet_in->pi_data_sz
|
|
- packet_in->pi_header_sz, s_str));
|
|
}
|
|
if (!EVP_AEAD_CTX_open(&crypto_ctx->yk_aead_ctx,
|
|
dst + packet_in->pi_header_sz, &out_sz,
|
|
dst_sz - packet_in->pi_header_sz, nonce, crypto_ctx->yk_iv_sz,
|
|
packet_in->pi_data + packet_in->pi_header_sz,
|
|
packet_in->pi_data_sz - packet_in->pi_header_sz,
|
|
dst, packet_in->pi_header_sz))
|
|
{
|
|
LSQ_INFO("cannot open packet #%"PRIu64": %s", packet_in->pi_packno,
|
|
ERR_error_string(ERR_get_error(), errbuf));
|
|
dec_packin = DECPI_BADCRYPT;
|
|
goto err;
|
|
}
|
|
|
|
if (enc_sess->esi_flags & ESI_SEND_QL_BITS)
|
|
{
|
|
packet_in->pi_flags |= PI_LOG_QL_BITS;
|
|
if (dst[0] & 0x10)
|
|
packet_in->pi_flags |= PI_SQUARE_BIT;
|
|
if (dst[0] & 0x08)
|
|
packet_in->pi_flags |= PI_LOSS_BIT;
|
|
}
|
|
else if (dst[0] & (0x0C << (packet_in->pi_header_type == HETY_NOT_SET)))
|
|
{
|
|
LSQ_DEBUG("reserved bits are not set to zero");
|
|
dec_packin = DECPI_VIOLATION;
|
|
goto err;
|
|
}
|
|
|
|
if (crypto_ctx == &crypto_ctx_buf)
|
|
{
|
|
LSQ_DEBUG("decryption in the new key phase %u successful, rotate "
|
|
"keys", key_phase);
|
|
const struct ku_label kl = select_ku_label(enc_sess);
|
|
pair->ykp_thresh = packet_in->pi_packno;
|
|
pair->ykp_ctx[ 0 ] = crypto_ctx_buf;
|
|
memcpy(enc_sess->esi_traffic_secrets[ 0 ], new_secret,
|
|
enc_sess->esi_trasec_sz);
|
|
lsquic_qhkdf_expand(enc_sess->esi_md,
|
|
enc_sess->esi_traffic_secrets[1], enc_sess->esi_trasec_sz,
|
|
kl.str, kl.len, new_secret, enc_sess->esi_trasec_sz);
|
|
memcpy(enc_sess->esi_traffic_secrets[1], new_secret,
|
|
enc_sess->esi_trasec_sz);
|
|
s = init_crypto_ctx(&pair->ykp_ctx[1], enc_sess->esi_md,
|
|
enc_sess->esi_aead, new_secret, enc_sess->esi_trasec_sz,
|
|
evp_aead_seal);
|
|
if (s != 0)
|
|
{
|
|
LSQ_ERROR("could not init seal crypto ctx (key phase)");
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[1]);
|
|
/* This is a severe error, abort connection */
|
|
enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn,
|
|
"crypto ctx failure during key phase shift");
|
|
dec_packin = DECPI_BADCRYPT;
|
|
goto err;
|
|
}
|
|
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
|
|
log_crypto_pair(enc_sess, pair, "updated");
|
|
enc_sess->esi_key_phase = key_phase;
|
|
}
|
|
|
|
packet_in->pi_data_sz = packet_in->pi_header_sz + out_sz;
|
|
if (packet_in->pi_flags & PI_OWN_DATA)
|
|
lsquic_mm_put_packet_in_buf(&enpub->enp_mm, packet_in->pi_data,
|
|
packet_in->pi_data_sz);
|
|
packet_in->pi_data = dst;
|
|
packet_in->pi_flags |= PI_OWN_DATA | PI_DECRYPTED
|
|
| (enc_level << PIBIT_ENC_LEV_SHIFT);
|
|
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "decrypted packet %"PRIu64,
|
|
packet_in->pi_packno);
|
|
pns = lsquic_enclev2pns[enc_level];
|
|
if (packet_in->pi_packno > enc_sess->esi_max_packno[pns]
|
|
|| !(enc_sess->esi_flags & (ESI_MAX_PACKNO_INIT << pns)))
|
|
enc_sess->esi_max_packno[pns] = packet_in->pi_packno;
|
|
enc_sess->esi_flags |= ESI_MAX_PACKNO_INIT << pns;
|
|
if (is_valid_packno(pair->ykp_thresh)
|
|
&& packet_in->pi_packno > pair->ykp_thresh)
|
|
pair->ykp_thresh = packet_in->pi_packno;
|
|
return DECPI_OK;
|
|
|
|
err:
|
|
if (crypto_ctx == &crypto_ctx_buf)
|
|
cleanup_crypto_ctx(crypto_ctx);
|
|
if (dst)
|
|
lsquic_mm_put_packet_in_buf(&enpub->enp_mm, dst, dst_sz);
|
|
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "could not decrypt packet (type %s, "
|
|
"number %"PRIu64")", lsquic_hety2str[packet_in->pi_header_type],
|
|
packet_in->pi_packno);
|
|
return dec_packin;
|
|
}
|
|
|
|
|
|
static const char *
|
|
iquic_esf_get_sni (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
const char *server_name;
|
|
|
|
server_name = SSL_get_servername(enc_sess->esi_ssl, TLSEXT_NAMETYPE_host_name);
|
|
#ifndef NDEBUG
|
|
if (!server_name)
|
|
server_name = enc_sess->esi_sni_bypass;
|
|
#endif
|
|
return server_name;
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esf_global_init (int flags)
|
|
{
|
|
s_idx = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
|
|
if (s_idx >= 0)
|
|
{
|
|
LSQ_LOG1(LSQ_LOG_DEBUG, "SSL extra data index: %d", s_idx);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
LSQ_LOG1(LSQ_LOG_ERROR, "%s: could not select index", __func__);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
iquic_esf_global_cleanup (void)
|
|
{
|
|
}
|
|
|
|
|
|
static void *
|
|
copy_X509 (void *cert)
|
|
{
|
|
X509_up_ref(cert);
|
|
return cert;
|
|
}
|
|
|
|
|
|
static struct stack_st_X509 *
|
|
iquic_esf_get_server_cert_chain (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
STACK_OF(X509) *chain;
|
|
|
|
if (enc_sess->esi_ssl)
|
|
{
|
|
chain = SSL_get_peer_cert_chain(enc_sess->esi_ssl);
|
|
return (struct stack_st_X509 *)
|
|
sk_deep_copy((const _STACK *) chain, sk_X509_call_copy_func,
|
|
copy_X509, sk_X509_call_free_func, (void(*)(void*))X509_free);
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const char *
|
|
iquic_esf_cipher (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
const SSL_CIPHER *cipher;
|
|
|
|
if (enc_sess->esi_flags & ESI_CACHED_INFO)
|
|
return enc_sess->esi_cached_info.cipher_name;
|
|
else if (enc_sess->esi_ssl)
|
|
{
|
|
cipher = SSL_get_current_cipher(enc_sess->esi_ssl);
|
|
return SSL_CIPHER_get_name(cipher);
|
|
}
|
|
else
|
|
{
|
|
LSQ_WARN("SSL session is not set");
|
|
return "null";
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esf_keysize (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
const SSL_CIPHER *cipher;
|
|
uint32_t id;
|
|
|
|
if (enc_sess->esi_flags & ESI_CACHED_INFO)
|
|
return enc_sess->esi_cached_info.alg_bits / 8;
|
|
else if (enc_sess->esi_ssl)
|
|
{
|
|
cipher = SSL_get_current_cipher(enc_sess->esi_ssl);
|
|
id = SSL_CIPHER_get_id(cipher);
|
|
|
|
/* RFC 8446, Appendix B.4 */
|
|
switch (id)
|
|
{
|
|
case 0x03000000 | 0x1301: /* TLS_AES_128_GCM_SHA256 */
|
|
return 128 / 8;
|
|
case 0x03000000 | 0x1302: /* TLS_AES_256_GCM_SHA384 */
|
|
return 256 / 8;
|
|
case 0x03000000 | 0x1303: /* TLS_CHACHA20_POLY1305_SHA256 */
|
|
return 256 / 8;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LSQ_WARN("SSL session is not set");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esf_alg_keysize (enc_session_t *enc_session_p)
|
|
{
|
|
/* Modeled on SslConnection::getEnv() */
|
|
return iquic_esf_keysize(enc_session_p);
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esf_sess_resume_enabled (enc_session_t *enc_session_p)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
return !!(enc_sess->esi_flags & ESI_USE_SSL_TICKET);
|
|
}
|
|
|
|
|
|
static void
|
|
iquic_esfi_set_iscid (enc_session_t *enc_session_p,
|
|
const struct lsquic_packet_in *packet_in)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
|
|
if (!(enc_sess->esi_flags & ESI_ISCID))
|
|
{
|
|
lsquic_scid_from_packet_in(packet_in, &enc_sess->esi_iscid);
|
|
enc_sess->esi_flags |= ESI_ISCID;
|
|
LSQ_DEBUGC("set ISCID to %"CID_FMT, CID_BITS(&enc_sess->esi_iscid));
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esfi_reset_dcid (enc_session_t *enc_session_p,
|
|
const lsquic_cid_t *old_dcid, const lsquic_cid_t *new_dcid)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = enc_session_p;
|
|
struct crypto_ctx_pair *pair;
|
|
|
|
enc_sess->esi_odcid = *old_dcid;
|
|
enc_sess->esi_rscid = *new_dcid;
|
|
enc_sess->esi_flags |= ESI_ODCID|ESI_RSCID|ESI_RETRY;
|
|
|
|
/* Free previous handshake keys */
|
|
assert(enc_sess->esi_hsk_pairs);
|
|
pair = &enc_sess->esi_hsk_pairs[ENC_LEV_CLEAR];
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[0]);
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[1]);
|
|
|
|
if (0 == setup_handshake_keys(enc_sess, new_dcid))
|
|
{
|
|
LSQ_INFOC("reset DCID to %"CID_FMT, CID_BITS(new_dcid));
|
|
return 0;
|
|
}
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void
|
|
iquic_esfi_handshake_confirmed (enc_session_t *sess)
|
|
{
|
|
struct enc_sess_iquic *enc_sess = (struct enc_sess_iquic *) sess;
|
|
|
|
if (!(enc_sess->esi_flags & ESI_HSK_CONFIRMED))
|
|
{
|
|
LSQ_DEBUG("handshake has been confirmed");
|
|
enc_sess->esi_flags |= ESI_HSK_CONFIRMED;
|
|
maybe_drop_SSL(enc_sess);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esfi_in_init (enc_session_t *sess)
|
|
{
|
|
struct enc_sess_iquic *enc_sess = (struct enc_sess_iquic *) sess;
|
|
int in_init;
|
|
|
|
if (enc_sess->esi_ssl)
|
|
{
|
|
in_init = SSL_in_init(enc_sess->esi_ssl);
|
|
LSQ_DEBUG("in_init: %d", in_init);
|
|
return in_init;
|
|
}
|
|
else
|
|
{
|
|
LSQ_DEBUG("no SSL object, in_init: 0");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
iquic_esfi_data_in (enc_session_t *sess, enum enc_level enc_level,
|
|
const unsigned char *buf, size_t len)
|
|
{
|
|
struct enc_sess_iquic *enc_sess = (struct enc_sess_iquic *) sess;
|
|
int s;
|
|
size_t str_sz;
|
|
char str[MAX(1500 * 5, ERR_ERROR_STRING_BUF_LEN)];
|
|
|
|
if (!enc_sess->esi_ssl)
|
|
return -1;
|
|
|
|
s = SSL_provide_quic_data(enc_sess->esi_ssl,
|
|
(enum ssl_encryption_level_t) enc_level, buf, len);
|
|
if (!s)
|
|
{
|
|
LSQ_WARN("SSL_provide_quic_data returned false: %s",
|
|
ERR_error_string(ERR_get_error(), str));
|
|
return -1;
|
|
}
|
|
LSQ_DEBUG("provided %zu bytes of %u-level data to SSL", len, enc_level);
|
|
str_sz = lsquic_hexdump(buf, len, str, sizeof(str));
|
|
LSQ_DEBUG("\n%.*s", (int) str_sz, str);
|
|
s = SSL_do_handshake(enc_sess->esi_ssl);
|
|
LSQ_DEBUG("do_handshake returns %d", s);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void iquic_esfi_shake_stream (enc_session_t *sess,
|
|
struct lsquic_stream *stream, const char *what);
|
|
|
|
|
|
const struct enc_session_funcs_iquic lsquic_enc_session_iquic_ietf_v1 =
|
|
{
|
|
.esfi_create_client = iquic_esfi_create_client,
|
|
.esfi_destroy = iquic_esfi_destroy,
|
|
.esfi_get_peer_transport_params
|
|
= iquic_esfi_get_peer_transport_params,
|
|
.esfi_reset_dcid = iquic_esfi_reset_dcid,
|
|
.esfi_init_server = iquic_esfi_init_server,
|
|
.esfi_set_iscid = iquic_esfi_set_iscid,
|
|
.esfi_set_streams = iquic_esfi_set_streams,
|
|
.esfi_create_server = iquic_esfi_create_server,
|
|
.esfi_shake_stream = iquic_esfi_shake_stream,
|
|
.esfi_handshake_confirmed
|
|
= iquic_esfi_handshake_confirmed,
|
|
.esfi_in_init = iquic_esfi_in_init,
|
|
.esfi_data_in = iquic_esfi_data_in,
|
|
};
|
|
|
|
|
|
const struct enc_session_funcs_common lsquic_enc_session_common_ietf_v1 =
|
|
{
|
|
.esf_encrypt_packet = iquic_esf_encrypt_packet,
|
|
.esf_decrypt_packet = iquic_esf_decrypt_packet,
|
|
.esf_flush_encryption= iquic_esf_flush_encryption,
|
|
.esf_global_cleanup = iquic_esf_global_cleanup,
|
|
.esf_global_init = iquic_esf_global_init,
|
|
.esf_tag_len = IQUIC_TAG_LEN,
|
|
.esf_get_server_cert_chain
|
|
= iquic_esf_get_server_cert_chain,
|
|
.esf_get_sni = iquic_esf_get_sni,
|
|
.esf_cipher = iquic_esf_cipher,
|
|
.esf_keysize = iquic_esf_keysize,
|
|
.esf_alg_keysize = iquic_esf_alg_keysize,
|
|
.esf_is_sess_resume_enabled = iquic_esf_sess_resume_enabled,
|
|
.esf_set_conn = iquic_esf_set_conn,
|
|
};
|
|
|
|
|
|
static
|
|
const struct enc_session_funcs_common lsquic_enc_session_common_ietf_v1_no_flush =
|
|
{
|
|
.esf_encrypt_packet = iquic_esf_encrypt_packet,
|
|
.esf_decrypt_packet = iquic_esf_decrypt_packet,
|
|
.esf_global_cleanup = iquic_esf_global_cleanup,
|
|
.esf_global_init = iquic_esf_global_init,
|
|
.esf_tag_len = IQUIC_TAG_LEN,
|
|
.esf_get_server_cert_chain
|
|
= iquic_esf_get_server_cert_chain,
|
|
.esf_get_sni = iquic_esf_get_sni,
|
|
.esf_cipher = iquic_esf_cipher,
|
|
.esf_keysize = iquic_esf_keysize,
|
|
.esf_alg_keysize = iquic_esf_alg_keysize,
|
|
.esf_is_sess_resume_enabled = iquic_esf_sess_resume_enabled,
|
|
.esf_set_conn = iquic_esf_set_conn,
|
|
};
|
|
|
|
|
|
static void
|
|
cache_info (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
const SSL_CIPHER *cipher;
|
|
|
|
cipher = SSL_get_current_cipher(enc_sess->esi_ssl);
|
|
enc_sess->esi_cached_info.cipher_name = SSL_CIPHER_get_name(cipher);
|
|
SSL_CIPHER_get_bits(cipher, &enc_sess->esi_cached_info.alg_bits);
|
|
enc_sess->esi_flags |= ESI_CACHED_INFO;
|
|
}
|
|
|
|
|
|
static void
|
|
drop_SSL (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
LSQ_DEBUG("drop SSL object");
|
|
if (enc_sess->esi_conn->cn_if->ci_drop_crypto_streams)
|
|
enc_sess->esi_conn->cn_if->ci_drop_crypto_streams(
|
|
enc_sess->esi_conn);
|
|
cache_info(enc_sess);
|
|
SSL_free(enc_sess->esi_ssl);
|
|
enc_sess->esi_ssl = NULL;
|
|
free_handshake_keys(enc_sess);
|
|
}
|
|
|
|
|
|
static void
|
|
maybe_drop_SSL (struct enc_sess_iquic *enc_sess)
|
|
{
|
|
/* We rely on the following BoringSSL property: it writes new session
|
|
* tickets before marking handshake as complete. In this case, the new
|
|
* session tickets have either been successfully written to crypto stream,
|
|
* in which case we can close it, or (unlikely) they are buffered in the
|
|
* frab list.
|
|
*/
|
|
if ((enc_sess->esi_flags & (ESI_HSK_CONFIRMED|ESI_HANDSHAKE_OK))
|
|
== (ESI_HSK_CONFIRMED|ESI_HANDSHAKE_OK)
|
|
&& enc_sess->esi_ssl
|
|
&& lsquic_frab_list_empty(&enc_sess->esi_frals[ENC_LEV_FORW]))
|
|
{
|
|
if ((enc_sess->esi_flags & (ESI_SERVER|ESI_WANT_TICKET))
|
|
!= ESI_WANT_TICKET)
|
|
drop_SSL(enc_sess);
|
|
else if (enc_sess->esi_alset
|
|
&& !lsquic_alarmset_is_set(enc_sess->esi_alset, AL_SESS_TICKET))
|
|
{
|
|
LSQ_DEBUG("no session ticket: delay dropping SSL object");
|
|
lsquic_alarmset_set(enc_sess->esi_alset, AL_SESS_TICKET,
|
|
/* Wait up to two seconds for session tickets */
|
|
lsquic_time_now() + 2000000);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
no_sess_ticket (enum alarm_id alarm_id, void *ctx,
|
|
lsquic_time_t expiry, lsquic_time_t now)
|
|
{
|
|
struct enc_sess_iquic *enc_sess = ctx;
|
|
|
|
LSQ_DEBUG("no session tickets forthcoming -- drop SSL");
|
|
drop_SSL(enc_sess);
|
|
}
|
|
|
|
|
|
typedef char enums_have_the_same_value[
|
|
(int) ssl_encryption_initial == (int) ENC_LEV_CLEAR &&
|
|
(int) ssl_encryption_early_data == (int) ENC_LEV_EARLY &&
|
|
(int) ssl_encryption_handshake == (int) ENC_LEV_INIT &&
|
|
(int) ssl_encryption_application == (int) ENC_LEV_FORW ? 1 : -1];
|
|
|
|
static int
|
|
set_secret (SSL *ssl, enum ssl_encryption_level_t level,
|
|
const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len, int rw)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
struct crypto_ctx_pair *pair;
|
|
struct header_prot *hp;
|
|
struct crypto_params crypa;
|
|
int have_alpn;
|
|
const unsigned char *alpn;
|
|
unsigned alpn_len;
|
|
size_t key_len;
|
|
const enum enc_level enc_level = (enum enc_level) level;
|
|
unsigned char key[EVP_MAX_KEY_LENGTH];
|
|
char errbuf[ERR_ERROR_STRING_BUF_LEN];
|
|
#define hexbuf errbuf
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
if (!enc_sess)
|
|
return 0;
|
|
|
|
if ((enc_sess->esi_flags & (ESI_ALPN_CHECKED|ESI_SERVER)) == ESI_SERVER
|
|
&& enc_sess->esi_alpn)
|
|
{
|
|
enc_sess->esi_flags |= ESI_ALPN_CHECKED;
|
|
SSL_get0_alpn_selected(enc_sess->esi_ssl, &alpn, &alpn_len);
|
|
have_alpn = alpn && alpn_len == enc_sess->esi_alpn[0]
|
|
&& 0 == memcmp(alpn, enc_sess->esi_alpn + 1, alpn_len);
|
|
if (have_alpn)
|
|
LSQ_DEBUG("Selected ALPN %.*s", (int) alpn_len, (char *) alpn);
|
|
else
|
|
{
|
|
LSQ_INFO("No ALPN is selected: send fatal alert");
|
|
SSL_send_fatal_alert(ssl, ALERT_NO_APPLICATION_PROTOCOL);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (0 != get_crypto_params(enc_sess, cipher, &crypa))
|
|
return 0;
|
|
|
|
/*
|
|
if (enc_sess->esi_flags & ESI_SERVER)
|
|
secrets[0] = read_secret, secrets[1] = write_secret;
|
|
else
|
|
secrets[0] = write_secret, secrets[1] = read_secret;
|
|
*/
|
|
|
|
if (enc_level < ENC_LEV_FORW)
|
|
{
|
|
assert(enc_sess->esi_hsk_pairs);
|
|
pair = &enc_sess->esi_hsk_pairs[enc_level];
|
|
hp = &enc_sess->esi_hsk_hps[enc_level];
|
|
}
|
|
else
|
|
{
|
|
pair = &enc_sess->esi_pairs[0];
|
|
hp = &enc_sess->esi_hp;
|
|
enc_sess->esi_trasec_sz = secret_len;
|
|
memcpy(enc_sess->esi_traffic_secrets[rw], secret, secret_len);
|
|
enc_sess->esi_md = crypa.md;
|
|
enc_sess->esi_aead = crypa.aead;
|
|
if (!(hp->hp_flags & (HP_CAN_READ|HP_CAN_WRITE))
|
|
&& crypa.aead == EVP_aead_chacha20_poly1305())
|
|
{
|
|
LSQ_DEBUG("turn off header protection batching (chacha not "
|
|
"supported)");
|
|
enc_sess->esi_conn->cn_esf_c = &lsquic_enc_session_common_ietf_v1_no_flush;
|
|
}
|
|
}
|
|
pair->ykp_thresh = IQUIC_INVALID_PACKNO;
|
|
|
|
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
|
|
LSQ_DEBUG("set %s secret for level %u: %s", rw2str[rw], enc_level,
|
|
HEXSTR(secret, secret_len, hexbuf));
|
|
else
|
|
LSQ_DEBUG("set %s for level %u", rw2str[rw], enc_level);
|
|
|
|
if (0 != init_crypto_ctx(&pair->ykp_ctx[rw], crypa.md,
|
|
crypa.aead, secret, secret_len, rw2dir(rw)))
|
|
goto err;
|
|
|
|
if (pair->ykp_ctx[!rw].yk_flags & YK_INITED)
|
|
{
|
|
/* Sanity check that the two sides end up with the same header
|
|
* protection logic, as they should.
|
|
*/
|
|
assert(hp->hp_gen_mask == crypa.gen_hp_mask);
|
|
}
|
|
else
|
|
{
|
|
hp->hp_enc_level = enc_level;
|
|
hp->hp_gen_mask = crypa.gen_hp_mask;
|
|
}
|
|
key_len = EVP_AEAD_key_length(crypa.aead);
|
|
if (hp->hp_gen_mask == gen_hp_mask_aes)
|
|
{
|
|
lsquic_qhkdf_expand(crypa.md, secret, secret_len, PN_LABEL, PN_LABEL_SZ,
|
|
key, key_len);
|
|
EVP_CIPHER_CTX_init(&hp->hp_u.cipher_ctx[rw]);
|
|
if (!EVP_EncryptInit_ex(&hp->hp_u.cipher_ctx[rw], crypa.hp, NULL, key, 0))
|
|
{
|
|
LSQ_ERROR("cannot initialize cipher on level %u", enc_level);
|
|
goto err;
|
|
}
|
|
}
|
|
else
|
|
lsquic_qhkdf_expand(crypa.md, secret, secret_len, PN_LABEL, PN_LABEL_SZ,
|
|
hp->hp_u.buf[rw], key_len);
|
|
hp->hp_flags |= 1 << rw;
|
|
|
|
if (enc_sess->esi_flags & ESI_LOG_SECRETS)
|
|
{
|
|
log_crypto_ctx(enc_sess, &pair->ykp_ctx[rw], "new", rw);
|
|
LSQ_DEBUG("%s hp: %s", rw2str[rw],
|
|
HEXSTR(hp->hp_gen_mask == gen_hp_mask_aes ? key : hp->hp_u.buf[rw],
|
|
key_len, hexbuf));
|
|
}
|
|
|
|
if (rw && enc_level == ENC_LEV_FORW)
|
|
enc_sess->esi_have_forw = 1;
|
|
|
|
return 1;
|
|
|
|
err:
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[0]);
|
|
cleanup_crypto_ctx(&pair->ykp_ctx[1]);
|
|
return 0;
|
|
#undef hexbuf
|
|
}
|
|
|
|
|
|
static int
|
|
cry_sm_set_read_secret (SSL *ssl, enum ssl_encryption_level_t level,
|
|
const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
|
|
{
|
|
return set_secret(ssl, level, cipher, secret, secret_len, 0);
|
|
}
|
|
|
|
|
|
static int
|
|
cry_sm_set_write_secret (SSL *ssl, enum ssl_encryption_level_t level,
|
|
const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len)
|
|
{
|
|
return set_secret(ssl, level, cipher, secret, secret_len, 1);
|
|
}
|
|
|
|
|
|
static int
|
|
cry_sm_write_message (SSL *ssl, enum ssl_encryption_level_t level,
|
|
const uint8_t *data, size_t len)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
void *stream;
|
|
ssize_t nw;
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
if (!enc_sess)
|
|
return 0;
|
|
|
|
stream = enc_sess->esi_streams[level];
|
|
if (!stream)
|
|
return 0;
|
|
|
|
/* The frab list logic is only applicable on the client. XXX This is
|
|
* likely to change when support for key updates is added.
|
|
*/
|
|
if (enc_sess->esi_flags & (ESI_ON_WRITE|ESI_SERVER))
|
|
nw = enc_sess->esi_cryst_if->csi_write(stream, data, len);
|
|
else
|
|
{
|
|
LSQ_DEBUG("not in on_write event: buffer in a frab list");
|
|
if (0 == lsquic_frab_list_write(&enc_sess->esi_frals[level], data, len))
|
|
{
|
|
if (!lsquic_frab_list_empty(&enc_sess->esi_frals[level]))
|
|
enc_sess->esi_cryst_if->csi_wantwrite(stream, 1);
|
|
nw = len;
|
|
}
|
|
else
|
|
nw = -1;
|
|
}
|
|
|
|
if (nw >= 0 && (size_t) nw == len)
|
|
{
|
|
enc_sess->esi_last_w = (enum enc_level) level;
|
|
LSQ_DEBUG("wrote %zu bytes to stream at encryption level %u",
|
|
len, level);
|
|
maybe_drop_SSL(enc_sess);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
LSQ_INFO("could not write %zu bytes: returned %zd", len, nw);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
cry_sm_flush_flight (SSL *ssl)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
void *stream;
|
|
unsigned level;
|
|
int s;
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
if (!enc_sess)
|
|
return 0;
|
|
|
|
level = enc_sess->esi_last_w;
|
|
stream = enc_sess->esi_streams[level];
|
|
if (!stream)
|
|
return 0;
|
|
|
|
if (lsquic_frab_list_empty(&enc_sess->esi_frals[level]))
|
|
{
|
|
s = enc_sess->esi_cryst_if->csi_flush(stream);
|
|
return s == 0;
|
|
}
|
|
else
|
|
/* Frab list will get flushed */ /* TODO: add support for
|
|
recording flush points in frab list. */
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
cry_sm_send_alert (SSL *ssl, enum ssl_encryption_level_t level, uint8_t alert)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
if (!enc_sess)
|
|
return 0;
|
|
|
|
LSQ_INFO("got alert %"PRIu8, alert);
|
|
enc_sess->esi_conn->cn_if->ci_tls_alert(enc_sess->esi_conn, alert);
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static const SSL_QUIC_METHOD cry_quic_method =
|
|
{
|
|
.set_read_secret = cry_sm_set_read_secret,
|
|
.set_write_secret = cry_sm_set_write_secret,
|
|
.add_handshake_data = cry_sm_write_message,
|
|
.flush_flight = cry_sm_flush_flight,
|
|
.send_alert = cry_sm_send_alert,
|
|
};
|
|
|
|
|
|
static lsquic_stream_ctx_t *
|
|
chsk_ietf_on_new_stream (void *stream_if_ctx, struct lsquic_stream *stream)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = stream_if_ctx;
|
|
enum enc_level enc_level;
|
|
|
|
enc_level = enc_sess->esi_cryst_if->csi_enc_level(stream);
|
|
if (enc_level == ENC_LEV_CLEAR)
|
|
enc_sess->esi_cryst_if->csi_wantwrite(stream, 1);
|
|
|
|
LSQ_DEBUG("handshake stream created successfully");
|
|
|
|
return stream_if_ctx;
|
|
}
|
|
|
|
|
|
static lsquic_stream_ctx_t *
|
|
shsk_ietf_on_new_stream (void *stream_if_ctx, struct lsquic_stream *stream)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = stream_if_ctx;
|
|
enum enc_level enc_level;
|
|
|
|
enc_level = enc_sess->esi_cryst_if->csi_enc_level(stream);
|
|
LSQ_DEBUG("on_new_stream called on level %u", enc_level);
|
|
|
|
enc_sess->esi_cryst_if->csi_wantread(stream, 1);
|
|
|
|
return stream_if_ctx;
|
|
}
|
|
|
|
|
|
static void
|
|
chsk_ietf_on_close (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = (struct enc_sess_iquic *) ctx;
|
|
if (enc_sess && enc_sess->esi_cryst_if)
|
|
LSQ_DEBUG("crypto stream level %u is closed",
|
|
(unsigned) enc_sess->esi_cryst_if->csi_enc_level(stream));
|
|
}
|
|
|
|
|
|
static const char *const ihs2str[] = {
|
|
[IHS_WANT_READ] = "want read",
|
|
[IHS_WANT_WRITE] = "want write",
|
|
[IHS_WANT_RW] = "want rw",
|
|
[IHS_STOP] = "stop",
|
|
};
|
|
|
|
|
|
static void
|
|
iquic_esfi_shake_stream (enc_session_t *sess,
|
|
struct lsquic_stream *stream, const char *what)
|
|
{
|
|
struct enc_sess_iquic *enc_sess = (struct enc_sess_iquic *)sess;
|
|
enum iquic_handshake_status st;
|
|
enum enc_level enc_level;
|
|
int write;
|
|
if (0 == (enc_sess->esi_flags & ESI_HANDSHAKE_OK))
|
|
st = iquic_esfi_handshake(enc_sess);
|
|
else
|
|
st = iquic_esfi_post_handshake(enc_sess);
|
|
enc_level = enc_sess->esi_cryst_if->csi_enc_level(stream);
|
|
LSQ_DEBUG("enc level %s after %s: %s", lsquic_enclev2str[enc_level], what,
|
|
ihs2str[st]);
|
|
switch (st)
|
|
{
|
|
case IHS_WANT_READ:
|
|
write = !lsquic_frab_list_empty(&enc_sess->esi_frals[enc_level]);
|
|
enc_sess->esi_cryst_if->csi_wantwrite(stream, write);
|
|
enc_sess->esi_cryst_if->csi_wantread(stream, 1);
|
|
break;
|
|
case IHS_WANT_WRITE:
|
|
enc_sess->esi_cryst_if->csi_wantwrite(stream, 1);
|
|
enc_sess->esi_cryst_if->csi_wantread(stream, 0);
|
|
break;
|
|
case IHS_WANT_RW:
|
|
enc_sess->esi_cryst_if->csi_wantwrite(stream, 1);
|
|
enc_sess->esi_cryst_if->csi_wantread(stream, 1);
|
|
break;
|
|
default:
|
|
assert(st == IHS_STOP);
|
|
write = !lsquic_frab_list_empty(&enc_sess->esi_frals[enc_level]);
|
|
enc_sess->esi_cryst_if->csi_wantwrite(stream, write);
|
|
enc_sess->esi_cryst_if->csi_wantread(stream, 0);
|
|
break;
|
|
}
|
|
LSQ_DEBUG("Exit shake_stream");
|
|
maybe_drop_SSL(enc_sess);
|
|
}
|
|
|
|
|
|
struct readf_ctx
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
enum enc_level enc_level;
|
|
int err;
|
|
};
|
|
|
|
|
|
static size_t
|
|
readf_cb (void *ctx, const unsigned char *buf, size_t len, int fin)
|
|
{
|
|
struct readf_ctx *const readf_ctx = (void *) ctx;
|
|
struct enc_sess_iquic *const enc_sess = readf_ctx->enc_sess;
|
|
int s;
|
|
size_t str_sz;
|
|
char str[MAX(1500 * 5, ERR_ERROR_STRING_BUF_LEN)];
|
|
|
|
s = SSL_provide_quic_data(enc_sess->esi_ssl,
|
|
(enum ssl_encryption_level_t) readf_ctx->enc_level, buf, len);
|
|
if (s)
|
|
{
|
|
LSQ_DEBUG("provided %zu bytes of %u-level data to SSL", len,
|
|
readf_ctx->enc_level);
|
|
str_sz = lsquic_hexdump(buf, len, str, sizeof(str));
|
|
LSQ_DEBUG("\n%.*s", (int) str_sz, str);
|
|
return len;
|
|
}
|
|
else
|
|
{
|
|
LSQ_WARN("SSL_provide_quic_data returned false: %s",
|
|
ERR_error_string(ERR_get_error(), str));
|
|
readf_ctx->err++;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
static size_t
|
|
discard_cb (void *ctx, const unsigned char *buf, size_t len, int fin)
|
|
{
|
|
return len;
|
|
}
|
|
|
|
|
|
static void
|
|
chsk_ietf_on_read (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = (void *) ctx;
|
|
enum enc_level enc_level = enc_sess->esi_cryst_if->csi_enc_level(stream);
|
|
struct readf_ctx readf_ctx = { enc_sess, enc_level, 0, };
|
|
ssize_t nread;
|
|
|
|
|
|
if (enc_sess->esi_ssl)
|
|
{
|
|
nread = enc_sess->esi_cryst_if->csi_readf(stream, readf_cb, &readf_ctx);
|
|
if (!(nread < 0 || readf_ctx.err))
|
|
iquic_esfi_shake_stream((enc_session_t *)enc_sess, stream,
|
|
"on_read");
|
|
else
|
|
enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn,
|
|
"shaking stream failed: nread: %zd, err: %d, SSL err: %"PRIu32,
|
|
nread, readf_ctx.err, ERR_get_error());
|
|
}
|
|
else
|
|
{
|
|
/* This branch is reached when we don't want TLS ticket and drop
|
|
* the SSL object before we process TLS tickets that have been
|
|
* already received and waiting in the incoming stream buffer.
|
|
*/
|
|
nread = enc_sess->esi_cryst_if->csi_readf(stream, discard_cb, NULL);
|
|
lsquic_stream_wantread(stream, 0);
|
|
LSQ_DEBUG("no SSL object: discard %zd bytes of SSL data", nread);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
maybe_write_from_fral (struct enc_sess_iquic *enc_sess,
|
|
struct lsquic_stream *stream)
|
|
{
|
|
enum enc_level enc_level = enc_sess->esi_cryst_if->csi_enc_level(stream);
|
|
struct frab_list *const fral = &enc_sess->esi_frals[enc_level];
|
|
struct lsquic_reader reader = {
|
|
.lsqr_read = lsquic_frab_list_read,
|
|
.lsqr_size = lsquic_frab_list_size,
|
|
.lsqr_ctx = fral,
|
|
};
|
|
ssize_t nw;
|
|
|
|
if (lsquic_frab_list_empty(fral))
|
|
return;
|
|
|
|
nw = lsquic_stream_writef(stream, &reader);
|
|
if (nw >= 0)
|
|
{
|
|
LSQ_DEBUG("wrote %zd bytes to stream from frab list", nw);
|
|
(void) lsquic_stream_flush(stream);
|
|
if (lsquic_frab_list_empty(fral))
|
|
lsquic_stream_wantwrite(stream, 0);
|
|
}
|
|
else
|
|
{
|
|
enc_sess->esi_conn->cn_if->ci_internal_error(enc_sess->esi_conn,
|
|
"cannot write to stream: %s", strerror(errno));
|
|
lsquic_stream_wantwrite(stream, 0);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
chsk_ietf_on_write (struct lsquic_stream *stream, lsquic_stream_ctx_t *ctx)
|
|
{
|
|
struct enc_sess_iquic *const enc_sess = (void *) ctx;
|
|
|
|
maybe_write_from_fral(enc_sess, stream);
|
|
|
|
enc_sess->esi_flags |= ESI_ON_WRITE;
|
|
iquic_esfi_shake_stream(enc_sess, stream, "on_write");
|
|
enc_sess->esi_flags &= ~ESI_ON_WRITE;
|
|
}
|
|
|
|
|
|
const struct lsquic_stream_if lsquic_cry_sm_if =
|
|
{
|
|
.on_new_stream = chsk_ietf_on_new_stream,
|
|
.on_read = chsk_ietf_on_read,
|
|
.on_write = chsk_ietf_on_write,
|
|
.on_close = chsk_ietf_on_close,
|
|
};
|
|
|
|
|
|
const struct lsquic_stream_if lsquic_mini_cry_sm_if =
|
|
{
|
|
.on_new_stream = shsk_ietf_on_new_stream,
|
|
.on_read = chsk_ietf_on_read,
|
|
.on_write = chsk_ietf_on_write,
|
|
.on_close = chsk_ietf_on_close,
|
|
};
|
|
|
|
|
|
|
|
|
|
const unsigned char *const lsquic_retry_key_buf[N_IETF_RETRY_VERSIONS] =
|
|
{
|
|
/* [draft-ietf-quic-tls-25] Section 5.8 */
|
|
(unsigned char *)
|
|
"\x4d\x32\xec\xdb\x2a\x21\x33\xc8\x41\xe4\x04\x3d\xf2\x7d\x44\x30",
|
|
/* [draft-ietf-quic-tls-29] Section 5.8 */
|
|
(unsigned char *)
|
|
"\xcc\xce\x18\x7e\xd0\x9a\x09\xd0\x57\x28\x15\x5a\x6c\xb9\x6b\xe1",
|
|
/* [draft-ietf-quic-tls-33] Section 5.8 */
|
|
(unsigned char *)
|
|
"\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e",
|
|
};
|
|
|
|
|
|
const unsigned char *const lsquic_retry_nonce_buf[N_IETF_RETRY_VERSIONS] =
|
|
{
|
|
/* [draft-ietf-quic-tls-25] Section 5.8 */
|
|
(unsigned char *) "\x4d\x16\x11\xd0\x55\x13\xa5\x52\xc5\x87\xd5\x75",
|
|
/* [draft-ietf-quic-tls-29] Section 5.8 */
|
|
(unsigned char *) "\xe5\x49\x30\xf9\x7f\x21\x36\xf0\x53\x0a\x8c\x1c",
|
|
/* [draft-ietf-quic-tls-33] Section 5.8 */
|
|
(unsigned char *) "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb",
|
|
};
|
|
|
|
|
|
int
|
|
lsquic_enc_sess_ietf_gen_quic_ctx (
|
|
const struct lsquic_engine_settings *settings,
|
|
enum lsquic_version version, unsigned char *buf, size_t bufsz)
|
|
{
|
|
struct transport_params params;
|
|
int len;
|
|
|
|
/* This code is pretty much copied from gen_trans_params(), with
|
|
* small (but important) exceptions.
|
|
*/
|
|
|
|
memset(¶ms, 0, sizeof(params));
|
|
params.tp_init_max_data = settings->es_init_max_data;
|
|
params.tp_init_max_stream_data_bidi_local
|
|
= settings->es_init_max_stream_data_bidi_local;
|
|
params.tp_init_max_stream_data_bidi_remote
|
|
= settings->es_init_max_stream_data_bidi_remote;
|
|
params.tp_init_max_stream_data_uni
|
|
= settings->es_init_max_stream_data_uni;
|
|
params.tp_init_max_streams_uni
|
|
= settings->es_init_max_streams_uni;
|
|
params.tp_init_max_streams_bidi
|
|
= settings->es_init_max_streams_bidi;
|
|
params.tp_ack_delay_exponent
|
|
= TP_DEF_ACK_DELAY_EXP;
|
|
params.tp_max_idle_timeout = settings->es_idle_timeout * 1000;
|
|
params.tp_max_ack_delay = TP_DEF_MAX_ACK_DELAY;
|
|
params.tp_active_connection_id_limit = MAX_IETF_CONN_DCIDS;
|
|
params.tp_set |= (1 << TPI_INIT_MAX_DATA)
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_BIDI_LOCAL)
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_BIDI_REMOTE)
|
|
| (1 << TPI_INIT_MAX_STREAM_DATA_UNI)
|
|
| (1 << TPI_INIT_MAX_STREAMS_UNI)
|
|
| (1 << TPI_INIT_MAX_STREAMS_BIDI)
|
|
| (1 << TPI_ACK_DELAY_EXPONENT)
|
|
| (1 << TPI_MAX_IDLE_TIMEOUT)
|
|
| (1 << TPI_MAX_ACK_DELAY)
|
|
| (1 << TPI_ACTIVE_CONNECTION_ID_LIMIT)
|
|
;
|
|
if (settings->es_max_udp_payload_size_rx)
|
|
{
|
|
params.tp_max_udp_payload_size = settings->es_max_udp_payload_size_rx;
|
|
params.tp_set |= 1 << TPI_MAX_UDP_PAYLOAD_SIZE;
|
|
}
|
|
if (!settings->es_allow_migration)
|
|
params.tp_set |= 1 << TPI_DISABLE_ACTIVE_MIGRATION;
|
|
if (settings->es_ql_bits)
|
|
{
|
|
params.tp_loss_bits = settings->es_ql_bits - 1;
|
|
params.tp_set |= 1 << TPI_LOSS_BITS;
|
|
}
|
|
if (settings->es_delayed_acks)
|
|
{
|
|
params.tp_numerics[TPI_MIN_ACK_DELAY] = TP_MIN_ACK_DELAY;
|
|
params.tp_set |= 1 << TPI_MIN_ACK_DELAY;
|
|
params.tp_numerics[TPI_MIN_ACK_DELAY_02] = TP_MIN_ACK_DELAY;
|
|
params.tp_set |= 1 << TPI_MIN_ACK_DELAY_02;
|
|
}
|
|
if (settings->es_timestamps)
|
|
{
|
|
params.tp_numerics[TPI_TIMESTAMPS] = TS_GENERATE_THEM;
|
|
params.tp_set |= 1 << TPI_TIMESTAMPS;
|
|
}
|
|
if (settings->es_datagrams)
|
|
{
|
|
if (params.tp_set & (1 << TPI_MAX_UDP_PAYLOAD_SIZE))
|
|
params.tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE]
|
|
= params.tp_max_udp_payload_size;
|
|
else
|
|
params.tp_numerics[TPI_MAX_DATAGRAM_FRAME_SIZE]
|
|
= TP_DEF_MAX_UDP_PAYLOAD_SIZE;
|
|
params.tp_set |= 1 << TPI_MAX_DATAGRAM_FRAME_SIZE;
|
|
}
|
|
|
|
params.tp_set &= SERVER_0RTT_TPS;
|
|
|
|
len = (version == LSQVER_ID27 ? lsquic_tp_encode_27 : lsquic_tp_encode)(
|
|
¶ms, 1, buf, bufsz);
|
|
if (len >= 0)
|
|
{
|
|
char str[MAX_TP_STR_SZ];
|
|
LSQ_LOG1(LSQ_LOG_DEBUG, "generated QUIC server context of %d bytes "
|
|
"for version %s", len, lsquic_ver2str[version]);
|
|
LSQ_LOG1(LSQ_LOG_DEBUG, "%s", ((version == LSQVER_ID27
|
|
? lsquic_tp_to_str_27 : lsquic_tp_to_str)(¶ms, str,
|
|
sizeof(str)), str));
|
|
}
|
|
else
|
|
LSQ_LOG1(LSQ_LOG_WARN, "cannot generate QUIC server context: %d",
|
|
errno);
|
|
return len;
|
|
}
|
|
|
|
|
|
struct lsquic_conn *
|
|
lsquic_ssl_to_conn (const struct ssl_st *ssl)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
|
|
if (s_idx < 0)
|
|
return NULL;
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
if (!enc_sess)
|
|
return NULL;
|
|
|
|
return enc_sess->esi_conn;
|
|
}
|
|
|
|
|
|
int
|
|
lsquic_ssl_sess_to_resume_info (SSL *ssl, SSL_SESSION *session,
|
|
unsigned char **buf, size_t *buf_sz)
|
|
{
|
|
struct enc_sess_iquic *enc_sess;
|
|
int status;
|
|
|
|
if (s_idx < 0)
|
|
return -1;
|
|
|
|
enc_sess = SSL_get_ex_data(ssl, s_idx);
|
|
if (!enc_sess)
|
|
return -1;
|
|
|
|
status = iquic_ssl_sess_to_resume_info(enc_sess, ssl, session, buf, buf_sz);
|
|
if (status == 0)
|
|
{
|
|
LSQ_DEBUG("%s called successfully, unset WANT_TICKET flag", __func__);
|
|
enc_sess->esi_flags &= ~ESI_WANT_TICKET;
|
|
lsquic_alarmset_unset(enc_sess->esi_alset, AL_SESS_TICKET);
|
|
}
|
|
return status;
|
|
}
|