litespeed-quic/src/liblsquic/lsquic_handshake.c

4355 lines
139 KiB
C
Raw Normal View History

2022-05-06 16:49:46 +00:00
/* Copyright (c) 2017 - 2022 LiteSpeed Technologies Inc. See LICENSE. */
#define _GNU_SOURCE /* for memmem */
2017-09-22 21:00:03 +00:00
#include <assert.h>
#include <errno.h>
#include <limits.h>
2017-09-22 21:00:03 +00:00
#include <time.h>
#include <string.h>
#include <sys/queue.h>
#ifndef WIN32
2020-06-03 04:13:30 +00:00
#include <netinet/in.h>
#include <netdb.h>
2017-09-22 21:00:03 +00:00
#include <sys/socket.h>
2020-10-07 15:05:18 +00:00
#else
#include <malloc.h>
#endif
2017-09-22 21:00:03 +00:00
#include <openssl/ssl.h>
#include <openssl/crypto.h>
#include <openssl/stack.h>
#include <openssl/x509.h>
#include <openssl/rand.h>
#include <openssl/nid.h>
#include <openssl/bn.h>
#include <openssl/hkdf.h>
2017-09-22 21:00:03 +00:00
#include <zlib.h>
#include "lsquic.h"
#include "lsquic_types.h"
#include "lsquic_crypto.h"
#include "lsquic_str.h"
#include "lsquic_enc_sess.h"
2017-09-22 21:00:03 +00:00
#include "lsquic_parse.h"
#include "lsquic_crt_compress.h"
#include "lsquic_util.h"
#include "lsquic_version.h"
#include "lsquic_mm.h"
#include "lsquic_engine_public.h"
#include "lsquic_hash.h"
#include "lsquic_qtags.h"
#include "lsquic_byteswap.h"
#include "lsquic_sizes.h"
#include "lsquic_tokgen.h"
#include "lsquic_conn.h"
#include "lsquic_packet_common.h"
#include "lsquic_packet_out.h"
#include "lsquic_packet_in.h"
#include "lsquic_handshake.h"
#include "lsquic_hkdf.h"
#include "lsquic_packet_ietf.h"
#if __GNUC__
# define UNLIKELY(cond) __builtin_expect(cond, 0)
#else
# define UNLIKELY(cond) cond
#endif
2017-09-22 21:00:03 +00:00
#include "fiu-local.h"
#include "lsquic_ev_log.h"
#define MIN_CHLO_SIZE 1024
#define MAX_SCFG_LENGTH 512
#define MAX_SPUBS_LENGTH 32
2017-09-22 21:00:03 +00:00
#define LSQUIC_LOGGER_MODULE LSQLM_HANDSHAKE
#define LSQUIC_LOG_CONN_ID lsquic_conn_log_cid( \
enc_session && enc_session->es_conn ? enc_session->es_conn : \
lconn && lconn != &dummy_lsquic_conn ? lconn : \
&dummy_lsquic_conn)
2017-09-22 21:00:03 +00:00
#include "lsquic_logger.h"
/* enc_session may be NULL when encrypt and decrypt packet functions are
* called. This is a workaround.
*/
static struct conn_cid_elem dummy_cce;
static const struct lsquic_conn dummy_lsquic_conn = { .cn_cces = &dummy_cce, };
static const struct lsquic_conn *const lconn = &dummy_lsquic_conn;
static int s_ccrt_idx;
static const int s_log_seal_and_open;
static char s_str[0x1000];
static const unsigned char salt_Q050[] = {
0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94,
0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45,
};
enum handshake_state
{
HSK_CHLO_REJ = 0,
HSK_SHLO,
HSK_COMPLETED,
N_HSK_STATES
};
#if LSQUIC_KEEP_ENC_SESS_HISTORY
typedef unsigned char eshist_idx_t;
enum enc_sess_history_event
{
ESHE_EMPTY = '\0',
ESHE_SET_SNI = 'I',
ESHE_SET_SNO = 'O',
ESHE_SET_STK = 'K',
ESHE_SET_SCID = 'D',
ESHE_SET_PROF = 'P',
2018-08-15 19:06:31 +00:00
ESHE_SET_SRST = 'S',
ESHE_VSTK_OK = 'V',
ESHE_VSTK_FAILED = 'W',
ESHE_SNI_FAIL = 'J',
ESHE_HAS_SSTK = 'H',
ESHE_UNKNOWN_CONFIG = 'a',
ESHE_MISSING_SCID = 'b',
ESHE_EMPTY_CCRT = 'c',
ESHE_MISSING_SNO = 'd',
ESHE_SNO_MISMATCH = 'e',
ESHE_SNO_OK = 'f',
ESHE_MULTI2_2BITS = 'i',
ESHE_SNI_DELAYED = 'Y',
ESHE_XLCT_MISMATCH = 'x',
};
#endif
typedef struct hs_ctx_st
{
enum {
HSET_TCID = (1 << 0), /* tcid is set */
HSET_SMHL = (1 << 1), /* smhl is set */
HSET_SCID = (1 << 2),
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
HSET_IRTT = (1 << 3),
2018-08-15 19:06:31 +00:00
HSET_SRST = (1 << 4),
HSET_XLCT = (1 << 5), /* xlct is set */
} set;
enum {
HOPT_NSTP = (1 << 0), /* NSTP option present in COPT */
HOPT_SREJ = (1 << 1), /* SREJ option present in COPT */
} opts;
uint32_t pdmd;
uint32_t aead;
uint32_t kexs;
uint32_t mids;
uint32_t scls;
uint32_t cfcw;
uint32_t sfcw;
uint32_t smids;
uint32_t scfcw;
uint32_t ssfcw;
uint32_t icsl;
uint32_t irtt;
uint64_t rcid;
uint32_t tcid;
uint32_t smhl;
uint64_t sttl;
uint64_t xlct;
unsigned char scid[SCID_LENGTH];
//unsigned char chlo_hash[32]; //SHA256 HASH of CHLO
unsigned char nonc[DNONC_LENGTH]; /* 4 tm, 8 orbit ---> REJ, 20 rand */
unsigned char pubs[32];
2018-08-15 19:06:31 +00:00
unsigned char srst[SRST_LENGTH];
uint32_t rrej;
struct lsquic_str ccs;
struct lsquic_str uaid;
struct lsquic_str sni; /* 0 rtt */
struct lsquic_str ccrt;
struct lsquic_str stk;
struct lsquic_str sno;
struct lsquic_str prof;
struct lsquic_str csct;
struct compressed_cert *ccert;
struct lsquic_str scfg_pubs; /* Need to copy PUBS, as KEXS comes after it */
} hs_ctx_t;
/* client side need to store 0rtt info per STK */
typedef struct lsquic_session_cache_info_st
{
unsigned char sscid[SCID_LENGTH];
unsigned char spubs[32]; /* server pub key for next time 0rtt */
uint32_t ver; /* one VERSION */
uint32_t aead;
uint32_t kexs;
uint32_t pdmd;
uint64_t orbt;
uint64_t expy;
int scfg_flag; /* 0, no-init, 1, no parse, 2, parsed */
struct lsquic_str sstk;
struct lsquic_str scfg;
struct lsquic_str sni_key; /* This is only used as key */
struct lsquic_hash_elem hash_el;
} lsquic_session_cache_info_t;
/* client */
typedef struct c_cert_item_st
{
struct lsquic_str* crts;
struct lsquic_str* hashs;
int count;
} c_cert_item_t;
struct lsquic_sess_resume_storage
{
uint32_t quic_version_tag;
uint32_t serializer_version;
uint32_t ver;
uint32_t aead;
uint32_t kexs;
uint32_t pdmd;
uint64_t orbt;
uint64_t expy;
uint64_t sstk_len;
uint64_t scfg_len;
uint64_t scfg_flag;
uint8_t sstk[STK_LENGTH];
uint8_t scfg[MAX_SCFG_LENGTH];
uint8_t sscid[SCID_LENGTH];
uint8_t spubs[MAX_SPUBS_LENGTH];
uint32_t cert_count;
};
/* gQUIC crypto has three crypto levels. */
enum gel { GEL_CLEAR, GEL_EARLY, GEL_FORW, N_GELS /* Angels! */ };
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define IQUIC_IV_LEN 12
#define IQUIC_HP_LEN 16
#define MAX_IV_LEN MAX(aes128_iv_len, IQUIC_IV_LEN)
struct lsquic_enc_session
{
struct lsquic_conn *es_conn;
enum handshake_state hsk_state;
enum {
ES_SERVER = 1 << 0,
ES_RECV_REJ = 1 << 1,
ES_RECV_SREJ = 1 << 2,
ES_FREE_CERT_PTR = 1 << 3,
ES_LOG_SECRETS = 1 << 4,
ES_GQUIC2 = 1 << 5,
} es_flags;
uint8_t have_key; /* 0, no 1, I, 2, D, 3, F */
uint8_t peer_have_final_key;
uint8_t server_start_use_final_key;
lsquic_cid_t cid;
unsigned char priv_key[32];
/* Have to save the initial key for diversification need */
unsigned char enc_key_i[aes128_key_len];
unsigned char dec_key_i[aes128_key_len];
#define enc_ctx_i es_aead_ctxs[GEL_EARLY][0]
#define dec_ctx_i es_aead_ctxs[GEL_EARLY][1]
#define enc_ctx_f es_aead_ctxs[GEL_FORW][0]
#define dec_ctx_f es_aead_ctxs[GEL_FORW][1]
EVP_AEAD_CTX *es_aead_ctxs[N_GELS][2];
#define enc_key_nonce_i es_ivs[GEL_EARLY][0]
#define dec_key_nonce_i es_ivs[GEL_EARLY][1]
#define enc_key_nonce_f es_ivs[GEL_FORW][0]
#define dec_key_nonce_f es_ivs[GEL_FORW][1]
unsigned char es_ivs[N_GELS][2][MAX_IV_LEN];
unsigned char es_hps[N_GELS][2][IQUIC_HP_LEN];
hs_ctx_t hs_ctx;
lsquic_session_cache_info_t *info;
c_cert_item_t *cert_item;
lsquic_server_config_t *server_config;
SSL_CTX * ssl_ctx;
struct lsquic_engine_public *enpub;
struct lsquic_str * cert_ptr; /* pointer to the leaf cert of the server, not real copy */
uint64_t cert_hash;
struct lsquic_str chlo; /* real copy of CHLO message */
struct lsquic_str sstk;
struct lsquic_str ssno;
#if LSQUIC_KEEP_ENC_SESS_HISTORY
eshist_idx_t es_hist_idx;
unsigned char es_hist_buf[1 << ESHIST_BITS];
#endif
/* The remaining fields in the struct are used for Q050+ crypto */
lsquic_packno_t es_max_packno;
};
2017-09-22 21:00:03 +00:00
/* server side */
typedef struct compress_cert_hash_item_st
{
struct lsquic_str* domain; /*with port, such as "xyz.com:8088" as the key */
struct lsquic_str* crts_compress_buf;
struct lsquic_hash_elem hash_el;
} compress_cert_hash_item_t;
/* server side, only one cert */
typedef struct cert_item_st
{
struct lsquic_str* crt;
uint64_t hash; /* Hash of `crt' */
struct lsquic_hash_elem hash_el;
unsigned char key[0];
} cert_item_t;
/* server */
static cert_item_t* find_cert(struct lsquic_engine_public *, const unsigned char *, size_t);
static void s_free_cert_hash_item(cert_item_t *item);
static cert_item_t* insert_cert(struct lsquic_engine_public *,
const unsigned char *key, size_t key_sz, const struct lsquic_str *crt);
#ifdef NDEBUG
static
enum hsk_failure_reason
lsquic_verify_stk (enc_session_t *,
const struct sockaddr *ip_addr, uint64_t tm, lsquic_str_t *stk);
static
#endif
void lsquic_gen_stk(lsquic_server_config_t *, const struct sockaddr *, uint64_t tm,
unsigned char stk_out[STK_LENGTH]);
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
/* client */
static c_cert_item_t *make_c_cert_item(struct lsquic_str **certs, int count);
static void free_c_cert_item(c_cert_item_t *item);
2017-09-22 21:00:03 +00:00
static int get_tag_val_u32 (unsigned char *v, int len, uint32_t *val);
static uint32_t get_tag_value_i32(unsigned char *, int);
static uint64_t get_tag_value_i64(unsigned char *, int);
static void determine_keys(struct lsquic_enc_session *enc_session);
2017-09-22 21:00:03 +00:00
static void put_compressed_cert (struct compressed_cert *);
2017-09-22 21:00:03 +00:00
#if LSQUIC_KEEP_ENC_SESS_HISTORY
static void
eshist_append (struct lsquic_enc_session *enc_session,
2017-09-22 21:00:03 +00:00
enum enc_sess_history_event eh_event)
{
enc_session->es_hist_buf[
ESHIST_MASK & enc_session->es_hist_idx++ ] = eh_event;
}
# define ESHIST_APPEND(sess, event) eshist_append(sess, event)
#else
# define ESHIST_APPEND(sess, event) do { } while (0)
#endif
static void
free_compressed_cert (void *parent, void *ptr, CRYPTO_EX_DATA *ad,
int index, long argl, void *argp)
{
put_compressed_cert(ptr);
}
static int
lsquic_handshake_init(int flags)
2017-09-22 21:00:03 +00:00
{
lsquic_crypto_init();
if (flags & LSQUIC_GLOBAL_SERVER)
{
s_ccrt_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
free_compressed_cert);
if (s_ccrt_idx < 0)
return -1;
}
return lsquic_crt_init();
2017-09-22 21:00:03 +00:00
}
void
lsquic_cleanup_gquic_crypto (struct lsquic_engine_public *enpub)
{
struct lsquic_hash_elem *el;
if (enpub->enp_compressed_server_certs)
{
for (el = lsquic_hash_first(enpub->enp_compressed_server_certs); el;
el = lsquic_hash_next(enpub->enp_compressed_server_certs))
{
compress_cert_hash_item_t *item = lsquic_hashelem_getdata(el);
lsquic_str_delete(item->domain);
lsquic_str_delete(item->crts_compress_buf);
free(item);
}
lsquic_hash_destroy(enpub->enp_compressed_server_certs);
enpub->enp_compressed_server_certs = NULL;
}
if (enpub->enp_server_certs)
{
for (el = lsquic_hash_first(enpub->enp_server_certs); el;
el = lsquic_hash_next(enpub->enp_server_certs))
{
s_free_cert_hash_item( lsquic_hashelem_getdata(el) );
}
lsquic_hash_destroy(enpub->enp_server_certs);
enpub->enp_server_certs = NULL;
}
free(enpub->enp_server_config);
}
static void
lsquic_handshake_cleanup (void)
2017-09-22 21:00:03 +00:00
{
lsquic_crt_cleanup();
}
int
lsquic_init_gquic_crypto (struct lsquic_engine_public *enpub)
2017-09-22 21:00:03 +00:00
{
enpub->enp_server_config = calloc(1, sizeof(*enpub->enp_server_config));
if (!enpub->enp_server_config)
return -1;
enpub->enp_compressed_server_certs = lsquic_hash_create();
if (!enpub->enp_compressed_server_certs)
return -1;
enpub->enp_server_certs = lsquic_hash_create();
if (!enpub->enp_server_certs)
{
lsquic_hash_destroy(enpub->enp_compressed_server_certs);
enpub->enp_compressed_server_certs = NULL;
return -1;
}
2017-09-22 21:00:03 +00:00
return 0;
}
/* server */
static cert_item_t *
find_cert (struct lsquic_engine_public *enpub, const unsigned char *key,
size_t key_sz)
{
struct lsquic_hash_elem *el;
if (!enpub->enp_server_certs)
return NULL;
el = lsquic_hash_find(enpub->enp_server_certs, key, key_sz);
if (el == NULL)
return NULL;
return lsquic_hashelem_getdata(el);
}
2017-09-22 21:00:03 +00:00
/* client */
static c_cert_item_t *
make_c_cert_item (lsquic_str_t **certs, int count)
2017-09-22 21:00:03 +00:00
{
int i;
uint64_t hash;
c_cert_item_t *item = calloc(1, sizeof(*item));
2017-09-22 21:00:03 +00:00
item->crts = (lsquic_str_t *)malloc(count * sizeof(lsquic_str_t));
item->hashs = lsquic_str_new(NULL, 0);
item->count = count;
for (i = 0; i < count; ++i)
2017-09-22 21:00:03 +00:00
{
lsquic_str_copy(&item->crts[i], certs[i]);
hash = lsquic_fnv1a_64((const uint8_t *)lsquic_str_cstr(certs[i]),
lsquic_str_len(certs[i]));
2017-09-22 21:00:03 +00:00
lsquic_str_append(item->hashs, (char *)&hash, 8);
}
return item;
}
/* client */
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
static void
free_c_cert_item (c_cert_item_t *item)
2017-09-22 21:00:03 +00:00
{
int i;
if (item)
{
lsquic_str_delete(item->hashs);
for(i=0; i<item->count; ++i)
lsquic_str_d(&item->crts[i]);
free(item->crts);
free(item);
}
}
/* server */
static void
s_free_cert_hash_item (cert_item_t *item)
{
if (item)
{
lsquic_str_delete(item->crt);
free(item);
}
}
/* server */
static cert_item_t *
insert_cert (struct lsquic_engine_public *enpub, const unsigned char *key,
size_t key_sz, const lsquic_str_t *crt)
{
struct lsquic_hash_elem *el;
lsquic_str_t *crt_copy;
cert_item_t *item;
crt_copy = lsquic_str_new(lsquic_str_cstr(crt), lsquic_str_len(crt));
if (!crt_copy)
return NULL;
item = calloc(1, sizeof(*item) + key_sz);
if (!item)
{
lsquic_str_delete(crt_copy);
return NULL;
}
item->crt = crt_copy;
memcpy(item->key, key, key_sz);
item->hash = lsquic_fnv1a_64((const uint8_t *)lsquic_str_buf(crt),
lsquic_str_len(crt));
el = lsquic_hash_insert(enpub->enp_server_certs, item->key, key_sz,
item, &item->hash_el);
if (el)
return lsquic_hashelem_getdata(el);
else
{
s_free_cert_hash_item(item);
return NULL;
}
}
enum rtt_deserialize_return_type
2017-09-22 21:00:03 +00:00
{
RTT_DESERIALIZE_OK = 0,
RTT_DESERIALIZE_BAD_QUIC_VER = 1,
RTT_DESERIALIZE_BAD_SERIAL_VER = 2,
RTT_DESERIALIZE_BAD_CERT_SIZE = 3,
};
2017-09-22 21:00:03 +00:00
#define RTT_SERIALIZER_VERSION (1 << 0)
2017-09-22 21:00:03 +00:00
static void
lsquic_enc_session_serialize_sess_resume(struct lsquic_sess_resume_storage *storage,
enum lsquic_version version,
const lsquic_session_cache_info_t *info,
const c_cert_item_t *cert_item)
{
uint32_t i;
uint32_t *cert_len;
uint8_t *cert_data;
/*
* assign versions
*/
storage->quic_version_tag = lsquic_ver2tag(version);
storage->serializer_version = RTT_SERIALIZER_VERSION;
/*
* server config
*/
storage->ver = info->ver;
storage->aead = info->aead;
storage->kexs = info->kexs;
storage->pdmd = info->pdmd;
storage->orbt = info->orbt;
storage->expy = info->expy;
storage->sstk_len = lsquic_str_len(&info->sstk);
storage->scfg_len = lsquic_str_len(&info->scfg);
storage->scfg_flag = info->scfg_flag;
memcpy(storage->sstk, lsquic_str_buf(&info->sstk), storage->sstk_len);
memcpy(storage->scfg, lsquic_str_buf(&info->scfg), storage->scfg_len);
memcpy(storage->sscid, &info->sscid, SCID_LENGTH);
memcpy(storage->spubs, &info->spubs, MAX_SPUBS_LENGTH);
/*
* certificate chain
*/
storage->cert_count = (uint32_t)cert_item->count;
cert_len = (uint32_t *)(storage + 1);
cert_data = (uint8_t *)(cert_len + 1);
for (i = 0; i < storage->cert_count; i++)
2017-09-22 21:00:03 +00:00
{
*cert_len = lsquic_str_len(&cert_item->crts[i]);
memcpy(cert_data, lsquic_str_buf(&cert_item->crts[i]), *cert_len);
cert_len = (uint32_t *)(cert_data + *cert_len);
cert_data = (uint8_t *)(cert_len + 1);
2017-09-22 21:00:03 +00:00
}
}
#define CHECK_SPACE(need, start, end) \
do { if ((intptr_t) (need) > ((intptr_t) (end) - (intptr_t) (start))) \
{ return RTT_DESERIALIZE_BAD_CERT_SIZE; } \
} while (0) \
2017-09-22 21:00:03 +00:00
static enum rtt_deserialize_return_type
lsquic_enc_session_deserialize_sess_resume(
const struct lsquic_sess_resume_storage *storage,
size_t storage_size,
const struct lsquic_engine_settings *settings,
lsquic_session_cache_info_t *info,
c_cert_item_t *cert_item)
2017-09-22 21:00:03 +00:00
{
enum lsquic_version ver;
uint32_t i, len;
uint64_t hash;
uint32_t *cert_len;
uint8_t *cert_data;
void *storage_end = (uint8_t *)storage + storage_size;
/*
* check versions
*/
ver = lsquic_tag2ver(storage->quic_version_tag);
if ((int)ver == -1 || !((1 << ver) & settings->es_versions))
return RTT_DESERIALIZE_BAD_QUIC_VER;
if (storage->serializer_version != RTT_SERIALIZER_VERSION)
return RTT_DESERIALIZE_BAD_SERIAL_VER;
/*
* server config
*/
info->ver = storage->ver;
info->aead = storage->aead;
info->kexs = storage->kexs;
info->pdmd = storage->pdmd;
info->orbt = storage->orbt;
info->expy = storage->expy;
info->scfg_flag = storage->scfg_flag;
lsquic_str_setto(&info->sstk, storage->sstk, storage->sstk_len);
lsquic_str_setto(&info->scfg, storage->scfg, storage->scfg_len);
memcpy(&info->sscid, storage->sscid, SCID_LENGTH);
memcpy(&info->spubs, storage->spubs, MAX_SPUBS_LENGTH);
/*
* certificate chain
*/
cert_item->count = storage->cert_count;
cert_item->crts = malloc(cert_item->count * sizeof(lsquic_str_t));
cert_item->hashs = lsquic_str_new(NULL, 0);
cert_len = (uint32_t *)(storage + 1);
for (i = 0; i < storage->cert_count; i++)
2017-09-22 21:00:03 +00:00
{
CHECK_SPACE(sizeof(uint32_t), cert_len, storage_end);
cert_data = (uint8_t *)(cert_len + 1);
memcpy(&len, cert_len, sizeof(len));
CHECK_SPACE(len, cert_data, storage_end);
lsquic_str_prealloc(&cert_item->crts[i], len);
lsquic_str_setlen(&cert_item->crts[i], len);
memcpy(lsquic_str_buf(&cert_item->crts[i]), cert_data, len);
hash = lsquic_fnv1a_64((const uint8_t *)cert_data, len);
lsquic_str_append(cert_item->hashs, (char *)&hash, 8);
cert_len = (uint32_t *)(cert_data + len);
2017-09-22 21:00:03 +00:00
}
return RTT_DESERIALIZE_OK;
2017-09-22 21:00:03 +00:00
}
#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)
static int
gquic2_init_crypto_ctx (struct lsquic_enc_session *enc_session,
unsigned idx, const unsigned char *secret, size_t secret_sz)
{
const EVP_MD *const md = EVP_sha256();
const EVP_AEAD *const aead = EVP_aead_aes_128_gcm();
unsigned char key[aes128_key_len];
char hexbuf[sizeof(key) * 2 + 1];
lsquic_qhkdf_expand(md, secret, secret_sz, KEY_LABEL, KEY_LABEL_SZ,
key, sizeof(key));
if (enc_session->es_flags & ES_LOG_SECRETS)
LSQ_DEBUG("handshake key idx %u: %s", idx,
HEXSTR(key, sizeof(key), hexbuf));
lsquic_qhkdf_expand(md, secret, secret_sz, IV_LABEL, IV_LABEL_SZ,
enc_session->es_ivs[GEL_CLEAR][idx], IQUIC_IV_LEN);
lsquic_qhkdf_expand(md, secret, secret_sz, PN_LABEL, PN_LABEL_SZ,
enc_session->es_hps[GEL_CLEAR][idx], IQUIC_HP_LEN);
assert(!enc_session->es_aead_ctxs[GEL_CLEAR][idx]);
enc_session->es_aead_ctxs[GEL_CLEAR][idx]
= malloc(sizeof(*enc_session->es_aead_ctxs[GEL_CLEAR][idx]));
if (!enc_session->es_aead_ctxs[GEL_CLEAR][idx])
return -1;
if (!EVP_AEAD_CTX_init(enc_session->es_aead_ctxs[GEL_CLEAR][idx], aead,
key, sizeof(key), IQUIC_TAG_LEN, NULL))
{
free(enc_session->es_aead_ctxs[GEL_CLEAR][idx]);
enc_session->es_aead_ctxs[GEL_CLEAR][idx] = NULL;
return -1;
}
return 0;
}
static void
log_crypto_ctx (const struct lsquic_enc_session *enc_session,
enum enc_level enc_level, int idx)
{
char hexbuf[EVP_MAX_MD_SIZE * 2 + 1];
LSQ_DEBUG("%s keys for level %s", lsquic_enclev2str[enc_level],
idx == 0 ? "encrypt" : "decrypt");
LSQ_DEBUG("iv: %s",
HEXSTR(enc_session->es_ivs[enc_level][idx], IQUIC_IV_LEN, hexbuf));
LSQ_DEBUG("hp: %s",
HEXSTR(enc_session->es_hps[enc_level][idx], IQUIC_HP_LEN, hexbuf));
}
static int
gquic2_setup_handshake_keys (struct lsquic_enc_session *enc_session)
{
const unsigned char *const cid_buf = enc_session->es_conn->cn_cid.idbuf;
const size_t cid_buf_sz = enc_session->es_conn->cn_cid.len;
size_t hsk_secret_sz;
int i, idx;
const EVP_MD *const md = EVP_sha256();
const char *const labels[] = { CLIENT_LABEL, SERVER_LABEL, };
const size_t label_sizes[] = { CLIENT_LABEL_SZ, SERVER_LABEL_SZ, };
const unsigned dirs[2] = {
(enc_session->es_flags & ES_SERVER),
!(enc_session->es_flags & ES_SERVER),
};
unsigned char hsk_secret[EVP_MAX_MD_SIZE];
unsigned char secret[SHA256_DIGEST_LENGTH];
if (!HKDF_extract(hsk_secret, &hsk_secret_sz, md, cid_buf, cid_buf_sz,
salt_Q050, sizeof(salt_Q050)))
{
LSQ_WARN("HKDF extract failed");
return -1;
}
for (i = 0; i < 2; ++i)
{
idx = dirs[i];
lsquic_qhkdf_expand(md, hsk_secret, hsk_secret_sz, labels[idx],
label_sizes[idx], secret, sizeof(secret));
/*
LSQ_DEBUG("`%s' handshake secret: %s",
HEXSTR(secret, sizeof(secret), hexbuf));
*/
if (0 != gquic2_init_crypto_ctx(enc_session, i,
secret, sizeof(secret)))
goto err;
if (enc_session->es_flags & ES_LOG_SECRETS)
log_crypto_ctx(enc_session, ENC_LEV_CLEAR, i);
}
return 0;
err:
return -1;
}
static void
maybe_log_secrets (struct lsquic_enc_session *enc_session)
{
const char *log;
log = getenv("LSQUIC_LOG_SECRETS");
if (log)
{
if (atoi(log))
enc_session->es_flags |= ES_LOG_SECRETS;
LSQ_DEBUG("will %slog secrets",
enc_session->es_flags & ES_LOG_SECRETS ? "" : "not ");
}
}
static enc_session_t *
lsquic_enc_session_create_client (struct lsquic_conn *lconn, const char *domain,
lsquic_cid_t cid, struct lsquic_engine_public *enpub,
const unsigned char *sess_resume, size_t sess_resume_len)
2017-09-22 21:00:03 +00:00
{
lsquic_session_cache_info_t *info;
struct lsquic_enc_session *enc_session;
c_cert_item_t *item;
const struct lsquic_sess_resume_storage *sess_resume_storage;
2017-09-22 21:00:03 +00:00
if (!domain)
{
errno = EINVAL;
return NULL;
}
enc_session = calloc(1, sizeof(*enc_session));
if (!enc_session)
return NULL;
/* have to allocate every time */
info = calloc(1, sizeof(*info));
if (!info)
{
free(enc_session);
return NULL;
}
if (sess_resume && sess_resume_len > sizeof(struct lsquic_sess_resume_storage))
2017-09-22 21:00:03 +00:00
{
item = calloc(1, sizeof(*item));
if (!item)
2017-09-22 21:00:03 +00:00
{
free(enc_session);
free(info);
2017-09-22 21:00:03 +00:00
return NULL;
}
sess_resume_storage = (const struct lsquic_sess_resume_storage *)sess_resume;
switch (lsquic_enc_session_deserialize_sess_resume(sess_resume_storage,
sess_resume_len,
&enpub->enp_settings,
info, item))
{
case RTT_DESERIALIZE_BAD_QUIC_VER:
LSQ_ERROR("provided sess_resume has unsupported QUIC version");
free(item);
break;
case RTT_DESERIALIZE_BAD_SERIAL_VER:
LSQ_ERROR("provided sess_resume has bad serializer version");
free(item);
break;
case RTT_DESERIALIZE_BAD_CERT_SIZE:
LSQ_ERROR("provided sess_resume has bad cert size");
free(item);
break;
case RTT_DESERIALIZE_OK:
memcpy(enc_session->hs_ctx.pubs, info->spubs, 32);
enc_session->cert_item = item;
break;
}
2017-09-22 21:00:03 +00:00
}
enc_session->es_conn = lconn;
2017-09-22 21:00:03 +00:00
enc_session->enpub = enpub;
enc_session->cid = cid;
enc_session->info = info;
/* FIXME: allocation may fail */
lsquic_str_append(&enc_session->hs_ctx.sni, domain, strlen(domain));
maybe_log_secrets(enc_session);
if (lconn->cn_version >= LSQVER_050)
{
enc_session->es_flags |= ES_GQUIC2;
gquic2_setup_handshake_keys(enc_session);
}
2017-09-22 21:00:03 +00:00
return enc_session;
}
/* Server side: Session_cache_entry can be saved for 0rtt */
static enc_session_t *
lsquic_enc_session_create_server (struct lsquic_conn *lconn, lsquic_cid_t cid,
struct lsquic_engine_public *enpub)
{
fiu_return_on("handshake/new_enc_session", NULL);
struct lsquic_enc_session *enc_session;
enc_session = calloc(1, sizeof(*enc_session));
if (!enc_session)
return NULL;
enc_session->es_conn = lconn;
enc_session->enpub = enpub;
enc_session->cid = cid;
enc_session->es_flags |= ES_SERVER;
maybe_log_secrets(enc_session);
if (lconn->cn_version >= LSQVER_050)
{
enc_session->es_flags |= ES_GQUIC2;
gquic2_setup_handshake_keys(enc_session);
}
return enc_session;
}
static void
lsquic_enc_session_reset_cid (enc_session_t *enc_session_p,
const lsquic_cid_t *new_cid)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
LSQ_INFOC("changing CID to %"CID_FMT, CID_BITS(new_cid));
enc_session->cid = *new_cid;
}
static void
put_compressed_cert (struct compressed_cert *ccert)
{
if (ccert)
{
assert(ccert->refcnt > 0);
--ccert->refcnt;
if (0 == ccert->refcnt)
free(ccert);
}
}
static struct compressed_cert *
new_compressed_cert (const unsigned char *buf, size_t len)
{
struct compressed_cert *ccert;
ccert = malloc(sizeof(*ccert) + len);
if (ccert)
{
ccert->refcnt = 1;
ccert->len = len;
memcpy(ccert->buf, buf, len);
}
return ccert;
}
static void
lsquic_enc_session_destroy (enc_session_t *enc_session_p)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
enum gel gel;
unsigned i;
2017-09-22 21:00:03 +00:00
if (!enc_session)
return ;
hs_ctx_t *hs_ctx = &enc_session->hs_ctx;
2017-09-22 21:00:03 +00:00
lsquic_str_d(&hs_ctx->sni);
lsquic_str_d(&hs_ctx->ccs);
lsquic_str_d(&hs_ctx->ccrt);
lsquic_str_d(&hs_ctx->stk);
lsquic_str_d(&hs_ctx->sno);
lsquic_str_d(&hs_ctx->prof);
lsquic_str_d(&hs_ctx->csct);
put_compressed_cert(hs_ctx->ccert);
hs_ctx->ccert = NULL;
lsquic_str_d(&hs_ctx->uaid);
lsquic_str_d(&hs_ctx->scfg_pubs);
2017-09-22 21:00:03 +00:00
lsquic_str_d(&enc_session->chlo);
lsquic_str_d(&enc_session->sstk);
lsquic_str_d(&enc_session->ssno);
for (gel = 0; gel < N_GELS; ++gel)
for (i = 0; i < 2; ++i)
if (enc_session->es_aead_ctxs[gel][i])
{
EVP_AEAD_CTX_cleanup(enc_session->es_aead_ctxs[gel][i]);
free(enc_session->es_aead_ctxs[gel][i]);
}
memset(enc_session->es_aead_ctxs, 0, sizeof(enc_session->es_aead_ctxs));
if (enc_session->info)
{
lsquic_str_d(&enc_session->info->sstk);
lsquic_str_d(&enc_session->info->scfg);
lsquic_str_d(&enc_session->info->sni_key);
free(enc_session->info);
}
if (enc_session->cert_item)
{
free_c_cert_item(enc_session->cert_item);
enc_session->cert_item = NULL;
}
if ((enc_session->es_flags & ES_FREE_CERT_PTR) && enc_session->cert_ptr)
lsquic_str_delete(enc_session->cert_ptr);
2017-09-22 21:00:03 +00:00
free(enc_session);
}
static int get_hs_state(struct lsquic_enc_session *enc_session)
2017-09-22 21:00:03 +00:00
{
return enc_session->hsk_state;
}
/* make sure have more room for encrypt */
static int
lsquic_enc_session_is_hsk_done (enc_session_t *enc_session_p)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
2017-09-22 21:00:03 +00:00
return (get_hs_state(enc_session) == HSK_COMPLETED);
}
static void
process_copt (struct lsquic_enc_session *enc_session, const uint32_t *const opts,
2017-09-22 21:00:03 +00:00
unsigned n_opts)
{
unsigned i;
for (i = 0; i < n_opts; ++i)
switch (opts[i])
{
case QTAG_NSTP:
enc_session->hs_ctx.opts |= HOPT_NSTP;
break;
case QTAG_SREJ:
enc_session->hs_ctx.opts |= HOPT_SREJ;
break;
}
}
static int parse_hs_data (struct lsquic_enc_session *enc_session, uint32_t tag,
2017-09-22 21:00:03 +00:00
unsigned char *val, int len, uint32_t head_tag)
{
hs_ctx_t * hs_ctx = &enc_session->hs_ctx;
int is_client = (head_tag != QTAG_CHLO);
2017-09-22 21:00:03 +00:00
switch(tag)
{
case QTAG_PDMD:
hs_ctx->pdmd = get_tag_value_i32(val, len);
break;
case QTAG_MIDS:
if (0 != get_tag_val_u32(val, len,
(is_client ? &hs_ctx->mids : &hs_ctx->smids)))
2017-09-22 21:00:03 +00:00
return -1;
break;
case QTAG_SCLS:
hs_ctx->scls = get_tag_value_i32(val, len);
break;
case QTAG_CFCW:
if (0 != get_tag_val_u32(val, len, (is_client ? &hs_ctx->cfcw : &hs_ctx->scfcw)))
2017-09-22 21:00:03 +00:00
return -1;
break;
case QTAG_SFCW:
if (0 != get_tag_val_u32(val, len, (is_client ? &hs_ctx->sfcw : &hs_ctx->ssfcw)))
2017-09-22 21:00:03 +00:00
return -1;
break;
case QTAG_ICSL:
hs_ctx->icsl = get_tag_value_i32(val, len);
break;
case QTAG_IRTT:
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
if (0 != get_tag_val_u32(val, len, &hs_ctx->irtt))
return -1;
hs_ctx->set |= HSET_IRTT;
2017-09-22 21:00:03 +00:00
break;
case QTAG_COPT:
if (0 == len % sizeof(uint32_t))
process_copt(enc_session, (uint32_t *) val, len / sizeof(uint32_t));
/* else ignore, following the reference implementation */
break;
case QTAG_SNI:
lsquic_str_setto(&hs_ctx->sni, val, len);
ESHIST_APPEND(enc_session, ESHE_SET_SNI);
break;
case QTAG_CCS:
lsquic_str_setto(&hs_ctx->ccs, val, len);
break;
case QTAG_CCRT:
lsquic_str_setto(&hs_ctx->ccrt, val, len);
break;
case QTAG_CRT:
put_compressed_cert(hs_ctx->ccert);
hs_ctx->ccert = new_compressed_cert(val, len);
2017-09-22 21:00:03 +00:00
break;
case QTAG_PUBS:
if (head_tag == QTAG_SCFG)
lsquic_str_setto(&hs_ctx->scfg_pubs, val, len);
else if (len == 32)
memcpy(hs_ctx->pubs, val, len);
2017-09-22 21:00:03 +00:00
break;
case QTAG_RCID:
hs_ctx->rcid = get_tag_value_i64(val, len);
break;
case QTAG_UAID:
lsquic_str_setto(&hs_ctx->uaid, val, len);
break;
2017-09-22 21:00:03 +00:00
case QTAG_SMHL:
if (0 != get_tag_val_u32(val, len, &hs_ctx->smhl))
return -1;
hs_ctx->set |= HSET_SMHL;
break;
case QTAG_TCID:
if (0 != get_tag_val_u32(val, len, &hs_ctx->tcid))
return -1;
hs_ctx->set |= HSET_TCID;
break;
case QTAG_EXPY:
enc_session->info->expy = get_tag_value_i64(val, len);
break;
case QTAG_ORBT:
enc_session->info->orbt = get_tag_value_i64(val, len);
break;
case QTAG_SNO:
if (is_client)
{
lsquic_str_setto(&enc_session->ssno, val, len);
}
else
{
/* Server side save a copy of SNO just for verify */
lsquic_str_setto(&hs_ctx->sno, val, len);
}
2017-09-22 21:00:03 +00:00
ESHIST_APPEND(enc_session, ESHE_SET_SNO);
break;
case QTAG_STK:
if (is_client)
{
lsquic_str_setto(&enc_session->info->sstk, val, len);
}
else
{
/* Server need to save a copy to verify */
lsquic_str_setto(&hs_ctx->stk, val, len);
}
ESHIST_APPEND(enc_session, ESHE_SET_STK);
break;
2017-09-22 21:00:03 +00:00
case QTAG_SCID:
if (len < SCID_LENGTH)
2017-09-22 21:00:03 +00:00
return -1;
if (is_client)
{
memcpy(enc_session->info->sscid, val, SCID_LENGTH);
}
else
{
memcpy(hs_ctx->scid, val, SCID_LENGTH);
hs_ctx->set |= HSET_SCID;
}
2017-09-22 21:00:03 +00:00
ESHIST_APPEND(enc_session, ESHE_SET_SCID);
break;
case QTAG_AEAD:
if (is_client)
enc_session->info->aead = get_tag_value_i32(val, len);
else
hs_ctx->aead = get_tag_value_i32(val, len);
2017-09-22 21:00:03 +00:00
break;
case QTAG_KEXS:
if (is_client)
{
if (head_tag == QTAG_SCFG && 0 == len % 4)
{
const unsigned char *p, *end;
unsigned pub_idx, idx;
#ifdef WIN32
pub_idx = 0;
#endif
for (p = val; p < val + len; p += 4)
if (0 == memcmp(p, "C255", 4))
{
memcpy(&enc_session->info->kexs, p, 4);
pub_idx = (p - val) / 4;
LSQ_DEBUG("Parsing SCFG: supported KEXS C255 at "
"index %u", pub_idx);
break;
}
if (p >= val + len)
{
LSQ_INFO("supported KEXS not found, trouble ahead");
break;
}
if (lsquic_str_len(&hs_ctx->scfg_pubs) > 0)
{
p = (const unsigned char *)
lsquic_str_cstr(&hs_ctx->scfg_pubs);
end = p + lsquic_str_len(&hs_ctx->scfg_pubs);
for (idx = 0; p < end; ++idx)
{
uint32_t sz = 0;
if (p + 3 > end)
break;
sz |= *p++;
sz |= *p++ << 8;
sz |= *p++ << 16;
if (p + sz > end)
break;
if (idx == pub_idx)
{
if (sz == 32)
{
memcpy(hs_ctx->pubs, p, 32);
memcpy(enc_session->info->spubs, p, 32);
}
break;
}
p += sz;
}
}
else
LSQ_INFO("No PUBS from SCFG to parse");
}
}
else
hs_ctx->kexs = get_tag_value_i32(val, len);
2017-09-22 21:00:03 +00:00
break;
case QTAG_NONC:
if (len != sizeof(hs_ctx->nonc))
return -1;
memcpy(hs_ctx->nonc, val, len);
break;
case QTAG_SCFG:
if (is_client)
{
lsquic_str_setto(&enc_session->info->scfg, val, len);
enc_session->info->scfg_flag = 1;
}
else
LSQ_INFO("unexpected SCFG");
2017-09-22 21:00:03 +00:00
break;
case QTAG_PROF:
lsquic_str_setto(&hs_ctx->prof, val, len);
ESHIST_APPEND(enc_session, ESHE_SET_PROF);
break;
case QTAG_STTL:
hs_ctx->sttl = get_tag_value_i64(val, len);
break;
2018-08-15 19:06:31 +00:00
case QTAG_SRST:
if (enc_session->es_flags & ES_SERVER)
break;
2018-08-15 19:06:31 +00:00
if (len != sizeof(hs_ctx->srst))
{
LSQ_INFO("Unexpected size of SRST: %u instead of %zu bytes",
len, sizeof(hs_ctx->srst));
return -1;
}
memcpy(hs_ctx->srst, val, len);
hs_ctx->set |= HSET_SRST;
ESHIST_APPEND(enc_session, ESHE_SET_SRST);
break;
case QTAG_XLCT:
if (len != sizeof(hs_ctx->xlct))
{
LSQ_INFO("Unexpected size of XLCT: %u instead of %zu bytes",
len, sizeof(hs_ctx->xlct));
return -1;
}
hs_ctx->set |= HSET_XLCT;
hs_ctx->xlct = get_tag_value_i64(val, len);
break;
2017-09-22 21:00:03 +00:00
default:
LSQ_DEBUG("Ignored tag '%.*s'", 4, (char *)&tag);
break;
}
return 0;
}
/* only for the hs stream-frame data, NOT with the packet header or frame header*/
static enum handshake_error parse_hs (struct lsquic_enc_session *enc_session,
2017-09-22 21:00:03 +00:00
const unsigned char *buf, int buf_len,
uint32_t *head_tag)
{
uint16_t i;
const unsigned char *p = buf;
const unsigned char *pend = buf + buf_len;
unsigned char *data;
uint32_t len = 0, offset = 0;
uint16_t num;
uint32_t tag;
if (buf_len < 6)
return DATA_FORMAT_ERROR;
memcpy(&tag, p, 4);
p += 4;
if (enc_session->es_flags & ES_SERVER)
{ /* Server only expects to receive CHLO messages from the client */
if (tag != QTAG_CHLO)
return DATA_FORMAT_ERROR;
}
else
2017-09-22 21:00:03 +00:00
{
if (tag != QTAG_SREJ && tag != QTAG_REJ && tag != QTAG_SHLO &&
tag != QTAG_SCFG)
return DATA_FORMAT_ERROR;
}
*head_tag = tag;
memcpy((char *)&num, p, 2);
p += 2 + 2; /* the 2 bytes padding 0x0000 need to be bypassed */
if (num < 1)
return DATA_FORMAT_ERROR;
data = (uint8_t *)(buf + 4 * 2 * (1 + num));
if ((const char *)data > (const char *)pend)
{
LSQ_DEBUG("parse_hs tag '%.*s' error: data not enough", 4, (char *)head_tag);
return DATA_NOT_ENOUGH;
}
/* check last offset */
memcpy((char *)&len, data - 4, 4);
if ((const char *)data + len > (const char *)pend)
{
LSQ_DEBUG("parse_hs tag '%.*s' error: data not enough!!!", 4, (char *)head_tag);
return DATA_NOT_ENOUGH;
}
for (i=0; i<num; ++i)
{
memcpy((char *)&tag, p, 4);
p += 4;
memcpy((char *)&len, p, 4);
len -= offset;
p += 4;
if ((const char *)data + offset + len > (const char *)pend)
return DATA_FORMAT_ERROR;
if (0 != parse_hs_data(enc_session, tag, data + offset, len,
*head_tag))
return DATA_FORMAT_ERROR;
offset += len;
}
LSQ_DEBUG("parse_hs tag '%.*s' no error.", 4, (char *)head_tag);
return DATA_NO_ERROR;
}
static uint32_t get_tag_value_i32(unsigned char *val, int len)
{
uint32_t v;
if (len < 4)
return 0;
memcpy(&v, val, 4);
return v;
}
static uint64_t get_tag_value_i64(unsigned char *val, int len)
{
uint64_t v;
if (len < 8)
return 0;
memcpy(&v, val, 8);
return v;
}
static int
get_tag_val_u32 (unsigned char *v, int len, uint32_t *val)
{
if (len != 4)
return -1;
memcpy(val, v, 4);
return 0;
}
/* From "QUIC Crypto" for easy reference:
*
* A handshake message consists of:
* - The tag of the message.
* - A uint16 containing the number of tag-value pairs.
* - Two bytes of padding which should be zero when sent but ignored when
* received.
* - A series of uint32 tags and uint32 end offsets, one for each
* tag-value pair. The tags must be strictly monotonically
* increasing, and the end-offsets must be monotonic non-decreasing.
* The end offset gives the offset, from the start of the value
* data, to a byte one beyond the end of the data for that tag.
* (Thus the end offset of the last tag contains the length of the
* value data).
* - The value data, concatenated without padding.
*/
struct table_entry { uint32_t tag, off; };
struct message_writer
{
unsigned char *mw_p;
struct table_entry mw_first_dummy_entry;
struct table_entry *mw_entry,
*mw_prev_entry,
*mw_end;
};
/* MW_ family of macros is used to write entries to handshake message
* (MW stands for "message writer").
*/
#define MW_BEGIN(mw, msg_tag, n_entries, data_ptr) do { \
uint32_t t_ = msg_tag; \
uint16_t n_ = n_entries; \
memcpy(data_ptr, &t_, 4); \
memcpy(data_ptr + 4, &n_, 2); \
memset(data_ptr + 4 + 2, 0, 2); \
(mw)->mw_entry = (void *) (data_ptr + 8); \
(mw)->mw_p = data_ptr + 8 + \
2018-08-15 19:06:31 +00:00
(n_entries) * sizeof((mw)->mw_entry[0]); \
2017-09-22 21:00:03 +00:00
(mw)->mw_first_dummy_entry.tag = 0; \
(mw)->mw_first_dummy_entry.off = 0; \
(mw)->mw_prev_entry = &(mw)->mw_first_dummy_entry; \
(mw)->mw_end = (void *) (mw)->mw_p; \
} while (0)
#ifndef NDEBUG
# define MW_END(mw) do { \
assert((mw)->mw_entry == (mw)->mw_end); \
} while (0)
#else
# define MW_END(mw)
#endif
#define MW_P(mw) ((mw)->mw_p)
#define MW_ADVANCE_P(mw, n) do { \
MW_P(mw) += (n); \
} while (0)
#define MW_WRITE_TABLE_ENTRY(mw, tag_, sz) do { \
assert((mw)->mw_prev_entry->tag < (tag_)); \
assert((mw)->mw_entry < (mw)->mw_end); \
(mw)->mw_entry->tag = (tag_); \
(mw)->mw_entry->off = (mw)->mw_prev_entry->off + (sz); \
(mw)->mw_prev_entry = (mw)->mw_entry; \
++(mw)->mw_entry; \
} while (0)
#define MW_WRITE_BUFFER(mw, tag, buf, sz) do { \
MW_WRITE_TABLE_ENTRY(mw, tag, sz); \
memcpy(MW_P(mw), buf, sz); \
MW_ADVANCE_P(mw, sz); \
} while (0)
#define MW_WRITE_LS_STR(mw, tag, s) \
MW_WRITE_BUFFER(mw, tag, lsquic_str_buf(s), lsquic_str_len(s))
#define MW_WRITE_UINT32(mw, tag, val) do { \
uint32_t v_ = (val); \
MW_WRITE_BUFFER(mw, tag, &v_, sizeof(v_)); \
} while (0)
#define MW_WRITE_UINT64(mw, tag, val) do { \
uint64_t v_ = (val); \
MW_WRITE_BUFFER(mw, tag, &v_, sizeof(v_)); \
} while (0)
/* MSG_LEN_ family of macros calculates buffer size required for a
* handshake message.
*/
#define MSG_LEN_INIT(len) do { \
len = 4 /* Tag */ + 2 /* # tags */ + 2 /* Two zero bytes */; \
} while (0)
#define MSG_LEN_ADD(len, payload_sz) do { \
len += 4 + 4 + (payload_sz); \
} while (0)
#define MSG_LEN_VAL(len) (+(len))
static int
lsquic_enc_session_gen_chlo (enc_session_t *enc_session_p,
enum lsquic_version version, uint8_t *buf, size_t *len)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
int include_pad;
const lsquic_str_t *const ccs = lsquic_get_common_certs_hash();
2017-09-22 21:00:03 +00:00
const struct lsquic_engine_settings *const settings =
&enc_session->enpub->enp_settings;
c_cert_item_t *const cert_item = enc_session->cert_item;
2017-09-22 21:00:03 +00:00
unsigned char pub_key[32];
size_t ua_len;
uint32_t opts[1]; /* Only NSTP is supported for now */
2018-03-30 14:57:17 +00:00
unsigned n_opts, msg_len, n_tags, pad_size;
2017-09-22 21:00:03 +00:00
struct message_writer mw;
/* Before we do anything else, sanity check: */
if (*len < MIN_CHLO_SIZE)
return -1;
n_opts = 0;
/* CHLO is not regenerated during version negotiation. Hence we always
* include this option to cover the case when Q044 or Q046 gets negotiated
* down.
*/
2017-09-22 21:00:03 +00:00
if (settings->es_support_nstp)
opts[ n_opts++ ] = QTAG_NSTP;
/* Count tags and calculate required buffer size: */
MSG_LEN_INIT(msg_len); n_tags = 0;
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* PDMD */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* AEAD */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* VER */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* MIDS */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* SCLS */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* CFCW */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* SFCW */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* ICSL */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* SMHL */
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* KEXS */
MSG_LEN_ADD(msg_len, 0); ++n_tags; /* CSCT */
if (n_opts > 0)
{
MSG_LEN_ADD(msg_len, sizeof(opts[0]) * n_opts);
++n_tags; /* COPT */
}
if (settings->es_ua)
{
ua_len = strlen(settings->es_ua);
if (ua_len > 0)
{
MSG_LEN_ADD(msg_len, ua_len); ++n_tags; /* UAID */
}
}
else
ua_len = 0;
if (settings->es_support_tcid0)
{
MSG_LEN_ADD(msg_len, 4); ++n_tags; /* TCID */
}
2017-09-22 21:00:03 +00:00
MSG_LEN_ADD(msg_len, lsquic_str_len(&enc_session->hs_ctx.sni));
++n_tags; /* SNI */
MSG_LEN_ADD(msg_len, lsquic_str_len(ccs)); ++n_tags; /* CCS */
if (cert_item)
2017-09-22 21:00:03 +00:00
{
enc_session->cert_ptr = &cert_item->crts[0];
MSG_LEN_ADD(msg_len, lsquic_str_len(cert_item->hashs));
2017-09-22 21:00:03 +00:00
++n_tags; /* CCRT */
MSG_LEN_ADD(msg_len, 8); ++n_tags; /* XLCT */
}
MSG_LEN_ADD(msg_len, lsquic_str_len(&enc_session->ssno));
++n_tags; /* SNO */
MSG_LEN_ADD(msg_len, lsquic_str_len(&enc_session->info->sstk));
++n_tags; /* STK */
if (lsquic_str_len(&enc_session->info->scfg) > 0)
{
MSG_LEN_ADD(msg_len, sizeof(enc_session->info->sscid));
++n_tags; /* SCID */
if (enc_session->cert_ptr)
{
MSG_LEN_ADD(msg_len, sizeof(pub_key));
++n_tags; /* PUBS */
MSG_LEN_ADD(msg_len, sizeof(enc_session->hs_ctx.nonc));
++n_tags; /* NONC */
RAND_bytes(enc_session->priv_key, 32);
lsquic_c255_get_pub_key(enc_session->priv_key, pub_key);
lsquic_gen_nonce_c(enc_session->hs_ctx.nonc, enc_session->info->orbt);
2017-09-22 21:00:03 +00:00
}
}
include_pad = MSG_LEN_VAL(msg_len) < MIN_CHLO_SIZE;
if (include_pad)
{
if (MSG_LEN_VAL(msg_len) + sizeof(struct table_entry) < MIN_CHLO_SIZE)
pad_size = MIN_CHLO_SIZE - MSG_LEN_VAL(msg_len) -
sizeof(struct table_entry);
else
pad_size = 0;
MSG_LEN_ADD(msg_len, pad_size); ++n_tags; /* PAD */
}
2018-03-30 14:57:17 +00:00
#ifdef WIN32
else
pad_size = 0;
#endif
2017-09-22 21:00:03 +00:00
/* Check that we have enough room in the output buffer: */
if (MSG_LEN_VAL(msg_len) > *len)
return -1;
/* Write CHLO: */
MW_BEGIN(&mw, QTAG_CHLO, n_tags, buf);
if (include_pad)
{
memset(MW_P(&mw), '-', pad_size);
MW_WRITE_TABLE_ENTRY(&mw, QTAG_PAD, pad_size);
MW_ADVANCE_P(&mw, pad_size);
}
MW_WRITE_LS_STR(&mw, QTAG_SNI, &enc_session->hs_ctx.sni);
MW_WRITE_LS_STR(&mw, QTAG_STK, &enc_session->info->sstk);
MW_WRITE_LS_STR(&mw, QTAG_SNO, &enc_session->ssno);
MW_WRITE_UINT32(&mw, QTAG_VER, lsquic_ver2tag(version));
MW_WRITE_LS_STR(&mw, QTAG_CCS, ccs);
if (lsquic_str_len(&enc_session->info->scfg) > 0 && enc_session->cert_ptr)
MW_WRITE_BUFFER(&mw, QTAG_NONC, enc_session->hs_ctx.nonc,
sizeof(enc_session->hs_ctx.nonc));
MW_WRITE_UINT32(&mw, QTAG_AEAD, settings->es_aead);
if (ua_len)
MW_WRITE_BUFFER(&mw, QTAG_UAID, settings->es_ua, ua_len);
if (lsquic_str_len(&enc_session->info->scfg) > 0)
MW_WRITE_BUFFER(&mw, QTAG_SCID, enc_session->info->sscid,
sizeof(enc_session->info->sscid));
if (settings->es_support_tcid0)
MW_WRITE_UINT32(&mw, QTAG_TCID, 0);
2017-09-22 21:00:03 +00:00
MW_WRITE_UINT32(&mw, QTAG_PDMD, settings->es_pdmd);
MW_WRITE_UINT32(&mw, QTAG_SMHL, 1);
MW_WRITE_UINT32(&mw, QTAG_ICSL, settings->es_idle_conn_to / 1000000);
if (lsquic_str_len(&enc_session->info->scfg) > 0 && enc_session->cert_ptr)
MW_WRITE_BUFFER(&mw, QTAG_PUBS, pub_key, sizeof(pub_key));
MW_WRITE_UINT32(&mw, QTAG_MIDS, settings->es_max_streams_in);
MW_WRITE_UINT32(&mw, QTAG_SCLS, settings->es_silent_close);
MW_WRITE_UINT32(&mw, QTAG_KEXS, settings->es_kexs);
if (cert_item)
MW_WRITE_BUFFER(&mw, QTAG_XLCT, lsquic_str_buf(cert_item->hashs), 8);
2017-09-22 21:00:03 +00:00
/* CSCT is empty on purpose (retained from original code) */
MW_WRITE_TABLE_ENTRY(&mw, QTAG_CSCT, 0);
if (n_opts > 0)
MW_WRITE_BUFFER(&mw, QTAG_COPT, opts, n_opts * sizeof(opts[0]));
if (cert_item)
MW_WRITE_LS_STR(&mw, QTAG_CCRT, cert_item->hashs);
2017-09-22 21:00:03 +00:00
MW_WRITE_UINT32(&mw, QTAG_CFCW, settings->es_cfcw);
MW_WRITE_UINT32(&mw, QTAG_SFCW, settings->es_sfcw);
MW_END(&mw);
assert(buf + *len >= MW_P(&mw));
*len = MW_P(&mw) - buf;
lsquic_str_setto(&enc_session->chlo, buf, *len);
if (lsquic_str_len(&enc_session->info->scfg) > 0 && enc_session->cert_ptr)
{
enc_session->have_key = 0;
assert(lsquic_str_len(enc_session->cert_ptr) > 0);
determine_keys(enc_session);
2017-09-22 21:00:03 +00:00
enc_session->have_key = 1;
}
LSQ_DEBUG("lsquic_enc_session_gen_chlo called, return 0, buf_len %zd.", *len);
return 0;
2017-09-22 21:00:03 +00:00
}
static enum handshake_error
determine_rtts (struct lsquic_enc_session *enc_session,
const struct sockaddr *ip_addr, time_t t)
2017-09-22 21:00:03 +00:00
{
hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
enum hsk_failure_reason hfr;
2017-09-22 21:00:03 +00:00
if (!(hs_ctx->set & HSET_SCID))
{
hs_ctx->rrej = HFR_CONFIG_INCHOATE_HELLO;
ESHIST_APPEND(enc_session, ESHE_MISSING_SCID);
goto fail_1rtt;
}
hfr = lsquic_verify_stk(enc_session, ip_addr, t, &hs_ctx->stk);
if (hfr != HFR_HANDSHAKE_OK)
{
hs_ctx->rrej = hfr;
ESHIST_APPEND(enc_session, ESHE_VSTK_FAILED);
goto fail_1rtt;
}
else
ESHIST_APPEND(enc_session, ESHE_VSTK_OK);
if (memcmp(enc_session->server_config->lsc_scfg->info.sscid, hs_ctx->scid, 16) != 0)
{
hs_ctx->rrej = HFR_CONFIG_UNKNOWN_CONFIG;
ESHIST_APPEND(enc_session, ESHE_UNKNOWN_CONFIG);
goto fail_1rtt;
}
if (!(lsquic_str_len(&hs_ctx->ccrt) > 0))
{
/* We provide incorrect RREJ here because there is not one that fits
* this case. We can tell them apart: one comes in SREJ, the other
* in REJ.
*/
hs_ctx->rrej = HFR_CONFIG_INCHOATE_HELLO;
ESHIST_APPEND(enc_session, ESHE_EMPTY_CCRT);
goto fail_1rtt;
}
if (hs_ctx->set & HSET_XLCT)
{
if (enc_session->cert_hash != hs_ctx->xlct)
{
/* The expected leaf certificate hash could not be validated. */
hs_ctx->rrej = HFR_INVALID_EXPECTED_LEAF_CERTIFICATE;
ESHIST_APPEND(enc_session, ESHE_XLCT_MISMATCH);
goto fail_1rtt;
}
}
if (lsquic_str_len(&enc_session->ssno) > 0)
{
if (lsquic_str_len(&hs_ctx->sno) == 0)
{
hs_ctx->rrej = HFR_SERVER_NONCE_REQUIRED;
ESHIST_APPEND(enc_session, ESHE_MISSING_SNO);
goto fail_1rtt;
}
else if (lsquic_str_bcmp(&enc_session->ssno, &hs_ctx->sno) != 0)
{
hs_ctx->rrej = HFR_SERVER_NONCE_INVALID;
ESHIST_APPEND(enc_session, ESHE_SNO_MISMATCH);
goto fail_1rtt;
}
else
ESHIST_APPEND(enc_session, ESHE_SNO_OK);
}
enc_session->hsk_state = HSK_SHLO;
memcpy(enc_session->priv_key, enc_session->server_config->lsc_scfg->info.priv_key, 32);
return HS_SHLO;
fail_1rtt:
enc_session->hsk_state = HSK_CHLO_REJ;
return HS_1RTT;
2017-09-22 21:00:03 +00:00
}
static int
config_has_correct_size (const struct lsquic_enc_session *enc_session,
const void *data, unsigned shm_len)
{
/* EVP_AEAD_CTX from boringssl after-18d9f28f0df9f95570. */
struct new_evp_aead_ctx_st {
void *ptr1; /* aead */
void *ptr2; /* aead_state */
uint8_t tag_len;
};
/* This is how SHM would like in 5.2.1 builds 7 and 8: */
struct old_scfg_info
{
unsigned char sscid[SCID_LENGTH];
unsigned char priv_key[32];
unsigned char skt_key[16];
uint32_t aead;
uint32_t kexs;
uint32_t pdmd;
uint64_t orbt;
uint64_t expy;
struct new_evp_aead_ctx_st ctx;
short scfg_len;
};
const SCFG_t *const modern_config = data;
const struct old_scfg_info *const old_info = data;
size_t expected_size;
expected_size = modern_config->info.scfg_len + sizeof(*modern_config);
if (expected_size == shm_len)
return 1;
if (old_info->scfg_len + sizeof(*old_info) == shm_len)
{
LSQ_WARN("Generating new server config");
return 0;
}
LSQ_ERROR("Server config has size %u -- expected %zd", shm_len,
expected_size);
return 0;
}
static lsquic_server_config_t *
get_valid_scfg (const struct lsquic_enc_session *enc_session,
const struct lsquic_engine_public *enpub)
{
#define SERVER_SCFG_KEY "SERVER_SCFG"
#define SERVER_SCFG_KEY_SIZE (sizeof(SERVER_SCFG_KEY) - 1)
const struct lsquic_engine_settings *const settings = &enpub->enp_settings;
const struct lsquic_shared_hash_if *const shi = enpub->enp_shi;
void *const shi_ctx = enpub->enp_shi_ctx;
uint8_t spubs[35] = {0x20, 0, 0, };/* need to init first 3 bytes */
time_t t = time(NULL);
unsigned int real_len;
SCFG_info_t *temp_scfg;
SCFG_t *tmp_scfg_copy = NULL;
void *scfg_ptr;
int ret;
unsigned msg_len, server_config_sz;
struct message_writer mw;
2022-04-27 19:40:59 +00:00
if (enpub->enp_server_config->lsc_scfg && (enpub->enp_server_config->lsc_scfg->info.expy > (uint64_t)t))
return enpub->enp_server_config;
ret = shi->shi_lookup(shi_ctx, SERVER_SCFG_KEY, SERVER_SCFG_KEY_SIZE,
&scfg_ptr, &real_len);
if (ret == 1)
{
if (config_has_correct_size(enc_session, scfg_ptr, real_len) &&
2022-04-27 19:40:59 +00:00
(enpub->enp_server_config->lsc_scfg = scfg_ptr,
enpub->enp_server_config->lsc_scfg->info.expy > (uint64_t)t))
{
/* Why need to init here, because this memory may be read from SHM,
* the struct is ready but AEAD_CTX is not ready.
**/
2022-04-27 19:40:59 +00:00
EVP_AEAD_CTX_init(&enpub->enp_server_config->lsc_stk_ctx, EVP_aead_aes_128_gcm(),
enpub->enp_server_config->lsc_scfg->info.skt_key, 16, 12, NULL);
return enpub->enp_server_config;
}
else
{
shi->shi_delete(shi_ctx, SERVER_SCFG_KEY, SERVER_SCFG_KEY_SIZE);
}
}
MSG_LEN_INIT(msg_len);
MSG_LEN_ADD(msg_len, sizeof(temp_scfg->sscid));
MSG_LEN_ADD(msg_len, sizeof(spubs));
MSG_LEN_ADD(msg_len, enpub->enp_ver_tags_len);
MSG_LEN_ADD(msg_len, sizeof(temp_scfg->aead));
MSG_LEN_ADD(msg_len, sizeof(temp_scfg->kexs));
MSG_LEN_ADD(msg_len, sizeof(temp_scfg->pdmd));
MSG_LEN_ADD(msg_len, sizeof(temp_scfg->orbt));
MSG_LEN_ADD(msg_len, sizeof(temp_scfg->expy));
2022-04-27 19:40:59 +00:00
server_config_sz = sizeof(*enpub->enp_server_config->lsc_scfg) + MSG_LEN_VAL(msg_len);
enpub->enp_server_config->lsc_scfg = malloc(server_config_sz);
if (!enpub->enp_server_config->lsc_scfg)
return NULL;
2022-04-27 19:40:59 +00:00
temp_scfg = &enpub->enp_server_config->lsc_scfg->info;
RAND_bytes(temp_scfg->skt_key, sizeof(temp_scfg->skt_key));
RAND_bytes(temp_scfg->sscid, sizeof(temp_scfg->sscid));
RAND_bytes(temp_scfg->priv_key, sizeof(temp_scfg->priv_key));
lsquic_c255_get_pub_key(temp_scfg->priv_key, spubs + 3);
temp_scfg->aead = settings->es_aead;
temp_scfg->kexs = settings->es_kexs;
temp_scfg->pdmd = settings->es_pdmd;
temp_scfg->orbt = 0;
temp_scfg->expy = t + settings->es_sttl;
2022-04-27 19:40:59 +00:00
MW_BEGIN(&mw, QTAG_SCFG, 8, enpub->enp_server_config->lsc_scfg->scfg);
MW_WRITE_BUFFER(&mw, QTAG_VER, enpub->enp_ver_tags_buf,
enpub->enp_ver_tags_len);
MW_WRITE_UINT32(&mw, QTAG_AEAD, temp_scfg->aead);
MW_WRITE_BUFFER(&mw, QTAG_SCID, temp_scfg->sscid, sizeof(temp_scfg->sscid));
MW_WRITE_UINT32(&mw, QTAG_PDMD, temp_scfg->pdmd);
MW_WRITE_BUFFER(&mw, QTAG_PUBS, spubs, sizeof(spubs));
MW_WRITE_UINT32(&mw, QTAG_KEXS, temp_scfg->kexs);
MW_WRITE_UINT64(&mw, QTAG_ORBT, temp_scfg->orbt);
MW_WRITE_UINT64(&mw, QTAG_EXPY, temp_scfg->expy);
MW_END(&mw);
2022-04-27 19:40:59 +00:00
assert(MW_P(&mw) == enpub->enp_server_config->lsc_scfg->scfg + MSG_LEN_VAL(msg_len));
temp_scfg->scfg_len = MSG_LEN_VAL(msg_len);
LSQ_DEBUG("%s called, return len %d.", __func__, temp_scfg->scfg_len);
// /* TODO: will shi_delete call free to release the buffer? */
// shi->shi_delete(shi_ctx, SERVER_SCFG_KEY, SERVER_SCFG_KEY_SIZE);
void *scfg_key = strdup(SERVER_SCFG_KEY);
shi->shi_insert(shi_ctx, scfg_key, SERVER_SCFG_KEY_SIZE,
2022-04-27 19:40:59 +00:00
enpub->enp_server_config->lsc_scfg, server_config_sz, t + settings->es_sttl);
ret = shi->shi_lookup(shi_ctx, scfg_key, SERVER_SCFG_KEY_SIZE,
&scfg_ptr, &real_len);
if (ret == 1)
{
tmp_scfg_copy = scfg_ptr;
2022-04-27 19:40:59 +00:00
if (tmp_scfg_copy != enpub->enp_server_config->lsc_scfg)
{
2022-04-27 19:40:59 +00:00
free(enpub->enp_server_config->lsc_scfg);
enpub->enp_server_config->lsc_scfg = tmp_scfg_copy;
}
}
else
{
/* Since internal error occured, but I have to use a SCFG, log it*/
LSQ_DEBUG("get_valid_scfg got an shi internal error.\n");
}
2022-04-27 19:40:59 +00:00
ret = EVP_AEAD_CTX_init(&enpub->enp_server_config->lsc_stk_ctx, EVP_aead_aes_128_gcm(),
enpub->enp_server_config->lsc_scfg->info.skt_key,
sizeof(enpub->enp_server_config->lsc_scfg->info.skt_key), 12, NULL);
LSQ_DEBUG("get_valid_scfg::EVP_AEAD_CTX_init return %d.", ret);
2022-04-27 19:40:59 +00:00
return enpub->enp_server_config;
}
static int
generate_crt (struct lsquic_enc_session *enc_session, int common_case)
{
int i, n, len, crt_num, rv = -1;
lsquic_str_t **crts;
unsigned char *out;
X509* crt;
STACK_OF(X509) *pXchain;
SSL_CTX *const ctx = enc_session->ssl_ctx;
hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
struct compressed_cert *ccert;
SSL_CTX_get0_chain_certs(ctx, &pXchain);
n = sk_X509_num(pXchain);
crt_num = n + 1;
crts = calloc(crt_num, sizeof(crts[0]));
if (!crts)
return -1;
crts[0] = lsquic_str_new(lsquic_str_cstr(enc_session->cert_ptr),
lsquic_str_len(enc_session->cert_ptr));
if (!crts[0])
goto cleanup;
for (i = 1; i < crt_num; i++)
{
crt = sk_X509_value(pXchain, i - 1);
out = NULL;
len = i2d_X509(crt, &out);
if (len < 0)
goto cleanup;
crts[i] = lsquic_str_new((const char *) out, len);
OPENSSL_free(out);
}
ccert = lsquic_compress_certs(crts, crt_num, &hs_ctx->ccs, &hs_ctx->ccrt);
if (!ccert)
goto cleanup;
if (common_case)
{
if (SSL_CTX_set_ex_data(ctx, s_ccrt_idx, ccert))
++ccert->refcnt;
else
{
free(ccert);
ccert = NULL;
goto cleanup;
}
}
++ccert->refcnt;
hs_ctx->ccert = ccert;
/* We got here, set rv to 0: success */
rv = 0;
cleanup:
for (i = 0; i < crt_num; ++i)
if (crts[i])
lsquic_str_delete(crts[i]);
free(crts);
return rv;
}
/* rtt == 1 case */
static int
gen_rej1_data (struct lsquic_enc_session *enc_session, uint8_t *data,
size_t max_len, const struct sockaddr *ip, time_t t)
{
#ifndef WIN32
# define ERR(e_) do { return (e_); } while (0)
#else
# define ERR(e_) do { len = (e_); goto end; } while (0)
#endif
int len;
EVP_PKEY * rsa_priv_key;
SSL_CTX *ctx = enc_session->ssl_ctx;
hs_ctx_t *const hs_ctx = &enc_session->hs_ctx;
int scfg_len = enc_session->server_config->lsc_scfg->info.scfg_len;
uint8_t *scfg_data = enc_session->server_config->lsc_scfg->scfg;
int common_case;
size_t msg_len;
struct message_writer mw;
uint64_t sttl;
rsa_priv_key = SSL_CTX_get0_privatekey(ctx);
if (!rsa_priv_key)
return -1;
size_t prof_len = (size_t) EVP_PKEY_size(rsa_priv_key);
#ifndef WIN32
char prof_buf[prof_len];
#else
2020-10-07 15:05:18 +00:00
char *prof_buf = _malloca(prof_len);
if (!prof_buf)
return -1;
#endif
if (hs_ctx->ccert)
{
put_compressed_cert(hs_ctx->ccert);
hs_ctx->ccert = NULL;
}
/**
* Only cache hs_ctx->ccs is the hardcoded common certs and hs_ctx->ccrt is empty case
* This is the most common case
*/
common_case = lsquic_str_len(&hs_ctx->ccrt) == 0
&& lsquic_str_bcmp(&hs_ctx->ccs, lsquic_get_common_certs_hash()) == 0;
if (common_case)
hs_ctx->ccert = SSL_CTX_get_ex_data(ctx, s_ccrt_idx);
if (hs_ctx->ccert)
{
++hs_ctx->ccert->refcnt;
LSQ_DEBUG("use cached compressed cert");
}
else if (0 == generate_crt(enc_session, common_case))
LSQ_DEBUG("generated compressed cert");
else
{
LSQ_INFO("cannot could not generate compressed cert for");
ERR(-1);
}
LSQ_DEBUG("gQUIC rej1 data");
LSQ_DEBUG("gQUIC NOT enabled");
const int s = lsquic_gen_prof((const uint8_t *)lsquic_str_cstr(&enc_session->chlo),
(size_t)lsquic_str_len(&enc_session->chlo),
scfg_data, scfg_len,
rsa_priv_key, (uint8_t *)prof_buf, &prof_len);
if (s != 0)
{
LSQ_INFO("could not generate server proof, code %d", s);
ERR(-1);
}
lsquic_str_setto(&hs_ctx->prof, prof_buf, prof_len);
if (!hs_ctx->rrej)
{
LSQ_WARN("REJ: RREJ is not set, use default");
hs_ctx->rrej = HFR_CLIENT_NONCE_UNKNOWN;
}
MSG_LEN_INIT(msg_len);
MSG_LEN_ADD(msg_len, sizeof(hs_ctx->rrej));
MSG_LEN_ADD(msg_len, scfg_len);
MSG_LEN_ADD(msg_len, STK_LENGTH);
MSG_LEN_ADD(msg_len, SNO_LENGTH);
MSG_LEN_ADD(msg_len, sizeof(sttl));
MSG_LEN_ADD(msg_len, lsquic_str_len(&hs_ctx->prof));
if (hs_ctx->ccert)
MSG_LEN_ADD(msg_len, hs_ctx->ccert->len);
if (MSG_LEN_VAL(msg_len) > max_len)
ERR(-1);
memcpy(enc_session->priv_key, enc_session->server_config->lsc_scfg->info.priv_key, 32);
if (lsquic_str_len(&enc_session->sstk) != STK_LENGTH)
{
lsquic_str_d(&enc_session->sstk);
lsquic_str_prealloc(&enc_session->sstk, STK_LENGTH);
lsquic_str_setlen(&enc_session->sstk, STK_LENGTH);
}
lsquic_gen_stk(enc_session->server_config, ip, t,
(unsigned char *) lsquic_str_buf(&enc_session->sstk));
if (lsquic_str_len(&enc_session->ssno) != SNO_LENGTH)
{
lsquic_str_d(&enc_session->ssno);
lsquic_str_prealloc(&enc_session->ssno, SNO_LENGTH);
lsquic_str_setlen(&enc_session->ssno, SNO_LENGTH);
}
RAND_bytes((uint8_t *) lsquic_str_buf(&enc_session->ssno), SNO_LENGTH);
sttl = enc_session->enpub->enp_server_config->lsc_scfg->info.expy
- (uint64_t) time(NULL);
MW_BEGIN(&mw, QTAG_REJ, 7, data);
MW_WRITE_LS_STR(&mw, QTAG_STK, &enc_session->sstk);
MW_WRITE_LS_STR(&mw, QTAG_SNO, &enc_session->ssno);
MW_WRITE_LS_STR(&mw, QTAG_PROF, &hs_ctx->prof);
MW_WRITE_BUFFER(&mw, QTAG_SCFG, scfg_data, scfg_len);
MW_WRITE_BUFFER(&mw, QTAG_RREJ, &hs_ctx->rrej, sizeof(hs_ctx->rrej));
MW_WRITE_BUFFER(&mw, QTAG_STTL, &sttl, sizeof(sttl));
if (hs_ctx->ccert)
MW_WRITE_BUFFER(&mw, QTAG_CRT, hs_ctx->ccert->buf, hs_ctx->ccert->len);
MW_END(&mw);
assert(data + max_len >= MW_P(&mw));
len = MW_P(&mw) - data;
LSQ_DEBUG("gen_rej1_data called, return len %d.", len);
#ifdef WIN32
end:
_freea(prof_buf);
#endif
return len;
#undef ERR
}
/* rtt == 0 case */
static int
gen_shlo_data (uint8_t *buf, size_t buf_len, struct lsquic_enc_session *enc_session,
enum lsquic_version version, const struct sockaddr *ip,
time_t t, uint8_t *nonce)
{
char pub_key[32];
const struct lsquic_engine_settings *const settings =
&enc_session->enpub->enp_settings;
struct message_writer mw;
int len;
const int include_reset_token = version >= LSQVER_046;
size_t msg_len;
MSG_LEN_INIT(msg_len);
MSG_LEN_ADD(msg_len, enc_session->enpub->enp_ver_tags_len);
MSG_LEN_ADD(msg_len, sizeof(pub_key));
MSG_LEN_ADD(msg_len, 4); /* MIDS */
MSG_LEN_ADD(msg_len, 4); /* CFCW */
MSG_LEN_ADD(msg_len, 4); /* SFCW */
MSG_LEN_ADD(msg_len, 4); /* ICSL */
MSG_LEN_ADD(msg_len, 4); /* SMHL */
MSG_LEN_ADD(msg_len, lsquic_str_len(&enc_session->sstk));
MSG_LEN_ADD(msg_len, lsquic_str_len(&enc_session->ssno));
if (include_reset_token)
MSG_LEN_ADD(msg_len, SRST_LENGTH);
if (MSG_LEN_VAL(msg_len) > buf_len)
return -1;
RAND_bytes(nonce, 32);
RAND_bytes(enc_session->priv_key, 32);
lsquic_c255_get_pub_key(enc_session->priv_key, (unsigned char *)pub_key);
if (lsquic_str_len(&enc_session->sstk) != STK_LENGTH)
{
lsquic_str_d(&enc_session->sstk);
lsquic_str_prealloc(&enc_session->sstk, STK_LENGTH);
lsquic_str_setlen(&enc_session->sstk, STK_LENGTH);
}
lsquic_gen_stk(enc_session->server_config, ip, t,
(unsigned char *) lsquic_str_buf(&enc_session->sstk));
if (lsquic_str_len(&enc_session->ssno) != SNO_LENGTH)
{
lsquic_str_d(&enc_session->ssno);
lsquic_str_prealloc(&enc_session->ssno, SNO_LENGTH);
lsquic_str_setlen(&enc_session->ssno, SNO_LENGTH);
}
RAND_bytes((uint8_t *) lsquic_str_buf(&enc_session->ssno), SNO_LENGTH);
MW_BEGIN(&mw, QTAG_SHLO, 9 + include_reset_token, buf);
MW_WRITE_LS_STR(&mw, QTAG_STK, &enc_session->sstk);
MW_WRITE_LS_STR(&mw, QTAG_SNO, &enc_session->ssno);
MW_WRITE_BUFFER(&mw, QTAG_VER, enc_session->enpub->enp_ver_tags_buf,
enc_session->enpub->enp_ver_tags_len);
MW_WRITE_UINT32(&mw, QTAG_SMHL, 1);
MW_WRITE_UINT32(&mw, QTAG_ICSL, settings->es_idle_conn_to / 1000000);
MW_WRITE_BUFFER(&mw, QTAG_PUBS, pub_key, sizeof(pub_key));
MW_WRITE_UINT32(&mw, QTAG_MIDS, settings->es_max_streams_in);
if (include_reset_token)
{
MW_WRITE_TABLE_ENTRY(&mw, QTAG_SRST, SRST_LENGTH);
lsquic_tg_generate_sreset(enc_session->enpub->enp_tokgen,
&enc_session->cid, MW_P(&mw));
MW_ADVANCE_P(&mw, SRST_LENGTH);
}
MW_WRITE_UINT32(&mw, QTAG_CFCW, settings->es_cfcw);
MW_WRITE_UINT32(&mw, QTAG_SFCW, settings->es_sfcw);
MW_END(&mw);
assert(buf + buf_len >= MW_P(&mw));
len = MW_P(&mw) - buf;
LSQ_DEBUG("gen_shlo_data called, return len %d.", len);
return len;
}
/* Generate key based on issuer and serial number. The key has the following
* structure:
*
* size_t length of issuer. This field is required to prevent
* the chance (however remote) that concatenation of
* the next two fields is ambiguous.
* uint8_t[] DER-encoded issuer
* uint8_t[] Serial number represented as sequence of bytes output
* by BN_bn2bin
*
* Return size of the key or zero on error.
*/
static size_t
gen_iasn_key (X509 *cert, unsigned char *const out, size_t const sz)
{
const unsigned char *name_bytes;
size_t name_sz;
X509_NAME *name;
ASN1_INTEGER *sernum;
BIGNUM *bn;
unsigned bn_sz;
name = X509_get_issuer_name(cert);
if (!name)
return 0;
if (!X509_NAME_get0_der(name, &name_bytes, &name_sz))
return 0;
sernum = X509_get_serialNumber(cert);
if (!sernum)
return 0;
bn = ASN1_INTEGER_to_BN(sernum, NULL);
if (!bn)
return 0;
bn_sz = BN_num_bytes(bn);
if (sizeof(size_t) + name_sz + bn_sz > sz)
{
BN_free(bn);
return 0;
}
memcpy(out, &name_sz, sizeof(name_sz));
memcpy(out + sizeof(name_sz), name_bytes, name_sz);
BN_bn2bin(bn, out + sizeof(name_sz) + name_sz);
BN_free(bn);
return sizeof(name_sz) + name_sz + bn_sz;
}
static enum {
GET_SNI_OK,
GET_SNI_ERR,
}
get_sni_SSL_CTX(struct lsquic_enc_session *enc_session, lsquic_lookup_cert_f cb,
void *cb_ctx, const struct sockaddr *local)
{
X509 *crt = NULL;
unsigned char *out;
int len;
lsquic_str_t crtstr;
cert_item_t *item;
struct ssl_ctx_st *ssl_ctx;
size_t key_sz;
unsigned char key[0x400];
if (!enc_session->ssl_ctx)
{
if (!cb)
return GET_SNI_ERR;
ssl_ctx = cb(cb_ctx, local, lsquic_str_cstr(&enc_session->hs_ctx.sni));
if (ssl_ctx == NULL)
return GET_SNI_ERR;
enc_session->ssl_ctx = ssl_ctx;
}
if (enc_session->cert_ptr == NULL)
{
crt = SSL_CTX_get0_certificate(enc_session->ssl_ctx);
if (!crt)
return GET_SNI_ERR;
key_sz = gen_iasn_key(crt, key, sizeof(key));
if (key_sz)
{
item = find_cert(enc_session->enpub, key, key_sz);
if (item)
LSQ_DEBUG("found cert in cache");
else
{
out = NULL;
len = i2d_X509(crt, &out);
if (len < 0)
return GET_SNI_ERR;
lsquic_str_set(&crtstr, (char *) out, len);
item = insert_cert(enc_session->enpub, key, key_sz, &crtstr);
if (item)
{
OPENSSL_free(out);
LSQ_DEBUG("inserted cert into cache");
}
else
{
LSQ_DEBUG("cert insertion failed, keep own copy");
goto copy;
}
}
enc_session->cert_ptr = item->crt;
enc_session->cert_hash = item->hash;
}
else
{
LSQ_INFO("cannot generate cert cache key, make copy");
out = NULL;
len = i2d_X509(crt, &out);
if (len < 0)
return GET_SNI_ERR;
copy: enc_session->cert_ptr = lsquic_str_new((char *) out, len);
OPENSSL_free(out);
if (!enc_session->cert_ptr)
return GET_SNI_ERR;
enc_session->es_flags |= ES_FREE_CERT_PTR;
enc_session->cert_hash = lsquic_fnv1a_64(
(const uint8_t *) lsquic_str_buf(enc_session->cert_ptr),
lsquic_str_len(enc_session->cert_ptr));
}
}
return GET_SNI_OK;
}
/***
* Comments: data and len are the frame(s) parsed data, no packet header.
* return rtt number:
* 1 for rej1 and 0 for shlo
* DATA_NOT_ENOUGH(-2) for not enough data,
* DATA_FORMAT_ERROR(-1) all other errors
* HS_DELAYED handshake delayed
*/
static enum handshake_error
handle_chlo_frames_data(const uint8_t *data, int len,
struct lsquic_enc_session *enc_session,
lsquic_lookup_cert_f cb, void *cb_ctx,
const struct sockaddr *local,
const struct lsquic_shared_hash_if *shi,
void *shi_ctx, const struct sockaddr *ip, time_t t)
{
/* start to parse it */
// struct lsquic_enc_session *enc_session = retrive_enc_session(cid);
uint32_t head_tag;
enum handshake_error rtt;
int ret;
LSQ_DEBUG("handle_chlo_frames_data called.");
ret = parse_hs(enc_session, data, len, &head_tag);
if (ret)
{
LSQ_DEBUG("handle_chlo_frames_data parse_hs error,s o quit.");
return ret;
}
if (head_tag != QTAG_CHLO)
{
LSQ_DEBUG("handle_chlo_frames_data got data format error 1.");
return DATA_FORMAT_ERROR;
}
switch (get_sni_SSL_CTX(enc_session, cb, cb_ctx, local))
{
case GET_SNI_ERR:
ESHIST_APPEND(enc_session, ESHE_SNI_FAIL);
LSQ_DEBUG("handle_chlo_frames_data got data format error 2.");
return DATA_FORMAT_ERROR;
default:
break;
}
rtt = determine_rtts(enc_session, ip, t);
ESHIST_APPEND(enc_session, ESHE_MULTI2_2BITS + rtt);
lsquic_str_setto(&enc_session->chlo, (const char *)data, len);
LSQ_DEBUG("handle_chlo_frames_data return %d.", rtt);
return rtt;
}
static int handle_chlo_reply_verify_prof(struct lsquic_enc_session *enc_session,
lsquic_str_t **out_certs,
size_t *out_certs_count,
lsquic_str_t *cached_certs,
int cached_certs_count)
{
const unsigned char *dummy = (unsigned char *) "";
const unsigned char *const in = enc_session->hs_ctx.ccert
? enc_session->hs_ctx.ccert->buf : dummy;
const unsigned char *const in_end = enc_session->hs_ctx.ccert
? in + enc_session->hs_ctx.ccert->len : 0;
EVP_PKEY *pub_key;
int ret;
size_t i;
X509 *cert, *server_cert;
STACK_OF(X509) *chain = NULL;
ret = lsquic_decompress_certs(in, in_end,cached_certs, cached_certs_count,
out_certs, out_certs_count);
if (ret)
return ret;
server_cert = lsquic_bio_to_crt((const char *)lsquic_str_cstr(out_certs[0]),
lsquic_str_len(out_certs[0]), 0);
pub_key = X509_get_pubkey(server_cert);
ret = lsquic_verify_prof((const uint8_t *)lsquic_str_cstr(&enc_session->chlo),
(size_t)lsquic_str_len(&enc_session->chlo),
&enc_session->info->scfg,
pub_key,
(const uint8_t *)lsquic_str_cstr(&enc_session->hs_ctx.prof),
lsquic_str_len(&enc_session->hs_ctx.prof));
EVP_PKEY_free(pub_key);
if (ret != 0)
{
LSQ_DEBUG("cannot verify server proof");
goto cleanup;
}
if (enc_session->enpub->enp_verify_cert)
{
chain = sk_X509_new_null();
sk_X509_push(chain, server_cert);
for (i = 1; i < *out_certs_count; ++i)
{
cert = lsquic_bio_to_crt((const char *)lsquic_str_cstr(out_certs[i]),
lsquic_str_len(out_certs[i]), 0);
if (cert)
sk_X509_push(chain, cert);
else
{
LSQ_WARN("cannot push certificate to stack");
ret = -1;
goto cleanup;
}
}
ret = enc_session->enpub->enp_verify_cert(
enc_session->enpub->enp_verify_ctx, chain);
LSQ_INFO("server certificate verification %ssuccessful",
ret == 0 ? "" : "not ");
}
EV_LOG_CHECK_CERTS(&enc_session->cid, (const lsquic_str_t **)out_certs, *out_certs_count);
cleanup:
if (chain)
sk_X509_free(chain);
X509_free(server_cert);
return ret;
}
static void
setup_aead_ctx (const struct lsquic_enc_session *enc_session,
EVP_AEAD_CTX **ctx, unsigned char key[], int key_len,
unsigned char *key_copy)
2017-09-22 21:00:03 +00:00
{
const EVP_AEAD *aead_ = EVP_aead_aes_128_gcm();
const int auth_tag_size = enc_session->es_flags & ES_GQUIC2
? IQUIC_TAG_LEN : GQUIC_PACKET_HASH_SZ;
2017-09-22 21:00:03 +00:00
if (*ctx)
{
EVP_AEAD_CTX_cleanup(*ctx);
}
else
*ctx = (EVP_AEAD_CTX *)malloc(sizeof(EVP_AEAD_CTX));
EVP_AEAD_CTX_init(*ctx, aead_, key, key_len, auth_tag_size, NULL);
if (key_copy)
memcpy(key_copy, key, key_len);
}
static int
determine_diversification_key (enc_session_t *enc_session_p,
uint8_t *diversification_nonce)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
const int is_client = !(enc_session->es_flags & ES_SERVER);
2017-09-22 21:00:03 +00:00
EVP_AEAD_CTX **ctx_s_key;
unsigned char *key_i, *iv;
const size_t iv_len = enc_session->es_flags & ES_GQUIC2
? IQUIC_IV_LEN : aes128_iv_len;
uint8_t ikm[aes128_key_len + MAX_IV_LEN];
char str_buf[DNONC_LENGTH * 2 + 1];
2017-09-22 21:00:03 +00:00
if (is_client)
{
ctx_s_key = &enc_session->dec_ctx_i;
key_i = enc_session->dec_key_i;
iv = enc_session->dec_key_nonce_i;
}
else
{
ctx_s_key = &enc_session->enc_ctx_i;
key_i = enc_session->enc_key_i;
iv = enc_session->enc_key_nonce_i;
}
2017-09-22 21:00:03 +00:00
memcpy(ikm, key_i, aes128_key_len);
memcpy(ikm + aes128_key_len, iv, iv_len);
lsquic_export_key_material(ikm, aes128_key_len + iv_len,
2017-09-22 21:00:03 +00:00
diversification_nonce, DNONC_LENGTH,
(const unsigned char *) "QUIC key diversification", 24,
0, NULL, aes128_key_len, key_i, 0, NULL,
iv_len, iv, NULL, NULL, NULL);
2017-09-22 21:00:03 +00:00
setup_aead_ctx(enc_session, ctx_s_key, key_i, aes128_key_len, NULL);
if (enc_session->es_flags & ES_LOG_SECRETS)
{
LSQ_DEBUG("determine_diversification_keys nonce: %s",
HEXSTR(diversification_nonce, DNONC_LENGTH, str_buf));
LSQ_DEBUG("determine_diversification_keys diversification key: %s",
HEXSTR(key_i, aes128_key_len, str_buf));
LSQ_DEBUG("determine_diversification_keys diversification iv: %s",
HEXSTR(iv, iv_len, str_buf));
}
2017-09-22 21:00:03 +00:00
return 0;
}
/* After CHLO msg generatered, call it to determine_keys */
static void
determine_keys (struct lsquic_enc_session *enc_session)
2017-09-22 21:00:03 +00:00
{
lsquic_str_t *chlo = &enc_session->chlo;
const int is_client = !(enc_session->es_flags & ES_SERVER);
2017-09-22 21:00:03 +00:00
uint8_t shared_key_c[32];
const size_t iv_len = enc_session->es_flags & ES_GQUIC2
? IQUIC_IV_LEN : aes128_iv_len;
unsigned char *nonce_c;
unsigned char *hkdf_input, *hkdf_input_p;
2017-09-22 21:00:03 +00:00
unsigned char c_key[aes128_key_len];
unsigned char s_key[aes128_key_len];
unsigned char *c_key_bin = NULL;
unsigned char *s_key_bin = NULL;
unsigned char *c_iv;
unsigned char *s_iv;
uint8_t *c_hp, *s_hp;
size_t nonce_len, hkdf_input_len;
2017-09-22 21:00:03 +00:00
unsigned char sub_key[32];
EVP_AEAD_CTX **ctx_c_key, **ctx_s_key;
char key_flag;
char str_buf[512];
hkdf_input_len = (enc_session->have_key == 0 ? 18 + 1 : 33 + 1)
+ enc_session->cid.len
+ lsquic_str_len(chlo)
+ (is_client
? lsquic_str_len(&enc_session->info->scfg)
: (size_t) enc_session->server_config->lsc_scfg->info.scfg_len)
+ lsquic_str_len(enc_session->cert_ptr);
hkdf_input = malloc(hkdf_input_len);
if (UNLIKELY(!hkdf_input))
{
LSQ_WARN("cannot allocate memory for hkdf_input");
return;
}
hkdf_input_p = hkdf_input;
2017-09-22 21:00:03 +00:00
if (enc_session->have_key == 0)
{
memcpy(hkdf_input_p, "QUIC key expansion\0", 18 + 1); /* Add a 0x00 */
hkdf_input_p += 18 + 1;
2017-09-22 21:00:03 +00:00
key_flag = 'I';
}
else
{
memcpy(hkdf_input_p, "QUIC forward secure key expansion\0", 33 + 1); /* Add a 0x00 */
hkdf_input_p += 33 + 1;
2017-09-22 21:00:03 +00:00
key_flag = 'F';
}
lsquic_c255_gen_share_key(enc_session->priv_key,
2017-09-22 21:00:03 +00:00
enc_session->hs_ctx.pubs,
(unsigned char *)shared_key_c);
if (is_client)
2017-09-22 21:00:03 +00:00
{
if (enc_session->have_key == 0)
{
ctx_c_key = &enc_session->enc_ctx_i;
ctx_s_key = &enc_session->dec_ctx_i;
c_iv = (unsigned char *) enc_session->enc_key_nonce_i;
s_iv = (unsigned char *) enc_session->dec_key_nonce_i;
c_key_bin = enc_session->enc_key_i;
s_key_bin = enc_session->dec_key_i;
c_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_EARLY][0] : NULL;
s_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_EARLY][1] : NULL;
2017-09-22 21:00:03 +00:00
}
else
{
ctx_c_key = &enc_session->enc_ctx_f;
ctx_s_key = &enc_session->dec_ctx_f;
c_iv = (unsigned char *) enc_session->enc_key_nonce_f;
s_iv = (unsigned char *) enc_session->dec_key_nonce_f;
c_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_FORW][0] : NULL;
s_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_FORW][1] : NULL;
2017-09-22 21:00:03 +00:00
}
}
else
{
if (enc_session->have_key == 0)
{
ctx_c_key = &enc_session->dec_ctx_i;
ctx_s_key = &enc_session->enc_ctx_i;
c_iv = (unsigned char *) enc_session->dec_key_nonce_i;
s_iv = (unsigned char *) enc_session->enc_key_nonce_i;
c_key_bin = enc_session->dec_key_i;
s_key_bin = enc_session->enc_key_i;
c_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_EARLY][1] : NULL;
s_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_EARLY][0] : NULL;
}
else
{
ctx_c_key = &enc_session->dec_ctx_f;
ctx_s_key = &enc_session->enc_ctx_f;
c_iv = (unsigned char *) enc_session->dec_key_nonce_f;
s_iv = (unsigned char *) enc_session->enc_key_nonce_f;
c_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_FORW][1] : NULL;
s_hp = enc_session->es_flags & ES_GQUIC2
? enc_session->es_hps[GEL_FORW][0] : NULL;
}
}
2017-09-22 21:00:03 +00:00
LSQ_DEBUG("export_key_material lsquic_c255_gen_share_key %s",
lsquic_get_bin_str(shared_key_c, 32, 512));
2017-09-22 21:00:03 +00:00
memcpy(hkdf_input_p, enc_session->cid.idbuf, enc_session->cid.len);
hkdf_input_p += enc_session->cid.len;
memcpy(hkdf_input_p, lsquic_str_cstr(chlo), lsquic_str_len(chlo)); /* CHLO msg */
hkdf_input_p += lsquic_str_len(chlo);
if (is_client)
2017-09-22 21:00:03 +00:00
{
memcpy(hkdf_input_p, lsquic_str_cstr(&enc_session->info->scfg),
2017-09-22 21:00:03 +00:00
lsquic_str_len(&enc_session->info->scfg)); /* scfg msg */
hkdf_input_p += lsquic_str_len(&enc_session->info->scfg);
2017-09-22 21:00:03 +00:00
}
else
{
memcpy(hkdf_input_p,
(const char *) enc_session->server_config->lsc_scfg->scfg,
enc_session->server_config->lsc_scfg->info.scfg_len);
hkdf_input_p += enc_session->server_config->lsc_scfg->info.scfg_len;
}
memcpy(hkdf_input_p, lsquic_str_cstr(enc_session->cert_ptr),
2017-09-22 21:00:03 +00:00
lsquic_str_len(enc_session->cert_ptr));
assert(hkdf_input + hkdf_input_len
== hkdf_input_p + lsquic_str_len(enc_session->cert_ptr));
2017-09-22 21:00:03 +00:00
LSQ_DEBUG("export_key_material hkdf_input %s",
HEXSTR(hkdf_input, hkdf_input_len, str_buf));
2017-09-22 21:00:03 +00:00
/* then need to use the salts and the shared_key_* to get the real aead key */
nonce_len = sizeof(enc_session->hs_ctx.nonc)
+ lsquic_str_len(&enc_session->ssno);
nonce_c = malloc(nonce_len);
if (UNLIKELY(!nonce_c))
{
LSQ_WARN("cannot allocate memory for nonce_c");
free(hkdf_input);
return;
}
memcpy(nonce_c, enc_session->hs_ctx.nonc, sizeof(enc_session->hs_ctx.nonc));
memcpy(nonce_c + sizeof(enc_session->hs_ctx.nonc),
lsquic_str_cstr(&enc_session->ssno),
lsquic_str_len(&enc_session->ssno));
2017-09-22 21:00:03 +00:00
LSQ_DEBUG("export_key_material nonce %s",
HEXSTR(nonce_c, nonce_len, str_buf));
2017-09-22 21:00:03 +00:00
lsquic_export_key_material(shared_key_c, 32,
nonce_c, nonce_len,
hkdf_input, hkdf_input_len,
2017-09-22 21:00:03 +00:00
aes128_key_len, c_key,
aes128_key_len, s_key,
iv_len, c_iv,
iv_len, s_iv,
sub_key,
c_hp, s_hp
);
2017-09-22 21:00:03 +00:00
setup_aead_ctx(enc_session, ctx_c_key, c_key, aes128_key_len, c_key_bin);
setup_aead_ctx(enc_session, ctx_s_key, s_key, aes128_key_len, s_key_bin);
2017-09-22 21:00:03 +00:00
free(nonce_c);
free(hkdf_input);
2017-09-22 21:00:03 +00:00
if (enc_session->es_flags & ES_LOG_SECRETS)
{
LSQ_DEBUG("***export_key_material '%c' c_key: %s", key_flag,
HEXSTR(c_key, aes128_key_len, str_buf));
LSQ_DEBUG("***export_key_material '%c' s_key: %s", key_flag,
HEXSTR(s_key, aes128_key_len, str_buf));
LSQ_DEBUG("***export_key_material '%c' c_iv: %s", key_flag,
HEXSTR(c_iv, iv_len, str_buf));
LSQ_DEBUG("***export_key_material '%c' s_iv: %s", key_flag,
HEXSTR(s_iv, iv_len, str_buf));
LSQ_DEBUG("***export_key_material '%c' subkey: %s", key_flag,
HEXSTR(sub_key, 32, str_buf));
if (c_hp)
LSQ_DEBUG("***export_key_material '%c' c_hp: %s", key_flag,
HEXSTR(c_hp, IQUIC_HP_LEN, str_buf));
if (s_hp)
LSQ_DEBUG("***export_key_material '%c' s_hp: %s", key_flag,
HEXSTR(s_hp, IQUIC_HP_LEN, str_buf));
}
2017-09-22 21:00:03 +00:00
}
/* 0 Match */
static int cached_certs_match(c_cert_item_t *item,
lsquic_str_t **certs, int count)
2017-09-22 21:00:03 +00:00
{
int i;
if (!item || item->count != count)
2017-09-22 21:00:03 +00:00
return -1;
for (i=0; i<count; ++i)
2017-09-22 21:00:03 +00:00
{
if (lsquic_str_bcmp(certs[i], &item->crts[i]) != 0)
2017-09-22 21:00:03 +00:00
return -1;
}
return 0;
}
static const char *
he2str (enum handshake_error he)
{
switch (he)
{
case DATA_NOT_ENOUGH: return "DATA_NOT_ENOUGH";
case HS_ERROR: return "HS_ERROR";
case HS_SHLO: return "HS_SHLO";
case HS_1RTT: return "HS_1RTT";
case HS_SREJ: return "HS_SREJ";
2017-09-22 21:00:03 +00:00
default:
assert(0); return "<unknown enum value>";
}
}
/* NOT packet, just the frames-data */
/* return rtt number:
* 0 OK
* DATA_NOT_ENOUGH(-2) for not enough data,
* DATA_FORMAT_ERROR(-1) all other errors
*/
static int
lsquic_enc_session_handle_chlo_reply (enc_session_t *enc_session_p,
const uint8_t *data, int len)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
2017-09-22 21:00:03 +00:00
uint32_t head_tag;
int ret, got_srej;
2017-09-22 21:00:03 +00:00
lsquic_session_cache_info_t *info = enc_session->info;
c_cert_item_t *cert_item = enc_session->cert_item;
2017-09-22 21:00:03 +00:00
/* FIXME get the number first */
lsquic_str_t **out_certs = NULL;
size_t out_certs_count = 0, i;
ret = parse_hs(enc_session, data, len, &head_tag);
if (ret)
goto end;
got_srej = head_tag == QTAG_SREJ;
switch (head_tag)
{
case QTAG_SREJ:
if (enc_session->es_flags & ES_RECV_SREJ)
{
LSQ_DEBUG("received second SREJ: handshake failed");
ret = -1;
goto end;
}
enc_session->es_flags |= ES_RECV_SREJ;
/* fall-through */
case QTAG_REJ:
2017-09-22 21:00:03 +00:00
enc_session->hsk_state = HSK_CHLO_REJ;
enc_session->es_flags |= ES_RECV_REJ;
break;
case QTAG_SHLO:
2017-09-22 21:00:03 +00:00
enc_session->hsk_state = HSK_COMPLETED;
EV_LOG_HSK_COMPLETED(&enc_session->cid);
if (!(enc_session->es_flags & ES_RECV_REJ))
EV_LOG_SESSION_RESUMPTION(&enc_session->cid);
break;
default:
ret = 1; /* XXX Why 1? */
goto end;
2017-09-22 21:00:03 +00:00
}
if (info->scfg_flag == 1)
{
ret = parse_hs(enc_session, (uint8_t *)lsquic_str_cstr(&info->scfg),
lsquic_str_len(&info->scfg), &head_tag);
/* After handled, set the length to 0 to avoid do it again*/
enc_session->info->scfg_flag = 2;
if (ret)
goto end;
if (got_srej)
{
if (lsquic_str_len(&enc_session->info->sstk))
ret = HS_SREJ;
else
{
LSQ_DEBUG("expected STK in SREJ message from the server");
ret = -1;
}
goto end;
}
if (enc_session->hs_ctx.ccert)
2017-09-22 21:00:03 +00:00
{
out_certs_count = lsquic_get_certs_count(enc_session->hs_ctx.ccert);
2017-09-22 21:00:03 +00:00
if (out_certs_count > 0)
{
out_certs = malloc(out_certs_count * sizeof(lsquic_str_t *));
2017-09-22 21:00:03 +00:00
if (!out_certs)
{
ret = -1;
goto end;
}
for (i=0; i<out_certs_count; ++i)
out_certs[i] = lsquic_str_new(NULL, 0);
ret = handle_chlo_reply_verify_prof(enc_session, out_certs,
&out_certs_count,
(cert_item ? cert_item->crts : NULL),
(cert_item ? cert_item->count : 0));
2017-09-22 21:00:03 +00:00
if (ret == 0)
{
if (out_certs_count > 0)
{
if (cached_certs_match(cert_item, out_certs,
out_certs_count) != 0)
2017-09-22 21:00:03 +00:00
{
cert_item = make_c_cert_item(out_certs,
out_certs_count);
enc_session->cert_item = cert_item;
enc_session->cert_ptr = &cert_item->crts[0];
2017-09-22 21:00:03 +00:00
}
}
}
for (i=0; i<out_certs_count; ++i)
lsquic_str_delete(out_certs[i]);
free(out_certs);
if (ret)
goto end;
}
}
}
if (enc_session->hsk_state == HSK_COMPLETED)
{
determine_keys(enc_session);
enc_session->have_key = 3;
}
end:
LSQ_DEBUG("lsquic_enc_session_handle_chlo_reply called, buf in %d, return %d.", len, ret);
EV_LOG_CONN_EVENT(&enc_session->cid, "%s returning %s", __func__,
he2str(ret));
return ret;
}
/** stk = 16 bytes IP ( V4 is 4 bytes, will add 12 byes 0 )
* + 8 bytes time
* + 36 bytes random bytes (24 bytes can be reserved for other using)
* then stk first 48 byte will be encrypted with AES128-GCM
* when encrypting, the salt is the last 12 bytes
*/
#ifdef NDEBUG
static
#endif
void
lsquic_gen_stk (lsquic_server_config_t *server_config, const struct sockaddr *ip_addr,
uint64_t tm, unsigned char stk_out[STK_LENGTH])
{
unsigned char stk[STK_LENGTH + 16];
size_t out_len = STK_LENGTH + 16;
memset(stk, 0 , 24);
if (AF_INET == ip_addr->sa_family)
memcpy(stk, &((struct sockaddr_in *)ip_addr)->sin_addr.s_addr, 4);
else
memcpy(stk, &((struct sockaddr_in6 *)ip_addr)->sin6_addr, 16);
memcpy(stk + 16, &tm, 8);
RAND_bytes(stk + 24, STK_LENGTH - 24 - 12);
RAND_bytes(stk_out + STK_LENGTH - 12, 12);
lsquic_aes_aead_enc(&server_config->lsc_stk_ctx, NULL, 0, stk_out + STK_LENGTH - 12, 12, stk,
STK_LENGTH - 12 - 12, stk_out, &out_len);
}
/* server using */
#ifdef NDEBUG
static
#endif
enum hsk_failure_reason
lsquic_verify_stk0 (const struct lsquic_enc_session *enc_session,
lsquic_server_config_t *server_config,
const struct sockaddr *ip_addr, uint64_t tm, lsquic_str_t *stk,
unsigned secs_since_stk_generated)
{
uint64_t tm0, exp;
unsigned char *const stks = (unsigned char *) lsquic_str_buf(stk);
unsigned char stk_out[STK_LENGTH];
size_t out_len = STK_LENGTH;
if (lsquic_str_len(stk) < STK_LENGTH)
return HFR_SRC_ADDR_TOKEN_INVALID;
int ret = lsquic_aes_aead_dec(&server_config->lsc_stk_ctx, NULL, 0,
stks + STK_LENGTH - 12, 12, stks,
STK_LENGTH - 12, stk_out, &out_len);
if (ret != 0)
{
LSQ_DEBUG("***lsquic_verify_stk decrypted failed.");
return HFR_SRC_ADDR_TOKEN_DECRYPTION;
}
if (AF_INET == ip_addr->sa_family)
{
if (memcmp(stk_out, &((struct sockaddr_in *)ip_addr)->sin_addr.s_addr, 4) != 0)
{
LSQ_DEBUG("***lsquic_verify_stk for ipv4 failed.");
return HFR_SRC_ADDR_TOKEN_DIFFERENT_IP_ADDRESS;
}
}
else
{
if (memcmp(stk_out, &((struct sockaddr_in6 *)ip_addr)->sin6_addr, 16) != 0)
{
LSQ_DEBUG("***lsquic_verify_stk for ipv6 failed.");
return HFR_SRC_ADDR_TOKEN_DIFFERENT_IP_ADDRESS;
}
}
memcpy((void *)&tm0, stk_out + 16, 8);
if (tm < tm0)
2017-09-22 21:00:03 +00:00
{
LSQ_DEBUG("***lsquic_verify_stk timestamp is in the future.");
return HFR_SRC_ADDR_TOKEN_CLOCK_SKEW;
2017-09-22 21:00:03 +00:00
}
if (secs_since_stk_generated)
exp = tm0 + secs_since_stk_generated;
else
exp = server_config->lsc_scfg->info.expy;
if (tm > server_config->lsc_scfg->info.expy /* XXX this check does not seem needed */ ||
tm0 > exp)
{
LSQ_DEBUG("***lsquic_verify_stk stk expired");
return HFR_SRC_ADDR_TOKEN_EXPIRED;
}
LSQ_DEBUG("***lsquic_verify_stk pass.");
return HFR_HANDSHAKE_OK;
}
/* 0, verified, other fail */
#ifdef NDEBUG
static
#endif
enum hsk_failure_reason
lsquic_verify_stk (enc_session_t *enc_session_p,
const struct sockaddr *ip_addr, uint64_t tm, lsquic_str_t *stk)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
if (lsquic_str_len(&enc_session->sstk) > 0)
{
ESHIST_APPEND(enc_session, ESHE_HAS_SSTK);
if (0 == lsquic_str_bcmp(&enc_session->sstk, &enc_session->hs_ctx.stk))
return HFR_HANDSHAKE_OK;
else
return HFR_SRC_ADDR_TOKEN_INVALID;
}
else
return lsquic_verify_stk0(enc_session, enc_session->server_config,
ip_addr, tm, stk, 0);
}
2017-09-22 21:00:03 +00:00
static uint64_t combine_path_id_pack_num(uint8_t path_id, uint64_t pack_num)
{
uint64_t v = ((uint64_t)path_id << 56) | pack_num;
return v;
}
# define IS_SERVER(session) ((session)->es_flags & ES_SERVER)
2017-09-22 21:00:03 +00:00
static int
verify_packet_hash (const struct lsquic_enc_session *enc_session,
2017-09-22 21:00:03 +00:00
enum lsquic_version version, const unsigned char *buf, size_t *header_len,
size_t data_len, unsigned char *buf_out, size_t max_out_len,
size_t *out_len)
{
uint8_t md[HS_PKT_HASH_LENGTH];
uint128 hash;
int ret;
if (data_len < HS_PKT_HASH_LENGTH)
return -1;
if (!enc_session || (IS_SERVER(enc_session)))
hash = lsquic_fnv1a_128_3(buf, *header_len,
buf + *header_len + HS_PKT_HASH_LENGTH,
data_len - HS_PKT_HASH_LENGTH,
(unsigned char *) "Client", 6);
2017-09-22 21:00:03 +00:00
else
hash = lsquic_fnv1a_128_3(buf, *header_len,
buf + *header_len + HS_PKT_HASH_LENGTH,
data_len - HS_PKT_HASH_LENGTH,
(unsigned char *) "Server", 6);
2017-09-22 21:00:03 +00:00
lsquic_serialize_fnv128_short(hash, md);
2017-09-22 21:00:03 +00:00
ret = memcmp(md, buf + *header_len, HS_PKT_HASH_LENGTH);
if(ret == 0)
{
*header_len += HS_PKT_HASH_LENGTH;
*out_len = data_len - HS_PKT_HASH_LENGTH;
if (max_out_len < *header_len + *out_len)
return -1;
memcpy(buf_out, buf, *header_len + *out_len);
return 0;
}
else
return -1;
}
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
static enum enc_level
decrypt_packet (struct lsquic_enc_session *enc_session, uint8_t path_id,
2017-09-22 21:00:03 +00:00
uint64_t pack_num, unsigned char *buf, size_t *header_len,
size_t data_len, unsigned char *buf_out, size_t max_out_len,
size_t *out_len)
{
int ret;
/* Comment: 12 = sizeof(dec_key_iv] 4 + sizeof(pack_num) 8 */
uint8_t nonce[12];
uint64_t path_id_packet_number;
EVP_AEAD_CTX *key = NULL;
int try_times = 0;
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
enum enc_level enc_level;
2017-09-22 21:00:03 +00:00
path_id_packet_number = combine_path_id_pack_num(path_id, pack_num);
memcpy(buf_out, buf, *header_len);
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
do
2017-09-22 21:00:03 +00:00
{
if (enc_session->have_key == 3 && try_times == 0)
{
key = enc_session->dec_ctx_f;
memcpy(nonce, enc_session->dec_key_nonce_f, 4);
LSQ_DEBUG("decrypt_packet using 'F' key...");
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
enc_level = ENC_LEV_FORW;
2017-09-22 21:00:03 +00:00
}
else
{
key = enc_session->dec_ctx_i;
memcpy(nonce, enc_session->dec_key_nonce_i, 4);
LSQ_DEBUG("decrypt_packet using 'I' key...");
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
enc_level = ENC_LEV_INIT;
2017-09-22 21:00:03 +00:00
}
memcpy(nonce + 4, &path_id_packet_number,
sizeof(path_id_packet_number));
*out_len = data_len;
ret = lsquic_aes_aead_dec(key,
2017-09-22 21:00:03 +00:00
buf, *header_len,
nonce, 12,
buf + *header_len, data_len,
buf_out + *header_len, out_len);
if (ret != 0)
++try_times;
else
{
if (enc_session->peer_have_final_key == 0 &&
enc_session->have_key == 3 &&
try_times == 0)
{
LSQ_DEBUG("!!!decrypt_packet find peer have final key.");
enc_session->peer_have_final_key = 1;
EV_LOG_CONN_EVENT(&enc_session->cid, "settled on private key "
2017-09-22 21:00:03 +00:00
"'%c' after %d tries (packet number %"PRIu64")",
key == enc_session->dec_ctx_f ? 'F' : 'I',
try_times, pack_num);
}
break;
}
}
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
while (try_times < 2);
2017-09-22 21:00:03 +00:00
LSQ_DEBUG("***decrypt_packet %s.", (ret == 0 ? "succeed" : "failed"));
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
return ret == 0 ? enc_level : (enum enc_level) -1;
2017-09-22 21:00:03 +00:00
}
static int
lsquic_enc_session_have_key_gt_one (enc_session_t *enc_session_p)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
2017-09-22 21:00:03 +00:00
return enc_session && enc_session->have_key > 1;
}
/* The size of `buf' is *header_len plus data_len. The two parts of the
* buffer correspond to the header and the payload of incoming QUIC packet.
*/
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
static enum enc_level
lsquic_enc_session_decrypt (struct lsquic_enc_session *enc_session,
enum lsquic_version version,
2017-09-22 21:00:03 +00:00
uint8_t path_id, uint64_t pack_num,
unsigned char *buf, size_t *header_len, size_t data_len,
unsigned char *diversification_nonce,
unsigned char *buf_out, size_t max_out_len, size_t *out_len)
{
/* Client: got SHLO which should have diversification_nonce */
if (diversification_nonce && enc_session && enc_session->have_key == 1)
{
determine_diversification_key(enc_session, diversification_nonce);
2017-09-22 21:00:03 +00:00
enc_session->have_key = 2;
}
if (lsquic_enc_session_have_key_gt_one(enc_session))
return decrypt_packet(enc_session, path_id, pack_num, buf,
header_len, data_len, buf_out, max_out_len, out_len);
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
else if (0 == verify_packet_hash(enc_session, version, buf, header_len,
data_len, buf_out, max_out_len, out_len))
return ENC_LEV_CLEAR;
2017-09-22 21:00:03 +00:00
else
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
return -1;
2017-09-22 21:00:03 +00:00
}
static enum dec_packin
gquic_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 lsquic_enc_session *const enc_session = enc_session_p;
size_t header_len, data_len;
enum enc_level enc_level;
size_t out_len = 0;
unsigned char *copy = lsquic_mm_get_packet_in_buf(&enpub->enp_mm, 1370);
if (!copy)
{
LSQ_WARN("cannot allocate memory to copy incoming packet data");
return DECPI_NOMEM;
}
assert(packet_in->pi_data);
header_len = packet_in->pi_header_sz;
data_len = packet_in->pi_data_sz - packet_in->pi_header_sz;
enc_level = lsquic_enc_session_decrypt(enc_session,
lconn->cn_version, 0,
packet_in->pi_packno, packet_in->pi_data,
&header_len, data_len,
lsquic_packet_in_nonce(packet_in),
copy, 1370, &out_len);
if ((enum enc_level) -1 == enc_level)
{
lsquic_mm_put_packet_in_buf(&enpub->enp_mm, copy, 1370);
EV_LOG_CONN_EVENT(&lconn->cn_cid, "could not decrypt packet %"PRIu64,
packet_in->pi_packno);
return DECPI_BADCRYPT;
}
assert(header_len + out_len <= 1370);
if (packet_in->pi_flags & PI_OWN_DATA)
lsquic_mm_put_packet_in_buf(&enpub->enp_mm, packet_in->pi_data, 1370);
packet_in->pi_data = copy;
packet_in->pi_flags |= PI_OWN_DATA | PI_DECRYPTED
| (enc_level << PIBIT_ENC_LEV_SHIFT);
packet_in->pi_header_sz = header_len;
packet_in->pi_data_sz = out_len + header_len;
EV_LOG_CONN_EVENT(&lconn->cn_cid, "decrypted packet %"PRIu64,
packet_in->pi_packno);
return DECPI_OK;
}
2018-08-15 19:06:31 +00:00
static enum enc_level
gquic_encrypt_buf (struct lsquic_enc_session *enc_session,
enum lsquic_version version,
2017-09-22 21:00:03 +00:00
uint8_t path_id, uint64_t pack_num,
const unsigned char *header, size_t header_len,
const unsigned char *data, size_t data_len,
unsigned char *buf_out, size_t max_out_len, size_t *out_len,
int is_hello)
{
uint8_t md[HS_PKT_HASH_LENGTH];
uint128 hash;
int ret;
2018-08-15 19:06:31 +00:00
enum enc_level enc_level;
2017-09-22 21:00:03 +00:00
int is_chlo = (is_hello && ((IS_SERVER(enc_session)) == 0));
int is_shlo = (is_hello && (IS_SERVER(enc_session)));
/* Comment: 12 = sizeof(dec_key_iv] 4 + sizeof(pack_num) 8 */
uint8_t nonce[12];
uint64_t path_id_packet_number;
EVP_AEAD_CTX *key;
if (enc_session)
LSQ_DEBUG("%s: hsk_state: %d", __func__, enc_session->hsk_state);
else
LSQ_DEBUG("%s: enc_session is not set", __func__);
if (!enc_session || enc_session->have_key == 0 || is_chlo)
{
*out_len = header_len + data_len + HS_PKT_HASH_LENGTH;
if (max_out_len < *out_len)
return -1;
if (!enc_session || (IS_SERVER(enc_session)))
hash = lsquic_fnv1a_128_3(header, header_len, data, data_len,
(unsigned char *) "Server", 6);
2017-09-22 21:00:03 +00:00
else
hash = lsquic_fnv1a_128_3(header, header_len, data, data_len,
(unsigned char *) "Client", 6);
2017-09-22 21:00:03 +00:00
lsquic_serialize_fnv128_short(hash, md);
2017-09-22 21:00:03 +00:00
memcpy(buf_out, header, header_len);
memcpy(buf_out + header_len, md, HS_PKT_HASH_LENGTH);
memcpy(buf_out + header_len + HS_PKT_HASH_LENGTH, data, data_len);
2018-08-15 19:06:31 +00:00
return ENC_LEV_CLEAR;
2017-09-22 21:00:03 +00:00
}
else
{
if (enc_session->have_key != 3 || is_shlo ||
((IS_SERVER(enc_session)) &&
enc_session->server_start_use_final_key == 0))
{
LSQ_DEBUG("lsquic_enc_session_encrypt using 'I' key...");
2017-09-22 21:00:03 +00:00
key = enc_session->enc_ctx_i;
memcpy(nonce, enc_session->enc_key_nonce_i, 4);
if (is_shlo && enc_session->have_key == 3)
{
enc_session->server_start_use_final_key = 1;
}
2018-08-15 19:06:31 +00:00
enc_level = ENC_LEV_INIT;
2017-09-22 21:00:03 +00:00
}
else
{
LSQ_DEBUG("lsquic_enc_session_encrypt using 'F' key...");
2017-09-22 21:00:03 +00:00
key = enc_session->enc_ctx_f;
memcpy(nonce, enc_session->enc_key_nonce_f, 4);
2018-08-15 19:06:31 +00:00
enc_level = ENC_LEV_FORW;
2017-09-22 21:00:03 +00:00
}
path_id_packet_number = combine_path_id_pack_num(path_id, pack_num);
memcpy(nonce + 4, &path_id_packet_number,
sizeof(path_id_packet_number));
memcpy(buf_out, header, header_len);
*out_len = max_out_len - header_len;
ret = lsquic_aes_aead_enc(key, header, header_len, nonce, 12, data,
2017-09-22 21:00:03 +00:00
data_len, buf_out + header_len, out_len);
2018-08-15 19:06:31 +00:00
if (ret == 0)
{
*out_len += header_len;
return enc_level;
}
else
return -1;
2017-09-22 21:00:03 +00:00
}
}
/* server */
/* out_len should have init value as the max length of out */
/* return -1 error, 0, SHLO, 1, RTT1, 2, RTT2, DELAYED */
static enum handshake_error
lsquic_enc_session_handle_chlo(enc_session_t *enc_session_p,
enum lsquic_version version,
const uint8_t *in, int in_len, time_t t,
const struct sockaddr *peer,
const struct sockaddr *local,
uint8_t *out, size_t *out_len,
uint8_t nonce[DNONC_LENGTH], int *nonce_set)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
enum handshake_error rtt;
int len;
lsquic_server_config_t *server_config;
const struct lsquic_engine_public *const enpub = enc_session->enpub;
const struct lsquic_shared_hash_if *const shi = enpub->enp_shi;
void *const shi_ctx = enpub->enp_shi_ctx;
server_config = get_valid_scfg(enc_session, enpub);
if (!server_config)
return HS_ERROR;
assert(server_config->lsc_scfg);
enc_session->server_config = server_config;
*nonce_set = 0;
rtt = handle_chlo_frames_data(in, in_len, enc_session,
enpub->enp_lookup_cert, enpub->enp_cert_lu_ctx,
local, shi, shi_ctx, peer, t);
if (rtt == HS_1RTT)
{
LSQ_DEBUG("lsquic_enc_session_handle_chlo call gen_rej1_data");
len = gen_rej1_data(enc_session, out, *out_len, peer, t);
if (len < 0)
{
rtt = HS_ERROR;
goto end;
}
*out_len = len;
}
else if (rtt == HS_SHLO)
{
enc_session->have_key = 0;
determine_keys(enc_session);
enc_session->have_key = 1;
LSQ_DEBUG("lsquic_enc_session_handle_chlo call gen_shlo_data");
len = gen_shlo_data(out, *out_len, enc_session, version, peer,
t, nonce);
if (len < 0)
{
rtt = HS_ERROR;
goto end;
}
*out_len = len;
*nonce_set = 1;
determine_diversification_key(enc_session, nonce);
enc_session->have_key = 2;
determine_keys(enc_session);
enc_session->have_key = 3;
enc_session->hsk_state = HSK_COMPLETED;
LSQ_DEBUG("lsquic_enc_session_handle_chlo have_key 3 hsk_state HSK_COMPLETED.");
}
end:
EV_LOG_CONN_EVENT(&enc_session->cid, "%s returning %s", __func__,
he2str(rtt));
return rtt;
}
static int
lsquic_enc_session_get_peer_option (enc_session_t *enc_session_p,
uint32_t tag)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
2017-09-22 21:00:03 +00:00
switch (tag)
{
case QTAG_NSTP:
return !!(enc_session->hs_ctx.opts & HOPT_NSTP);
case QTAG_SREJ:
return !!(enc_session->hs_ctx.opts & HOPT_SREJ);
default:
assert(0);
return 0;
}
}
/* Query a several parameters sent by the peer that are required by
* connection.
*/
static int
lsquic_enc_session_get_peer_setting (enc_session_t *enc_session_p,
uint32_t tag, uint32_t *val)
2017-09-22 21:00:03 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
2017-09-22 21:00:03 +00:00
switch (tag)
{
case QTAG_TCID:
if (enc_session->hs_ctx.set & HSET_TCID)
{
*val = enc_session->hs_ctx.tcid;
return 0;
}
else
return -1;
case QTAG_SMHL:
if (enc_session->hs_ctx.set & HSET_SMHL)
{
*val = enc_session->hs_ctx.smhl;
return 0;
}
else
return -1;
Latest changes - [API Change] lsquic_engine_connect() returns pointer to the connection object. - [API Change] Add lsquic_conn_get_engine() to get engine object from connection object. - [API Change] Add lsquic_conn_status() to query connection status. - [API Change] Add add lsquic_conn_set_ctx(). - [API Change] Add new timestamp format, e.g. 2017-03-21 13:43:46.671345 - [OPTIMIZATION] Process handshake STREAM frames as soon as packet arrives. - [OPTIMIZATION] Do not compile expensive send controller sanity check by default. - [OPTIMIZATION] Add fast path to gquic_be_gen_reg_pkt_header. - [OPTIMIZATION] Only make squeeze function call if necessary. - [OPTIMIZATION] Speed up Q039 ACK frame parsing. - [OPTIMIZATION] Fit most used elements of packet_out into first 64 bytes. - [OPTIMIZATION] Keep track of scheduled bytes instead of calculating. - [OPTIMIZATION] Prefetch next unacked packet when processing ACK. - [OPTIMIZATION] Leverage fact that ACK ranges and unacked list are. ordered. - [OPTIMIZATION] Reduce function pointer use for STREAM frame generation - Fix: reset incoming streams that arrive after we send GOAWAY. - Fix: delay client on_new_conn() call until connection is fully set up. - Fixes to buffered packets logic: splitting, STREAM frame elision. - Fix: do not dispatch on_write callback if no packets are available. - Fix WINDOW_UPDATE send and resend logic. - Fix STREAM frame extension code. - Fix: Drop unflushed data when stream is reset. - Switch to tracking CWND using bytes rather than packets. - Fix TCP friendly adjustment in cubic. - Fix: do not generate invalid STOP_WAITING frames during high packet loss. - Pacer fixes.
2018-02-26 21:01:16 +00:00
case QTAG_IRTT:
if (enc_session->hs_ctx.set & HSET_IRTT)
{
*val = enc_session->hs_ctx.irtt;
return 0;
}
else
return -1;
2017-09-22 21:00:03 +00:00
}
/* XXX For the following values, there is no record which were present
* in CHLO or SHLO and which were not. Assume that zero means that
* they weren't present.
*/
if (IS_SERVER(enc_session))
switch (tag)
{
case QTAG_CFCW:
if (enc_session->hs_ctx.scfcw)
{
*val = enc_session->hs_ctx.scfcw;
return 0;
}
else
return -1;
case QTAG_SFCW:
if (enc_session->hs_ctx.ssfcw)
{
*val = enc_session->hs_ctx.ssfcw;
return 0;
}
else
return -1;
case QTAG_MIDS:
if (enc_session->hs_ctx.smids)
{
*val = enc_session->hs_ctx.smids;
return 0;
}
else
return -1;
default:
return -1;
}
else
2017-09-22 21:00:03 +00:00
switch (tag)
{
case QTAG_CFCW:
if (enc_session->hs_ctx.cfcw)
{
*val = enc_session->hs_ctx.cfcw;
return 0;
}
else
return -1;
case QTAG_SFCW:
if (enc_session->hs_ctx.sfcw)
{
*val = enc_session->hs_ctx.sfcw;
return 0;
}
else
return -1;
case QTAG_MIDS:
if (enc_session->hs_ctx.mids)
{
*val = enc_session->hs_ctx.mids;
return 0;
}
else
return -1;
default:
return -1;
}
}
static const char *
lsquic_enc_session_cipher (enc_session_t *enc_session_p)
{
return LN_aes_128_gcm; /* TODO: get this string from enc_session */
}
static int
lsquic_enc_session_keysize (enc_session_t *enc_session_p)
{
return 128 /* bits */ / 8; /* TODO: get this info from enc_session */
}
static int
lsquic_enc_session_alg_keysize (enc_session_t *enc_session_p)
{
return 16; /* TODO: get this info from enc_session */
}
2017-09-22 21:00:03 +00:00
#if LSQUIC_KEEP_ENC_SESS_HISTORY
static void
lsquic_get_enc_hist (enc_session_t *enc_session_p,
2017-09-22 21:00:03 +00:00
char buf[(1 << ESHIST_BITS) + 1])
{
struct lsquic_enc_session *const enc_session = enc_session_p;
2017-09-22 21:00:03 +00:00
const unsigned hist_idx = ESHIST_MASK & enc_session->es_hist_idx;
if (enc_session->es_hist_buf[hist_idx] == ESHE_EMPTY)
memcpy(buf, enc_session->es_hist_buf, hist_idx + 1);
else
{
memcpy(buf, enc_session->es_hist_buf + hist_idx, sizeof(enc_session->es_hist_buf) - hist_idx);
memcpy(buf + hist_idx, enc_session->es_hist_buf, hist_idx);
buf[(1 << ESHIST_BITS)] = '\0';
}
}
#endif
static const char *
lsquic_enc_session_get_ua (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
if (enc_session && lsquic_str_len(&enc_session->hs_ctx.uaid) > 0)
return lsquic_str_buf(&enc_session->hs_ctx.uaid);
else
return NULL;
}
static const char *
lsquic_enc_session_get_sni (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return lsquic_str_cstr(&enc_session->hs_ctx.sni);
}
#ifndef NDEBUG
static uint8_t
lsquic_enc_session_have_key (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->have_key;
}
static void
lsquic_enc_session_set_have_key (enc_session_t *enc_session_p, uint8_t val)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
enc_session->have_key = val;
}
static const unsigned char *
lsquic_enc_session_get_enc_key_i (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->enc_key_i;
}
static const unsigned char *
lsquic_enc_session_get_dec_key_i (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->dec_key_i;
}
static const unsigned char *
lsquic_enc_session_get_enc_key_nonce_i (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->enc_key_nonce_i;
}
static const unsigned char *
lsquic_enc_session_get_dec_key_nonce_i (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->dec_key_nonce_i;
}
static const unsigned char *
lsquic_enc_session_get_enc_key_nonce_f (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->enc_key_nonce_f;
}
static const unsigned char *
lsquic_enc_session_get_dec_key_nonce_f (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->dec_key_nonce_f;
}
#endif /* not defined NDEBUG */
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
static size_t
lsquic_enc_session_mem_used (enc_session_t *enc_session_p)
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
{
struct lsquic_enc_session *const enc_session = enc_session_p;
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
size_t size;
size = sizeof(*enc_session);
size += lsquic_str_len(&enc_session->chlo);
size += lsquic_str_len(&enc_session->sstk);
size += lsquic_str_len(&enc_session->ssno);
size += lsquic_str_len(&enc_session->hs_ctx.ccs);
size += lsquic_str_len(&enc_session->hs_ctx.uaid);
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
size += lsquic_str_len(&enc_session->hs_ctx.sni);
size += lsquic_str_len(&enc_session->hs_ctx.ccrt);
size += lsquic_str_len(&enc_session->hs_ctx.stk);
size += lsquic_str_len(&enc_session->hs_ctx.sno);
size += lsquic_str_len(&enc_session->hs_ctx.prof);
size += lsquic_str_len(&enc_session->hs_ctx.csct);
if (enc_session->hs_ctx.ccert)
size += enc_session->hs_ctx.ccert->len
+ sizeof(*enc_session->hs_ctx.ccert);
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
if (enc_session->info)
{
size += sizeof(*enc_session->info);
size += lsquic_str_len(&enc_session->info->sstk);
size += lsquic_str_len(&enc_session->info->scfg);
size += lsquic_str_len(&enc_session->info->sni_key);
}
/* TODO: calculate memory taken up by SSL stuff */
return size;
}
2018-08-15 19:06:31 +00:00
static int
lsquic_enc_session_verify_reset_token (enc_session_t *enc_session_p,
2018-08-15 19:06:31 +00:00
const unsigned char *buf, size_t bufsz)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
2018-08-15 19:06:31 +00:00
if (bufsz == SRST_LENGTH
&& 0 == (enc_session->es_flags & ES_SERVER)
2018-08-15 19:06:31 +00:00
&& (enc_session->hs_ctx.set & HSET_SRST)
&& 0 == memcmp(buf, enc_session->hs_ctx.srst, SRST_LENGTH))
return 0;
else
return -1;
}
static int
lsquic_enc_session_did_sess_resume_succeed (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return !(enc_session->es_flags & ES_RECV_REJ);
}
static int
lsquic_enc_session_is_sess_resume_enabled (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
return enc_session->info && enc_session->cert_item;
}
static ssize_t
gquic_really_encrypt_packet (struct lsquic_enc_session *enc_session,
const struct lsquic_conn *lconn, struct lsquic_packet_out *packet_out,
unsigned char *buf, size_t bufsz)
{
int header_sz, is_hello_packet;
enum enc_level enc_level;
size_t packet_sz;
unsigned char header_buf[GQUIC_MAX_PUBHDR_SZ];
header_sz = lconn->cn_pf->pf_gen_reg_pkt_header(lconn, packet_out,
header_buf, sizeof(header_buf), NULL, NULL);
if (header_sz < 0)
return -1;
is_hello_packet = !!(packet_out->po_flags & PO_HELLO);
enc_level = gquic_encrypt_buf(enc_session, lconn->cn_version, 0,
packet_out->po_packno, header_buf, header_sz,
packet_out->po_data, packet_out->po_data_sz,
buf, bufsz, &packet_sz, is_hello_packet);
if ((int) enc_level >= 0)
{
LSQ_DEBUG("encrypted packet %"PRIu64"; plaintext is %zu bytes, "
"ciphertext is %zd bytes",
packet_out->po_packno,
lconn->cn_pf->pf_packout_size(lconn, packet_out) +
packet_out->po_data_sz,
packet_sz);
lsquic_packet_out_set_enc_level(packet_out, enc_level);
return packet_sz;
}
else
return -1;
}
static STACK_OF(X509) *
lsquic_enc_session_get_server_cert_chain (enc_session_t *enc_session_p)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
const struct c_cert_item_st *item;
STACK_OF(X509) *chain;
X509 *cert;
int i;
if (enc_session->es_flags & ES_SERVER)
return NULL;
item = enc_session->cert_item;
if (!item)
{
LSQ_WARN("could not find certificates for `%.*s'",
(int) lsquic_str_len(&enc_session->hs_ctx.sni),
lsquic_str_cstr(&enc_session->hs_ctx.sni));
return NULL;
}
chain = sk_X509_new_null();
for (i = 0; i < item->count; ++i)
{
cert = lsquic_bio_to_crt(lsquic_str_cstr(&item->crts[i]),
lsquic_str_len(&item->crts[i]), 0);
if (cert)
sk_X509_push(chain, cert);
else
{
sk_X509_free(chain);
return NULL;
}
}
return chain;
}
static void
maybe_dispatch_sess_resume (enc_session_t *enc_session_p,
void (*cb)(struct lsquic_conn *, const unsigned char *, size_t))
{
struct lsquic_enc_session *const enc_session = enc_session_p;
struct lsquic_conn *const lconn = enc_session->es_conn;
void *buf;
size_t sz;
int i;
if (!(enc_session->info && enc_session->cert_item && cb))
{
LSQ_DEBUG("no session resumption information or callback is not set");
return;
}
for (sz = 0, i = 0; i < enc_session->cert_item->count; ++i)
{
sz += sizeof(uint32_t);
sz += lsquic_str_len(&enc_session->cert_item->crts[i]);
}
sz += sizeof(struct lsquic_sess_resume_storage);
buf = malloc(sz);
if (!buf)
{
LSQ_WARN("malloc failed: cannot allocate %zu bytes for session "
"resumption", sz);
return;
}
lsquic_enc_session_serialize_sess_resume(
(struct lsquic_sess_resume_storage *) buf, lconn->cn_version,
enc_session->info, enc_session->cert_item);
cb(lconn, buf, sz);
free(buf);
}
static enum enc_packout
gquic_encrypt_packet (enc_session_t *enc_session_p,
const struct lsquic_engine_public *enpub,
struct lsquic_conn *lconn, struct lsquic_packet_out *packet_out)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
ssize_t enc_sz;
size_t bufsz;
unsigned char *buf;
int ipv6;
assert(!enc_session || lconn == enc_session->es_conn);
bufsz = lconn->cn_pf->pf_packout_size(lconn, packet_out);
if (bufsz > USHRT_MAX)
return ENCPA_BADCRYPT; /* To cause connection to close */
ipv6 = NP_IS_IPv6(packet_out->po_path);
buf = enpub->enp_pmi->pmi_allocate(enpub->enp_pmi_ctx,
packet_out->po_path->np_peer_ctx, lconn->cn_conn_ctx,
bufsz, ipv6);
if (!buf)
{
LSQ_DEBUG("could not allocate memory for outgoing packet of size %zd",
bufsz);
return ENCPA_NOMEM;
}
enc_sz = gquic_really_encrypt_packet(enc_session,
lconn, packet_out, buf, bufsz);
if (enc_sz < 0)
{
enpub->enp_pmi->pmi_return(enpub->enp_pmi_ctx,
packet_out->po_path->np_peer_ctx, buf, ipv6);
return ENCPA_BADCRYPT;
}
packet_out->po_enc_data = buf;
packet_out->po_enc_data_sz = enc_sz;
packet_out->po_sent_sz = enc_sz;
packet_out->po_flags &= ~PO_IPv6;
packet_out->po_flags |= PO_ENCRYPTED|PO_SENT_SZ|(ipv6 << POIPv6_SHIFT);
packet_out->po_dcid_len = GQUIC_CID_LEN;
return ENCPA_OK;
}
static void
gquic_esf_set_conn (enc_session_t *enc_session_p, struct lsquic_conn *lconn)
{
struct lsquic_enc_session *const enc_session = enc_session_p;
enc_session->es_conn = lconn;
LSQ_DEBUG("updated conn reference");
}
#ifdef NDEBUG
const
#endif
struct enc_session_funcs_common lsquic_enc_session_common_gquic_1 =
{
.esf_global_init = lsquic_handshake_init,
.esf_global_cleanup = lsquic_handshake_cleanup,
.esf_cipher = lsquic_enc_session_cipher,
.esf_keysize = lsquic_enc_session_keysize,
.esf_alg_keysize = lsquic_enc_session_alg_keysize,
.esf_get_sni = lsquic_enc_session_get_sni,
.esf_encrypt_packet = gquic_encrypt_packet,
.esf_decrypt_packet = gquic_decrypt_packet,
.esf_tag_len = GQUIC_PACKET_HASH_SZ,
.esf_get_server_cert_chain = lsquic_enc_session_get_server_cert_chain,
.esf_verify_reset_token = lsquic_enc_session_verify_reset_token,
.esf_did_sess_resume_succeed = lsquic_enc_session_did_sess_resume_succeed,
.esf_is_sess_resume_enabled = lsquic_enc_session_is_sess_resume_enabled,
.esf_set_conn = gquic_esf_set_conn,
};
static void
gquic2_gen_hp_mask (struct lsquic_enc_session *enc_session,
const unsigned char hp[IQUIC_HP_LEN],
const unsigned char *sample, unsigned char mask[EVP_MAX_BLOCK_LENGTH])
{
const EVP_CIPHER *const cipher = EVP_aes_128_ecb();
EVP_CIPHER_CTX hp_ctx;
int out_len;
EVP_CIPHER_CTX_init(&hp_ctx);
if (EVP_EncryptInit_ex(&hp_ctx, cipher, NULL, hp, 0)
&& EVP_EncryptUpdate(&hp_ctx, mask, &out_len, sample, 16))
{
assert(out_len >= 5);
}
else
{
LSQ_WARN("cannot generate hp mask, error code: %"PRIu32,
ERR_get_error());
enc_session->es_conn->cn_if->ci_internal_error(enc_session->es_conn,
"cannot generate hp mask, error code: %"PRIu32, ERR_get_error());
}
(void) EVP_CIPHER_CTX_cleanup(&hp_ctx);
if (0)
{
char hp_str[IQUIC_HP_LEN * 2 + 1], sample_str[16 * 2 + 1];
LSQ_DEBUG("generated hp mask using hp %s and sample %s",
HEXSTR(hp, IQUIC_HP_LEN, hp_str),
HEXSTR(sample, 16, sample_str));
}
}
static void
gquic2_apply_hp (struct lsquic_enc_session *enc_session,
enum gel gel, unsigned char *dst, unsigned packno_off,
unsigned sample_off, unsigned packno_len)
{
unsigned char mask[EVP_MAX_BLOCK_LENGTH];
char mask_str[5 * 2 + 1];
gquic2_gen_hp_mask(enc_session, enc_session->es_hps[gel][0],
dst + sample_off, mask);
LSQ_DEBUG("apply header protection using mask %s",
HEXSTR(mask, 5, mask_str));
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 const enum gel hety2gel[] =
{
[HETY_NOT_SET] = GEL_FORW,
[HETY_VERNEG] = 0,
[HETY_INITIAL] = GEL_CLEAR,
[HETY_RETRY] = 0,
[HETY_HANDSHAKE] = GEL_CLEAR,
[HETY_0RTT] = GEL_EARLY,
};
static const char *const gel2str[] =
{
[GEL_CLEAR] = "clear",
[GEL_EARLY] = "early",
[GEL_FORW] = "forw-secure",
};
static const enum enc_level gel2el[] =
{
[GEL_CLEAR] = ENC_LEV_CLEAR,
[GEL_EARLY] = ENC_LEV_EARLY,
[GEL_FORW] = ENC_LEV_FORW,
};
static enum enc_packout
gquic2_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 lsquic_enc_session *const enc_session = enc_session_p;
struct lsquic_conn *const lconn = enc_session->es_conn;
EVP_AEAD_CTX *aead_ctx;
unsigned char *dst;
enum gel gel;
unsigned char nonce_buf[ IQUIC_IV_LEN + 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, sample_off, divers_nonce_len;
char errbuf[ERR_ERROR_STRING_BUF_LEN];
gel = hety2gel[ packet_out->po_header_type ];
aead_ctx = enc_session->es_aead_ctxs[gel][0];
if (UNLIKELY(!aead_ctx))
{
LSQ_WARN("encrypt crypto context at level %s not initialized",
gel2str[gel]);
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);
/* XXX same packet rules as in IETF QUIC? */
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 - IQUIC_IV_LEN + 8;
memcpy(nonce, enc_session->es_ivs[gel][0], IQUIC_IV_LEN);
packno = packet_out->po_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 (s_log_seal_and_open)
{
LSQ_DEBUG("seal: iv (%u bytes): %s", IQUIC_IV_LEN,
HEXSTR(nonce, IQUIC_IV_LEN, s_str));
LSQ_DEBUG("seal: ad (%u bytes): %s", header_sz,
HEXSTR(dst, header_sz, s_str));
LSQ_DEBUG("seal: in (%hu bytes): %s", packet_out->po_data_sz,
HEXSTR(packet_out->po_data, packet_out->po_data_sz, s_str));
}
if (!EVP_AEAD_CTX_seal(aead_ctx, dst + header_sz, &out_sz,
dst_sz - header_sz, nonce, IQUIC_IV_LEN,
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);
if (!packet_out->po_nonce)
divers_nonce_len = 0;
else
{
assert(enc_session->es_flags & ES_SERVER);
assert(gel == GEL_EARLY);
divers_nonce_len = DNONC_LENGTH;
}
sample_off = packno_off + divers_nonce_len + 4;
assert(sample_off + IQUIC_TAG_LEN <= dst_sz);
gquic2_apply_hp(enc_session, gel, dst, packno_off, sample_off, packno_len);
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);
lsquic_packet_out_set_enc_level(packet_out, gel2el[gel]);
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;
}
/* XXX this is an exact copy, can reuse */
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
gquic2_strip_hp (struct lsquic_enc_session *enc_session,
enum gel gel, const unsigned char *iv, unsigned char *dst,
unsigned packno_off, unsigned *packno_len)
{
lsquic_packno_t packno;
unsigned shift;
unsigned char mask[EVP_MAX_BLOCK_LENGTH];
char mask_str[5 * 2 + 1];
gquic2_gen_hp_mask(enc_session, enc_session->es_hps[gel][1], iv, mask);
LSQ_DEBUG("strip header protection using mask %s",
HEXSTR(mask, 5, mask_str));
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;
}
return decode_packno(enc_session->es_max_packno, packno, shift);
}
static enum dec_packin
gquic2_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 lsquic_enc_session *const enc_session = enc_session_p;
unsigned char *dst;
unsigned char nonce_buf[ IQUIC_IV_LEN + 8 ];
unsigned char *nonce, *begin_xor;
unsigned sample_off, packno_len, divers_nonce_len;
enum gel gel;
lsquic_packno_t packno;
size_t out_sz;
enum dec_packin dec_packin;
const size_t dst_sz = packet_in->pi_data_sz;
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;
}
if (!(HETY_0RTT == packet_in->pi_header_type
&& !(enc_session->es_flags & ES_SERVER)))
divers_nonce_len = 0;
else
divers_nonce_len = DNONC_LENGTH;
gel = hety2gel[packet_in->pi_header_type];
if (UNLIKELY(!enc_session->es_aead_ctxs[gel][1]))
{
LSQ_INFO("decrypt crypto context at level %s not initialized",
gel2str[gel]);
dec_packin = DECPI_BADCRYPT;
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 + divers_nonce_len + 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 = gquic2_strip_hp(enc_session, gel,
packet_in->pi_data + sample_off,
dst, packet_in->pi_header_sz, &packno_len);
packet_in->pi_header_sz += packno_len;
if (UNLIKELY(divers_nonce_len))
{
if (enc_session->have_key == 1)
{
determine_diversification_key(enc_session,
dst + packet_in->pi_header_sz);
enc_session->have_key = 2;
}
packet_in->pi_header_sz += divers_nonce_len;
}
/* 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 - IQUIC_IV_LEN + 8;
memcpy(nonce, enc_session->es_ivs[gel][1], IQUIC_IV_LEN);
#if __BYTE_ORDER == __LITTLE_ENDIAN
packno = bswap_64(packno);
#endif
*((uint64_t *) begin_xor) ^= packno;
if (s_log_seal_and_open)
{
LSQ_DEBUG("open: iv (%u bytes): %s", IQUIC_IV_LEN,
HEXSTR(nonce, IQUIC_IV_LEN, 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(enc_session->es_aead_ctxs[gel][1],
dst + packet_in->pi_header_sz, &out_sz,
dst_sz - packet_in->pi_header_sz, nonce, IQUIC_IV_LEN,
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;
}
/* Bits 2 and 3 are not set and don't need to be checked in gQUIC */
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
| (gel2el[gel] << PIBIT_ENC_LEV_SHIFT);
EV_LOG_CONN_EVENT(LSQUIC_LOG_CONN_ID, "decrypted packet %"PRIu64,
packet_in->pi_packno);
if (packet_in->pi_packno > enc_session->es_max_packno)
enc_session->es_max_packno = packet_in->pi_packno;
return DECPI_OK;
err:
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;
}
#ifdef NDEBUG
const
#endif
/* Q050 and later */
struct enc_session_funcs_common lsquic_enc_session_common_gquic_2 =
{
.esf_get_sni = lsquic_enc_session_get_sni,
.esf_global_init = lsquic_handshake_init,
.esf_global_cleanup = lsquic_handshake_cleanup,
.esf_cipher = lsquic_enc_session_cipher,
.esf_keysize = lsquic_enc_session_keysize,
.esf_alg_keysize = lsquic_enc_session_alg_keysize,
.esf_get_server_cert_chain = lsquic_enc_session_get_server_cert_chain,
.esf_verify_reset_token = lsquic_enc_session_verify_reset_token,
.esf_did_sess_resume_succeed = lsquic_enc_session_did_sess_resume_succeed,
.esf_is_sess_resume_enabled = lsquic_enc_session_is_sess_resume_enabled,
.esf_set_conn = gquic_esf_set_conn,
/* These are different from gquic_1: */
.esf_encrypt_packet = gquic2_esf_encrypt_packet,
.esf_decrypt_packet = gquic2_esf_decrypt_packet,
.esf_tag_len = IQUIC_TAG_LEN,
};
#ifdef NDEBUG
const
#endif
struct enc_session_funcs_gquic lsquic_enc_session_gquic_gquic_1 =
{
#if LSQUIC_KEEP_ENC_SESS_HISTORY
.esf_get_hist = lsquic_get_enc_hist,
#endif
.esf_destroy = lsquic_enc_session_destroy,
.esf_is_hsk_done = lsquic_enc_session_is_hsk_done,
.esf_get_peer_setting = lsquic_enc_session_get_peer_setting,
.esf_get_peer_option = lsquic_enc_session_get_peer_option,
.esf_create_server = lsquic_enc_session_create_server,
.esf_handle_chlo = lsquic_enc_session_handle_chlo,
.esf_get_ua = lsquic_enc_session_get_ua,
.esf_have_key_gt_one = lsquic_enc_session_have_key_gt_one,
#ifndef NDEBUG
.esf_determine_diversification_key = determine_diversification_key,
.esf_have_key = lsquic_enc_session_have_key,
.esf_set_have_key = lsquic_enc_session_set_have_key,
.esf_get_enc_key_i = lsquic_enc_session_get_enc_key_i,
.esf_get_dec_key_i = lsquic_enc_session_get_dec_key_i,
.esf_get_enc_key_nonce_i = lsquic_enc_session_get_enc_key_nonce_i,
.esf_get_dec_key_nonce_i = lsquic_enc_session_get_dec_key_nonce_i,
.esf_get_enc_key_nonce_f = lsquic_enc_session_get_enc_key_nonce_f,
.esf_get_dec_key_nonce_f = lsquic_enc_session_get_dec_key_nonce_f,
#endif /* !defined(NDEBUG) */
.esf_create_client = lsquic_enc_session_create_client,
.esf_gen_chlo = lsquic_enc_session_gen_chlo,
.esf_handle_chlo_reply = lsquic_enc_session_handle_chlo_reply,
Latest changes - [API Change] Sendfile-like functionality is gone. The stream no longer opens files and deals with file descriptors. (Among other things, this makes the code more portable.) Three writing functions are provided: lsquic_stream_write lsquic_stream_writev lsquic_stream_writef (NEW) lsquic_stream_writef() is given an abstract reader that has function pointers for size() and read() functions which the user can implement. This is the most flexible way. lsquic_stream_write() and lsquic_stream_writev() are now both implemented as wrappers around lsquic_stream_writef(). - [OPTIMIZATION] When writing to stream, be it within or without the on_write() callback, place data directly into packet buffer, bypassing auxiliary data structures. This reduces amount of memory required, for the amount of data that can be written is limited by the congestion window. To support writes outside the on_write() callback, we keep N outgoing packet buffers per connection which can be written to by any stream. One half of these are reserved for the highest priority stream(s), the other half for all other streams. This way, low-priority streams cannot write instead of high-priority streams and, on the other hand, low-priority streams get a chance to send their packets out. The algorithm is as follows: - When user writes to stream outside of the callback: - If this is the highest priority stream, place it onto the reserved N/2 queue or fail. (The actual size of this queue is dynamic -- MAX(N/2, CWND) -- rather than N/2, allowing high-priority streams to write as much as can be sent.) - If the stream is not the highest priority, try to place the data onto the reserved N/2 queue or fail. - When tick occurs *and* more packets can be scheduled: - Transfer packets from the high N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for highest-priority streams, placing resulting packets directly onto the scheduled queue. - If more scheduling is allowed: - Transfer packets from the low N/2 queue to the scheduled queue. - If more scheduling is allowed: - Call on_write callbacks for non-highest-priority streams, placing resulting packets directly onto the scheduled queue The number N is currently 20, but it could be varied based on resource usage. - If stream is created due to incoming headers, make headers readable from on_new. - Outgoing packets are no longer marked non-writeable to prevent placing more than one STREAM frame from the same stream into a single packet. This property is maintained via code flow and an explicit check. Packets for stream data are allocated using a special function. - STREAM frame elision is cheaper, as we only perform it if a reset stream has outgoing packets referencing it. - lsquic_packet_out_t is smaller, as stream_rec elements are now inside a union.
2017-10-31 13:35:58 +00:00
.esf_mem_used = lsquic_enc_session_mem_used,
.esf_maybe_dispatch_sess_resume = maybe_dispatch_sess_resume,
.esf_reset_cid = lsquic_enc_session_reset_cid,
};
typedef char reset_token_lengths_match[
SRST_LENGTH == IQUIC_SRESET_TOKEN_SZ ? 1 : -1];