nghttpx: Send NEW_TOKEN and very token from client

This commit is contained in:
Tatsuhiro Tsujikawa 2021-08-26 18:01:53 +09:00
parent 7a5082e8c4
commit a0066a1ccf
6 changed files with 251 additions and 16 deletions

View File

@ -401,6 +401,41 @@ int stream_stop_sending(ngtcp2_conn *conn, int64_t stream_id,
} }
} // namespace } // namespace
namespace {
int handshake_completed(ngtcp2_conn *conn, void *user_data) {
auto upstream = static_cast<Http3Upstream *>(user_data);
if (upstream->handshake_completed() != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
return 0;
}
} // namespace
int Http3Upstream::handshake_completed() {
std::array<uint8_t, SHRPX_QUIC_MAX_TOKENLEN> 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, int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const Address &local_addr,
const ngtcp2_pkt_hd &initial_hd, const ngtcp2_pkt_hd &initial_hd,
@ -414,7 +449,7 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
nullptr, // client_initial nullptr, // client_initial
ngtcp2_crypto_recv_client_initial_cb, ngtcp2_crypto_recv_client_initial_cb,
ngtcp2_crypto_recv_crypto_data_cb, ngtcp2_crypto_recv_crypto_data_cb,
nullptr, // handshake_completed shrpx::handshake_completed,
nullptr, // recv_version_negotiation nullptr, // recv_version_negotiation
ngtcp2_crypto_encrypt_cb, ngtcp2_crypto_encrypt_cb,
ngtcp2_crypto_decrypt_cb, ngtcp2_crypto_decrypt_cb,

View File

@ -143,6 +143,7 @@ public:
int http_send_stop_sending(int64_t stream_id, uint64_t app_error_code); int http_send_stop_sending(int64_t stream_id, uint64_t app_error_code);
int http_recv_data(Downstream *downstream, const uint8_t *data, int http_recv_data(Downstream *downstream, const uint8_t *data,
size_t datalen); size_t datalen);
int handshake_completed();
private: private:
ClientHandler *handler_; ClientHandler *handler_;

View File

@ -403,4 +403,182 @@ int verify_retry_token(ngtcp2_cid *odcid, const uint8_t *token, size_t tokenlen,
return 0; 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<const uint8_t *>(
&reinterpret_cast<const sockaddr_in *>(sa)->sin_addr);
addrlen = sizeof(reinterpret_cast<const sockaddr_in *>(sa)->sin_addr);
break;
case AF_INET6:
addr = reinterpret_cast<const uint8_t *>(
&reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr);
addrlen = sizeof(reinterpret_cast<const sockaddr_in6 *>(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<uint8_t, 8> plaintext;
uint64_t t = std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
std::array<uint8_t, 256> 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<uint8_t *>(&t), sizeof(t), p);
std::array<uint8_t, SHRPX_QUIC_TOKEN_RAND_DATALEN> rand_data;
std::array<uint8_t, 32> 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_CIPHER *>(EVP_aes_128_gcm()));
ngtcp2_crypto_md md;
ngtcp2_crypto_md_init(&md, const_cast<EVP_MD *>(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<char, NI_MAXHOST> host;
std::array<char, NI_MAXSERV> 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<uint8_t, 256> 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<uint8_t, 32> key, iv;
auto keylen = key.size();
auto ivlen = iv.size();
ngtcp2_crypto_aead aead;
ngtcp2_crypto_aead_init(&aead, const_cast<EVP_CIPHER *>(EVP_aes_128_gcm()));
ngtcp2_crypto_md md;
ngtcp2_crypto_md_init(&md, const_cast<EVP_MD *>(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<uint8_t, SHRPX_QUIC_MAX_TOKENLEN> 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::nanoseconds>(
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 } // namespace shrpx

View File

@ -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, const ngtcp2_cid *dcid, const sockaddr *sa,
socklen_t salen, const uint8_t *token_secret); 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 } // namespace shrpx
#endif // SHRPX_QUIC_H #endif // SHRPX_QUIC_H

View File

@ -85,6 +85,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
ngtcp2_pkt_hd hd; ngtcp2_pkt_hd hd;
ngtcp2_cid odcid, *podcid = nullptr; ngtcp2_cid odcid, *podcid = nullptr;
const uint8_t *token = nullptr;
size_t tokenlen = 0;
switch (ngtcp2_accept(&hd, data, datalen)) { switch (ngtcp2_accept(&hd, data, datalen)) {
case 0: { case 0: {
@ -95,12 +97,31 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
auto &quic_secret = worker_->get_quic_secret(); auto &quic_secret = worker_->get_quic_secret();
auto &secret = quic_secret->token_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, if (verify_retry_token(&odcid, hd.token.base, hd.token.len, &hd.dcid,
&remote_addr.su.sa, remote_addr.len, &remote_addr.su.sa, remote_addr.len,
secret.data()) == 0) { secret.data()) != 0) {
podcid = &odcid; 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; break;
@ -139,7 +160,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
return 0; 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) { if (handler == nullptr) {
return 0; return 0;
} }
@ -159,8 +181,8 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
ClientHandler *QUICConnectionHandler::handle_new_connection( ClientHandler *QUICConnectionHandler::handle_new_connection(
const UpstreamAddr *faddr, const Address &remote_addr, const UpstreamAddr *faddr, const Address &remote_addr,
const Address &local_addr, const ngtcp2_pkt_hd &hd, const Address &local_addr, const ngtcp2_pkt_hd &hd, const ngtcp2_cid *odcid,
const ngtcp2_cid *odcid) { const uint8_t *token, size_t tokenlen) {
std::array<char, NI_MAXHOST> host; std::array<char, NI_MAXHOST> host;
std::array<char, NI_MAXSERV> service; std::array<char, NI_MAXSERV> service;
int rv; int rv;
@ -198,14 +220,6 @@ ClientHandler *QUICConnectionHandler::handle_new_connection(
worker_, faddr->fd, ssl, StringRef{host.data()}, worker_, faddr->fd, ssl, StringRef{host.data()},
StringRef{service.data()}, remote_addr.su.sa.sa_family, faddr); 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<Http3Upstream>(handler.get()); auto upstream = std::make_unique<Http3Upstream>(handler.get());
if (upstream->init(faddr, remote_addr, local_addr, hd, odcid, token, if (upstream->init(faddr, remote_addr, local_addr, hd, odcid, token,
tokenlen) != 0) { tokenlen) != 0) {

View File

@ -75,7 +75,8 @@ public:
const Address &remote_addr, const Address &remote_addr,
const Address &local_addr, const Address &local_addr,
const ngtcp2_pkt_hd &hd, 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 add_connection_id(const ngtcp2_cid *cid, ClientHandler *handler);
void remove_connection_id(const ngtcp2_cid *cid); void remove_connection_id(const ngtcp2_cid *cid);