From 08e8cc1915c77d7f71c73c87def3650c9a0b1e67 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 8 Jan 2015 01:26:30 +0900 Subject: [PATCH] nghttpx: Add --tls-ticket-key-file option This option specifies files contains 48 random bytes to construct session ticket key data. This option can be used repeatedly to specify multiple keys, but only the first one is used to encrypt tickets. --- src/shrpx-unittest.cc | 2 ++ src/shrpx.cc | 38 +++++++++++++++++++++++++++++++++++- src/shrpx_config.cc | 42 ++++++++++++++++++++++++++++++++++++++++ src/shrpx_config.h | 9 +++++++++ src/shrpx_config_test.cc | 38 ++++++++++++++++++++++++++++++++++++ src/shrpx_config_test.h | 1 + src/shrpx_ssl.cc | 4 +++- 7 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 6fe0456e..d38acb5a 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -112,6 +112,8 @@ int main(int argc, char *argv[]) { shrpx::test_shrpx_config_parse_header) || !CU_add_test(pSuite, "config_parse_log_format", shrpx::test_shrpx_config_parse_log_format) || + !CU_add_test(pSuite, "config_read_tls_ticket_key_file", + shrpx::test_shrpx_config_read_tls_ticket_key_file) || !CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) || !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) || !CU_add_test(pSuite, "util_inp_strlower", diff --git a/src/shrpx.cc b/src/shrpx.cc index aa63e313..6f290368 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -577,7 +577,7 @@ int event_loop() { ev_timer_again(loop, &refresh_timer); ev_timer renew_ticket_key_timer; - if (sv_ssl_ctx) { + if (sv_ssl_ctx && get_config()->auto_tls_ticket_key) { // 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); @@ -746,6 +746,7 @@ void fill_default_config() { mod_config()->downstream_connections_per_host = 8; mod_config()->downstream_connections_per_frontend = 0; mod_config()->listener_disable_timeout = 0.; + mod_config()->auto_tls_ticket_key = true; } } // namespace @@ -993,6 +994,25 @@ SSL/TLS: only and any white spaces are treated as a part of protocol string. Default: )" << DEFAULT_TLS_PROTO_LIST << R"( + --tls-ticket-key-file= + Path to file that contains 48 bytes random data + to construct TLS session ticket parameters. This + options can be used repeatedly to specify + multiple ticket parameters. If several files are + given, only the first key is used to encrypt TLS + session tickets. Other keys are accepted but + server will issue new session ticket with first + key. This allows session key rotation. Please + note that key rotation does not occur + automatically. User should rearrange files or + change options values and restart nghttpx + gracefully. If opening or reading given file + fails, all loaded keys are discarded and it is + treated as if none of this option is given. If + this option is not given or an error occurred + while opening or reading a file, key is generated + automatically and renewed every 12hrs. At most 2 + keys are stored in memory. HTTP/2 and SPDY: -c, --http2-max-concurrent-streams= @@ -1261,6 +1281,7 @@ int main(int argc, char **argv) { {"accesslog-format", required_argument, &flag, 66}, {"backend-http1-connections-per-frontend", required_argument, &flag, 67}, + {"tls-ticket-key-file", required_argument, &flag, 68}, {nullptr, 0, nullptr, 0}}; int option_index = 0; @@ -1570,6 +1591,10 @@ int main(int argc, char **argv) { cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, optarg); break; + case 68: + // --tls-ticket-key-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_FILE, optarg); + break; default: break; } @@ -1722,6 +1747,17 @@ int main(int argc, char **argv) { } } + if (!get_config()->tls_ticket_key_files.empty()) { + auto ticket_keys = + read_tls_ticket_key_file(get_config()->tls_ticket_key_files); + if (!ticket_keys) { + LOG(WARN) << "Use internal session ticket key generator"; + } else { + mod_config()->ticket_keys = std::move(ticket_keys); + mod_config()->auto_tls_ticket_key = false; + } + } + if (get_config()->backend_ipv4 && get_config()->backend_ipv6) { LOG(FATAL) << "--backend-ipv4 and --backend-ipv6 cannot be used at the " << "same time."; diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 4772763e..1a3362a5 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -138,6 +138,7 @@ const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[] = const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[] = "backend-http1-connections-per-frontend"; const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[] = "listener-disable-timeout"; +const char SHRPX_OPT_TLS_TICKET_KEY_FILE[] = "tls-ticket-key-file"; namespace { Config *config = nullptr; @@ -200,6 +201,42 @@ bool is_secure(const char *filename) { } } // namespace +std::unique_ptr +read_tls_ticket_key_file(const std::vector &files) { + auto ticket_keys = util::make_unique(); + auto &keys = ticket_keys->keys; + keys.resize(files.size()); + size_t i = 0; + for (auto &file : files) { + std::ifstream f(file.c_str()); + if (!f) { + LOG(ERROR) << "tls-ticket-key-file: could not open file " << file; + return nullptr; + } + char buf[48]; + f.read(buf, sizeof(buf)); + if (f.gcount() != sizeof(buf)) { + LOG(ERROR) << "tls-ticket-key-file: want to read 48 bytes but read " + << f.gcount() << " bytes from " << file; + return nullptr; + } + + auto &key = keys[i++]; + auto p = buf; + memcpy(key.name, p, sizeof(key.name)); + p += sizeof(key.name); + memcpy(key.aes_key, p, sizeof(key.aes_key)); + p += sizeof(key.aes_key); + memcpy(key.hmac_key, p, sizeof(key.hmac_key)); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "session ticket key: " << util::format_hex(key.name, + sizeof(key.name)); + } + } + return ticket_keys; +} + FILE *open_file_for_write(const char *filename) { auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); @@ -1067,6 +1104,11 @@ int parse_config(const char *opt, const char *optarg) { return parse_timeval(&mod_config()->listener_disable_timeout, opt, optarg); } + if (util::strieq(opt, SHRPX_OPT_TLS_TICKET_KEY_FILE)) { + mod_config()->tls_ticket_key_files.push_back(optarg); + return 0; + } + if (util::strieq(opt, "conf")) { LOG(WARN) << "conf: ignored"; diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 3743cd0e..0582112f 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -127,6 +127,7 @@ extern const char SHRPX_OPT_NO_LOCATION_REWRITE[]; extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[]; extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND[]; extern const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[]; +extern const char SHRPX_OPT_TLS_TICKET_KEY_FILE[]; union sockaddr_union { sockaddr sa; @@ -181,6 +182,7 @@ struct Config { std::vector alpn_prefs; std::vector accesslog_format; std::vector downstream_addrs; + std::vector tls_ticket_key_files; std::shared_ptr ticket_keys; // binary form of http proxy host and port sockaddr_union downstream_http_proxy_addr; @@ -293,6 +295,7 @@ struct Config { bool http2_no_cookie_crumbling; bool upstream_frame_debug; bool no_location_rewrite; + bool auto_tls_ticket_key; }; const Config *get_config(); @@ -353,6 +356,12 @@ int int_syslog_facility(const char *strfacility); FILE *open_file_for_write(const char *filename); +// Reads TLS ticket key file in |files| and returns TicketKey which +// stores read key data. This function returns TicketKey if it +// succeeds, or nullptr. +std::unique_ptr +read_tls_ticket_key_file(const std::vector &files); + } // namespace shrpx #endif // SHRPX_CONFIG_H diff --git a/src/shrpx_config_test.cc b/src/shrpx_config_test.cc index 63a67603..e326b4af 100644 --- a/src/shrpx_config_test.cc +++ b/src/shrpx_config_test.cc @@ -24,6 +24,10 @@ */ #include "shrpx_config_test.h" +#include + +#include + #include #include "shrpx_config.h" @@ -132,4 +136,38 @@ void test_shrpx_config_parse_log_format(void) { CU_ASSERT(0 == strcmp("\"", res[13].value.get())); } +void test_shrpx_config_read_tls_ticket_key_file(void) { + char file1[] = "/tmp/nghttpx-unittest.XXXXXX"; + auto fd1 = mkstemp(file1); + assert(fd1 != -1); + assert(48 == + write(fd1, "0..............12..............34..............5", 48)); + char file2[] = "/tmp/nghttpx-unittest.XXXXXX"; + auto fd2 = mkstemp(file2); + assert(fd2 != -1); + assert(48 == + write(fd2, "6..............78..............9a..............b", 48)); + + close(fd1); + close(fd2); + auto ticket_keys = read_tls_ticket_key_file({file1, file2}); + unlink(file1); + unlink(file2); + CU_ASSERT(ticket_keys.get() != nullptr); + CU_ASSERT(2 == ticket_keys->keys.size()); + auto key = &ticket_keys->keys[0]; + CU_ASSERT(0 == memcmp("0..............1", key->name, sizeof(key->name))); + CU_ASSERT(0 == + memcmp("2..............3", key->aes_key, sizeof(key->aes_key))); + CU_ASSERT(0 == + memcmp("4..............5", key->hmac_key, sizeof(key->hmac_key))); + + key = &ticket_keys->keys[1]; + CU_ASSERT(0 == memcmp("6..............7", key->name, sizeof(key->name))); + CU_ASSERT(0 == + memcmp("8..............9", key->aes_key, sizeof(key->aes_key))); + CU_ASSERT(0 == + memcmp("a..............b", key->hmac_key, sizeof(key->hmac_key))); +} + } // namespace shrpx diff --git a/src/shrpx_config_test.h b/src/shrpx_config_test.h index 9df73736..9db5d4e3 100644 --- a/src/shrpx_config_test.h +++ b/src/shrpx_config_test.h @@ -30,6 +30,7 @@ namespace shrpx { void test_shrpx_config_parse_config_str_list(void); void test_shrpx_config_parse_header(void); void test_shrpx_config_parse_log_format(void); +void test_shrpx_config_read_tls_ticket_key_file(void); } // namespace shrpx diff --git a/src/shrpx_ssl.cc b/src/shrpx_ssl.cc index 4fd603c1..84c28198 100644 --- a/src/shrpx_ssl.cc +++ b/src/shrpx_ssl.cc @@ -147,7 +147,9 @@ 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); + auto ticket_keys = get_config()->auto_tls_ticket_key + ? std::atomic_load(&get_config()->ticket_keys) + : get_config()->ticket_keys; if (!ticket_keys) { /* No ticket keys available. Perform full handshake */ return 0;