#define NGX_QUIC_STREAM_GONE (void *) -1
+/*
+ * Endpoints MUST discard packets that are too small to be valid QUIC
+ * packets. With the set of AEAD functions defined in [QUIC-TLS],
+ * packets that are smaller than 21 bytes are never valid.
+ */
+#define NGX_QUIC_MIN_PKT_LEN 21
+
+#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 random + 16 srt + 22 padding */
+#define NGX_QUIC_MAX_SR_PACKET 1200
+
typedef struct {
ngx_rbtree_t tree;
uint64_t seqnum;
size_t len;
u_char id[NGX_QUIC_CID_LEN_MAX];
- u_char sr_token[NGX_QUIC_SRT_LEN];
+ u_char sr_token[NGX_QUIC_SR_TOKEN_LEN];
} ngx_quic_client_id_t;
static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c,
ngx_ssl_t *ssl, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c,
+ ngx_quic_conf_t *conf, ngx_quic_header_t *pkt);
+static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c,
+ ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c,
ngx_quic_header_t *inpkt);
static ngx_int_t ngx_quic_new_dcid(ngx_connection_t *c,
}
+static ngx_int_t
+ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf,
+ ngx_quic_header_t *pkt)
+{
+ u_char *token;
+ size_t len, max;
+ uint16_t rndbytes;
+ u_char buf[NGX_QUIC_MAX_SR_PACKET];
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "quic handle stateless reset output");
+
+ if (conf->sr_token_key.len == 0) {
+ return NGX_DECLINED;
+ }
+
+ if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) {
+ return NGX_DECLINED;
+ }
+
+ if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) {
+ len = pkt->len - 1;
+
+ } else {
+ max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3);
+
+ if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) {
+ return NGX_ERROR;
+ }
+
+ len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1))
+ + NGX_QUIC_MIN_SR_PACKET;
+ }
+
+ if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) {
+ return NGX_ERROR;
+ }
+
+ buf[0] &= ~NGX_QUIC_PKT_LONG;
+ buf[0] |= NGX_QUIC_PKT_FIXED_BIT;
+
+ token = &buf[len - NGX_QUIC_SR_TOKEN_LEN];
+
+ if (ngx_quic_new_sr_token(c, &pkt->dcid, &conf->sr_token_key, token)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ (void) c->send(c, buf, len);
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt)
+{
+ u_char *tail, ch;
+ ngx_uint_t i;
+ ngx_queue_t *q;
+ ngx_quic_client_id_t *cid;
+ ngx_quic_connection_t *qc;
+
+ qc = c->quic;
+
+ /* A stateless reset uses an entire UDP datagram */
+ if (pkt->raw->start != pkt->data) {
+ return NGX_DECLINED;
+ }
+
+ tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN;
+
+ for (q = ngx_queue_head(&qc->client_ids);
+ q != ngx_queue_sentinel(&qc->client_ids);
+ q = ngx_queue_next(q))
+ {
+ cid = ngx_queue_data(q, ngx_quic_client_id_t, queue);
+
+ if (cid->seqnum == 0) {
+ /* no stateless reset token in initial connection id */
+ continue;
+ }
+
+ /* constant time comparison */
+
+ for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) {
+ ch |= tail[i] ^ cid->sr_token[i];
+ }
+
+ if (ch == 0) {
+ return NGX_OK;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
static ngx_int_t
ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt)
{
}
#endif
+ if (qc->conf->sr_token_key.len) {
+ qc->tp.sr_enabled = 1;
+
+ if (ngx_quic_new_sr_token(c, &qc->dcid, &qc->conf->sr_token_key,
+ qc->tp.sr_token)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ ngx_quic_hexdump(c->log, "quic stateless reset token",
+ qc->tp.sr_token, NGX_QUIC_SR_TOKEN_LEN);
+ }
+
len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen);
/* always succeeds */
}
if (ngx_quic_check_peer(qc, pkt) != NGX_OK) {
+
+ if (pkt->level == ssl_encryption_application) {
+ if (ngx_quic_process_stateless_reset(c, pkt) == NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "quic stateless reset packet detected");
+
+ qc->draining = 1;
+ ngx_quic_close_connection(c, NGX_OK);
+
+ return NGX_OK;
+ }
+
+ return ngx_quic_send_stateless_reset(c, qc->conf, pkt);
+ }
+
return NGX_DECLINED;
}
}
} else if (pkt->level == ssl_encryption_application) {
- return NGX_DECLINED;
+ return ngx_quic_send_stateless_reset(c, conf, pkt);
} else {
return NGX_ERROR;
if (cid->len != f->len
|| ngx_strncmp(cid->id, f->cid, f->len) != 0
- || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SRT_LEN) != 0)
+ || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0)
{
/*
* ..a sequence number is used for different connection IDs,
cid->len = f->len;
ngx_memcpy(cid->id, f->cid, f->len);
- ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SRT_LEN);
+ ngx_memcpy(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN);
ngx_queue_insert_tail(&qc->client_ids, &cid->queue);
qc->nclient_ids++;
}
+ngx_int_t
+ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, ngx_str_t *secret,
+ u_char *token)
+{
+ uint8_t *p;
+ size_t is_len, key_len, info_len;
+ ngx_str_t label;
+ const EVP_MD *digest;
+ uint8_t info[20];
+ uint8_t is[SHA256_DIGEST_LENGTH];
+ uint8_t key[SHA256_DIGEST_LENGTH];
+
+ /* 10.4.2. Calculating a Stateless Reset Token */
+
+ digest = EVP_sha256();
+ ngx_str_set(&label, "sr_token_key");
+
+ if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len,
+ cid->data, cid->len)
+ != NGX_OK)
+ {
+ ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
+ "ngx_hkdf_extract(%V) failed", &label);
+ return NGX_ERROR;
+ }
+
+ key_len = SHA256_DIGEST_LENGTH;
+
+ info_len = 2 + 1 + label.len + 1;
+
+ info[0] = 0;
+ info[1] = key_len;
+ info[2] = label.len;
+
+ p = ngx_cpymem(&info[3], label.data, label.len);
+ *p = '\0';
+
+ if (ngx_hkdf_expand(key, key_len, digest, is, is_len, info, info_len)
+ != NGX_OK)
+ {
+ ngx_ssl_error(NGX_LOG_INFO, c->log, 0,
+ "ngx_hkdf_expand(%V) failed", &label);
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(token, key, NGX_QUIC_SR_TOKEN_LEN);
+
+#if (NGX_DEBUG)
+ ngx_quic_hexdump(c->log, "quic stateless reset token", token,
+ NGX_QUIC_SR_TOKEN_LEN);
+#endif
+
+ return NGX_OK;
+}
+
+
static uint64_t
ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask,
uint64_t *largest_pn)
goto error;
}
- p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SRT_LEN, f->u.ncid.srt);
+ p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt);
if (p == NULL) {
goto error;
}
case NGX_QUIC_TP_ORIGINAL_DCID:
case NGX_QUIC_TP_PREFERRED_ADDRESS:
case NGX_QUIC_TP_RETRY_SCID:
- case NGX_QUIC_TP_STATELESS_RESET_TOKEN:
+ case NGX_QUIC_TP_SR_TOKEN:
ngx_log_error(NGX_LOG_INFO, log, 0,
"quic client sent forbidden transport param"
" id 0x%xL", id);
}
#endif
+ if (tp->sr_enabled) {
+ len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN);
+ len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN);
+ len += NGX_QUIC_SR_TOKEN_LEN;
+ }
+
if (pos == NULL) {
return len;
}
}
#endif
+ if (tp->sr_enabled) {
+ ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN);
+ ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN);
+ p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN);
+ }
+
return p - pos;
}