Added functions to decrypt long packets.

This commit is contained in:
Vladimir Homutov 2020-03-05 17:18:33 +03:00
parent 12fc8a8bac
commit 32b2728ebb

View File

@ -185,9 +185,22 @@ typedef struct {
ngx_uint_t type;
ngx_uint_t *number;
ngx_uint_t flags;
ngx_uint_t version;
ngx_str_t *token;
uint32_t version;
ngx_str_t token;
ngx_quic_level_t level;
/* filled in by parser */
ngx_str_t buf; /* quic packet from wire */
u_char *pos; /* current parser position */
/* cleartext fields */
ngx_str_t dcid;
ngx_str_t scid;
uint64_t pn;
ngx_str_t payload; /* decrypted payload */
} ngx_quic_header_t;
@ -210,6 +223,15 @@ static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn);
static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn,
enum ssl_encryption_level_t level, uint8_t alert);
static ngx_int_t ngx_quic_process_long_header(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_process_initial_header(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_process_handshake_header(ngx_connection_t *c,
ngx_quic_header_t *pkt);
static ngx_int_t ngx_quic_initial_secret(ngx_connection_t *c);
static ngx_int_t ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt);
static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask);
static uint64_t ngx_quic_parse_int(u_char **pos);
static void ngx_quic_build_int(u_char **pos, uint64_t value);
@ -287,7 +309,7 @@ ngx_quic_send_packet(ngx_connection_t *c, ngx_quic_connection_t *qc,
pkt.number = &qc->initial_pn;
pkt.flags = NGX_QUIC_PKT_INITIAL;
pkt.secret = &qc->server_in;
pkt.token = &initial_token;
pkt.token = initial_token;
if (ngx_quic_create_long_packet(c, c->ssl->connection,
&pkt, payload, &res)
@ -599,8 +621,8 @@ ngx_quic_create_long_packet(ngx_connection_t *c, ngx_ssl_conn_t *ssl_conn,
*p++ = qc->dcid.len;
p = ngx_cpymem(p, qc->dcid.data, qc->dcid.len);
if (pkt->token) { // if pkt->flags & initial ?
ngx_quic_build_int(&p, pkt->token->len);
if (pkt->level == ssl_encryption_initial) {
ngx_quic_build_int(&p, pkt->token.len);
}
ngx_quic_build_int(&p, out.len + 1); // length (inc. pnl)
@ -865,90 +887,114 @@ ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level,
static ngx_int_t
ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
ngx_quic_process_long_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
int n, sslerr;
ngx_quic_connection_t *qc;
u_char *p;
if ((b->pos[0] & 0xf0) != NGX_QUIC_PKT_INITIAL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid initial packet");
p = pkt->buf.data;
ngx_quic_hexdump0(c->log, "input", pkt->buf.data, pkt->buf.len);
if (!(p[0] & NGX_QUIC_PKT_LONG)) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "not a long packet");
return NGX_ERROR;
}
if (ngx_buf_size(b) < 1200) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
return NGX_ERROR;
}
pkt->flags = *p++;
ngx_int_t flags = *b->pos++;
uint32_t version = ngx_quic_parse_uint32(b->pos);
b->pos += sizeof(uint32_t);
pkt->version = ngx_quic_parse_uint32(p);
p += sizeof(uint32_t);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic flags:%xi version:%xD", flags, version);
"quic flags:%xi version:%xD", pkt->flags, pkt->version);
if (version != quic_version) {
if (pkt->version != quic_version) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
return NGX_ERROR;
}
qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
if (qc == NULL) {
return NGX_ERROR;
}
pkt->dcid.len = *p++;
pkt->dcid.data = p;
p += pkt->dcid.len;
c->quic = qc;
pkt->scid.len = *p++;
pkt->scid.data = p;
p += pkt->scid.len;
qc->dcid.len = *b->pos++;
qc->dcid.data = ngx_pnalloc(c->pool, qc->dcid.len);
if (qc->dcid.data == NULL) {
return NGX_ERROR;
}
pkt->pos = p;
ngx_memcpy(qc->dcid.data, b->pos, qc->dcid.len);
b->pos += qc->dcid.len;
return NGX_OK;
}
qc->scid.len = *b->pos++;
qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
if (qc->scid.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->scid.data, b->pos, qc->scid.len);
b->pos += qc->scid.len;
static ngx_int_t
ngx_quic_process_initial_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char *p;
ngx_int_t plen;
qc->token.len = ngx_quic_parse_int(&b->pos);
qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
if (qc->token.data == NULL) {
return NGX_ERROR;
}
p = pkt->pos;
ngx_memcpy(qc->token.data, b->pos, qc->token.len);
b->pos += qc->token.len;
pkt->token.len = ngx_quic_parse_int(&p);
pkt->token.data = p;
ngx_int_t plen = ngx_quic_parse_int(&b->pos);
p += pkt->token.len;
if (plen > b->last - b->pos) {
plen = ngx_quic_parse_int(&p);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
if (plen > pkt->buf.data + pkt->buf.len - p) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated initial packet");
return NGX_ERROR;
}
/* draft-ietf-quic-tls-23#section-5.4.2:
* the Packet Number field is assumed to be 4 bytes long
* draft-ietf-quic-tls-23#section-5.4.[34]:
* AES-Based and ChaCha20-Based header protections sample 16 bytes
*/
u_char *sample = b->pos + 4;
pkt->pos = p;
pkt->buf.len = plen;
ngx_quic_hexdump0(c->log, "DCID", pkt->dcid.data, pkt->dcid.len);
ngx_quic_hexdump0(c->log, "SCID", pkt->scid.data, pkt->scid.len);
ngx_quic_hexdump0(c->log, "token", pkt->token.data, pkt->token.len);
ngx_quic_hexdump0(c->log, "DCID", qc->dcid.data, qc->dcid.len);
ngx_quic_hexdump0(c->log, "SCID", qc->scid.data, qc->scid.len);
ngx_quic_hexdump0(c->log, "token", qc->token.data, qc->token.len);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
ngx_quic_hexdump0(c->log, "sample", sample, 16);
return NGX_OK;
}
static ngx_int_t
ngx_quic_process_handshake_header(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char *p;
ngx_int_t plen;
p = pkt->pos;
plen = ngx_quic_parse_int(&p);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
if (plen > pkt->buf.data + pkt->buf.len - p) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
return NGX_ERROR;
}
pkt->pos = p;
pkt->buf.len = plen;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
return NGX_OK;
}
// initial secret
static ngx_int_t
ngx_quic_initial_secret(ngx_connection_t *c)
{
ngx_quic_connection_t *qc = c->quic;
size_t is_len;
uint8_t is[SHA256_DIGEST_LENGTH];
@ -1047,57 +1093,176 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
}
}
// header protection
return NGX_OK;
}
uint8_t mask[16];
if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_in, mask, sample)
static ngx_int_t
ngx_quic_decrypt(ngx_connection_t *c, ngx_quic_header_t *pkt)
{
u_char clearflags, *p, *sample;
uint8_t *nonce;
uint64_t pn;
ngx_int_t pnl, rc;
ngx_str_t in, ad;
const EVP_CIPHER *cipher;
uint8_t mask[16];
p = pkt->pos;
/* draft-ietf-quic-tls-23#section-5.4.2:
* the Packet Number field is assumed to be 4 bytes long
* draft-ietf-quic-tls-23#section-5.4.[34]:
* AES-Based and ChaCha20-Based header protections sample 16 bytes
*/
sample = p + 4;
ngx_quic_hexdump0(c->log, "sample", sample, 16);
/* header protection */
if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), pkt->secret, mask, sample)
!= NGX_OK)
{
return NGX_ERROR;
}
u_char clearflags = flags ^ (mask[0] & 0x0f);
ngx_int_t pnl = (clearflags & 0x03) + 1;
uint64_t pn = ngx_quic_parse_pn(&b->pos, pnl, &mask[1]);
clearflags = pkt->flags ^ (mask[0] & 0x0f);
pnl = (clearflags & 0x03) + 1;
pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
pkt->pn = pn;
ngx_quic_hexdump0(c->log, "sample", sample, 16);
ngx_quic_hexdump0(c->log, "mask", mask, 5);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic clear flags: %xi", clearflags);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet number: %uL, len: %xi", pn, pnl);
// packet protection
/* packet protection */
ngx_str_t in;
in.data = b->pos;
in.len = plen - pnl;
in.data = p;
in.len = pkt->buf.len - pnl;
ngx_str_t ad;
ad.len = b->pos - b->start;
ad.len = p - pkt->buf.data;;
ad.data = ngx_pnalloc(c->pool, ad.len);
if (ad.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(ad.data, b->start, ad.len);
ngx_memcpy(ad.data, pkt->buf.data, ad.len);
ad.data[0] = clearflags;
ad.data[ad.len - pnl] = (u_char)pn;
ad.data[ad.len - pnl] = (u_char) pn;
uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_in.iv);
nonce = ngx_pstrdup(c->pool, &pkt->secret->iv);
nonce[11] ^= pn;
ngx_quic_hexdump0(c->log, "nonce", nonce, 12);
ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len);
ngx_str_t out;
if (c->ssl) {
switch (SSL_CIPHER_get_id(SSL_get_current_cipher(c->ssl->connection)) & 0xffff) {
if (ngx_quic_tls_open(c, EVP_aes_128_gcm(), &qc->client_in, &out, nonce,
&in, &ad)
!= NGX_OK)
{
case NGX_AES_128_GCM_SHA256:
cipher = EVP_aes_128_gcm();
break;
case NGX_AES_256_GCM_SHA384:
cipher = EVP_aes_256_gcm();
break;
default:
ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
return NGX_ERROR;
}
} else {
/* initial packets */
cipher = EVP_aes_128_gcm();
}
rc = ngx_quic_tls_open(c, cipher, pkt->secret, &pkt->payload,
nonce, &in, &ad);
ngx_quic_hexdump0(c->log, "packet payload",
pkt->payload.data, pkt->payload.len);
return rc;
}
static ngx_int_t
ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
{
int n, sslerr;
ngx_str_t out;
ngx_quic_connection_t *qc;
ngx_quic_header_t pkt = { 0 };
pkt.buf.data = b->start;
pkt.buf.len = b->last - b->pos;
if (ngx_buf_size(b) < 1200) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "too small UDP datagram");
return NGX_ERROR;
}
ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len);
if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_INITIAL) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"invalid initial packet: 0x%x", pkt.flags);
return NGX_ERROR;
}
if (ngx_quic_process_initial_header(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t));
if (qc == NULL) {
return NGX_ERROR;
}
c->quic = qc;
qc->dcid.len = pkt.dcid.len;
qc->dcid.data = ngx_pnalloc(c->pool, pkt.dcid.len);
if (qc->dcid.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->dcid.data, pkt.dcid.data, qc->dcid.len);
qc->scid.len = pkt.scid.len;
qc->scid.data = ngx_pnalloc(c->pool, qc->scid.len);
if (qc->scid.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->scid.data, pkt.scid.data, qc->scid.len);
qc->token.len = pkt.token.len;
qc->token.data = ngx_pnalloc(c->pool, qc->token.len);
if (qc->token.data == NULL) {
return NGX_ERROR;
}
ngx_memcpy(qc->token.data, pkt.token.data, qc->token.len);
if (ngx_quic_initial_secret(c) != NGX_OK) {
return NGX_ERROR;
}
pkt.secret = &qc->client_in;
if (ngx_quic_decrypt(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
out = pkt.payload;
if (out.data[0] != 0x06) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
@ -1183,144 +1348,64 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_ssl_t *ssl, ngx_buf_t *b)
static ngx_int_t
ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb)
ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *b)
{
int sslerr;
u_char *p, *b;
ssize_t n;
ngx_str_t out;
ngx_ssl_conn_t *ssl_conn;
const EVP_CIPHER *cipher;
ngx_quic_connection_t *qc;
ngx_quic_header_t pkt = { 0 };
qc = c->quic;
ssl_conn = c->ssl->connection;
n = bb->last - bb->pos;
p = bb->pos;
b = bb->start;
pkt.buf.data = b->start;
pkt.buf.len = b->last - b->pos;
ngx_quic_hexdump0(c->log, "input", p, n);
if ((p[0] & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "invalid packet type");
/* extract cleartext data into pkt */
if (ngx_quic_process_long_header(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
ngx_int_t flags = *p++;
uint32_t version = ngx_quic_parse_uint32(p);
p += sizeof(uint32_t);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic flags:%xi version:%xD", flags, version);
if (version != quic_version) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unsupported quic version");
return NGX_ERROR;
}
if (*p++ != qc->dcid.len) {
if (pkt.dcid.len != qc->dcid.len) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcidl");
return NGX_ERROR;
}
if (ngx_memcmp(p, qc->dcid.data, qc->dcid.len) != 0) {
if (ngx_memcmp(pkt.dcid.data, qc->dcid.data, qc->dcid.len) != 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic dcid");
return NGX_ERROR;
}
p += qc->dcid.len;
if (*p++ != qc->scid.len) {
if (pkt.scid.len != qc->scid.len) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scidl");
return NGX_ERROR;
}
if (ngx_memcmp(p, qc->scid.data, qc->scid.len) != 0) {
if (ngx_memcmp(pkt.scid.data, qc->scid.data, qc->scid.len) != 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "unexpected quic scid");
return NGX_ERROR;
}
p += qc->scid.len;
ngx_int_t plen = ngx_quic_parse_int(&p);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet length: %d", plen);
if (plen > b + n - p) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, "truncated handshake packet");
if ((pkt.flags & 0xf0) != NGX_QUIC_PKT_HANDSHAKE) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"invalid packet type: 0x%x", pkt.flags);
return NGX_ERROR;
}
u_char *sample = p + 4;
ngx_quic_hexdump0(c->log, "sample", sample, 16);
// header protection
uint8_t mask[16];
if (ngx_quic_tls_hp(c, EVP_aes_128_ecb(), &qc->client_hs, mask, sample)
!= NGX_OK)
{
if (ngx_quic_process_handshake_header(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
u_char clearflags = flags ^ (mask[0] & 0x0f);
ngx_int_t pnl = (clearflags & 0x03) + 1;
uint64_t pn = ngx_quic_parse_pn(&p, pnl, &mask[1]);
pkt.secret = &qc->client_hs;
ngx_quic_hexdump0(c->log, "mask", mask, 5);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic clear flags: %xi", clearflags);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
"quic packet number: %uL, len: %xi", pn, pnl);
// packet protection
ngx_str_t in;
in.data = p;
in.len = plen - pnl;
ngx_str_t ad;
ad.len = p - b;
ad.data = ngx_pnalloc(c->pool, ad.len);
if (ad.data == NULL) {
if (ngx_quic_decrypt(c, &pkt) != NGX_OK) {
return NGX_ERROR;
}
ngx_memcpy(ad.data, b, ad.len);
ad.data[0] = clearflags;
ad.data[ad.len - pnl] = (u_char)pn;
uint8_t *nonce = ngx_pstrdup(c->pool, &qc->client_hs.iv);
nonce[11] ^= pn;
ngx_quic_hexdump0(c->log, "nonce", nonce, 12);
ngx_quic_hexdump0(c->log, "ad", ad.data, ad.len);
switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl_conn)) & 0xffff) {
case NGX_AES_128_GCM_SHA256:
cipher = EVP_aes_128_gcm();
break;
case NGX_AES_256_GCM_SHA384:
cipher = EVP_aes_256_gcm();
break;
default:
ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher");
return NGX_ERROR;
}
if (ngx_quic_tls_open(c, cipher, &qc->client_hs, &out, nonce, &in, &ad)
!= NGX_OK)
{
return NGX_ERROR;
}
ngx_quic_hexdump0(c->log, "packet payload", out.data, out.len);
out = pkt.payload;
if (out.data[0] != 0x06) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
@ -1388,9 +1473,9 @@ ngx_quic_handshake_input(ngx_connection_t *c, ngx_buf_t *bb)
frame->level = ssl_encryption_handshake;
frame->type = NGX_QUIC_FT_ACK;
frame->u.ack.pn = pn;
frame->u.ack.pn = pkt.pn;
ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pn);
ngx_sprintf(frame->info, "ACK for PN=%d at handshake level, in respond to client finished", pkt.pn);
ngx_quic_queue_frame(qc, frame);
if (ngx_quic_output(c) != NGX_OK) {