From 8c6612d33820cad61d2d683c941ad559e9fedb65 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Wed, 10 May 2017 21:12:12 +0900 Subject: [PATCH] nghttpx: Implement TLSv1.3 0-RTT anti-replay with ClientHello cache --- gennghttpxfun.py | 4 ++ src/shrpx.cc | 74 +++++++++++++++++++++++ src/shrpx_config.cc | 42 ++++++++++++- src/shrpx_config.h | 29 +++++++++ src/shrpx_connection.cc | 70 +++++++++++++++++++--- src/shrpx_connection.h | 10 ++++ src/shrpx_connection_handler.cc | 41 +++++++++++-- src/shrpx_tls.cc | 101 ++++++++++++++++++++++++++++++++ src/shrpx_worker.cc | 14 +++++ src/shrpx_worker.h | 3 + 10 files changed, 375 insertions(+), 13 deletions(-) diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 7e391d21..9cd4fcc3 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -168,6 +168,10 @@ OPTIONS = [ "no-strip-incoming-x-forwarded-proto", "ocsp-startup", "no-verify-ocsp", + "tls-anti-replay-memcached", + "tls-anti-replay-memcached-cert-file", + "tls-anti-replay-memcached-private-key-file", + "tls-anti-replay-memcached-address-family", ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index c660b1f2..427891a9 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -1438,6 +1438,12 @@ void fill_default_config(Config *config) { memcachedconf.family = AF_UNSPEC; } + auto &anti_replayconf = tlsconf.anti_replay; + { + auto &memcachedconf = anti_replayconf.memcached; + memcachedconf.family = AF_UNSPEC; + } + ticketconf.cipher = EVP_aes_128_cbc(); } @@ -2284,6 +2290,25 @@ SSL/TLS: --tls-session-cache-memcached-private-key-file= Path to client private key for memcached connections to store session cache. + --tls-anti-replay-memcached=,[;tls] + Specify address of memcached server to store ClientHello + to avoid 0-RTT early data replay. This enables shared + storage between multiple nghttpx instances. Optionally, + memcached connection can be encrypted with TLS by + specifying "tls" parameter. + --tls-anti-replay-memcached-address-family=(auto|IPv4|IPv6) + Specify address family of memcached connections to store + ClientHello to avoid 0-RTT early data replay. If "auto" + is given, both IPv4 and IPv6 are considered. If "IPv4" + is given, only IPv4 address is considered. If "IPv6" is + given, only IPv6 address is considered. + Default: auto + --tls-anti-replay-memcached-cert-file= + Path to client certificate for memcached connections to + store ClientHello to avoid 0-RTT early data replay. + --tls-anti-replay-memcached-private-key-file= + Path to client private key for memcached connections to + store ClientHello to avoid 0-RTT early data replay. --tls-dyn-rec-warmup-threshold= Specify the threshold size for TLS dynamic record size behaviour. During a TLS session, after the threshold @@ -2995,6 +3020,26 @@ int process_options(Config *config, } } + { + auto &memcachedconf = tlsconf.anti_replay.memcached; + if (!memcachedconf.host.empty()) { + auto hostport = util::make_hostport(StringRef{memcachedconf.host}, + memcachedconf.port); + if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(), + memcachedconf.port, memcachedconf.family) == -1) { + LOG(FATAL) << "Resolving memcached address for TLS anti-replay failed: " + << hostport; + return -1; + } + LOG(NOTICE) << "Memcached address for TLS anti-replay: " << hostport + << " -> " << util::to_numeric_addr(&memcachedconf.addr); + if (memcachedconf.tls) { + LOG(NOTICE) << "Connection to memcached for TLS anti-replay will be " + "encrypted by TLS"; + } + } + } + if (config->rlimit_nofile) { struct rlimit lim = {static_cast(config->rlimit_nofile), static_cast(config->rlimit_nofile)}; @@ -3404,6 +3449,14 @@ int main(int argc, char **argv) { {SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO.c_str(), no_argument, &flag, 158}, {SHRPX_OPT_SINGLE_PROCESS.c_str(), no_argument, &flag, 159}, + {SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED.c_str(), required_argument, &flag, + 160}, + {SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_ADDRESS_FAMILY.c_str(), + required_argument, &flag, 161}, + {SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_CERT_FILE.c_str(), + required_argument, &flag, 162}, + {SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_PRIVATE_KEY_FILE.c_str(), + required_argument, &flag, 163}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -4165,6 +4218,27 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_PROCESS, StringRef::from_lit("yes")); break; + case 160: + // --tls-anti-replay-memcached + cmdcfgs.emplace_back(SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED, + StringRef{optarg}); + break; + case 161: + // --tls-anti-replay-memcached-address-family + cmdcfgs.emplace_back(SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 162: + // --tls-anti-replay-memcached-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_CERT_FILE, + StringRef{optarg}); + break; + case 163: + // --tls-anti-replay-memcached-private-key-file + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 04d89c34..2123de31 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -2072,6 +2072,11 @@ int option_lookup_token(const char *name, size_t namelen) { break; case 25: switch (name[24]) { + case 'd': + if (util::strieq_l("tls-anti-replay-memcache", name, 24)) { + return SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED; + } + break; case 'e': if (util::strieq_l("backend-http2-window-siz", name, 24)) { return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE; @@ -2255,6 +2260,9 @@ int option_lookup_token(const char *name, size_t namelen) { if (util::strieq_l("frontend-http2-optimize-window-siz", name, 34)) { return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE; } + if (util::strieq_l("tls-anti-replay-memcached-cert-fil", name, 34)) { + return SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_CERT_FILE; + } break; case 'o': if (util::strieq_l("no-strip-incoming-x-forwarded-prot", name, 34)) { @@ -2338,6 +2346,11 @@ int option_lookup_token(const char *name, size_t namelen) { return SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; } break; + case 'y': + if (util::strieq_l("tls-anti-replay-memcached-address-famil", name, 39)) { + return SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_ADDRESS_FAMILY; + } + break; } break; case 41: @@ -2364,6 +2377,12 @@ int option_lookup_token(const char *name, size_t namelen) { break; case 42: switch (name[41]) { + case 'e': + if (util::strieq_l("tls-anti-replay-memcached-private-key-fil", name, + 41)) { + return SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_PRIVATE_KEY_FILE; + } + break; case 'y': if (util::strieq_l("tls-session-cache-memcached-address-famil", name, 41)) { @@ -3153,7 +3172,8 @@ int parse_config(Config *config, int optid, const StringRef &opt, return 0; case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: - case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: { + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: + case SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED: { auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); auto src_params = StringRef{addr_end, std::end(optarg)}; @@ -3183,6 +3203,13 @@ int parse_config(Config *config, int optid, const StringRef &opt, memcachedconf.tls = params.tls; break; } + case SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED: { + auto &memcachedconf = config->tls.anti_replay.memcached; + memcachedconf.host = make_string_ref(config->balloc, StringRef{host}); + memcachedconf.port = port; + memcachedconf.tls = params.tls; + break; + } }; return 0; @@ -3330,6 +3357,16 @@ int parse_config(Config *config, int optid, const StringRef &opt, config->tls.ticket.memcached.private_key_file = make_string_ref(config->balloc, optarg); + return 0; + case SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_CERT_FILE: + config->tls.anti_replay.memcached.cert_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_PRIVATE_KEY_FILE: + config->tls.anti_replay.memcached.private_key_file = + make_string_ref(config->balloc, optarg); + return 0; case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY: return parse_address_family(&config->tls.ticket.memcached.family, opt, @@ -3337,6 +3374,9 @@ int parse_config(Config *config, int optid, const StringRef &opt, case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY: return parse_address_family(&config->tls.session_cache.memcached.family, opt, optarg); + case SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_ADDRESS_FAMILY: + return parse_address_family(&config->tls.anti_replay.memcached.family, opt, + optarg); case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY: return parse_address_family(&config->conn.downstream->family, opt, optarg); case SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS: diff --git a/src/shrpx_config.h b/src/shrpx_config.h index a69c1ce1..f00e10ba 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -343,6 +343,14 @@ constexpr auto SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO = StringRef::from_lit("no-strip-incoming-x-forwarded-proto"); constexpr auto SHRPX_OPT_OCSP_STARTUP = StringRef::from_lit("ocsp-startup"); constexpr auto SHRPX_OPT_NO_VERIFY_OCSP = StringRef::from_lit("no-verify-ocsp"); +constexpr auto SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED = + StringRef::from_lit("tls-anti-replay-memcached"); +constexpr auto SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_CERT_FILE = + StringRef::from_lit("tls-anti-replay-memcached-cert-file"); +constexpr auto SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_PRIVATE_KEY_FILE = + StringRef::from_lit("tls-anti-replay-memcached-private-key-file"); +constexpr auto SHRPX_OPT_TLS_ANTI_REPLAY_MEMCACHED_ADDRESS_FAMILY = + StringRef::from_lit("tls-anti-replay-memcached-address-family"); constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -577,6 +585,23 @@ struct TLSConfig { } memcached; } session_cache; + struct { + struct { + Address addr; + uint16_t port; + // Hostname of memcached server. This is also used as SNI field + // if TLS is enabled. + StringRef host; + // Client private key and certificate for authentication + StringRef private_key_file; + StringRef cert_file; + // Address family of memcached connection. One of either + // AF_INET, AF_INET6 or AF_UNSPEC. + int family; + bool tls; + } memcached; + } anti_replay; + // Dynamic record sizing configurations struct { size_t warmup_threshold; @@ -1097,6 +1122,10 @@ enum { SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR, SHRPX_OPTID_SUBCERT, SHRPX_OPTID_SYSLOG_FACILITY, + SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED, + SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_ADDRESS_FAMILY, + SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_CERT_FILE, + SHRPX_OPTID_TLS_ANTI_REPLAY_MEMCACHED_PRIVATE_KEY_FILE, SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT, SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD, SHRPX_OPTID_TLS_MAX_PROTO_VERSION, diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc index abc9f7c8..162ca266 100644 --- a/src/shrpx_connection.cc +++ b/src/shrpx_connection.cc @@ -38,7 +38,6 @@ #include "shrpx_log.h" #include "memchunk.h" #include "util.h" -#include "ssl_compat.h" using namespace nghttp2; @@ -93,7 +92,15 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl, } } -Connection::~Connection() { disconnect(); } +Connection::~Connection() { + disconnect(); + +#if OPENSSL_1_1_1_API + if (tls.ch_md_ctx) { + EVP_MD_CTX_free(tls.ch_md_ctx); + } +#endif // OPENSSL_1_1_1_API +} void Connection::disconnect() { if (tls.ssl) { @@ -111,10 +118,21 @@ void Connection::disconnect() { tls.cached_session_lookup_req = nullptr; } + if (tls.anti_replay_req) { + tls.anti_replay_req->canceled = true; + tls.anti_replay_req = nullptr; + } + SSL_shutdown(tls.ssl); SSL_free(tls.ssl); tls.ssl = nullptr; +#if OPENSSL_1_1_1_API + if (tls.ch_md_ctx) { + EVP_MD_CTX_reset(tls.ch_md_ctx); + } +#endif // OPENSSL_1_1_1_API + tls.wbuf.reset(); tls.rbuf.reset(); tls.last_write_idle = 0.; @@ -126,6 +144,7 @@ void Connection::disconnect() { tls.reneg_started = false; tls.sct_requested = false; tls.early_data_finish = false; + tls.early_cb_called = false; } if (fd != -1) { @@ -152,6 +171,14 @@ void Connection::prepare_client_handshake() { void Connection::prepare_server_handshake() { SSL_set_accept_state(tls.ssl); tls.server_handshake = true; + +#if OPENSSL_1_1_1_API + if (!tls.ch_md_ctx) { + tls.ch_md_ctx = EVP_MD_CTX_new(); + } + + EVP_DigestInit_ex(tls.ch_md_ctx, EVP_sha256(), nullptr); +#endif // OPENSSL_1_1_1_API } // BIO implementation is inspired by openldap implementation: @@ -225,7 +252,19 @@ int shrpx_bio_read(BIO *b, char *buf, int len) { return -1; } - return rbuf.remove(buf, len); + len = rbuf.remove(buf, len); + + if (conn->tls.early_cb_called) { + return len; + } + +#if OPENSSL_1_1_1_API + if (EVP_DigestUpdate(conn->tls.ch_md_ctx, buf, len) == 0) { + return -1; + } +#endif // OPENSSL_1_1_1_API + + return len; } } // namespace @@ -355,6 +394,7 @@ int Connection::tls_handshake() { switch (tls.handshake_state) { case TLS_CONN_WAIT_FOR_SESSION_CACHE: + case TLS_CONN_WAIT_FOR_ANTI_REPLAY: return SHRPX_ERR_INPROGRESS; case TLS_CONN_GOT_SESSION_CACHE: { // Use the same trick invented by @kazuho in h2o project. @@ -401,15 +441,27 @@ int Connection::tls_handshake() { rv = SSL_read_early_data(tls.ssl, buf.data(), buf.size(), &nread); if (rv == SSL_READ_EARLY_DATA_ERROR) { + if (SSL_get_error(tls.ssl, rv) == SSL_ERROR_WANT_EARLY) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) + << "tls: early_cb returns negative return value; handshake " + "interrupted"; + } + break; + } + // If we have early data, and server sends ServerHello, assume // that handshake is completed in server side, and start // processing request. If we don't exit handshake code here, // server waits for EndOfEarlyData and Finished message from // client, which voids the purpose of 0-RTT data. The left // over of handshake is done through write_tls or read_tls. - rv = (tls.handshake_state == TLS_CONN_WRITE_STARTED || - tls.wbuf.rleft()) && - tls.earlybuf.rleft(); + if ((tls.handshake_state == TLS_CONN_WRITE_STARTED || + tls.wbuf.rleft()) && + tls.earlybuf.rleft()) { + rv = 1; + } + break; } @@ -454,6 +506,9 @@ int Connection::tls_handshake() { } break; case SSL_ERROR_WANT_WRITE: +#if OPENSSL_1_1_1_API + case SSL_ERROR_WANT_EARLY: +#endif // OPENSSL_1_1_1_API break; case SSL_ERROR_SSL: if (LOG_ENABLED(INFO)) { @@ -469,7 +524,8 @@ int Connection::tls_handshake() { } } - if (tls.handshake_state == TLS_CONN_WAIT_FOR_SESSION_CACHE) { + if (tls.handshake_state == TLS_CONN_WAIT_FOR_SESSION_CACHE || + tls.handshake_state == TLS_CONN_WAIT_FOR_ANTI_REPLAY) { if (LOG_ENABLED(INFO)) { LOG(INFO) << "tls: handshake is still in progress"; } diff --git a/src/shrpx_connection.h b/src/shrpx_connection.h index d9ca5c0f..d63d3b1f 100644 --- a/src/shrpx_connection.h +++ b/src/shrpx_connection.h @@ -32,10 +32,12 @@ #include #include +#include #include "shrpx_rate_limit.h" #include "shrpx_error.h" #include "memchunk.h" +#include "ssl_compat.h" namespace shrpx { @@ -50,6 +52,7 @@ enum { TLS_CONN_WAIT_FOR_SESSION_CACHE, TLS_CONN_GOT_SESSION_CACHE, TLS_CONN_CANCEL_SESSION_CACHE, + TLS_CONN_WAIT_FOR_ANTI_REPLAY, TLS_CONN_WRITE_STARTED, }; @@ -62,6 +65,11 @@ struct TLSConnection { SSL_SESSION *cached_session; MemcachedRequest *cached_session_lookup_req; tls::TLSSessionCache *client_session_cache; +#if OPENSSL_1_1_1_API + // Message digest context to calculate ClientHello for anti-replay. + EVP_MD_CTX *ch_md_ctx; +#endif // !OPENSSL_1_1_1_API + MemcachedRequest *anti_replay_req; ev_tstamp last_write_idle; size_t warmup_writelen; // length passed to SSL_write and SSL_read last time. This is @@ -82,6 +90,8 @@ struct TLSConnection { // This value is also true if this is client side connection for // convenience. bool early_data_finish; + // true if early_cb gets called. + bool early_cb_called; }; struct TCPHint { diff --git a/src/shrpx_connection_handler.cc b/src/shrpx_connection_handler.cc index ca84d53c..3533176c 100644 --- a/src/shrpx_connection_handler.cc +++ b/src/shrpx_connection_handler.cc @@ -235,9 +235,24 @@ int ConnectionHandler::create_single_worker() { } } + SSL_CTX *anti_replay_ssl_ctx = nullptr; + { + auto &memcachedconf = config->tls.anti_replay.memcached; + + if (memcachedconf.tls) { + anti_replay_ssl_ctx = tls::create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + nb_.get(), +#endif // HAVE_NEVERBLEED + tlsconf.cacert, memcachedconf.cert_file, + memcachedconf.private_key_file, nullptr); + all_ssl_ctx_.push_back(anti_replay_ssl_ctx); + } + } + single_worker_ = make_unique( - loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), - ticket_keys_, this, config->conn.downstream); + loop_, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, anti_replay_ssl_ctx, + cert_tree_.get(), ticket_keys_, this, config->conn.downstream); #ifdef HAVE_MRUBY if (single_worker_->create_mruby_context() != 0) { return -1; @@ -293,12 +308,28 @@ int ConnectionHandler::create_worker_thread(size_t num) { } } + SSL_CTX *anti_replay_ssl_ctx = nullptr; + { + auto &memcachedconf = config->tls.anti_replay.memcached; + + if (memcachedconf.tls) { + anti_replay_ssl_ctx = tls::create_ssl_client_context( +#ifdef HAVE_NEVERBLEED + nb_.get(), +#endif // HAVE_NEVERBLEED + tlsconf.cacert, memcachedconf.cert_file, + memcachedconf.private_key_file, nullptr); + all_ssl_ctx_.push_back(anti_replay_ssl_ctx); + } + } + for (size_t i = 0; i < num; ++i) { auto loop = ev_loop_new(config->ev_loop_flags); - auto worker = make_unique( - loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(), - ticket_keys_, this, config->conn.downstream); + auto worker = + make_unique(loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, + anti_replay_ssl_ctx, cert_tree_.get(), ticket_keys_, + this, config->conn.downstream); #ifdef HAVE_MRUBY if (worker->create_mruby_context() != 0) { return -1; diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index 3ec1b47c..3c7fb0df 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -534,6 +534,103 @@ void info_callback(const SSL *ssl, int where, int ret) { } } // namespace +#if OPENSSL_1_1_1_API +constexpr auto MEMCACHED_ANTI_REPLY_KEY_PREFIX = + StringRef::from_lit("nghttpx:anti-reply:"); + +namespace { +int early_cb(SSL *ssl, int *al, void *arg) { + auto conn = static_cast(SSL_get_app_data(ssl)); + if (conn->tls.early_cb_called) { + return 1; + } + + conn->tls.early_cb_called = true; + + const unsigned char *ext; + size_t extlen; + + if (!SSL_early_get0_ext(conn->tls.ssl, TLSEXT_TYPE_early_data, &ext, + &extlen)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "early_data extension does not exist"; + } + return 1; + } + + if (!SSL_early_get0_ext(conn->tls.ssl, TLSEXT_TYPE_psk, &ext, &extlen)) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "pre_shared_key extension does not exist"; + } + return 1; + } + + std::array md; + unsigned int mdlen; + if (EVP_DigestFinal_ex(conn->tls.ch_md_ctx, md.data(), &mdlen) == 0) { + LOG(ERROR) << "EVP_DigestFinal_ex failed"; + return 0; + } + assert(md.size() == mdlen); + + auto handler = static_cast(conn->data); + auto worker = handler->get_worker(); + auto dispatcher = worker->get_anti_replay_memcached_dispatcher(); + auto &balloc = handler->get_block_allocator(); + + auto &tlsconf = get_config()->tls; + + auto hex_md = + util::format_hex(balloc, StringRef{std::begin(md), std::end(md)}); + + if (tlsconf.anti_replay.memcached.host.empty()) { + return 1; + } + + auto req = make_unique(); + req->op = MEMCACHED_OP_ADD; + req->key = MEMCACHED_ANTI_REPLY_KEY_PREFIX.str(); + req->key += hex_md; + + // TODO No value at the moment + + // Set the same timeout value for session with the hope that + // OpenSSL library invalidates the outdated ticket. + req->expiry = tlsconf.session_timeout.count(); + req->cb = [conn](MemcachedRequest *req, MemcachedResult res) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Memcached: ClientHello anti-replay registration done. key=" + << req->key << ", status_code=" << res.status_code; + } + + // We might stop reading, so start it again + conn->rlimit.startw(); + ev_timer_again(conn->loop, &conn->rt); + + conn->wlimit.startw(); + ev_timer_again(conn->loop, &conn->wt); + + conn->tls.anti_replay_req = nullptr; + + if (res.status_code != 0) { + // If we cannot add key/value, just disable 0-RTT early data. + // Note that memcached atomically adds key/value. + conn->tls.early_data_finish = true; + } + + conn->tls.handshake_state = TLS_CONN_NORMAL; + }; + + conn->tls.handshake_state = TLS_CONN_WAIT_FOR_ANTI_REPLAY; + conn->tls.anti_replay_req = req.get(); + + dispatcher->add_request(std::move(req)); + + return -1; +} +} // namespace +#endif // OPENSSL_1_1_1_API + #if OPENSSL_VERSION_NUMBER >= 0x10002000L namespace { int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, @@ -920,6 +1017,10 @@ SSL_CTX *create_ssl_context(const char *private_key_file, const char *cert_file, #endif // OPENSSL_IS_BORINGSSL SSL_CTX_set_info_callback(ssl_ctx, info_callback); +#if OPENSSL_1_1_1_API + SSL_CTX_set_early_cb(ssl_ctx, early_cb, nullptr); +#endif // OPENSSL_1_1_1_API + #ifdef OPENSSL_IS_BORINGSSL SSL_CTX_set_early_data_enabled(ssl_ctx, 1); #endif // OPENSSL_IS_BORINGSSL diff --git a/src/shrpx_worker.cc b/src/shrpx_worker.cc index 3c6b270c..541c4818 100644 --- a/src/shrpx_worker.cc +++ b/src/shrpx_worker.cc @@ -118,6 +118,7 @@ bool match_shared_downstream_addr( Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, SSL_CTX *tls_session_cache_memcached_ssl_ctx, + SSL_CTX *tls_anti_replay_memcached_ssl_ctx, tls::CertLookupTree *cert_tree, const std::shared_ptr &ticket_keys, ConnectionHandler *conn_handler, @@ -153,6 +154,15 @@ Worker::Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, StringRef{session_cacheconf.memcached.host}, &mcpool_, randgen_); } + auto &anti_replayconf = get_config()->tls.anti_replay; + + if (!anti_replayconf.memcached.host.empty()) { + anti_replay_memcached_dispatcher_ = make_unique( + &anti_replayconf.memcached.addr, loop, + tls_anti_replay_memcached_ssl_ctx, anti_replayconf.memcached.host, + &mcpool_, randgen_); + } + replace_downstream_config(std::move(downstreamconf)); } @@ -474,6 +484,10 @@ MemcachedDispatcher *Worker::get_session_cache_memcached_dispatcher() { return session_cache_memcached_dispatcher_.get(); } +MemcachedDispatcher *Worker::get_anti_replay_memcached_dispatcher() const { + return anti_replay_memcached_dispatcher_.get(); +} + std::mt19937 &Worker::get_randgen() { return randgen_; } #ifdef HAVE_MRUBY diff --git a/src/shrpx_worker.h b/src/shrpx_worker.h index c53376db..111cef16 100644 --- a/src/shrpx_worker.h +++ b/src/shrpx_worker.h @@ -221,6 +221,7 @@ class Worker { public: Worker(struct ev_loop *loop, SSL_CTX *sv_ssl_ctx, SSL_CTX *cl_ssl_ctx, SSL_CTX *tls_session_cache_memcached_ssl_ctx, + SSL_CTX *tls_anti_replay_memcached_ssl_ctx, tls::CertLookupTree *cert_tree, const std::shared_ptr &ticket_keys, ConnectionHandler *conn_handler, @@ -250,6 +251,7 @@ public: void schedule_clear_mcpool(); MemcachedDispatcher *get_session_cache_memcached_dispatcher(); + MemcachedDispatcher *get_anti_replay_memcached_dispatcher() const; std::mt19937 &get_randgen(); @@ -289,6 +291,7 @@ private: std::shared_ptr downstreamconf_; std::unique_ptr session_cache_memcached_dispatcher_; + std::unique_ptr anti_replay_memcached_dispatcher_; #ifdef HAVE_MRUBY std::unique_ptr mruby_ctx_; #endif // HAVE_MRUBY