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/ssl.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
#include <openssl/conf.h>
|
#include <openssl/conf.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
#include <ev.h>
|
#include <ev.h>
|
||||||
|
|
||||||
|
@ -435,6 +436,47 @@ void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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 {
|
namespace {
|
||||||
int event_loop() {
|
int event_loop() {
|
||||||
auto loop = EV_DEFAULT;
|
auto loop = EV_DEFAULT;
|
||||||
|
@ -534,6 +576,14 @@ int event_loop() {
|
||||||
refresh_timer.data = listener_handler.get();
|
refresh_timer.data = listener_handler.get();
|
||||||
ev_timer_again(loop, &refresh_timer);
|
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)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Entering event loop";
|
LOG(INFO) << "Entering event loop";
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,13 @@ Config *mod_config() { return config; }
|
||||||
|
|
||||||
void create_config() { config = new Config(); }
|
void create_config() { config = new Config(); }
|
||||||
|
|
||||||
|
TicketKeys::~TicketKeys() {
|
||||||
|
/* Erase keys from memory */
|
||||||
|
for (auto &key : keys) {
|
||||||
|
memset(&key, 0, sizeof(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
|
int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr,
|
||||||
const char *hostport) {
|
const char *hostport) {
|
||||||
|
|
|
@ -162,6 +162,17 @@ struct DownstreamAddr {
|
||||||
uint16_t port;
|
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 {
|
struct Config {
|
||||||
// The list of (private key file, certificate file) pair
|
// The list of (private key file, certificate file) pair
|
||||||
std::vector<std::pair<std::string, std::string>> subcerts;
|
std::vector<std::pair<std::string, std::string>> subcerts;
|
||||||
|
@ -170,6 +181,7 @@ struct Config {
|
||||||
std::vector<unsigned char> alpn_prefs;
|
std::vector<unsigned char> alpn_prefs;
|
||||||
std::vector<LogFragment> accesslog_format;
|
std::vector<LogFragment> accesslog_format;
|
||||||
std::vector<DownstreamAddr> downstream_addrs;
|
std::vector<DownstreamAddr> downstream_addrs;
|
||||||
|
std::shared_ptr<TicketKeys> ticket_keys;
|
||||||
// binary form of http proxy host and port
|
// binary form of http proxy host and port
|
||||||
sockaddr_union downstream_http_proxy_addr;
|
sockaddr_union downstream_http_proxy_addr;
|
||||||
ev_tstamp http2_upstream_read_timeout;
|
ev_tstamp http2_upstream_read_timeout;
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include <openssl/crypto.h>
|
#include <openssl/crypto.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
#include <openssl/x509v3.h>
|
#include <openssl/x509v3.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
|
||||||
#include <nghttp2/nghttp2.h>
|
#include <nghttp2/nghttp2.h>
|
||||||
|
|
||||||
|
@ -142,6 +143,71 @@ int servername_callback(SSL *ssl, int *al, void *arg) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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 {
|
namespace {
|
||||||
void info_callback(const SSL *ssl, int where, int ret) {
|
void info_callback(const SSL *ssl, int where, int ret) {
|
||||||
// To mitigate possible DOS attack using lots of renegotiations, we
|
// 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_COMPRESSION |
|
||||||
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
|
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
|
||||||
SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE |
|
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";
|
const unsigned char sid_ctx[] = "shrpx";
|
||||||
SSL_CTX_set_session_id_context(ssl_ctx, sid_ctx, sizeof(sid_ctx) - 1);
|
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);
|
verify_callback);
|
||||||
}
|
}
|
||||||
SSL_CTX_set_tlsext_servername_callback(ssl_ctx, servername_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);
|
SSL_CTX_set_info_callback(ssl_ctx, info_callback);
|
||||||
|
|
||||||
// NPN advertisement
|
// NPN advertisement
|
||||||
|
|
Loading…
Reference in New Issue