diff --git a/gennghttpxfun.py b/gennghttpxfun.py index 425ed74a..fecfafd5 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -170,6 +170,7 @@ OPTIONS = [ "no-verify-ocsp", "verify-client-tolerate-expired", "ignore-per-pattern-mruby-error", + "tls-postpone-early-data", ] LOGVARS = [ diff --git a/src/shrpx.cc b/src/shrpx.cc index be867a10..671adb65 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -2370,6 +2370,12 @@ SSL/TLS: HTTP/2. To use those cipher suites with HTTP/2, consider to use --client-no-http2-cipher-black-list option. But be aware its implications. + --tls-postpone-early-data + Postpone forwarding HTTP requests sent in early data, + including those sent in partially in it, until TLS + handshake finishes. This option must be used to + mitigate possible replay attack unless all backend + servers recognize "Early-Data" header field. HTTP/2: -c, --frontend-http2-max-concurrent-streams= @@ -3436,6 +3442,7 @@ int main(int argc, char **argv) { 160}, {SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR.c_str(), no_argument, &flag, 161}, + {SHRPX_OPT_TLS_POSTPONE_EARLY_DATA.c_str(), no_argument, &flag, 162}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -4207,6 +4214,11 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR, StringRef::from_lit("yes")); break; + case 162: + // --tls-postpone-early-data + cmdcfgs.emplace_back(SHRPX_OPT_TLS_POSTPONE_EARLY_DATA, + StringRef::from_lit("yes")); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 503007c8..e3675dc9 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -2040,6 +2040,11 @@ int option_lookup_token(const char *name, size_t namelen) { break; case 23: switch (name[22]) { + case 'a': + if (util::strieq_l("tls-postpone-early-dat", name, 22)) { + return SHRPX_OPTID_TLS_POSTPONE_EARLY_DATA; + } + break; case 'e': if (util::strieq_l("client-private-key-fil", name, 22)) { return SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE; @@ -3590,6 +3595,10 @@ int parse_config(Config *config, int optid, const StringRef &opt, case SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR: config->ignore_per_pattern_mruby_error = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_TLS_POSTPONE_EARLY_DATA: + config->tls.postpone_early_data = util::strieq_l("yes", optarg); + return 0; case SHRPX_OPTID_CONF: LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index d4cb4d9d..f2fc4511 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -347,6 +347,8 @@ constexpr auto SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED = StringRef::from_lit("verify-client-tolerate-expired"); constexpr auto SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR = StringRef::from_lit("ignore-per-pattern-mruby-error"); +constexpr auto SHRPX_OPT_TLS_POSTPONE_EARLY_DATA = + StringRef::from_lit("tls-postpone-early-data"); constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; @@ -656,6 +658,9 @@ struct TLSConfig { int max_proto_version; bool insecure; bool no_http2_cipher_black_list; + // true if forwarding requests included in TLS early data should be + // postponed until TLS handshake finishes. + bool postpone_early_data; }; // custom error page @@ -1116,6 +1121,7 @@ enum { SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD, SHRPX_OPTID_TLS_MAX_PROTO_VERSION, SHRPX_OPTID_TLS_MIN_PROTO_VERSION, + SHRPX_OPTID_TLS_POSTPONE_EARLY_DATA, SHRPX_OPTID_TLS_PROTO_LIST, SHRPX_OPTID_TLS_SCT_DIR, SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED, diff --git a/src/shrpx_connection.cc b/src/shrpx_connection.cc index 101435d4..3e5c67c3 100644 --- a/src/shrpx_connection.cc +++ b/src/shrpx_connection.cc @@ -396,6 +396,7 @@ int Connection::tls_handshake() { if (!tls.server_handshake || tls.early_data_finish) { rv = SSL_do_handshake(tls.ssl); } else { + auto &tlsconf = get_config()->tls; for (;;) { size_t nread; @@ -407,7 +408,8 @@ int Connection::tls_handshake() { // 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. - if ((tls.handshake_state == TLS_CONN_WRITE_STARTED || + if (!tlsconf.postpone_early_data && + (tls.handshake_state == TLS_CONN_WRITE_STARTED || tls.wbuf.rleft()) && tls.earlybuf.rleft()) { rv = 1; @@ -429,7 +431,8 @@ int Connection::tls_handshake() { } tls.early_data_finish = true; // The same reason stated above. - if ((tls.handshake_state == TLS_CONN_WRITE_STARTED || + if (!tlsconf.postpone_early_data && + (tls.handshake_state == TLS_CONN_WRITE_STARTED || tls.wbuf.rleft()) && tls.earlybuf.rleft()) { rv = 1;