nghttpx: Add OCSP stapling feature
This commit is contained in:
parent
ccea4d42b5
commit
4bc9afe20a
|
@ -28,6 +28,7 @@ TESTS =
|
|||
|
||||
AM_CFLAGS = $(WARNCFLAGS)
|
||||
AM_CPPFLAGS = \
|
||||
-DPKGDATADIR='"$(pkgdatadir)"' \
|
||||
-Wall \
|
||||
-I$(top_srcdir)/lib/includes \
|
||||
-I$(top_builddir)/lib/includes \
|
||||
|
|
64
src/shrpx.cc
64
src/shrpx.cc
|
@ -575,6 +575,8 @@ void refresh_cb(struct ev_loop *loop, ev_timer *w, int revents) {
|
|||
(!worker || worker->get_worker_stat()->num_connections == 0)) {
|
||||
ev_break(loop);
|
||||
}
|
||||
|
||||
conn_handler->handle_ocsp_completion();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -740,6 +742,10 @@ int event_loop() {
|
|||
refresh_timer.data = conn_handler.get();
|
||||
ev_timer_again(loop, &refresh_timer);
|
||||
|
||||
if (!get_config()->upstream_no_tls && !get_config()->no_ocsp) {
|
||||
conn_handler->update_ocsp_async();
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "Entering event loop";
|
||||
}
|
||||
|
@ -747,6 +753,7 @@ int event_loop() {
|
|||
ev_run(loop, 0);
|
||||
|
||||
conn_handler->join_worker();
|
||||
conn_handler->join_ocsp_thread();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -918,6 +925,11 @@ void fill_default_config() {
|
|||
mod_config()->no_server_push = false;
|
||||
mod_config()->host_unix = false;
|
||||
mod_config()->http2_downstream_connections_per_worker = 0;
|
||||
// ocsp update interval = 14400 secs = 4 hours, borrowed from h2o
|
||||
mod_config()->ocsp_update_interval = 14400.;
|
||||
mod_config()->fetch_ocsp_response_file =
|
||||
strcopy(PKGDATADIR "/fetch-ocsp-response");
|
||||
mod_config()->no_ocsp = false;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -942,7 +954,8 @@ void print_help(std::ostream &out) {
|
|||
Set path to server's private key. Required unless -p,
|
||||
--client or --frontend-no-tls are given.
|
||||
<CERT> Set path to server's certificate. Required unless -p,
|
||||
--client or --frontend-no-tls are given.
|
||||
--client or --frontend-no-tls are given. To make OCSP
|
||||
stapling work, this must be absolute path.
|
||||
|
||||
Options:
|
||||
The options are categorized into several groups.
|
||||
|
@ -1130,7 +1143,8 @@ SSL/TLS:
|
|||
Specify additional certificate and private key file.
|
||||
nghttpx will choose certificates based on the hostname
|
||||
indicated by client using TLS SNI extension. This
|
||||
option can be used multiple times.
|
||||
option can be used multiple times. To make OCSP
|
||||
stapling work, <CERTPATH> must be absolute path.
|
||||
--backend-tls-sni-field=<HOST>
|
||||
Explicitly set the content of the TLS SNI extension.
|
||||
This will default to the backend HOST name.
|
||||
|
@ -1191,6 +1205,15 @@ SSL/TLS:
|
|||
objects, which means session ID generated by one worker
|
||||
is not acceptable by another worker. On the other hand,
|
||||
session ticket key is shared across all worker threads.
|
||||
--fetch-ocsp-response-file=<PATH>
|
||||
Path to fetch-ocsp-response script file. It should be
|
||||
absolute path.
|
||||
Default: )" << get_config()->fetch_ocsp_response_file.get() << R"(
|
||||
--ocsp-update-interval=<DURATION>
|
||||
Set interval to update OCSP response cache.
|
||||
Default: )"
|
||||
<< util::duration_str(get_config()->ocsp_update_interval) << R"(
|
||||
--no-ocsp Disable OCSP stapling.
|
||||
|
||||
HTTP/2 and SPDY:
|
||||
-c, --http2-max-concurrent-streams=<N>
|
||||
|
@ -1379,8 +1402,9 @@ Misc:
|
|||
10 * 1024). Units are K, M and G (powers of 1024).
|
||||
|
||||
The <DURATION> argument is an integer and an optional unit (e.g., 1s
|
||||
is 1 second and 500ms is 500 milliseconds). Units are s or ms. If
|
||||
a unit is omitted, a second is used as unit.)" << std::endl;
|
||||
is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
|
||||
(hours, minutes, seconds and milliseconds, respectively). If a unit
|
||||
is omitted, a second is used as unit.)" << std::endl;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -1492,6 +1516,9 @@ int main(int argc, char **argv) {
|
|||
{"no-host-rewrite", no_argument, &flag, 73},
|
||||
{"no-server-push", no_argument, &flag, 74},
|
||||
{"backend-http2-connections-per-worker", required_argument, &flag, 76},
|
||||
{"fetch-ocsp-response-file", required_argument, &flag, 77},
|
||||
{"ocsp-update-interval", required_argument, &flag, 78},
|
||||
{"no-ocsp", no_argument, &flag, 79},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
int option_index = 0;
|
||||
|
@ -1834,6 +1861,18 @@ int main(int argc, char **argv) {
|
|||
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
|
||||
optarg);
|
||||
break;
|
||||
case 77:
|
||||
// --fetch-ocsp-response-file
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE, optarg);
|
||||
break;
|
||||
case 78:
|
||||
// --ocsp-update-interval
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_OCSP_UPDATE_INTERVAL, optarg);
|
||||
break;
|
||||
case 79:
|
||||
// --no-ocsp
|
||||
cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, "yes");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -2000,6 +2039,23 @@ int main(int argc, char **argv) {
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (!get_config()->upstream_no_tls && !get_config()->no_ocsp) {
|
||||
#ifdef NOTHREADS
|
||||
mod_config()->no_ocsp = true;
|
||||
LOG(WARN)
|
||||
<< "OCSP stapling has been disabled since it requires threading but"
|
||||
"threading disabled at build time.";
|
||||
#else // !NOTHREADS
|
||||
struct stat buf;
|
||||
if (stat(get_config()->fetch_ocsp_response_file.get(), &buf) != 0) {
|
||||
mod_config()->no_ocsp = true;
|
||||
LOG(WARN) << "--fetch-ocsp-response-file: "
|
||||
<< get_config()->fetch_ocsp_response_file.get()
|
||||
<< " not found. OCSP stapling has been disabled.";
|
||||
}
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
|
||||
if (get_config()->downstream_addrs.empty()) {
|
||||
DownstreamAddr addr;
|
||||
addr.host = strcopy(DEFAULT_DOWNSTREAM_HOST);
|
||||
|
|
|
@ -148,6 +148,9 @@ const char SHRPX_OPT_BACKEND_RESPONSE_BUFFER[] = "backend-response-buffer";
|
|||
const char SHRPX_OPT_NO_SERVER_PUSH[] = "no-server-push";
|
||||
const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[] =
|
||||
"backend-http2-connections-per-worker";
|
||||
const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[] = "fetch-ocsp-response-file";
|
||||
const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[] = "ocsp-update-interval";
|
||||
const char SHRPX_OPT_NO_OCSP[] = "no-ocsp";
|
||||
|
||||
namespace {
|
||||
Config *config = nullptr;
|
||||
|
@ -1200,6 +1203,22 @@ int parse_config(const char *opt, const char *optarg) {
|
|||
opt, optarg);
|
||||
}
|
||||
|
||||
if (util::strieq(opt, SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE)) {
|
||||
mod_config()->fetch_ocsp_response_file = strcopy(optarg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (util::strieq(opt, SHRPX_OPT_OCSP_UPDATE_INTERVAL)) {
|
||||
return parse_duration(&mod_config()->ocsp_update_interval, opt, optarg);
|
||||
}
|
||||
|
||||
if (util::strieq(opt, SHRPX_OPT_NO_OCSP)) {
|
||||
mod_config()->no_ocsp = util::strieq(optarg, "yes");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (util::strieq(opt, "conf")) {
|
||||
LOG(WARN) << "conf: ignored";
|
||||
|
||||
|
|
|
@ -137,6 +137,9 @@ extern const char SHRPX_OPT_BACKEND_REQUEST_BUFFER[];
|
|||
extern const char SHRPX_OPT_BACKEND_RESPONSE_BUFFER[];
|
||||
extern const char SHRPX_OPT_NO_SERVER_PUSH[];
|
||||
extern const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[];
|
||||
extern const char SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE[];
|
||||
extern const char SHRPX_OPT_OCSP_UPDATE_INTERVAL[];
|
||||
extern const char SHRPX_OPT_NO_OCSP[];
|
||||
|
||||
union sockaddr_union {
|
||||
sockaddr_storage storage;
|
||||
|
@ -209,6 +212,7 @@ struct Config {
|
|||
ev_tstamp stream_write_timeout;
|
||||
ev_tstamp downstream_idle_read_timeout;
|
||||
ev_tstamp listener_disable_timeout;
|
||||
ev_tstamp ocsp_update_interval;
|
||||
// address of frontend connection. This could be a path to UNIX
|
||||
// domain socket. In this case, |host_unix| must be true.
|
||||
std::unique_ptr<char[]> host;
|
||||
|
@ -246,6 +250,7 @@ struct Config {
|
|||
std::unique_ptr<char[]> client_cert_file;
|
||||
std::unique_ptr<char[]> accesslog_file;
|
||||
std::unique_ptr<char[]> errorlog_file;
|
||||
std::unique_ptr<char[]> fetch_ocsp_response_file;
|
||||
FILE *http2_upstream_dump_request_header;
|
||||
FILE *http2_upstream_dump_response_header;
|
||||
nghttp2_session_callbacks *http2_upstream_callbacks;
|
||||
|
@ -324,6 +329,7 @@ struct Config {
|
|||
bool no_server_push;
|
||||
// true if host contains UNIX domain socket path
|
||||
bool host_unix;
|
||||
bool no_ocsp;
|
||||
};
|
||||
|
||||
const Config *get_config();
|
||||
|
|
|
@ -58,15 +58,32 @@ void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void ocsp_cb(struct ev_loop *loop, ev_timer *w, int revent) {
|
||||
auto h = static_cast<ConnectionHandler *>(w->data);
|
||||
|
||||
// If we are in graceful shutdown period, we won't do ocsp query.
|
||||
if (h->get_graceful_shutdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
h->update_ocsp_async();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ConnectionHandler::ConnectionHandler(struct ev_loop *loop)
|
||||
: single_worker_(nullptr), loop_(loop), worker_round_robin_cnt_(0),
|
||||
graceful_shutdown_(false) {
|
||||
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
|
||||
disable_acceptor_timer_.data = this;
|
||||
|
||||
ev_timer_init(&ocsp_timer_, ocsp_cb, 0., 0.);
|
||||
ocsp_timer_.data = this;
|
||||
}
|
||||
|
||||
ConnectionHandler::~ConnectionHandler() {
|
||||
ev_timer_stop(loop_, &disable_acceptor_timer_);
|
||||
ev_timer_stop(loop_, &ocsp_timer_);
|
||||
}
|
||||
|
||||
void ConnectionHandler::worker_reopen_log_files() {
|
||||
|
@ -95,7 +112,7 @@ void ConnectionHandler::worker_renew_ticket_keys(
|
|||
|
||||
void ConnectionHandler::create_single_worker() {
|
||||
auto cert_tree = ssl::create_cert_lookup_tree();
|
||||
auto sv_ssl_ctx = ssl::setup_server_ssl_context(cert_tree);
|
||||
auto sv_ssl_ctx = ssl::setup_server_ssl_context(all_ssl_ctx_, cert_tree);
|
||||
auto cl_ssl_ctx = ssl::setup_client_ssl_context();
|
||||
|
||||
single_worker_ = make_unique<Worker>(loop_, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
||||
|
@ -111,7 +128,7 @@ void ConnectionHandler::create_worker_thread(size_t num) {
|
|||
|
||||
if (!get_config()->tls_ctx_per_worker) {
|
||||
cert_tree = ssl::create_cert_lookup_tree();
|
||||
sv_ssl_ctx = ssl::setup_server_ssl_context(cert_tree);
|
||||
sv_ssl_ctx = ssl::setup_server_ssl_context(all_ssl_ctx_, cert_tree);
|
||||
cl_ssl_ctx = ssl::setup_client_ssl_context();
|
||||
}
|
||||
|
||||
|
@ -120,7 +137,8 @@ void ConnectionHandler::create_worker_thread(size_t num) {
|
|||
|
||||
if (get_config()->tls_ctx_per_worker) {
|
||||
cert_tree = ssl::create_cert_lookup_tree();
|
||||
sv_ssl_ctx = ssl::setup_server_ssl_context(cert_tree);
|
||||
std::vector<SSL_CTX *> all_ssl_ctx;
|
||||
sv_ssl_ctx = ssl::setup_server_ssl_context(all_ssl_ctx, cert_tree);
|
||||
cl_ssl_ctx = ssl::setup_client_ssl_context();
|
||||
}
|
||||
|
||||
|
@ -309,4 +327,80 @@ bool ConnectionHandler::get_graceful_shutdown() const {
|
|||
return graceful_shutdown_;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void update_ocsp_ssl_ctx(SSL_CTX *ssl_ctx) {
|
||||
auto tls_ctx_data =
|
||||
static_cast<ssl::TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
|
||||
auto cert_file = tls_ctx_data->cert_file;
|
||||
|
||||
std::vector<uint8_t> out;
|
||||
if (ssl::get_ocsp_response(out, cert_file) != 0) {
|
||||
LOG(WARN) << "ocsp update for " << cert_file << " failed";
|
||||
return;
|
||||
}
|
||||
|
||||
if (LOG_ENABLED(INFO)) {
|
||||
LOG(INFO) << "ocsp update for " << cert_file << " finished successfully";
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> g(tls_ctx_data->mu);
|
||||
tls_ctx_data->ocsp_data = std::move(out);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ConnectionHandler::update_ocsp() {
|
||||
for (auto ssl_ctx : all_ssl_ctx_) {
|
||||
update_ocsp_ssl_ctx(ssl_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionHandler::update_ocsp_async() {
|
||||
#ifndef NOTHREADS
|
||||
ocsp_result_ = std::async(std::launch::async, [this]() {
|
||||
// Log files are managed per thread. We have to open log files
|
||||
// for this thread. We don't reopen log files in this thread when
|
||||
// signal is received. This is future TODO.
|
||||
reopen_log_files();
|
||||
auto closer = defer([]() {
|
||||
auto lgconf = log_config();
|
||||
if (lgconf->accesslog_fd != -1) {
|
||||
close(lgconf->accesslog_fd);
|
||||
}
|
||||
if (lgconf->errorlog_fd != -1) {
|
||||
close(lgconf->errorlog_fd);
|
||||
}
|
||||
});
|
||||
|
||||
update_ocsp();
|
||||
});
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
|
||||
void ConnectionHandler::handle_ocsp_completion() {
|
||||
#ifndef NOTHREADS
|
||||
if (!ocsp_result_.valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ocsp_result_.wait_for(std::chrono::seconds(0)) !=
|
||||
std::future_status::ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
ocsp_result_.get();
|
||||
|
||||
ev_timer_set(&ocsp_timer_, get_config()->ocsp_update_interval, 0.);
|
||||
ev_timer_start(loop_, &ocsp_timer_);
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
|
||||
void ConnectionHandler::join_ocsp_thread() {
|
||||
#ifndef NOTHREADS
|
||||
if (!ocsp_result_.valid()) {
|
||||
return;
|
||||
}
|
||||
ocsp_result_.get();
|
||||
#endif // !NOTHREADS
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#ifndef NOTHREADS
|
||||
#include <future>
|
||||
#endif // !NOTHREADS
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
|
@ -48,7 +51,6 @@ class Worker;
|
|||
struct WorkerStat;
|
||||
struct TicketKeys;
|
||||
|
||||
// TODO should be renamed as ConnectionHandler
|
||||
class ConnectionHandler {
|
||||
public:
|
||||
ConnectionHandler(struct ev_loop *loop);
|
||||
|
@ -77,8 +79,23 @@ public:
|
|||
void set_graceful_shutdown(bool f);
|
||||
bool get_graceful_shutdown() const;
|
||||
void join_worker();
|
||||
// Updates OCSP response cache for all server side SSL_CTX object
|
||||
void update_ocsp();
|
||||
// Just like update_ocsp(), but performed in new thread. Call
|
||||
// handle_ocsp_completion() to handle its completion and scheduling
|
||||
// next update.
|
||||
void update_ocsp_async();
|
||||
// Handles asynchronous OCSP update completion and schedules next
|
||||
// update.
|
||||
void handle_ocsp_completion();
|
||||
// Waits for OCSP thread finishes if it is still running.
|
||||
void join_ocsp_thread();
|
||||
|
||||
private:
|
||||
#ifndef NOTHREADS
|
||||
std::future<void> ocsp_result_;
|
||||
#endif // !NOTHREADS
|
||||
std::vector<SSL_CTX *> all_ssl_ctx_;
|
||||
// Worker instances when multi threaded mode (-nN, N >= 2) is used.
|
||||
std::vector<std::unique_ptr<Worker>> workers_;
|
||||
// Worker instance used when single threaded mode (-n1) is used.
|
||||
|
@ -94,6 +111,7 @@ private:
|
|||
// acceptor for IPv6 address
|
||||
std::unique_ptr<AcceptHandler> acceptor6_;
|
||||
ev_timer disable_acceptor_timer_;
|
||||
ev_timer ocsp_timer_;
|
||||
unsigned int worker_round_robin_cnt_;
|
||||
bool graceful_shutdown_;
|
||||
};
|
||||
|
|
151
src/shrpx_ssl.cc
151
src/shrpx_ssl.cc
|
@ -28,6 +28,11 @@
|
|||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#ifndef NOTHREADS
|
||||
#include <spawn.h>
|
||||
#endif // !NOTHREADS
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
@ -141,7 +146,32 @@ int servername_callback(SSL *ssl, int *al, void *arg) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int ocsp_resp_cb(SSL *ssl, void *arg) {
|
||||
auto ssl_ctx = SSL_get_SSL_CTX(ssl);
|
||||
auto tls_ctx_data =
|
||||
static_cast<TLSContextData *>(SSL_CTX_get_app_data(ssl_ctx));
|
||||
{
|
||||
std::lock_guard<std::mutex> g(tls_ctx_data->mu);
|
||||
auto &data = tls_ctx_data->ocsp_data;
|
||||
|
||||
if (!data.empty()) {
|
||||
auto buf = static_cast<uint8_t *>(
|
||||
CRYPTO_malloc(data.size(), __FILE__, __LINE__));
|
||||
|
||||
if (!buf) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
std::copy(std::begin(data), std::end(data), buf);
|
||||
|
||||
SSL_set_tlsext_status_ocsp_resp(ssl, buf, data.size());
|
||||
}
|
||||
}
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -418,6 +448,7 @@ SSL_CTX *create_ssl_context(const char *private_key_file,
|
|||
}
|
||||
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_tlsext_status_cb(ssl_ctx, ocsp_resp_cb);
|
||||
SSL_CTX_set_info_callback(ssl_ctx, info_callback);
|
||||
|
||||
// NPN advertisement
|
||||
|
@ -426,6 +457,12 @@ SSL_CTX *create_ssl_context(const char *private_key_file,
|
|||
// ALPN selection callback
|
||||
SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr);
|
||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
||||
|
||||
auto tls_ctx_data = new TLSContextData();
|
||||
tls_ctx_data->cert_file = cert_file;
|
||||
|
||||
SSL_CTX_set_app_data(ssl_ctx, tls_ctx_data);
|
||||
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
||||
|
@ -929,7 +966,8 @@ bool check_http2_requirement(SSL *ssl) {
|
|||
return true;
|
||||
}
|
||||
|
||||
SSL_CTX *setup_server_ssl_context(CertLookupTree *cert_tree) {
|
||||
SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
||||
CertLookupTree *cert_tree) {
|
||||
if (get_config()->upstream_no_tls) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -937,6 +975,8 @@ SSL_CTX *setup_server_ssl_context(CertLookupTree *cert_tree) {
|
|||
auto ssl_ctx = ssl::create_ssl_context(get_config()->private_key_file.get(),
|
||||
get_config()->cert_file.get());
|
||||
|
||||
all_ssl_ctx.push_back(ssl_ctx);
|
||||
|
||||
if (get_config()->subcerts.empty()) {
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
@ -950,6 +990,7 @@ SSL_CTX *setup_server_ssl_context(CertLookupTree *cert_tree) {
|
|||
for (auto &keycert : get_config()->subcerts) {
|
||||
auto ssl_ctx =
|
||||
ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str());
|
||||
all_ssl_ctx.push_back(ssl_ctx);
|
||||
if (ssl::cert_lookup_tree_add_cert_from_file(
|
||||
cert_tree, ssl_ctx, keycert.second.c_str()) == -1) {
|
||||
LOG(FATAL) << "Failed to add sub certificate.";
|
||||
|
@ -984,6 +1025,114 @@ CertLookupTree *create_cert_lookup_tree() {
|
|||
return new ssl::CertLookupTree();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// inspired by h2o_read_command function from h2o project:
|
||||
// https://github.com/h2o/h2o
|
||||
int exec_read_stdout(std::vector<uint8_t> &out, char *const *argv,
|
||||
char *const *envp) {
|
||||
#ifndef NOTHREADS
|
||||
int rv;
|
||||
int pfd[2];
|
||||
|
||||
#ifdef O_CLOEXEC
|
||||
if (pipe2(pfd, O_CLOEXEC) == -1) {
|
||||
return -1;
|
||||
}
|
||||
#else // !O_CLOEXEC
|
||||
if (pipe(pfd) == -1) {
|
||||
return -1;
|
||||
}
|
||||
util::make_socket_closeonexec(pfd[0]);
|
||||
util::make_socket_closeonexec(pfd[1]);
|
||||
#endif // !O_CLOEXEC
|
||||
|
||||
auto closer = defer([pfd]() {
|
||||
close(pfd[0]);
|
||||
|
||||
if (pfd[1] != -1) {
|
||||
close(pfd[1]);
|
||||
}
|
||||
});
|
||||
|
||||
// posix_spawn family functions are really interesting. They makes
|
||||
// fork + dup2 + execve pattern easier.
|
||||
|
||||
posix_spawn_file_actions_t file_actions;
|
||||
|
||||
if (posix_spawn_file_actions_init(&file_actions) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto file_actions_del =
|
||||
defer(posix_spawn_file_actions_destroy, &file_actions);
|
||||
|
||||
if (posix_spawn_file_actions_adddup2(&file_actions, pfd[1], 1) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (posix_spawn_file_actions_addclose(&file_actions, pfd[0]) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pid_t pid;
|
||||
rv = posix_spawn(&pid, argv[0], &file_actions, nullptr, argv, envp);
|
||||
if (rv != 0) {
|
||||
LOG(WARN) << "Cannot execute ocsp query command: " << argv[0]
|
||||
<< ", errno=" << rv;
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(pfd[1]);
|
||||
pfd[1] = -1;
|
||||
|
||||
std::array<uint8_t, 4096> buf;
|
||||
for (;;) {
|
||||
ssize_t n;
|
||||
while ((n = read(pfd[0], buf.data(), buf.size())) == -1 && errno == EINTR)
|
||||
;
|
||||
|
||||
if (n == -1) {
|
||||
auto error = errno;
|
||||
LOG(WARN) << "Reading from ocsp query command failed: errno=" << error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::copy_n(std::begin(buf), n, std::back_inserter(out));
|
||||
}
|
||||
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) == -1) {
|
||||
auto error = errno;
|
||||
LOG(WARN) << "waitpid for ocsp query command failed: errno=" << error;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!WIFEXITED(status)) {
|
||||
LOG(WARN) << "ocsp query command did not exit normally: " << status;
|
||||
return -1;
|
||||
}
|
||||
#endif // !NOTHREADS
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int get_ocsp_response(std::vector<uint8_t> &out, const char *cert_file) {
|
||||
char *const argv[] = {
|
||||
const_cast<char *>(get_config()->fetch_ocsp_response_file.get()),
|
||||
const_cast<char *>(cert_file), nullptr};
|
||||
char *const envp[] = {nullptr};
|
||||
|
||||
if (exec_read_stdout(out, argv, envp) != 0 || out.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace ssl
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "shrpx.h"
|
||||
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
|
@ -42,6 +43,18 @@ class DownstreamConnectionPool;
|
|||
|
||||
namespace ssl {
|
||||
|
||||
// This struct stores the additional information per SSL_CTX. This is
|
||||
// attached to SSL_CTX using SSL_CTX_set_app_data().
|
||||
struct TLSContextData {
|
||||
// Protects ocsp_data;
|
||||
std::mutex mu;
|
||||
// OCSP resonse
|
||||
std::vector<uint8_t> ocsp_data;
|
||||
|
||||
// Path to certificate file
|
||||
const char *cert_file;
|
||||
};
|
||||
|
||||
// Create server side SSL_CTX
|
||||
SSL_CTX *create_ssl_context(const char *private_key_file,
|
||||
const char *cert_file);
|
||||
|
@ -143,8 +156,10 @@ std::vector<unsigned char> set_alpn_prefs(const std::vector<char *> &protos);
|
|||
// and if upstream_no_tls is true, returns nullptr. Otherwise
|
||||
// construct default SSL_CTX. If subcerts are available
|
||||
// (get_config()->subcerts), caller should provide CertLookupTree
|
||||
// object as |cert_tree| parameter, otherwise SNI does not work.
|
||||
SSL_CTX *setup_server_ssl_context(CertLookupTree *cert_tree);
|
||||
// object as |cert_tree| parameter, otherwise SNI does not work. All
|
||||
// the created SSL_CTX is stored into |all_ssl_ctx|.
|
||||
SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
||||
CertLookupTree *cert_tree);
|
||||
|
||||
// Setups client side SSL_CTX. This function inspects get_config()
|
||||
// and if downstream_no_tls is true, returns nullptr. Otherwise, only
|
||||
|
@ -155,6 +170,8 @@ SSL_CTX *setup_client_ssl_context();
|
|||
// this function returns nullptr.
|
||||
CertLookupTree *create_cert_lookup_tree();
|
||||
|
||||
int get_ocsp_response(std::vector<uint8_t> &out, const char *cert_file);
|
||||
|
||||
} // namespace ssl
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
33
src/util.cc
33
src/util.cc
|
@ -963,6 +963,7 @@ int64_t parse_uint(const uint8_t *s, size_t len) {
|
|||
}
|
||||
|
||||
double parse_duration_with_unit(const char *s) {
|
||||
constexpr auto max = std::numeric_limits<int64_t>::max();
|
||||
int64_t n;
|
||||
size_t i;
|
||||
auto len = strlen(s);
|
||||
|
@ -976,17 +977,36 @@ double parse_duration_with_unit(const char *s) {
|
|||
switch (s[i]) {
|
||||
case 'S':
|
||||
case 's':
|
||||
// seconds
|
||||
if (i + 1 != len) {
|
||||
goto fail;
|
||||
}
|
||||
return static_cast<double>(n);
|
||||
break;
|
||||
case 'M':
|
||||
case 'm':
|
||||
if (i + 1 == len) {
|
||||
// minutes
|
||||
if (n > max / 60) {
|
||||
goto fail;
|
||||
}
|
||||
return static_cast<double>(n) * 60;
|
||||
}
|
||||
|
||||
if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) {
|
||||
goto fail;
|
||||
}
|
||||
// milliseconds
|
||||
return static_cast<double>(n) / 1000.;
|
||||
case 'H':
|
||||
case 'h':
|
||||
// hours
|
||||
if (i + 1 != len) {
|
||||
goto fail;
|
||||
}
|
||||
if (n > max / 3600) {
|
||||
goto fail;
|
||||
}
|
||||
return static_cast<double>(n) * 3600;
|
||||
}
|
||||
fail:
|
||||
return std::numeric_limits<double>::infinity();
|
||||
|
@ -1000,7 +1020,16 @@ std::string duration_str(double t) {
|
|||
if (frac > 0) {
|
||||
return utos(static_cast<int64_t>(t * 1000)) + "ms";
|
||||
}
|
||||
return utos(static_cast<int64_t>(t)) + "s";
|
||||
auto v = static_cast<int64_t>(t);
|
||||
if (v % 60) {
|
||||
return utos(v) + "s";
|
||||
}
|
||||
v /= 60;
|
||||
if (v % 60) {
|
||||
return utos(v) + "m";
|
||||
}
|
||||
v /= 60;
|
||||
return utos(v) + "h";
|
||||
}
|
||||
|
||||
std::string format_duration(const std::chrono::microseconds &u) {
|
||||
|
|
|
@ -303,6 +303,8 @@ void test_util_parse_duration_with_unit(void) {
|
|||
CU_ASSERT(0.500 == util::parse_duration_with_unit("500ms"));
|
||||
CU_ASSERT(123. == util::parse_duration_with_unit("123S"));
|
||||
CU_ASSERT(0.500 == util::parse_duration_with_unit("500MS"));
|
||||
CU_ASSERT(180 == util::parse_duration_with_unit("3m"));
|
||||
CU_ASSERT(3600 * 5 == util::parse_duration_with_unit("5h"));
|
||||
|
||||
auto err = std::numeric_limits<double>::infinity();
|
||||
// check overflow case
|
||||
|
@ -321,6 +323,9 @@ void test_util_duration_str(void) {
|
|||
CU_ASSERT("1s" == util::duration_str(1.));
|
||||
CU_ASSERT("500ms" == util::duration_str(0.5));
|
||||
CU_ASSERT("1500ms" == util::duration_str(1.5));
|
||||
CU_ASSERT("2m" == util::duration_str(120.));
|
||||
CU_ASSERT("121s" == util::duration_str(121.));
|
||||
CU_ASSERT("1h" == util::duration_str(3600.));
|
||||
}
|
||||
|
||||
void test_util_format_duration(void) {
|
||||
|
|
|
@ -27,3 +27,6 @@ noinst_LTLIBRARIES = libhttp-parser.la
|
|||
libhttp_parser_la_SOURCES = \
|
||||
http-parser/http_parser.c \
|
||||
http-parser/http_parser.h
|
||||
|
||||
|
||||
dist_pkgdata_SCRIPTS = h2o/fetch-ocsp-response
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
fetch-ocsp-response is a Perl script to perform OCSP query and get
|
||||
response. It uses openssl command under the hood. nghttpx uses it to
|
||||
enable OCSP stapling feature.
|
||||
|
||||
fetch-ocsp-response has been developed as part of h2o project
|
||||
(https://github.com/h2o/h2o). The script file with the same name in
|
||||
this directory was copied from their github repository.
|
||||
|
||||
fetch-ocsp-response is usually installed under $(pkgdatadir), which is
|
||||
$(prefix)/share/nghttp2.
|
|
@ -0,0 +1,150 @@
|
|||
#! /bin/sh
|
||||
exec perl -x $0 "$@"
|
||||
#! perl
|
||||
|
||||
# Copyright (c) 2015 DeNA Co., Ltd.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use File::Temp qw(tempdir);
|
||||
use Getopt::Long;
|
||||
|
||||
# from sysexits.h
|
||||
use constant EX_TEMPFAIL => 75;
|
||||
|
||||
my ($issuer_fn, $opt_help);
|
||||
my $openssl_cmd = 'openssl';
|
||||
|
||||
GetOptions(
|
||||
"issuer=s" => \$issuer_fn,
|
||||
"openssl=s", => \$openssl_cmd,
|
||||
help => \$opt_help,
|
||||
) or exit(1);
|
||||
if ($opt_help) {
|
||||
print << "EOT";
|
||||
Usage: $0 [<options>] <certificate-file>
|
||||
|
||||
Options:
|
||||
--issuer <file> issuer certificate (if omitted, is extracted from the
|
||||
certificate chain)
|
||||
--openssl <cmd> openssl command to use (default: "openssl")
|
||||
--help prints this help
|
||||
|
||||
The command issues an OCSP request for given server certificate, verifies the
|
||||
response and prints the resulting DER.
|
||||
|
||||
The command exits 0 if successful, or 75 (EX_TEMPFAIL) on temporary error.
|
||||
Other exit codes may be returned in case of hard errors.
|
||||
|
||||
EOT
|
||||
exit(0);
|
||||
}
|
||||
|
||||
die "no certificate file\n"
|
||||
if @ARGV == 0;
|
||||
my $cert_fn = shift @ARGV;
|
||||
|
||||
my $tempdir = tempdir(CLEANUP => 1);
|
||||
|
||||
my $openssl_version = run_openssl("version");
|
||||
chomp $openssl_version;
|
||||
print STDERR "fetch-ocsp-response (using $openssl_version)\n";
|
||||
|
||||
# obtain ocsp uri
|
||||
my $ocsp_uri = run_openssl("x509 -in $cert_fn -noout -ocsp_uri");
|
||||
chomp $ocsp_uri;
|
||||
die "failed to extract ocsp URI from $cert_fn\n"
|
||||
if $ocsp_uri !~ m{^https?://};
|
||||
my($ocsp_host) = $ocsp_uri =~ m{^https?://([^/:]+)};
|
||||
|
||||
# save issuer certificate
|
||||
if (! defined $issuer_fn) {
|
||||
my $chain = read_file($cert_fn);
|
||||
$chain =~ m{-----END CERTIFICATE-----.*?(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----)}s
|
||||
or die "--issuer option was not used, and failed to extract issuer certificate from the certificate\n";
|
||||
$issuer_fn = "$tempdir/issuer.crt";
|
||||
write_file($issuer_fn, "$1\n");
|
||||
}
|
||||
|
||||
# obtain response (without verification)
|
||||
print STDERR "sending OCSP request to $ocsp_uri\n";
|
||||
my $resp = run_openssl(
|
||||
"ocsp -issuer $issuer_fn -cert $cert_fn -url $ocsp_uri"
|
||||
. ($openssl_version =~ /^OpenSSL 1\./is ? " -header Host $ocsp_host" : "")
|
||||
. " -noverify -respout $tempdir/resp.der " . join(' ', @ARGV),
|
||||
1,
|
||||
);
|
||||
print STDERR $resp;
|
||||
|
||||
# verify the response
|
||||
print STDERR "verifying the response signature\n";
|
||||
my $success;
|
||||
for my $args (
|
||||
# try from exotic options
|
||||
"-VAfile $issuer_fn", # for comodo
|
||||
"-partial_chain -trusted_first -CAfile $issuer_fn", # these options are only available in OpenSSL >= 1.0.2
|
||||
"-CAfile $issuer_fn", # for OpenSSL <= 1.0.1
|
||||
) {
|
||||
if (system("$openssl_cmd ocsp -respin $tempdir/resp.der $args > $tempdir/verify.out 2>&1") == 0) {
|
||||
print STDERR "verify OK (used: $args)\n";
|
||||
$success = 1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if (! $success) {
|
||||
print STDERR read_file("$tempdir/verify.out");
|
||||
tempfail("failed to verify the response\n");
|
||||
}
|
||||
|
||||
# success
|
||||
print read_file("$tempdir/resp.der");
|
||||
exit 0;
|
||||
|
||||
sub run_openssl {
|
||||
my ($args, $tempfail) = @_;
|
||||
open my $fh, "-|", "$openssl_cmd $args"
|
||||
or die "failed to invoke $openssl_cmd:$!";
|
||||
my $resp = do { local $/; <$fh> };
|
||||
close $fh
|
||||
or ($tempfail ? \&tempfail : \&die)->("OpenSSL exitted abnormally: $openssl_cmd $args:$!");
|
||||
$resp;
|
||||
}
|
||||
|
||||
sub read_file {
|
||||
my $fn = shift;
|
||||
open my $fh, "<", $fn
|
||||
or die "failed to open file:$fn:$!";
|
||||
local $/;
|
||||
<$fh>;
|
||||
}
|
||||
|
||||
sub write_file {
|
||||
my ($fn, $data) = @_;
|
||||
open my $fh, ">", $fn
|
||||
or die "failed to open file:$fn:$!";
|
||||
print $fh $data;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
sub tempfail {
|
||||
print STDERR @_;
|
||||
exit EX_TEMPFAIL;
|
||||
}
|
Loading…
Reference in New Issue