Commit fd060eb9 authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

nghttpx: Connection ID encryption

parent 1feeda45
This diff is collapsed.
......@@ -192,6 +192,7 @@ OPTIONS = [
"frontend-quic-qlog-dir",
"frontend-quic-require-token",
"frontend-quic-congestion-controller",
"frontend-quic-connection-id-encryption-key",
]
LOGVARS = [
......
......@@ -1856,6 +1856,14 @@ void fill_default_config(Config *config) {
bpfconf.prog_file = StringRef::from_lit(PKGLIBDIR "/reuseport_kern.o");
upstreamconf.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
// TODO Not really nice to generate random key here, but fine for
// now.
if (RAND_bytes(upstreamconf.cid_encryption_key.data(),
upstreamconf.cid_encryption_key.size()) != 1) {
assert(0);
abort();
}
}
auto &http3conf = config->http3;
......@@ -3237,6 +3245,14 @@ HTTP/3 and QUIC:
? "cubic"
: "bbr")
<< R"(
--frontend-quic-connection-id-encryption-key=<HEXSTRING>
Specify Connection ID encryption key. The encryption
key must be 16 bytes, and it must be encoded in hex
string (which is 32 bytes long). If this option is
omitted, new key is generated. In order to survive QUIC
connection in a configuration reload event, old and new
configuration must have this option and share the same
key.
--no-quic-bpf
Disable eBPF.
--frontend-http3-window-size=<SIZE>
......@@ -4035,6 +4051,8 @@ int main(int argc, char **argv) {
182},
{SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER.c_str(),
required_argument, &flag, 183},
{SHRPX_OPT_FRONTEND_QUIC_CONNECTION_ID_ENCRYPTION_KEY.c_str(),
required_argument, &flag, 184},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
......@@ -4912,6 +4930,12 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER,
StringRef{optarg});
break;
case 184:
// --frontend-quic-connection-id-encryption-key
cmdcfgs.emplace_back(
SHRPX_OPT_FRONTEND_QUIC_CONNECTION_ID_ENCRYPTION_KEY,
StringRef{optarg});
break;
default:
break;
}
......
......@@ -2684,6 +2684,10 @@ int option_lookup_token(const char *name, size_t namelen) {
case 42:
switch (name[41]) {
case 'y':
if (util::strieq_l("frontend-quic-connection-id-encryption-ke", name,
41)) {
return SHRPX_OPTID_FRONTEND_QUIC_CONNECTION_ID_ENCRYPTION_KEY;
}
if (util::strieq_l("tls-session-cache-memcached-address-famil", name,
41)) {
return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY;
......@@ -4013,6 +4017,18 @@ int parse_config(Config *config, int optid, const StringRef &opt,
}
#endif // ENABLE_HTTP3
return 0;
case SHRPX_OPTID_FRONTEND_QUIC_CONNECTION_ID_ENCRYPTION_KEY:
#ifdef ENABLE_HTTP3
if (optarg.size() != config->quic.upstream.cid_encryption_key.size() * 2 ||
!util::is_hex_string(optarg)) {
LOG(ERROR) << opt << ": must be a hex-string";
return -1;
}
util::decode_hex(std::begin(config->quic.upstream.cid_encryption_key),
optarg);
#endif // ENABLE_HTTP3
return 0;
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";
......
......@@ -391,6 +391,8 @@ constexpr auto SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN =
StringRef::from_lit("frontend-quic-require-token");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER =
StringRef::from_lit("frontend-quic-congestion-controller");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_CONNECTION_ID_ENCRYPTION_KEY =
StringRef::from_lit("frontend-quic-connection-id-encryption-key");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
......@@ -761,6 +763,7 @@ struct QUICConfig {
ngtcp2_cc_algo congestion_controller;
bool early_data;
bool require_token;
std::array<uint8_t, SHRPX_QUIC_CID_ENCRYPTION_KEYLEN> cid_encryption_key;
} upstream;
struct {
StringRef prog_file;
......@@ -1214,6 +1217,7 @@ enum {
SHRPX_OPTID_FRONTEND_MAX_REQUESTS,
SHRPX_OPTID_FRONTEND_NO_TLS,
SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER,
SHRPX_OPTID_FRONTEND_QUIC_CONNECTION_ID_ENCRYPTION_KEY,
SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG,
SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA,
SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT,
......
......@@ -1265,8 +1265,8 @@ int ConnectionHandler::quic_ipc_read() {
return -1;
}
if (dcidlen < SHRPX_QUIC_CID_PREFIXLEN) {
LOG(ERROR) << "DCID is too short";
if (dcidlen != SHRPX_QUIC_SCIDLEN) {
LOG(ERROR) << "DCID length is invalid";
return -1;
}
......@@ -1287,8 +1287,20 @@ int ConnectionHandler::quic_ipc_read() {
return 0;
}
auto config = get_config();
auto &quicconf = config->quic;
std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
if (decrypt_quic_connection_id(decrypted_dcid.data(), dcid,
quicconf.upstream.cid_encryption_key.data()) !=
0) {
return -1;
}
for (auto &worker : workers_) {
if (!std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN,
if (!std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker->get_cid_prefix())) {
continue;
}
......
......@@ -216,7 +216,12 @@ int get_new_connection_id(ngtcp2_conn *conn, ngtcp2_cid *cid, uint8_t *token,
auto handler = upstream->get_client_handler();
auto worker = handler->get_worker();
if (generate_quic_connection_id(cid, cidlen, worker->get_cid_prefix()) != 0) {
auto config = get_config();
auto &quicconf = config->quic;
if (generate_encrypted_quic_connection_id(
cid, cidlen, worker->get_cid_prefix(),
quicconf.upstream.cid_encryption_key.data()) != 0) {
return NGTCP2_ERR_CALLBACK_FAILURE;
}
......@@ -546,17 +551,18 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
shrpx::stream_stop_sending,
};
auto config = get_config();
auto &quicconf = config->quic;
auto &http3conf = config->http3;
ngtcp2_cid scid;
if (generate_quic_connection_id(&scid, SHRPX_QUIC_SCIDLEN,
worker->get_cid_prefix()) != 0) {
if (generate_encrypted_quic_connection_id(
&scid, SHRPX_QUIC_SCIDLEN, worker->get_cid_prefix(),
quicconf.upstream.cid_encryption_key.data()) != 0) {
return -1;
}
auto config = get_config();
auto &quicconf = config->quic;
auto &http3conf = config->http3;
ngtcp2_settings settings;
ngtcp2_settings_default(&settings);
if (quicconf.upstream.debug.log) {
......
......@@ -155,8 +155,9 @@ int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen) {
return 0;
}
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen,
const uint8_t *cid_prefix) {
int generate_encrypted_quic_connection_id(ngtcp2_cid *cid, size_t cidlen,
const uint8_t *cid_prefix,
const uint8_t *key) {
assert(cidlen > SHRPX_QUIC_CID_PREFIXLEN);
auto p = std::copy_n(cid_prefix, SHRPX_QUIC_CID_PREFIXLEN, cid->data);
......@@ -167,6 +168,48 @@ int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen,
cid->datalen = cidlen;
return encrypt_quic_connection_id(cid->data, cid->data, key);
}
int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key) {
auto ctx = EVP_CIPHER_CTX_new();
auto d = defer(EVP_CIPHER_CTX_free, ctx);
if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
int len;
if (!EVP_EncryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
!EVP_EncryptFinal_ex(ctx, dest + len, &len)) {
return -1;
}
return 0;
}
int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key) {
auto ctx = EVP_CIPHER_CTX_new();
auto d = defer(EVP_CIPHER_CTX_free, ctx);
if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), nullptr, key, nullptr)) {
return -1;
}
EVP_CIPHER_CTX_set_padding(ctx, 0);
int len;
if (!EVP_DecryptUpdate(ctx, dest, &len, src, SHRPX_QUIC_DECRYPTED_DCIDLEN) ||
!EVP_DecryptFinal_ex(ctx, dest + len, &len)) {
return -1;
}
return 0;
}
......
......@@ -59,6 +59,8 @@ struct UpstreamAddr;
constexpr size_t SHRPX_QUIC_SCIDLEN = 20;
constexpr size_t SHRPX_QUIC_CID_PREFIXLEN = 8;
constexpr size_t SHRPX_QUIC_DECRYPTED_DCIDLEN = 16;
constexpr size_t SHRPX_QUIC_CID_ENCRYPTION_KEYLEN = 16;
constexpr size_t SHRPX_QUIC_MAX_UDP_PAYLOAD_SIZE = 1472;
constexpr size_t SHRPX_QUIC_STATELESS_RESET_SECRETLEN = 32;
constexpr size_t SHRPX_QUIC_TOKEN_SECRETLEN = 32;
......@@ -74,8 +76,15 @@ int quic_send_packet(const UpstreamAddr *faddr, const sockaddr *remote_sa,
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen);
int generate_quic_connection_id(ngtcp2_cid *cid, size_t cidlen,
const uint8_t *cid_prefix);
int generate_encrypted_quic_connection_id(ngtcp2_cid *cid, size_t cidlen,
const uint8_t *cid_prefix,
const uint8_t *key);
int encrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key);
int decrypt_quic_connection_id(uint8_t *dest, const uint8_t *src,
const uint8_t *key);
int generate_quic_stateless_reset_token(uint8_t *token, const ngtcp2_cid *cid,
const uint8_t *secret,
......
......@@ -90,21 +90,34 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
ClientHandler *handler;
auto &quicconf = config->quic;
auto it = connections_.find(dcid_key);
if (it == std::end(connections_)) {
if (!std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN,
worker_->get_cid_prefix())) {
auto quic_lwp =
conn_handler->match_quic_lingering_worker_process_cid_prefix(dcid,
dcidlen);
if (quic_lwp) {
if (conn_handler->forward_quic_packet_to_lingering_worker_process(
quic_lwp, remote_addr, local_addr, data, datalen) == 0) {
return 0;
}
std::array<uint8_t, SHRPX_QUIC_DECRYPTED_DCIDLEN> decrypted_dcid;
if (dcidlen == SHRPX_QUIC_SCIDLEN) {
if (decrypt_quic_connection_id(
decrypted_dcid.data(), dcid,
quicconf.upstream.cid_encryption_key.data()) != 0) {
return 0;
}
if (!std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker_->get_cid_prefix())) {
auto quic_lwp =
conn_handler->match_quic_lingering_worker_process_cid_prefix(
decrypted_dcid.data(), decrypted_dcid.size());
if (quic_lwp) {
if (conn_handler->forward_quic_packet_to_lingering_worker_process(
quic_lwp, remote_addr, local_addr, data, datalen) == 0) {
return 0;
}
return 0;
}
}
}
auto it = close_waits_.find(dcid_key);
......@@ -134,14 +147,14 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
const uint8_t *token = nullptr;
size_t tokenlen = 0;
auto &quicconf = config->quic;
switch (ngtcp2_accept(&hd, data, datalen)) {
case 0: {
// If we get Initial and it has the CID prefix of this worker, it
// is likely that client is intentionally use the our prefix.
// Just drop it.
if (std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN,
if (dcidlen == SHRPX_QUIC_SCIDLEN &&
std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker_->get_cid_prefix())) {
return 0;
}
......@@ -237,11 +250,13 @@ int QUICConnectionHandler::handle_packet(const UpstreamAddr *faddr,
return 0;
default:
if (!config->single_thread && !(data[0] & 0x80) &&
dcidlen > SHRPX_QUIC_CID_PREFIXLEN &&
!std::equal(dcid, dcid + SHRPX_QUIC_CID_PREFIXLEN,
dcidlen == SHRPX_QUIC_SCIDLEN &&
!std::equal(std::begin(decrypted_dcid),
std::begin(decrypted_dcid) + SHRPX_QUIC_CID_PREFIXLEN,
worker_->get_cid_prefix())) {
if (conn_handler->forward_quic_packet(faddr, remote_addr, local_addr,
dcid, data, datalen) == 0) {
decrypted_dcid.data(), data,
datalen) == 0) {
return 0;
}
}
......
......@@ -923,7 +923,7 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
}
constexpr uint32_t zero = 0;
uint32_t num_socks = config->num_worker;
uint64_t num_socks = config->num_worker;
if (bpf_map_update_elem(bpf_map__fd(sk_info), &zero, &num_socks,
BPF_ANY) != 0) {
......@@ -933,6 +933,29 @@ int Worker::create_quic_server_socket(UpstreamAddr &faddr) {
return -1;
}
auto &quicconf = config->quic;
constexpr uint32_t key_high_idx = 1;
constexpr uint32_t key_low_idx = 2;
if (bpf_map_update_elem(bpf_map__fd(sk_info), &key_high_idx,
quicconf.upstream.cid_encryption_key.data(),
BPF_ANY) != 0) {
LOG(FATAL) << "Failed to update key_high_idx sk_info: "
<< xsi_strerror(errno, errbuf.data(), errbuf.size());
close(fd);
return -1;
}
if (bpf_map_update_elem(bpf_map__fd(sk_info), &key_low_idx,
quicconf.upstream.cid_encryption_key.data() + 8,
BPF_ANY) != 0) {
LOG(FATAL) << "Failed to update key_low_idx sk_info: "
<< xsi_strerror(errno, errbuf.data(), errbuf.size());
close(fd);
return -1;
}
auto prog_fd = bpf_program__fd(prog);
if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &prog_fd,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment