From a0066a1ccf85de4b5e1cb05c28507c3ccbd9cde8 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 26 Aug 2021 18:01:53 +0900 Subject: [PATCH] nghttpx: Send NEW_TOKEN and very token from client --- src/shrpx_http3_upstream.cc | 37 +++++- src/shrpx_http3_upstream.h | 1 + src/shrpx_quic.cc | 178 +++++++++++++++++++++++++++ src/shrpx_quic.h | 6 + src/shrpx_quic_connection_handler.cc | 42 ++++--- src/shrpx_quic_connection_handler.h | 3 +- 6 files changed, 251 insertions(+), 16 deletions(-) diff --git a/src/shrpx_http3_upstream.cc b/src/shrpx_http3_upstream.cc index a9591a43..1c1cabce 100644 --- a/src/shrpx_http3_upstream.cc +++ b/src/shrpx_http3_upstream.cc @@ -401,6 +401,41 @@ int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id, } } // namespace +namespace { +int handshake_completed(ngtcp2_conn *conn, void *user_data) { + auto upstream = static_cast(user_data); + + if (upstream->handshake_completed() != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + + return 0; +} +} // namespace + +int Http3Upstream::handshake_completed() { + std::array token; + size_t tokenlen = token.size(); + + auto path = ngtcp2_conn_get_path(conn_); + auto worker = handler_->get_worker(); + auto &quic_secret = worker->get_quic_secret(); + auto &secret = quic_secret->token_secret; + + if (generate_token(token.data(), tokenlen, path->remote.addr, + path->remote.addrlen, secret.data()) != 0) { + return 0; + } + + auto rv = ngtcp2_conn_submit_new_token(conn_, token.data(), tokenlen); + if (rv != 0) { + LOG(ERROR) << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv); + return -1; + } + + return 0; +} + int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, const Address &local_addr, const ngtcp2_pkt_hd &initial_hd, @@ -414,7 +449,7 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr, nullptr, // client_initial ngtcp2_crypto_recv_client_initial_cb, ngtcp2_crypto_recv_crypto_data_cb, - nullptr, // handshake_completed + shrpx::handshake_completed, nullptr, // recv_version_negotiation ngtcp2_crypto_encrypt_cb, ngtcp2_crypto_decrypt_cb, diff --git a/src/shrpx_http3_upstream.h b/src/shrpx_http3_upstream.h index 049f7044..b3a15a0e 100644 --- a/src/shrpx_http3_upstream.h +++ b/src/shrpx_http3_upstream.h @@ -143,6 +143,7 @@ public: int http_send_stop_sending(int64_t stream_id, uint64_t app_error_code); int http_recv_data(Downstream *downstream, const uint8_t *data, size_t datalen); + int handshake_completed(); private: ClientHandler *handler_; diff --git a/src/shrpx_quic.cc b/src/shrpx_quic.cc index e891216f..0e3fbb17 100644 --- a/src/shrpx_quic.cc +++ b/src/shrpx_quic.cc @@ -403,4 +403,182 @@ int verify_retry_token(ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, return 0; } +namespace { +size_t generate_token_aad(uint8_t *dest, size_t destlen, const sockaddr *sa, + size_t salen) { + const uint8_t *addr; + size_t addrlen; + + switch (sa->sa_family) { + case AF_INET: + addr = reinterpret_cast( + &reinterpret_cast(sa)->sin_addr); + addrlen = sizeof(reinterpret_cast(sa)->sin_addr); + break; + case AF_INET6: + addr = reinterpret_cast( + &reinterpret_cast(sa)->sin6_addr); + addrlen = sizeof(reinterpret_cast(sa)->sin6_addr); + break; + default: + return 0; + } + + assert(destlen >= addrlen); + + return std::copy_n(addr, addrlen, dest) - dest; +} +} // namespace + +int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, + size_t salen, const uint8_t *token_secret) { + std::array plaintext; + + uint64_t t = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + std::array aad; + auto aadlen = generate_token_aad(aad.data(), aad.size(), sa, salen); + if (aadlen == 0) { + return -1; + } + + auto p = std::begin(plaintext); + // Host byte order + p = std::copy_n(reinterpret_cast(&t), sizeof(t), p); + + std::array rand_data; + std::array key, iv; + auto keylen = key.size(); + auto ivlen = iv.size(); + + if (RAND_bytes(rand_data.data(), rand_data.size()) != 1) { + return -1; + } + + ngtcp2_crypto_aead aead; + ngtcp2_crypto_aead_init(&aead, const_cast(EVP_aes_128_gcm())); + + ngtcp2_crypto_md md; + ngtcp2_crypto_md_init(&md, const_cast(EVP_sha256())); + + if (derive_token_key(key.data(), keylen, iv.data(), ivlen, token_secret, + rand_data.data(), rand_data.size(), &aead, &md) != 0) { + return -1; + } + + auto plaintextlen = std::distance(std::begin(plaintext), p); + + ngtcp2_crypto_aead_ctx aead_ctx; + if (ngtcp2_crypto_aead_ctx_encrypt_init(&aead_ctx, &aead, key.data(), + ivlen) != 0) { + return -1; + } + + token[0] = SHRPX_QUIC_TOKEN_MAGIC; + auto rv = + ngtcp2_crypto_encrypt(token + 1, &aead, &aead_ctx, plaintext.data(), + plaintextlen, iv.data(), ivlen, aad.data(), aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + /* 1 for magic byte */ + tokenlen = 1 + plaintextlen + aead.max_overhead; + memcpy(token + tokenlen, rand_data.data(), rand_data.size()); + tokenlen += rand_data.size(); + + return 0; +} + +int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa, + socklen_t salen, const uint8_t *token_secret) { + std::array host; + std::array port; + + if (getnameinfo(sa, salen, host.data(), host.size(), port.data(), port.size(), + NI_NUMERICHOST | NI_NUMERICSERV) != 0) { + return -1; + } + + /* 1 for TOKEN_MAGIC */ + if (tokenlen < SHRPX_QUIC_TOKEN_RAND_DATALEN + 1) { + return -1; + } + if (tokenlen > SHRPX_QUIC_MAX_TOKENLEN) { + return -1; + } + + assert(token[0] == SHRPX_QUIC_TOKEN_MAGIC); + + std::array aad; + auto aadlen = generate_token_aad(aad.data(), aad.size(), sa, salen); + if (aadlen == 0) { + return -1; + } + + auto rand_data = token + tokenlen - SHRPX_QUIC_TOKEN_RAND_DATALEN; + auto ciphertext = token + 1; + auto ciphertextlen = tokenlen - SHRPX_QUIC_TOKEN_RAND_DATALEN - 1; + + std::array key, iv; + auto keylen = key.size(); + auto ivlen = iv.size(); + + ngtcp2_crypto_aead aead; + ngtcp2_crypto_aead_init(&aead, const_cast(EVP_aes_128_gcm())); + + ngtcp2_crypto_md md; + ngtcp2_crypto_md_init(&md, const_cast(EVP_sha256())); + + if (derive_token_key(key.data(), keylen, iv.data(), ivlen, token_secret, + rand_data, SHRPX_QUIC_TOKEN_RAND_DATALEN, &aead, + &md) != 0) { + return -1; + } + + ngtcp2_crypto_aead_ctx aead_ctx; + if (ngtcp2_crypto_aead_ctx_decrypt_init(&aead_ctx, &aead, key.data(), + ivlen) != 0) { + return -1; + } + + std::array plaintext; + + auto rv = ngtcp2_crypto_decrypt(plaintext.data(), &aead, &aead_ctx, + ciphertext, ciphertextlen, iv.data(), ivlen, + aad.data(), aadlen); + + ngtcp2_crypto_aead_ctx_free(&aead_ctx); + + if (rv != 0) { + return -1; + } + + assert(ciphertextlen >= aead.max_overhead); + + auto plaintextlen = ciphertextlen - aead.max_overhead; + if (plaintextlen != sizeof(uint64_t)) { + return -1; + } + + uint64_t t; + memcpy(&t, plaintext.data(), sizeof(uint64_t)); + + uint64_t now = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + // Allow 1 hour window + if (t + 3600ULL * NGTCP2_SECONDS < now) { + return -1; + } + + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_quic.h b/src/shrpx_quic.h index 16238528..55724197 100644 --- a/src/shrpx_quic.h +++ b/src/shrpx_quic.h @@ -84,6 +84,12 @@ int verify_retry_token(ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen, const ngtcp2_cid *dcid, const sockaddr *sa, socklen_t salen, const uint8_t *token_secret); +int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, + size_t salen, const uint8_t *token_secret); + +int verify_token(const uint8_t *token, size_t tokenlen, const sockaddr *sa, + socklen_t salen, const uint8_t *token_secret); + } // namespace shrpx #endif // SHRPX_QUIC_H diff --git a/src/shrpx_quic_connection_handler.cc b/src/shrpx_quic_connection_handler.cc index ebfdb869..c3d19ac7 100644 --- a/src/shrpx_quic_connection_handler.cc +++ b/src/shrpx_quic_connection_handler.cc @@ -85,6 +85,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, ngtcp2_pkt_hd hd; ngtcp2_cid odcid, *podcid = nullptr; + const uint8_t *token = nullptr; + size_t tokenlen = 0; switch (ngtcp2_accept(&hd, data, datalen)) { case 0: { @@ -95,12 +97,31 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, auto &quic_secret = worker_->get_quic_secret(); auto &secret = quic_secret->token_secret; - if (hd.token.base[0] == SHRPX_QUIC_RETRY_TOKEN_MAGIC) { + switch (hd.token.base[0]) { + case SHRPX_QUIC_RETRY_TOKEN_MAGIC: if (verify_retry_token(&odcid, hd.token.base, hd.token.len, &hd.dcid, &remote_addr.su.sa, remote_addr.len, - secret.data()) == 0) { - podcid = &odcid; + secret.data()) != 0) { + break; } + + podcid = &odcid; + token = hd.token.base; + tokenlen = hd.token.len; + + break; + case SHRPX_QUIC_TOKEN_MAGIC: + if (verify_token(hd.token.base, hd.token.len, &remote_addr.su.sa, + remote_addr.len, secret.data()) != 0) { + break; + } + + token = hd.token.base; + tokenlen = hd.token.len; + + break; + default: + break; } break; @@ -139,7 +160,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, return 0; } - handler = handle_new_connection(faddr, remote_addr, local_addr, hd, podcid); + handler = handle_new_connection(faddr, remote_addr, local_addr, hd, podcid, + token, tokenlen); if (handler == nullptr) { return 0; } @@ -159,8 +181,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr, ClientHandler *QUICConnectionHandler::handle_new_connection( const UpstreamAddr *faddr, const Address &remote_addr, - const Address &local_addr, const ngtcp2_pkt_hd &hd, - const ngtcp2_cid *odcid) { + const Address &local_addr, const ngtcp2_pkt_hd &hd, const ngtcp2_cid *odcid, + const uint8_t *token, size_t tokenlen) { std::array host; std::array service; int rv; @@ -198,14 +220,6 @@ ClientHandler *QUICConnectionHandler::handle_new_connection( worker_, faddr->fd, ssl, StringRef{host.data()}, StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr); - const uint8_t *token = nullptr; - size_t tokenlen = 0; - - if (odcid) { - token = hd.token.base; - tokenlen = hd.token.len; - } - auto upstream = std::make_unique(handler.get()); if (upstream->init(faddr, remote_addr, local_addr, hd, odcid, token, tokenlen) != 0) { diff --git a/src/shrpx_quic_connection_handler.h b/src/shrpx_quic_connection_handler.h index 33b614b4..a536c170 100644 --- a/src/shrpx_quic_connection_handler.h +++ b/src/shrpx_quic_connection_handler.h @@ -75,7 +75,8 @@ public: const Address &remote_addr, const Address &local_addr, const ngtcp2_pkt_hd &hd, - const ngtcp2_cid *odcid); + const ngtcp2_cid *odcid, + const uint8_t *token, size_t tokenlen); void add_connection_id(const ngtcp2_cid *cid, ClientHandler *handler); void remove_connection_id(const ngtcp2_cid *cid);