nghttpx: Add OCSP stapling feature
This commit is contained in:
parent
ccea4d42b5
commit
4bc9afe20a
|
@ -28,6 +28,7 @@ TESTS =
|
||||||
|
|
||||||
AM_CFLAGS = $(WARNCFLAGS)
|
AM_CFLAGS = $(WARNCFLAGS)
|
||||||
AM_CPPFLAGS = \
|
AM_CPPFLAGS = \
|
||||||
|
-DPKGDATADIR='"$(pkgdatadir)"' \
|
||||||
-Wall \
|
-Wall \
|
||||||
-I$(top_srcdir)/lib/includes \
|
-I$(top_srcdir)/lib/includes \
|
||||||
-I$(top_builddir)/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)) {
|
(!worker || worker->get_worker_stat()->num_connections == 0)) {
|
||||||
ev_break(loop);
|
ev_break(loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn_handler->handle_ocsp_completion();
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -740,6 +742,10 @@ int event_loop() {
|
||||||
refresh_timer.data = conn_handler.get();
|
refresh_timer.data = conn_handler.get();
|
||||||
ev_timer_again(loop, &refresh_timer);
|
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)) {
|
if (LOG_ENABLED(INFO)) {
|
||||||
LOG(INFO) << "Entering event loop";
|
LOG(INFO) << "Entering event loop";
|
||||||
}
|
}
|
||||||
|
@ -747,6 +753,7 @@ int event_loop() {
|
||||||
ev_run(loop, 0);
|
ev_run(loop, 0);
|
||||||
|
|
||||||
conn_handler->join_worker();
|
conn_handler->join_worker();
|
||||||
|
conn_handler->join_ocsp_thread();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -918,6 +925,11 @@ void fill_default_config() {
|
||||||
mod_config()->no_server_push = false;
|
mod_config()->no_server_push = false;
|
||||||
mod_config()->host_unix = false;
|
mod_config()->host_unix = false;
|
||||||
mod_config()->http2_downstream_connections_per_worker = 0;
|
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
|
} // namespace
|
||||||
|
|
||||||
|
@ -942,7 +954,8 @@ void print_help(std::ostream &out) {
|
||||||
Set path to server's private key. Required unless -p,
|
Set path to server's private key. Required unless -p,
|
||||||
--client or --frontend-no-tls are given.
|
--client or --frontend-no-tls are given.
|
||||||
<CERT> Set path to server's certificate. Required unless -p,
|
<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:
|
Options:
|
||||||
The options are categorized into several groups.
|
The options are categorized into several groups.
|
||||||
|
@ -1130,7 +1143,8 @@ SSL/TLS:
|
||||||
Specify additional certificate and private key file.
|
Specify additional certificate and private key file.
|
||||||
nghttpx will choose certificates based on the hostname
|
nghttpx will choose certificates based on the hostname
|
||||||
indicated by client using TLS SNI extension. This
|
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>
|
--backend-tls-sni-field=<HOST>
|
||||||
Explicitly set the content of the TLS SNI extension.
|
Explicitly set the content of the TLS SNI extension.
|
||||||
This will default to the backend HOST name.
|
This will default to the backend HOST name.
|
||||||
|
@ -1191,6 +1205,15 @@ SSL/TLS:
|
||||||
objects, which means session ID generated by one worker
|
objects, which means session ID generated by one worker
|
||||||
is not acceptable by another worker. On the other hand,
|
is not acceptable by another worker. On the other hand,
|
||||||
session ticket key is shared across all worker threads.
|
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:
|
HTTP/2 and SPDY:
|
||||||
-c, --http2-max-concurrent-streams=<N>
|
-c, --http2-max-concurrent-streams=<N>
|
||||||
|
@ -1379,8 +1402,9 @@ Misc:
|
||||||
10 * 1024). Units are K, M and G (powers of 1024).
|
10 * 1024). Units are K, M and G (powers of 1024).
|
||||||
|
|
||||||
The <DURATION> argument is an integer and an optional unit (e.g., 1s
|
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
|
is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms
|
||||||
a unit is omitted, a second is used as unit.)" << std::endl;
|
(hours, minutes, seconds and milliseconds, respectively). If a unit
|
||||||
|
is omitted, a second is used as unit.)" << std::endl;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -1492,6 +1516,9 @@ int main(int argc, char **argv) {
|
||||||
{"no-host-rewrite", no_argument, &flag, 73},
|
{"no-host-rewrite", no_argument, &flag, 73},
|
||||||
{"no-server-push", no_argument, &flag, 74},
|
{"no-server-push", no_argument, &flag, 74},
|
||||||
{"backend-http2-connections-per-worker", required_argument, &flag, 76},
|
{"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}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
int option_index = 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,
|
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER,
|
||||||
optarg);
|
optarg);
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -2000,6 +2039,23 @@ int main(int argc, char **argv) {
|
||||||
exit(EXIT_FAILURE);
|
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()) {
|
if (get_config()->downstream_addrs.empty()) {
|
||||||
DownstreamAddr addr;
|
DownstreamAddr addr;
|
||||||
addr.host = strcopy(DEFAULT_DOWNSTREAM_HOST);
|
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_NO_SERVER_PUSH[] = "no-server-push";
|
||||||
const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[] =
|
const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[] =
|
||||||
"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 {
|
namespace {
|
||||||
Config *config = nullptr;
|
Config *config = nullptr;
|
||||||
|
@ -1200,6 +1203,22 @@ int parse_config(const char *opt, const char *optarg) {
|
||||||
opt, 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")) {
|
if (util::strieq(opt, "conf")) {
|
||||||
LOG(WARN) << "conf: ignored";
|
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_BACKEND_RESPONSE_BUFFER[];
|
||||||
extern const char SHRPX_OPT_NO_SERVER_PUSH[];
|
extern const char SHRPX_OPT_NO_SERVER_PUSH[];
|
||||||
extern const char SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER[];
|
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 {
|
union sockaddr_union {
|
||||||
sockaddr_storage storage;
|
sockaddr_storage storage;
|
||||||
|
@ -209,6 +212,7 @@ struct Config {
|
||||||
ev_tstamp stream_write_timeout;
|
ev_tstamp stream_write_timeout;
|
||||||
ev_tstamp downstream_idle_read_timeout;
|
ev_tstamp downstream_idle_read_timeout;
|
||||||
ev_tstamp listener_disable_timeout;
|
ev_tstamp listener_disable_timeout;
|
||||||
|
ev_tstamp ocsp_update_interval;
|
||||||
// address of frontend connection. This could be a path to UNIX
|
// address of frontend connection. This could be a path to UNIX
|
||||||
// domain socket. In this case, |host_unix| must be true.
|
// domain socket. In this case, |host_unix| must be true.
|
||||||
std::unique_ptr<char[]> host;
|
std::unique_ptr<char[]> host;
|
||||||
|
@ -246,6 +250,7 @@ struct Config {
|
||||||
std::unique_ptr<char[]> client_cert_file;
|
std::unique_ptr<char[]> client_cert_file;
|
||||||
std::unique_ptr<char[]> accesslog_file;
|
std::unique_ptr<char[]> accesslog_file;
|
||||||
std::unique_ptr<char[]> errorlog_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_request_header;
|
||||||
FILE *http2_upstream_dump_response_header;
|
FILE *http2_upstream_dump_response_header;
|
||||||
nghttp2_session_callbacks *http2_upstream_callbacks;
|
nghttp2_session_callbacks *http2_upstream_callbacks;
|
||||||
|
@ -324,6 +329,7 @@ struct Config {
|
||||||
bool no_server_push;
|
bool no_server_push;
|
||||||
// true if host contains UNIX domain socket path
|
// true if host contains UNIX domain socket path
|
||||||
bool host_unix;
|
bool host_unix;
|
||||||
|
bool no_ocsp;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Config *get_config();
|
const Config *get_config();
|
||||||
|
|
|
@ -58,15 +58,32 @@ void acceptor_disable_cb(struct ev_loop *loop, ev_timer *w, int revent) {
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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)
|
ConnectionHandler::ConnectionHandler(struct ev_loop *loop)
|
||||||
: single_worker_(nullptr), loop_(loop), worker_round_robin_cnt_(0),
|
: single_worker_(nullptr), loop_(loop), worker_round_robin_cnt_(0),
|
||||||
graceful_shutdown_(false) {
|
graceful_shutdown_(false) {
|
||||||
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
|
ev_timer_init(&disable_acceptor_timer_, acceptor_disable_cb, 0., 0.);
|
||||||
disable_acceptor_timer_.data = this;
|
disable_acceptor_timer_.data = this;
|
||||||
|
|
||||||
|
ev_timer_init(&ocsp_timer_, ocsp_cb, 0., 0.);
|
||||||
|
ocsp_timer_.data = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionHandler::~ConnectionHandler() {
|
ConnectionHandler::~ConnectionHandler() {
|
||||||
ev_timer_stop(loop_, &disable_acceptor_timer_);
|
ev_timer_stop(loop_, &disable_acceptor_timer_);
|
||||||
|
ev_timer_stop(loop_, &ocsp_timer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionHandler::worker_reopen_log_files() {
|
void ConnectionHandler::worker_reopen_log_files() {
|
||||||
|
@ -95,7 +112,7 @@ void ConnectionHandler::worker_renew_ticket_keys(
|
||||||
|
|
||||||
void ConnectionHandler::create_single_worker() {
|
void ConnectionHandler::create_single_worker() {
|
||||||
auto cert_tree = ssl::create_cert_lookup_tree();
|
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();
|
auto cl_ssl_ctx = ssl::setup_client_ssl_context();
|
||||||
|
|
||||||
single_worker_ = make_unique<Worker>(loop_, sv_ssl_ctx, cl_ssl_ctx, cert_tree,
|
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) {
|
if (!get_config()->tls_ctx_per_worker) {
|
||||||
cert_tree = ssl::create_cert_lookup_tree();
|
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();
|
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) {
|
if (get_config()->tls_ctx_per_worker) {
|
||||||
cert_tree = ssl::create_cert_lookup_tree();
|
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();
|
cl_ssl_ctx = ssl::setup_client_ssl_context();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,4 +327,80 @@ bool ConnectionHandler::get_graceful_shutdown() const {
|
||||||
return graceful_shutdown_;
|
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
|
} // namespace shrpx
|
||||||
|
|
|
@ -32,6 +32,9 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#ifndef NOTHREADS
|
||||||
|
#include <future>
|
||||||
|
#endif // !NOTHREADS
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
@ -48,7 +51,6 @@ class Worker;
|
||||||
struct WorkerStat;
|
struct WorkerStat;
|
||||||
struct TicketKeys;
|
struct TicketKeys;
|
||||||
|
|
||||||
// TODO should be renamed as ConnectionHandler
|
|
||||||
class ConnectionHandler {
|
class ConnectionHandler {
|
||||||
public:
|
public:
|
||||||
ConnectionHandler(struct ev_loop *loop);
|
ConnectionHandler(struct ev_loop *loop);
|
||||||
|
@ -77,8 +79,23 @@ public:
|
||||||
void set_graceful_shutdown(bool f);
|
void set_graceful_shutdown(bool f);
|
||||||
bool get_graceful_shutdown() const;
|
bool get_graceful_shutdown() const;
|
||||||
void join_worker();
|
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:
|
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.
|
// Worker instances when multi threaded mode (-nN, N >= 2) is used.
|
||||||
std::vector<std::unique_ptr<Worker>> workers_;
|
std::vector<std::unique_ptr<Worker>> workers_;
|
||||||
// Worker instance used when single threaded mode (-n1) is used.
|
// Worker instance used when single threaded mode (-n1) is used.
|
||||||
|
@ -94,6 +111,7 @@ private:
|
||||||
// acceptor for IPv6 address
|
// acceptor for IPv6 address
|
||||||
std::unique_ptr<AcceptHandler> acceptor6_;
|
std::unique_ptr<AcceptHandler> acceptor6_;
|
||||||
ev_timer disable_acceptor_timer_;
|
ev_timer disable_acceptor_timer_;
|
||||||
|
ev_timer ocsp_timer_;
|
||||||
unsigned int worker_round_robin_cnt_;
|
unsigned int worker_round_robin_cnt_;
|
||||||
bool graceful_shutdown_;
|
bool graceful_shutdown_;
|
||||||
};
|
};
|
||||||
|
|
151
src/shrpx_ssl.cc
151
src/shrpx_ssl.cc
|
@ -28,6 +28,11 @@
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <netinet/tcp.h>
|
#include <netinet/tcp.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#ifndef NOTHREADS
|
||||||
|
#include <spawn.h>
|
||||||
|
#endif // !NOTHREADS
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#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;
|
return SSL_TLSEXT_ERR_OK;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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_servername_callback(ssl_ctx, servername_callback);
|
||||||
SSL_CTX_set_tlsext_ticket_key_cb(ssl_ctx, ticket_key_cb);
|
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);
|
SSL_CTX_set_info_callback(ssl_ctx, info_callback);
|
||||||
|
|
||||||
// NPN advertisement
|
// NPN advertisement
|
||||||
|
@ -426,6 +457,12 @@ SSL_CTX *create_ssl_context(const char *private_key_file,
|
||||||
// ALPN selection callback
|
// ALPN selection callback
|
||||||
SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr);
|
SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, nullptr);
|
||||||
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
|
#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;
|
return ssl_ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,7 +966,8 @@ bool check_http2_requirement(SSL *ssl) {
|
||||||
return true;
|
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) {
|
if (get_config()->upstream_no_tls) {
|
||||||
return nullptr;
|
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(),
|
auto ssl_ctx = ssl::create_ssl_context(get_config()->private_key_file.get(),
|
||||||
get_config()->cert_file.get());
|
get_config()->cert_file.get());
|
||||||
|
|
||||||
|
all_ssl_ctx.push_back(ssl_ctx);
|
||||||
|
|
||||||
if (get_config()->subcerts.empty()) {
|
if (get_config()->subcerts.empty()) {
|
||||||
return ssl_ctx;
|
return ssl_ctx;
|
||||||
}
|
}
|
||||||
|
@ -950,6 +990,7 @@ SSL_CTX *setup_server_ssl_context(CertLookupTree *cert_tree) {
|
||||||
for (auto &keycert : get_config()->subcerts) {
|
for (auto &keycert : get_config()->subcerts) {
|
||||||
auto ssl_ctx =
|
auto ssl_ctx =
|
||||||
ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str());
|
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(
|
if (ssl::cert_lookup_tree_add_cert_from_file(
|
||||||
cert_tree, ssl_ctx, keycert.second.c_str()) == -1) {
|
cert_tree, ssl_ctx, keycert.second.c_str()) == -1) {
|
||||||
LOG(FATAL) << "Failed to add sub certificate.";
|
LOG(FATAL) << "Failed to add sub certificate.";
|
||||||
|
@ -984,6 +1025,114 @@ CertLookupTree *create_cert_lookup_tree() {
|
||||||
return new ssl::CertLookupTree();
|
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 ssl
|
||||||
|
|
||||||
} // namespace shrpx
|
} // namespace shrpx
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include "shrpx.h"
|
#include "shrpx.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <openssl/ssl.h>
|
#include <openssl/ssl.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
@ -42,6 +43,18 @@ class DownstreamConnectionPool;
|
||||||
|
|
||||||
namespace ssl {
|
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
|
// Create server side SSL_CTX
|
||||||
SSL_CTX *create_ssl_context(const char *private_key_file,
|
SSL_CTX *create_ssl_context(const char *private_key_file,
|
||||||
const char *cert_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
|
// and if upstream_no_tls is true, returns nullptr. Otherwise
|
||||||
// construct default SSL_CTX. If subcerts are available
|
// construct default SSL_CTX. If subcerts are available
|
||||||
// (get_config()->subcerts), caller should provide CertLookupTree
|
// (get_config()->subcerts), caller should provide CertLookupTree
|
||||||
// object as |cert_tree| parameter, otherwise SNI does not work.
|
// object as |cert_tree| parameter, otherwise SNI does not work. All
|
||||||
SSL_CTX *setup_server_ssl_context(CertLookupTree *cert_tree);
|
// 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()
|
// Setups client side SSL_CTX. This function inspects get_config()
|
||||||
// and if downstream_no_tls is true, returns nullptr. Otherwise, only
|
// 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.
|
// this function returns nullptr.
|
||||||
CertLookupTree *create_cert_lookup_tree();
|
CertLookupTree *create_cert_lookup_tree();
|
||||||
|
|
||||||
|
int get_ocsp_response(std::vector<uint8_t> &out, const char *cert_file);
|
||||||
|
|
||||||
} // namespace ssl
|
} // namespace ssl
|
||||||
|
|
||||||
} // namespace shrpx
|
} // 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) {
|
double parse_duration_with_unit(const char *s) {
|
||||||
|
constexpr auto max = std::numeric_limits<int64_t>::max();
|
||||||
int64_t n;
|
int64_t n;
|
||||||
size_t i;
|
size_t i;
|
||||||
auto len = strlen(s);
|
auto len = strlen(s);
|
||||||
|
@ -976,17 +977,36 @@ double parse_duration_with_unit(const char *s) {
|
||||||
switch (s[i]) {
|
switch (s[i]) {
|
||||||
case 'S':
|
case 'S':
|
||||||
case 's':
|
case 's':
|
||||||
|
// seconds
|
||||||
if (i + 1 != len) {
|
if (i + 1 != len) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
return static_cast<double>(n);
|
return static_cast<double>(n);
|
||||||
break;
|
|
||||||
case 'M':
|
case 'M':
|
||||||
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')) {
|
if (i + 2 != len || (s[i + 1] != 's' && s[i + 1] != 'S')) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
// milliseconds
|
||||||
return static_cast<double>(n) / 1000.;
|
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:
|
fail:
|
||||||
return std::numeric_limits<double>::infinity();
|
return std::numeric_limits<double>::infinity();
|
||||||
|
@ -1000,7 +1020,16 @@ std::string duration_str(double t) {
|
||||||
if (frac > 0) {
|
if (frac > 0) {
|
||||||
return utos(static_cast<int64_t>(t * 1000)) + "ms";
|
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) {
|
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(0.500 == util::parse_duration_with_unit("500ms"));
|
||||||
CU_ASSERT(123. == util::parse_duration_with_unit("123S"));
|
CU_ASSERT(123. == util::parse_duration_with_unit("123S"));
|
||||||
CU_ASSERT(0.500 == util::parse_duration_with_unit("500MS"));
|
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();
|
auto err = std::numeric_limits<double>::infinity();
|
||||||
// check overflow case
|
// check overflow case
|
||||||
|
@ -321,6 +323,9 @@ void test_util_duration_str(void) {
|
||||||
CU_ASSERT("1s" == util::duration_str(1.));
|
CU_ASSERT("1s" == util::duration_str(1.));
|
||||||
CU_ASSERT("500ms" == util::duration_str(0.5));
|
CU_ASSERT("500ms" == util::duration_str(0.5));
|
||||||
CU_ASSERT("1500ms" == util::duration_str(1.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) {
|
void test_util_format_duration(void) {
|
||||||
|
|
|
@ -27,3 +27,6 @@ noinst_LTLIBRARIES = libhttp-parser.la
|
||||||
libhttp_parser_la_SOURCES = \
|
libhttp_parser_la_SOURCES = \
|
||||||
http-parser/http_parser.c \
|
http-parser/http_parser.c \
|
||||||
http-parser/http_parser.h
|
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