diff --git a/doc/nghttpx.h2r b/doc/nghttpx.h2r index 384127ee..cad39b16 100644 --- a/doc/nghttpx.h2r +++ b/doc/nghttpx.h2r @@ -388,6 +388,16 @@ respectively. Return the serial number of a client certificate. + .. rb:attr_reader:: tls_client_not_before + + Return the start date of a client certificate in seconds since + the epoch. + + .. rb:attr_reader:: tls_client_not_after + + Return the end date of a client certificate in seconds since + the epoch. + .. rb:attr_reader:: tls_cipher Return a TLS cipher negotiated in this connection. diff --git a/src/shrpx_mruby_module_env.cc b/src/shrpx_mruby_module_env.cc index 70cdcea7..9a7c3640 100644 --- a/src/shrpx_mruby_module_env.cc +++ b/src/shrpx_mruby_module_env.cc @@ -259,6 +259,58 @@ mrb_value env_get_tls_client_serial(mrb_state *mrb, mrb_value self) { } } // namespace +namespace { +mrb_value env_get_tls_client_not_before(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_fixnum_value(0); + } + + auto x = SSL_get_peer_certificate(ssl); + if (!x) { + return mrb_fixnum_value(0); + } + + time_t t; + if (tls::get_x509_not_before(t, x) != 0) { + return mrb_fixnum_value(0); + } + + return mrb_fixnum_value(t); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_not_after(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + auto handler = upstream->get_client_handler(); + auto ssl = handler->get_ssl(); + + if (!ssl) { + return mrb_fixnum_value(0); + } + + auto x = SSL_get_peer_certificate(ssl); + if (!x) { + return mrb_fixnum_value(0); + } + + time_t t; + if (tls::get_x509_not_after(t, x) != 0) { + return mrb_fixnum_value(0); + } + + return mrb_fixnum_value(t); +} +} // namespace + namespace { mrb_value env_get_tls_cipher(mrb_state *mrb, mrb_value self) { auto data = static_cast(mrb->ud); @@ -374,6 +426,10 @@ void init_env_class(mrb_state *mrb, RClass *module) { env_get_tls_client_subject_name, MRB_ARGS_NONE()); mrb_define_method(mrb, env_class, "tls_client_serial", env_get_tls_client_serial, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_not_before", + env_get_tls_client_not_before, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_not_after", + env_get_tls_client_not_after, MRB_ARGS_NONE()); mrb_define_method(mrb, env_class, "tls_cipher", env_get_tls_cipher, MRB_ARGS_NONE()); mrb_define_method(mrb, env_class, "tls_protocol", env_get_tls_protocol, diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index d0bf6085..6d07e2cf 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -64,6 +64,7 @@ #include "tls.h" #include "template.h" #include "ssl_compat.h" +#include "timegm.h" using namespace nghttp2; @@ -1992,6 +1993,74 @@ StringRef get_x509_serial(BlockAllocator &balloc, X509 *x) { #endif // !OPENSSL_1_1_API } +namespace { +// Performs conversion from |at| to time_t. The result is stored in +// |t|. This function returns 0 if it succeeds, or -1. +int time_t_from_asn1_time(time_t &t, const ASN1_TIME *at) { + int rv; + +#if OPENSSL_1_1_1_API + struct tm tm; + rv = ASN1_TIME_to_tm(at, &tm); + if (rv != 1) { + return -1; + } + + t = nghttp2_timegm(&tm); +#else // !OPENSSL_1_1_1_API + auto b = BIO_new(BIO_s_mem()); + if (!b) { + return -1; + } + + auto bio_deleter = defer(BIO_free, b); + + rv = ASN1_TIME_print(b, at); + if (rv != 1) { + return -1; + } + + unsigned char *s; + auto slen = BIO_get_mem_data(b, &s); + auto tt = util::parse_openssl_asn1_time_print( + StringRef{s, static_cast(slen)}); + if (tt == 0) { + return -1; + } + + t = tt; +#endif // !OPENSSL_1_1_1_API + + return 0; +} +} // namespace + +int get_x509_not_before(time_t &t, X509 *x) { +#if OPENSSL_1_1_API + auto at = X509_get0_notBefore(x); +#else // !OPENSSL_1_1_API + auto at = X509_get_notBefore(x); +#endif // !OPENSSL_1_1_API + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + +int get_x509_not_after(time_t &t, X509 *x) { +#if OPENSSL_1_1_API + auto at = X509_get0_notAfter(x); +#else // !OPENSSL_1_1_API + auto at = X509_get_notAfter(x); +#endif // !OPENSSL_1_1_API + if (!at) { + return -1; + } + + return time_t_from_asn1_time(t, at); +} + } // namespace tls } // namespace shrpx diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h index a368dabf..f3ee63c2 100644 --- a/src/shrpx_tls.h +++ b/src/shrpx_tls.h @@ -288,6 +288,14 @@ StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x); // number, it returns an empty string. number StringRef get_x509_serial(BlockAllocator &balloc, X509 *x); +// Fills NotBefore of |x| in |t|. This function returns 0 if it +// succeeds, or -1. +int get_x509_not_before(time_t &t, X509 *x); + +// Fills NotAfter of |x| in |t|. This function returns 0 if it +// succeeds, or -1. +int get_x509_not_after(time_t &t, X509 *x); + } // namespace tls } // namespace shrpx diff --git a/src/util.cc b/src/util.cc index 606d1b15..36ca5b2e 100644 --- a/src/util.cc +++ b/src/util.cc @@ -407,6 +407,15 @@ time_t parse_http_date(const StringRef &s) { #endif // !_WIN32 } +time_t parse_openssl_asn1_time_print(const StringRef &s) { + tm tm{}; + auto r = strptime(s.c_str(), "%b %d %H:%M:%S %Y GMT", &tm); + if (r == nullptr) { + return 0; + } + return nghttp2_timegm_without_yday(&tm); +} + char upcase(char c) { if ('a' <= c && c <= 'z') { return c - 'a' + 'A'; diff --git a/src/util.h b/src/util.h index 66f83de7..cc85b2c5 100644 --- a/src/util.h +++ b/src/util.h @@ -196,6 +196,11 @@ char *iso8601_date(char *res, int64_t ms); time_t parse_http_date(const StringRef &s); +// Parses time formatted as "MMM DD HH:MM:SS YYYY [GMT]" (e.g., Feb 3 +// 00:55:52 2015 GMT), which is specifically used by OpenSSL +// ASN1_TIME_print(). +time_t parse_openssl_asn1_time_print(const StringRef &s); + char upcase(char c); inline char lowcase(char c) {