Compare commits

...

7 Commits

Author SHA1 Message Date
Tatsuhiro Tsujikawa 66eba46c8e fixup! nghttpx: Send nghttpx-0rtt-uniq header if request is replayable 2017-11-26 10:28:21 +09:00
Tatsuhiro Tsujikawa abcdca91ba nghttpx: Postpone early data processing if CH replay detected 2017-11-26 10:28:21 +09:00
Tatsuhiro Tsujikawa 5e59577e93 nghttpx: Send nghttpx-0rtt-uniq header if request is replayable
The incoming nghttpx-0rtt-uniq header fields from inbound client are
stripped by default.  Use --no-strip-incoming-nghttpx-0rtt-uniq in
order not to strip them.
2017-11-26 10:28:21 +09:00
Tatsuhiro Tsujikawa 8c6612d338 nghttpx: Implement TLSv1.3 0-RTT anti-replay with ClientHello cache 2017-11-26 10:28:21 +09:00
Tatsuhiro Tsujikawa b71d9ea58e Remove SSL_ERROR_WANT_WRITE handling 2017-11-26 10:28:21 +09:00
Tatsuhiro Tsujikawa aca99d42f1 Honor SSL_read semantics 2017-11-26 10:28:21 +09:00
Tatsuhiro Tsujikawa 90a9a804d0 nghttpx: Add TLSv1.3 0-RTT early data support 2017-11-26 10:28:21 +09:00
16 changed files with 600 additions and 23 deletions

View File

@ -31,6 +31,7 @@ HEADERS = [
"user-agent",
"date",
"content-type",
"nghttpx-0rtt-uniq",
# disallowed h1 headers
'connection',
'keep-alive',

View File

@ -168,6 +168,11 @@ 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",
"no-strip-incoming-nghttpx-0rtt-uniq",
]
LOGVARS = [

View File

@ -434,6 +434,11 @@ void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
kv = &(*it_via);
it_via = it;
break;
case HD_NGHTTPX_0RTT_UNIQ:
if (flags & HDOP_STRIP_NGHTTPX_ZERO_RTT_UNIQ) {
continue;
}
break;
}
nva.push_back(
make_nv_internal(kv->name, kv->value, kv->no_index, nv_flags));
@ -920,6 +925,11 @@ int lookup_token(const uint8_t *name, size_t namelen) {
return HD_X_FORWARDED_PROTO;
}
break;
case 'q':
if (util::streq_l("nghttpx-0rtt-uni", name, 16)) {
return HD_NGHTTPX_0RTT_UNIQ;
}
break;
}
break;
}

View File

@ -203,9 +203,13 @@ enum HeaderBuildOp {
// Via header fields must be stripped. If this flag is not set, all
// Via header fields other than last one are added.
HDOP_STRIP_VIA = 1 << 3,
// nghttpx-0rtt-uniq header fields must be stripped. If this flag
// is not set, all nghttpx-0rtt-uniq header fields are added.
HDOP_STRIP_NGHTTPX_ZERO_RTT_UNIQ = 1 << 4,
// Strip above all header fields.
HDOP_STRIP_ALL = HDOP_STRIP_FORWARDED | HDOP_STRIP_X_FORWARDED_FOR |
HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA,
HDOP_STRIP_X_FORWARDED_PROTO | HDOP_STRIP_VIA |
HDOP_STRIP_NGHTTPX_ZERO_RTT_UNIQ,
};
// Appends headers in |headers| to |nv|. |headers| must be indexed
@ -312,6 +316,7 @@ enum {
HD_KEEP_ALIVE,
HD_LINK,
HD_LOCATION,
HD_NGHTTPX_0RTT_UNIQ,
HD_PROXY_CONNECTION,
HD_SERVER,
HD_TE,

View File

@ -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();
}
@ -1480,6 +1486,7 @@ void fill_default_config(Config *config) {
httpconf.max_requests = std::numeric_limits<size_t>::max();
httpconf.xfp.add = true;
httpconf.xfp.strip_incoming = true;
httpconf.zero_rtt_uniq.strip_incoming = true;
auto &http2conf = config->http2;
{
@ -2284,6 +2291,25 @@ SSL/TLS:
--tls-session-cache-memcached-private-key-file=<PATH>
Path to client private key for memcached connections to
store session cache.
--tls-anti-replay-memcached=<HOST>,<PORT>[;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>
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>
Path to client private key for memcached connections to
store ClientHello to avoid 0-RTT early data replay.
--tls-dyn-rec-warmup-threshold=<SIZE>
Specify the threshold size for TLS dynamic record size
behaviour. During a TLS session, after the threshold
@ -2590,6 +2616,9 @@ HTTP:
Default: obfuscated
--no-via Don't append to Via header field. If Via header field
is received, it is left unaltered.
--no-strip-incoming-nghttpx-0rtt-uniq
Don't strip nghttpx-0rtt-uniq header field from inbound
client requests.
--no-location-rewrite
Don't rewrite location header field in default mode.
When --http2-proxy is used, location header field will
@ -2995,6 +3024,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<rlim_t>(config->rlimit_nofile),
static_cast<rlim_t>(config->rlimit_nofile)};
@ -3404,6 +3453,16 @@ 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},
{SHRPX_OPT_NO_STRIP_INCOMING_NGHTTPX_0RTT_UNIQ.c_str(), no_argument,
&flag, 164},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
@ -4165,6 +4224,32 @@ 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;
case 164:
// --no-strip-incoming-nghttpx-0rtt-uniq
cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_NGHTTPX_0RTT_UNIQ,
StringRef::from_lit("yes"));
break;
default:
break;
}

View File

@ -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,12 +2260,20 @@ 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)) {
return SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO;
}
break;
case 'q':
if (util::strieq_l("no-strip-incoming-nghttpx-0rtt-uni", name, 34)) {
return SHRPX_OPTID_NO_STRIP_INCOMING_NGHTTPX_0RTT_UNIQ;
}
break;
case 'r':
if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) {
return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER;
@ -2338,6 +2351,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 +2382,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 +3177,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 +3208,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 +3362,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 +3379,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:
@ -3550,6 +3595,10 @@ int parse_config(Config *config, int optid, const StringRef &opt,
case SHRPX_OPTID_NO_VERIFY_OCSP:
config->tls.ocsp.no_verify = util::strieq_l("yes", optarg);
return 0;
case SHRPX_OPTID_NO_STRIP_INCOMING_NGHTTPX_0RTT_UNIQ:
config->http.zero_rtt_uniq.strip_incoming = !util::strieq_l("yes", optarg);
return 0;
case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored";

View File

@ -343,6 +343,16 @@ 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 auto SHRPX_OPT_NO_STRIP_INCOMING_NGHTTPX_0RTT_UNIQ =
StringRef::from_lit("no-strip-incoming-nghttpx-0rtt-uniq");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -577,6 +587,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;
@ -679,6 +706,9 @@ struct HttpConfig {
bool add;
bool strip_incoming;
} xfp;
struct {
bool strip_incoming;
} zero_rtt_uniq;
std::vector<AltSvc> altsvcs;
std::vector<ErrorPage> error_pages;
HeaderRefs add_request_headers;
@ -1071,6 +1101,7 @@ enum {
SHRPX_OPTID_NO_OCSP,
SHRPX_OPTID_NO_SERVER_PUSH,
SHRPX_OPTID_NO_SERVER_REWRITE,
SHRPX_OPTID_NO_STRIP_INCOMING_NGHTTPX_0RTT_UNIQ,
SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO,
SHRPX_OPTID_NO_VERIFY_OCSP,
SHRPX_OPTID_NO_VIA,
@ -1097,6 +1128,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,

View File

@ -38,7 +38,6 @@
#include "shrpx_log.h"
#include "memchunk.h"
#include "util.h"
#include "ssl_compat.h"
using namespace nghttp2;
@ -60,7 +59,8 @@ Connection::Connection(struct ev_loop *loop, int fd, SSL *ssl,
IOCb readcb, TimerCb timeoutcb, void *data,
size_t tls_dyn_rec_warmup_threshold,
ev_tstamp tls_dyn_rec_idle_timeout, shrpx_proto proto)
: tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool)},
: tls{DefaultMemchunks(mcpool), DefaultPeekMemchunks(mcpool),
DefaultMemchunks(mcpool)},
wlimit(loop, &wev, write_limit.rate, write_limit.burst),
rlimit(loop, &rev, read_limit.rate, read_limit.burst, this),
loop(loop),
@ -92,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) {
@ -110,20 +118,34 @@ 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.;
tls.warmup_writelen = 0;
tls.last_writelen = 0;
tls.last_readlen = 0;
tls.handshake_state = 0;
tls.handshake_state = TLS_CONN_NORMAL;
tls.initial_handshake_done = false;
tls.reneg_started = false;
tls.sct_requested = false;
tls.early_data_finish = false;
tls.early_cb_called = false;
tls.postpone_early_data = false;
}
if (fd != -1) {
@ -141,11 +163,23 @@ void Connection::disconnect() {
wlimit.stopw();
}
void Connection::prepare_client_handshake() { SSL_set_connect_state(tls.ssl); }
void Connection::prepare_client_handshake() {
SSL_set_connect_state(tls.ssl);
// This prevents SSL_read_early_data from being called.
tls.early_data_finish = true;
}
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:
@ -219,7 +253,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
@ -327,8 +373,9 @@ int Connection::tls_handshake() {
wlimit.stopw();
ev_timer_stop(loop, &wt);
std::array<uint8_t, 16_k> buf;
if (ev_is_active(&rev)) {
std::array<uint8_t, 8_k> buf;
auto nread = read_clear(buf.data(), buf.size());
if (nread < 0) {
if (LOG_ENABLED(INFO)) {
@ -348,6 +395,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.
@ -381,9 +429,73 @@ int Connection::tls_handshake() {
break;
}
int rv;
ERR_clear_error();
auto rv = SSL_do_handshake(tls.ssl);
#if OPENSSL_1_1_1_API
if (!tls.server_handshake || tls.early_data_finish) {
rv = SSL_do_handshake(tls.ssl);
} else {
for (;;) {
size_t nread;
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_CLIENT_HELLO_CB) {
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.
if (!tls.postpone_early_data &&
(tls.handshake_state == TLS_CONN_WRITE_STARTED ||
tls.wbuf.rleft()) &&
tls.earlybuf.rleft()) {
rv = 1;
}
break;
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "tls: read early data " << nread << " bytes";
}
tls.earlybuf.append(buf.data(), nread);
if (rv == SSL_READ_EARLY_DATA_FINISH) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "tls: read all early data; total "
<< tls.earlybuf.rleft() << " bytes";
}
tls.early_data_finish = true;
// The same reason stated above.
if (!tls.postpone_early_data &&
(tls.handshake_state == TLS_CONN_WRITE_STARTED ||
tls.wbuf.rleft()) &&
tls.earlybuf.rleft()) {
rv = 1;
} else {
ERR_clear_error();
rv = SSL_do_handshake(tls.ssl);
}
break;
}
}
}
#else // !OPENSSL_1_1_1_API
rv = SSL_do_handshake(tls.ssl);
#endif // !OPENSSL_1_1_1_API
if (rv <= 0) {
auto err = SSL_get_error(tls.ssl, rv);
@ -397,6 +509,9 @@ int Connection::tls_handshake() {
}
break;
case SSL_ERROR_WANT_WRITE:
#if OPENSSL_1_1_1_API
case SSL_ERROR_WANT_CLIENT_HELLO_CB:
#endif // OPENSSL_1_1_1_API
break;
case SSL_ERROR_SSL:
if (LOG_ENABLED(INFO)) {
@ -412,7 +527,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";
}
@ -619,7 +735,21 @@ ssize_t Connection::write_tls(const void *data, size_t len) {
ERR_clear_error();
#if OPENSSL_1_1_1_API
int rv;
if (SSL_is_init_finished(tls.ssl)) {
rv = SSL_write(tls.ssl, data, len);
} else {
size_t nwrite;
rv = SSL_write_early_data(tls.ssl, data, len, &nwrite);
// Use the same semantics with SSL_write.
if (rv == 1) {
rv = nwrite;
}
}
#else // !OPENSSL_1_1_1_API
auto rv = SSL_write(tls.ssl, data, len);
#endif // !OPENSSL_1_1_1_API
if (rv <= 0) {
auto err = SSL_get_error(tls.ssl, rv);
@ -654,6 +784,14 @@ ssize_t Connection::write_tls(const void *data, size_t len) {
}
ssize_t Connection::read_tls(void *data, size_t len) {
ERR_clear_error();
#if OPENSSL_1_1_1_API
if (tls.earlybuf.rleft()) {
return tls.earlybuf.remove(data, len);
}
#endif // OPENSSL_1_1_1_API
// SSL_read requires the same arguments (buf pointer and its
// length) on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.
// rlimit_.avail() or rlimit_.avail() may return different length
@ -671,7 +809,46 @@ ssize_t Connection::read_tls(void *data, size_t len) {
tls.last_readlen = 0;
}
ERR_clear_error();
#if OPENSSL_1_1_1_API
if (!tls.early_data_finish) {
// TLSv1.3 handshake is still going on.
size_t nread;
auto rv = SSL_read_early_data(tls.ssl, data, len, &nread);
if (rv == SSL_READ_EARLY_DATA_ERROR) {
auto err = SSL_get_error(tls.ssl, rv);
switch (err) {
case SSL_ERROR_WANT_READ:
tls.last_readlen = len;
return 0;
case SSL_ERROR_SSL:
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "SSL_read: "
<< ERR_error_string(ERR_get_error(), nullptr);
}
return SHRPX_ERR_NETWORK;
default:
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "SSL_read: SSL_get_error returned " << err;
}
return SHRPX_ERR_NETWORK;
}
}
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "tls: read early data " << nread << " bytes";
}
if (rv == SSL_READ_EARLY_DATA_FINISH) {
if (LOG_ENABLED(INFO)) {
LOG(INFO) << "tls: read all early data";
}
tls.early_data_finish = true;
// We may have stopped write watcher in write_tls.
wlimit.startw();
}
return nread;
}
#endif // OPENSSL_1_1_1_API
auto rv = SSL_read(tls.ssl, data, len);

View File

@ -32,10 +32,12 @@
#include <ev.h>
#include <openssl/ssl.h>
#include <openssl/evp.h>
#include "shrpx_rate_limit.h"
#include "shrpx_error.h"
#include "memchunk.h"
#include "ssl_compat.h"
namespace shrpx {
@ -50,16 +52,26 @@ 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,
};
struct TLSConnection {
DefaultMemchunks wbuf;
DefaultPeekMemchunks rbuf;
// Stores TLSv1.3 early data.
DefaultMemchunks earlybuf;
// Message digest of ClientHello in hex string.
StringRef ch_hex_md;
SSL *ssl;
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
@ -74,6 +86,17 @@ struct TLSConnection {
// true if ssl is initialized as server, and client requested
// signed_certificate_timestamp extension.
bool sct_requested;
// true if TLSv1.3 early data has been completely received. Since
// SSL_read_early_data acts like SSL_do_handshake, this field may be
// true even if the negotiated TLS version is TLSv1.2 or earlier.
// 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;
// true if processing early data should be postponed until handshake
// finishes.
bool postpone_early_data;
};
struct TCPHint {

View File

@ -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<Worker>(
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<Worker>(
loop, sv_ssl_ctx, cl_ssl_ctx, session_cache_ssl_ctx, cert_tree_.get(),
ticket_keys_, this, config->conn.downstream);
auto worker =
make_unique<Worker>(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;

View File

@ -271,7 +271,7 @@ int Http2DownstreamConnection::push_request_headers() {
num_cookies = downstream_->count_crumble_request_cookie();
}
// 9 means:
// 10 means:
// 1. :method
// 2. :scheme
// 3. :path
@ -281,8 +281,9 @@ int Http2DownstreamConnection::push_request_headers() {
// 7. x-forwarded-proto (optional)
// 8. te (optional)
// 9. forwarded (optional)
// 10. nghttpx-0rtt-uniq (optional)
auto nva = std::vector<nghttp2_nv>();
nva.reserve(req.fs.headers().size() + 9 + num_cookies +
nva.reserve(req.fs.headers().size() + 10 + num_cookies +
httpconf.add_request_headers.size());
nva.push_back(
@ -311,11 +312,15 @@ int Http2DownstreamConnection::push_request_headers() {
auto &fwdconf = httpconf.forwarded;
auto &xffconf = httpconf.xff;
auto &xfpconf = httpconf.xfp;
auto &zero_rtt_uniqconf = httpconf.zero_rtt_uniq;
uint32_t build_flags =
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0);
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
(zero_rtt_uniqconf.strip_incoming
? http2::HDOP_STRIP_NGHTTPX_ZERO_RTT_UNIQ
: 0);
http2::copy_headers_to_nva_nocopy(nva, req.fs.headers(), build_flags);
@ -326,6 +331,15 @@ int Http2DownstreamConnection::push_request_headers() {
auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler();
#if OPENSSL_1_1_1_API
auto conn = handler->get_connection();
if (!SSL_is_init_finished(conn->tls.ssl)) {
nva.push_back(
http2::make_nv_ls_nocopy("nghttpx-0rtt-uniq", conn->tls.ch_hex_md));
}
#endif // OPENSSL_1_1_1_API
auto fwd =
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);

View File

@ -535,11 +535,15 @@ int HttpDownstreamConnection::push_request_headers() {
auto &fwdconf = httpconf.forwarded;
auto &xffconf = httpconf.xff;
auto &xfpconf = httpconf.xfp;
auto &zero_rtt_uniqconf = httpconf.zero_rtt_uniq;
uint32_t build_flags =
(fwdconf.strip_incoming ? http2::HDOP_STRIP_FORWARDED : 0) |
(xffconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_FOR : 0) |
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0);
(xfpconf.strip_incoming ? http2::HDOP_STRIP_X_FORWARDED_PROTO : 0) |
(zero_rtt_uniqconf.strip_incoming
? http2::HDOP_STRIP_NGHTTPX_ZERO_RTT_UNIQ
: 0);
http2::build_http1_headers_from_headers(buf, req.fs.headers(), build_flags);
@ -580,6 +584,16 @@ int HttpDownstreamConnection::push_request_headers() {
auto upstream = downstream_->get_upstream();
auto handler = upstream->get_client_handler();
#if OPENSSL_1_1_1_API
auto conn = handler->get_connection();
if (!SSL_is_init_finished(conn->tls.ssl)) {
buf->append("Nghttpx-0rtt-uniq: ");
buf->append(conn->tls.ch_hex_md);
buf->append("\r\n");
}
#endif // OPENSSL_1_1_1_API
auto fwd =
fwdconf.strip_incoming ? nullptr : req.fs.header(http2::HD_FORWARDED);

View File

@ -108,8 +108,9 @@ void RateLimit::stopw() {
}
void RateLimit::handle_tls_pending_read() {
if (!conn_ || !conn_->tls.ssl ||
(SSL_pending(conn_->tls.ssl) == 0 && conn_->tls.rbuf.rleft() == 0)) {
if (!conn_ || !conn_->tls.ssl || !conn_->tls.initial_handshake_done ||
(SSL_pending(conn_->tls.ssl) == 0 && conn_->tls.rbuf.rleft() == 0 &&
conn_->tls.earlybuf.rleft() == 0)) {
return;
}

View File

@ -510,6 +510,13 @@ int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv,
namespace {
void info_callback(const SSL *ssl, int where, int ret) {
#ifdef TLS1_3_VERSION
// TLSv1.3 has no renegotiation.
if (SSL_version(ssl) == TLS1_3_VERSION) {
return;
}
#endif // TLS1_3_VERSION
// To mitigate possible DOS attack using lots of renegotiations, we
// disable renegotiation. Since OpenSSL does not provide an easy way
// to disable it, we check that renegotiation is started in this
@ -527,6 +534,105 @@ 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<Connection *>(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_client_hello_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_client_hello_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<uint8_t, 32> 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<ClientHandler *>(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;
conn->tls.ch_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<MemcachedRequest>();
req->op = MEMCACHED_OP_ADD;
req->key = MEMCACHED_ANTI_REPLY_KEY_PREFIX.str();
req->key += conn->tls.ch_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 postpone processing 0-RTT
// early data until handshake finishes. Note that memcached
// atomically adds key/value.
conn->tls.postpone_early_data = 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,
@ -913,6 +1019,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_client_hello_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

View File

@ -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<TicketKeys> &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<MemcachedDispatcher>(
&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

View File

@ -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<TicketKeys> &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<DownstreamConfig> downstreamconf_;
std::unique_ptr<MemcachedDispatcher> session_cache_memcached_dispatcher_;
std::unique_ptr<MemcachedDispatcher> anti_replay_memcached_dispatcher_;
#ifdef HAVE_MRUBY
std::unique_ptr<mruby::MRubyContext> mruby_ctx_;
#endif // HAVE_MRUBY