diff --git a/src/shrpx.cc b/src/shrpx.cc index 1d60782b..aa63e313 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -47,6 +47,7 @@ #include #include #include +#include #include @@ -435,6 +436,47 @@ void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) { } } // namespace +namespace { +void renew_ticket_key_cb(struct ev_loop *loop, ev_timer *w, int revents) { + auto old_ticket_keys = std::atomic_load(&get_config()->ticket_keys); + auto ticket_keys = std::make_shared(); + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "renew ticket key"; + } + // We store at most 2 ticket keys + if (old_ticket_keys) { + auto &old_keys = old_ticket_keys->keys; + auto &new_keys = ticket_keys->keys; + + assert(!old_keys.empty()); + + new_keys.resize(2); + new_keys[1] = old_keys[0]; + } else { + ticket_keys->keys.resize(1); + } + + if (RAND_bytes(reinterpret_cast(&ticket_keys->keys[0]), + sizeof(ticket_keys->keys[0])) == 0) { + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "failed to renew ticket key"; + } + std::atomic_store(&mod_config()->ticket_keys, + std::shared_ptr()); + return; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "ticket keys generation done"; + for (auto &key : ticket_keys->keys) { + LOG(INFO) << "name: " << util::format_hex(key.name, sizeof(key.name)); + } + } + + std::atomic_store(&mod_config()->ticket_keys, ticket_keys); +} +} // namespace + namespace { int event_loop() { auto loop = EV_DEFAULT; @@ -534,6 +576,14 @@ int event_loop() { refresh_timer.data = listener_handler.get(); ev_timer_again(loop, &refresh_timer); + ev_timer renew_ticket_key_timer; + if (sv_ssl_ctx) { + // Renew ticket key every 12hrs + ev_timer_init(&renew_ticket_key_timer, renew_ticket_key_cb, 0., 12 * 3600.); + ev_timer_again(loop, &renew_ticket_key_timer); + renew_ticket_key_cb(loop, &renew_ticket_key_timer, 0); + } + if (LOG_ENABLED(INFO)) { LOG(INFO) << "Entering event loop"; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index b7a865f9..4772763e 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -149,6 +149,13 @@ Config *mod_config() { return config; } void create_config() { config = new Config(); } +TicketKeys::~TicketKeys() { + /* Erase keys from memory */ + for (auto &key : keys) { + memset(&key, 0, sizeof(key)); + } +} + namespace { int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, const char *hostport) { diff --git a/src/shrpx_config.h b/src/shrpx_config.h index bce05092..3743cd0e 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -162,6 +162,17 @@ struct DownstreamAddr { uint16_t port; }; +struct TicketKey { + uint8_t name[16]; + uint8_t aes_key[16]; + uint8_t hmac_key[16]; +}; + +struct TicketKeys { + ~TicketKeys(); + std::vector keys; +}; + struct Config { // The list of (private key file, certificate file) pair std::vector> subcerts; @@ -170,6 +181,7 @@ struct Config { std::vector alpn_prefs; std::vector accesslog_format; std::vector downstream_addrs; + std::shared_ptr ticket_keys; // binary form of http proxy host and port sockaddr_union downstream_http_proxy_addr; ev_tstamp http2_upstream_read_timeout; diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index deddd9ae..4fd603c1 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include @@ -142,6 +143,71 @@ int servername_callback(SSL *ssl, int *al, void *arg) { } } // namespace +namespace { +int ticket_key_cb(SSL *ssl, unsigned char *key_name, unsigned char *iv, + EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) { + auto handler = static_cast(SSL_get_app_data(ssl)); + auto ticket_keys = std::atomic_load(&get_config()->ticket_keys); + if (!ticket_keys) { + /* No ticket keys available. Perform full handshake */ + return 0; + } + + auto &keys = ticket_keys->keys; + assert(!keys.empty()); + + if (enc) { + if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) == 0) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "session ticket key: RAND_bytes failed"; + } + return 0; + } + + auto &key = keys[0]; + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "encrypt session ticket key: " + << util::format_hex(key.name, 16); + } + + memcpy(key_name, key.name, sizeof(key.name)); + + EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, key.aes_key, iv); + HMAC_Init_ex(hctx, key.hmac_key, sizeof(key.hmac_key), EVP_sha256(), + nullptr); + return 1; + } + + size_t i; + for (i = 0; i < keys.size(); ++i) { + auto &key = keys[0]; + if (memcmp(key.name, key_name, sizeof(key.name)) == 0) { + break; + } + } + + if (i == keys.size()) { + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "session ticket key " + << util::format_hex(key_name, 16) << " not found"; + } + return 0; + } + + if (LOG_ENABLED(INFO)) { + CLOG(INFO, handler) << "decrypt session ticket key: " + << util::format_hex(key_name, 16); + } + + auto &key = keys[i]; + HMAC_Init_ex(hctx, key.hmac_key, sizeof(key.hmac_key), EVP_sha256(), nullptr); + EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, key.aes_key, iv); + + return i == 0 ? 1 : 2; +} +} // namespace + namespace { void info_callback(const SSL *ssl, int where, int ret) { // To mitigate possible DOS attack using lots of renegotiations, we @@ -232,7 +298,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file, SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE | - SSL_OP_NO_TICKET | get_config()->tls_proto_mask); + get_config()->tls_proto_mask); const unsigned char sid_ctx[] = "shrpx"; SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1); @@ -346,6 +412,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file, verify_callback); } SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_callback); + SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb); SSL_CTX_set_info_callback(ssl_ctx, info_callback); // NPN advertisement