nghttpx: Enable TLS session tickets with session key rotation every 12hrs
This commit is contained in:
parent
a804117c83
commit
52f3572d5b
50
src/shrpx.cc
50
src/shrpx.cc
|
@ -47,6 +47,7 @@
|
|||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
|
@ -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<TicketKeys>();
|
||||
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<unsigned char *>(&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<TicketKeys>());
|
||||
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";
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<TicketKey> keys;
|
||||
};
|
||||
|
||||
struct Config {
|
||||
// The list of (private key file, certificate file) pair
|
||||
std::vector<std::pair<std::string, std::string>> subcerts;
|
||||
|
@ -170,6 +181,7 @@ struct Config {
|
|||
std::vector<unsigned char> alpn_prefs;
|
||||
std::vector<LogFragment> accesslog_format;
|
||||
std::vector<DownstreamAddr> downstream_addrs;
|
||||
std::shared_ptr<TicketKeys> ticket_keys;
|
||||
// binary form of http proxy host and port
|
||||
sockaddr_union downstream_http_proxy_addr;
|
||||
ev_tstamp http2_upstream_read_timeout;
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <openssl/crypto.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
|
@ -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<ClientHandler *>(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
|
||||
|
|
Loading…
Reference in New Issue