Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
N
nghttp2
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Libraries
nghttp2
Commits
4bc9afe2
Commit
4bc9afe2
authored
Mar 30, 2015
by
Tatsuhiro Tsujikawa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
nghttpx: Add OCSP stapling feature
parent
ccea4d42
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
570 additions
and
13 deletions
+570
-13
src/Makefile.am
src/Makefile.am
+1
-0
src/shrpx.cc
src/shrpx.cc
+60
-4
src/shrpx_config.cc
src/shrpx_config.cc
+19
-0
src/shrpx_config.h
src/shrpx_config.h
+6
-0
src/shrpx_connection_handler.cc
src/shrpx_connection_handler.cc
+97
-3
src/shrpx_connection_handler.h
src/shrpx_connection_handler.h
+19
-1
src/shrpx_ssl.cc
src/shrpx_ssl.cc
+150
-1
src/shrpx_ssl.h
src/shrpx_ssl.h
+19
-2
src/util.cc
src/util.cc
+31
-2
src/util_test.cc
src/util_test.cc
+5
-0
third-party/Makefile.am
third-party/Makefile.am
+3
-0
third-party/h2o/README.rst
third-party/h2o/README.rst
+10
-0
third-party/h2o/fetch-ocsp-response
third-party/h2o/fetch-ocsp-response
+150
-0
No files found.
src/Makefile.am
View file @
4bc9afe2
...
...
@@ -28,6 +28,7 @@ TESTS =
AM_CFLAGS
=
$(WARNCFLAGS)
AM_CPPFLAGS
=
\
-DPKGDATADIR
=
'"
$(pkgdatadir)
"'
\
-Wall
\
-I
$(top_srcdir)
/lib/includes
\
-I
$(top_builddir)
/lib/includes
\
...
...
src/shrpx.cc
View file @
4bc9afe2
...
...
@@ -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
);
...
...
src/shrpx_config.cc
View file @
4bc9afe2
...
...
@@ -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"
;
...
...
src/shrpx_config.h
View file @
4bc9afe2
...
...
@@ -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
();
...
...
src/shrpx_connection_handler.cc
View file @
4bc9afe2
...
...
@@ -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
src/shrpx_connection_handler.h
View file @
4bc9afe2
...
...
@@ -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_
;
};
...
...
src/shrpx_ssl.cc
View file @
4bc9afe2
...
...
@@ -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
src/shrpx_ssl.h
View file @
4bc9afe2
...
...
@@ -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
...
...
src/util.cc
View file @
4bc9afe2
...
...
@@ -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
)
{
...
...
src/util_test.cc
View file @
4bc9afe2
...
...
@@ -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
)
{
...
...
third-party/Makefile.am
View file @
4bc9afe2
...
...
@@ -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
third-party/h2o/README.rst
0 → 100644
View file @
4bc9afe2
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.
third-party/h2o/fetch-ocsp-response
0 → 100755
View file @
4bc9afe2
#! /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
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment