diff options
author | Pauli <pauli@openssl.org> | 2023-08-02 01:35:35 +0200 |
---|---|---|
committer | Tomas Mraz <tomas@openssl.org> | 2023-08-16 12:07:17 +0200 |
commit | cdd916313a89def99493e00b49958ced894ca209 (patch) | |
tree | 5cb1b4690011d15d974d3a820955cbf050468f79 /ssl/quic/quic_channel.c | |
parent | Check i2d_X509_NAME return in X509_NAME_hash_ex/old (diff) | |
download | openssl-cdd916313a89def99493e00b49958ced894ca209.tar.xz openssl-cdd916313a89def99493e00b49958ced894ca209.zip |
quic: process stateless resets
Reviewed-by: Hugo Landau <hlandau@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21649)
Diffstat (limited to 'ssl/quic/quic_channel.c')
-rw-r--r-- | ssl/quic/quic_channel.c | 196 |
1 files changed, 192 insertions, 4 deletions
diff --git a/ssl/quic/quic_channel.c b/ssl/quic/quic_channel.c index a27e19781b..f174814a8b 100644 --- a/ssl/quic/quic_channel.c +++ b/ssl/quic/quic_channel.c @@ -86,11 +86,13 @@ static int ch_discard_el(QUIC_CHANNEL *ch, static void ch_on_idle_timeout(QUIC_CHANNEL *ch); static void ch_update_idle(QUIC_CHANNEL *ch); static void ch_update_ping_deadline(QUIC_CHANNEL *ch); +static void ch_stateless_reset(QUIC_CHANNEL *ch); static void ch_raise_net_error(QUIC_CHANNEL *ch); static void ch_on_terminating_timeout(QUIC_CHANNEL *ch); static void ch_start_terminating(QUIC_CHANNEL *ch, const QUIC_TERMINATE_CAUSE *tcause, int force_immediate); +static int ch_stateless_reset_token_handler(const unsigned char *data, size_t datalen, void *arg); static void ch_default_packet_handler(QUIC_URXE *e, void *arg); static int ch_server_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer, const QUIC_CONN_ID *peer_scid, @@ -98,6 +100,8 @@ static int ch_server_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer, static void ch_on_txp_ack_tx(const OSSL_QUIC_FRAME_ACK *ack, uint32_t pn_space, void *arg); +DEFINE_LHASH_OF_EX(QUIC_SRT_ELEM); + static int gen_rand_conn_id(OSSL_LIB_CTX *libctx, size_t len, QUIC_CONN_ID *cid) { if (len > QUIC_MAX_CONN_ID_LEN) @@ -113,6 +117,136 @@ static int gen_rand_conn_id(OSSL_LIB_CTX *libctx, size_t len, QUIC_CONN_ID *cid) return 1; } +static unsigned long chan_reset_token_hash(const QUIC_SRT_ELEM *a) +{ + unsigned long h; + + assert(sizeof(h) <= sizeof(a->token)); + memcpy(&h, &a->token, sizeof(h)); + return h; +} + +static int chan_reset_token_cmp(const QUIC_SRT_ELEM *a, const QUIC_SRT_ELEM *b) +{ + /* RFC 9000 s. 10.3.1: + * When comparing a datagram to stateless reset token values, + * endpoints MUST perform the comparison without leaking + * information about the value of the token. For example, + * performing this comparison in constant time protects the + * value of individual stateless reset tokens from information + * leakage through timing side channels. + * + * TODO(QUIC FUTURE): make this a memcmp when obfuscation is done and update + * comment above. + */ + return CRYPTO_memcmp(&a->token, &b->token, sizeof(a->token)); +} + +static int reset_token_obfuscate(QUIC_SRT_ELEM *out, const unsigned char *in) +{ + /* + * TODO(QUIC FUTURE): update this to AES encrypt the token in ECB mode with a + * random (per channel) key. + */ + memcpy(&out->token, in, sizeof(out->token)); + return 1; +} + +/* + * Add a stateless reset token to the channel + */ +static int chan_add_reset_token(QUIC_CHANNEL *ch, const unsigned char *new, + uint64_t seq_num) +{ + QUIC_SRT_ELEM *srte; + int err; + + /* Add to list by sequence number (always the tail) */ + if ((srte = OPENSSL_malloc(sizeof(*srte))) == NULL) + return 0; + + ossl_list_stateless_reset_tokens_init_elem(srte); + ossl_list_stateless_reset_tokens_insert_tail(&ch->srt_list_seq, srte); + reset_token_obfuscate(srte, new); + srte->seq_num = seq_num; + + lh_QUIC_SRT_ELEM_insert(ch->srt_hash_tok, srte); + err = lh_QUIC_SRT_ELEM_error(ch->srt_hash_tok); + if (err > 0) { + ossl_list_stateless_reset_tokens_remove(&ch->srt_list_seq, srte); + OPENSSL_free(srte); + return 0; + } + return 1; +} + +/* + * Remove a stateless reset token from the channel + * If the token isn't known, we just ignore the remove request which is safe. + */ +static void chan_remove_reset_token(QUIC_CHANNEL *ch, uint64_t seq_num) +{ + QUIC_SRT_ELEM *srte; + + /* + * Because the list is ordered and we only ever remove CIDs in order, + * this loop should never iterate, but safer to provide the option. + */ + for (srte = ossl_list_stateless_reset_tokens_head(&ch->srt_list_seq); + srte != NULL; + srte = ossl_list_stateless_reset_tokens_next(srte)) { + if (srte->seq_num > seq_num) + return; + if (srte->seq_num == seq_num) { + ossl_list_stateless_reset_tokens_remove(&ch->srt_list_seq, srte); + (void)lh_QUIC_SRT_ELEM_delete(ch->srt_hash_tok, srte); + OPENSSL_free(srte); + return; + } + } +} + +/* + * This is called by the demux whenever a new datagram arrives + * + * TODO(QUIC FUTURE): optimise this to only be called for unparsable packets + */ +static int ch_stateless_reset_token_handler(const unsigned char *data, + size_t datalen, void *arg) +{ + QUIC_SRT_ELEM srte; + QUIC_CHANNEL *ch = (QUIC_CHANNEL *)arg; + + /* + * Perform some fast and cheap checks for a packet not being a stateless + * reset token. RFC 9000 s. 10.3 specifies this layout for stateless + * reset packets: + * + * Stateless Reset { + * Fixed Bits (2) = 1, + * Unpredictable Bits (38..), + * Stateless Reset Token (128), + * } + * + * It also specifies: + * However, endpoints MUST treat any packet ending in a valid + * stateless reset token as a Stateless Reset, as other QUIC + * versions might allow the use of a long header. + * + * We can rapidly check for the minimum length and that the first pair + * of bits in the first byte are 01 or 11. + * + * The function returns 1 if it is a stateless reset packet, 0 if it isn't + * and -1 if an error was encountered. + */ + if (datalen < QUIC_STATELESS_RESET_TOKEN_LEN + 5 || (0100 & *data) != 0100) + return 0; + memset(&srte, 0, sizeof(srte)); + if (!reset_token_obfuscate(&srte, data + datalen - sizeof(srte.token))) + return -1; + return lh_QUIC_SRT_ELEM_retrieve(ch->srt_hash_tok, &srte) != NULL; +} + /* * QUIC Channel Initialization and Teardown * ======================================== @@ -134,6 +268,12 @@ static int ch_init(QUIC_CHANNEL *ch) uint32_t pn_space; size_t rx_short_cid_len = ch->is_server ? INIT_DCID_LEN : 0; + ossl_list_stateless_reset_tokens_init(&ch->srt_list_seq); + ch->srt_hash_tok = lh_QUIC_SRT_ELEM_new(&chan_reset_token_hash, + &chan_reset_token_cmp); + if (ch->srt_hash_tok == NULL) + goto err; + /* For clients, generate our initial DCID. */ if (!ch->is_server && !gen_rand_conn_id(ch->libctx, INIT_DCID_LEN, &ch->init_dcid)) @@ -249,6 +389,13 @@ static int ch_init(QUIC_CHANNEL *ch) goto err; /* + * Setup a handler to detect stateless reset tokens. + */ + ossl_quic_demux_set_stateless_reset_handler(ch->demux, + &ch_stateless_reset_token_handler, + ch); + + /* * If we are a server, setup our handler for packets not corresponding to * any known DCID on our end. This is for handling clients establishing new * connections. @@ -338,6 +485,7 @@ err: static void ch_cleanup(QUIC_CHANNEL *ch) { + QUIC_SRT_ELEM *srte, *srte_next; uint32_t pn_space; if (ch->ackm != NULL) @@ -373,6 +521,17 @@ static void ch_cleanup(QUIC_CHANNEL *ch) OPENSSL_free(ch->local_transport_params); OPENSSL_free((char *)ch->terminate_cause.reason); OSSL_ERR_STATE_free(ch->err_state); + + /* Free the stateless reset tokens */ + for (srte = ossl_list_stateless_reset_tokens_head(&ch->srt_list_seq); + srte != NULL; + srte = srte_next) { + srte_next = ossl_list_stateless_reset_tokens_next(srte); + ossl_list_stateless_reset_tokens_remove(&ch->srt_list_seq, srte); + (void)lh_QUIC_SRT_ELEM_delete(ch->srt_hash_tok, srte); + OPENSSL_free(srte); + } + lh_QUIC_SRT_ELEM_free(ch->srt_hash_tok); } QUIC_CHANNEL *ossl_quic_channel_new(const QUIC_CHANNEL_ARGS *args) @@ -1047,6 +1206,8 @@ static int ch_on_handshake_alert(void *arg, unsigned char alert_code) x " sent when not performing a retry" #define TP_REASON_REQUIRED(x) \ x " was not sent but is required" +#define TP_REASON_INTERNAL_ERROR(x) \ + x " encountered internal error" static void txfc_bump_cwm_bidi(QUIC_STREAM *s, void *arg) { @@ -1392,10 +1553,11 @@ static int ch_on_transport_params(const unsigned char *params, break; case QUIC_TPARAM_STATELESS_RESET_TOKEN: - /* TODO(QUIC): Handle stateless reset tokens. */ /* - * We ignore these for now, but we must ensure a client doesn't - * send them. + * We must ensure a client doesn't send them because we don't have + * processing for them. + * + * TODO(QUIC SERVER): remove this restriction */ if (ch->is_server) { reason = TP_REASON_SERVER_ONLY("STATELESS_RESET_TOKEN"); @@ -1407,6 +1569,10 @@ static int ch_on_transport_params(const unsigned char *params, reason = TP_REASON_MALFORMED("STATELESS_RESET_TOKEN"); goto malformed; } + if (!chan_add_reset_token(ch, body, ch->cur_remote_seq_num)) { + reason = TP_REASON_INTERNAL_ERROR("STATELESS_RESET_TOKEN"); + goto malformed; + } break; @@ -1793,7 +1959,9 @@ static void ch_rx_pre(QUIC_CHANNEL *ch) * to the appropriate QRX instance. */ ret = ossl_quic_demux_pump(ch->demux); - if (ret == QUIC_DEMUX_PUMP_RES_PERMANENT_FAIL) + if (ret == QUIC_DEMUX_PUMP_RES_STATELESS_RESET) + ch_stateless_reset(ch); + else if (ret == QUIC_DEMUX_PUMP_RES_PERMANENT_FAIL) /* * We don't care about transient failure, but permanent failure means we * should tear down the connection as though a protocol violation @@ -2798,6 +2966,8 @@ static int ch_enqueue_retire_conn_id(QUIC_CHANNEL *ch, uint64_t seq_num) WPACKET wpkt; size_t l; + chan_remove_reset_token(ch, seq_num); + if ((buf_mem = BUF_MEM_new()) == NULL) return 0; @@ -2900,6 +3070,16 @@ void ossl_quic_channel_on_new_conn_id(QUIC_CHANNEL *ch, } if (new_remote_seq_num > ch->cur_remote_seq_num) { + /* Add new stateless reset token */ + if (!chan_add_reset_token(ch, f->stateless_reset.token, + new_remote_seq_num)) { + ossl_quic_channel_raise_protocol_error( + ch, QUIC_ERR_CONNECTION_ID_LIMIT_ERROR, + OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID, + "unable to store stateless reset token"); + + return; + } ch->cur_remote_seq_num = new_remote_seq_num; ch->cur_remote_dcid = f->conn_id; ossl_quic_tx_packetiser_set_cur_dcid(ch->txp, &ch->cur_remote_dcid); @@ -2943,6 +3123,14 @@ static void ch_save_err_state(QUIC_CHANNEL *ch) OSSL_ERR_STATE_save(ch->err_state); } +static void ch_stateless_reset(QUIC_CHANNEL *ch) +{ + QUIC_TERMINATE_CAUSE tcause = {0}; + + tcause.error_code = QUIC_ERR_NO_ERROR; + ch_start_terminating(ch, &tcause, 1); +} + static void ch_raise_net_error(QUIC_CHANNEL *ch) { QUIC_TERMINATE_CAUSE tcause = {0}; |