diff --git a/doc/nghttpx.h2r b/doc/nghttpx.h2r index 0ae9b3ce..667facaf 100644 --- a/doc/nghttpx.h2r +++ b/doc/nghttpx.h2r @@ -384,6 +384,11 @@ respectively. Return the subject name of a client certificate. + .. rb:attr_reader:: tls_client_serial + + Return the serial number of a client certificate in a format + like "ff:ff:ff:ff:ff:ff:ff:ff". + .. rb:attr_reader:: tls_cipher Return a TLS cipher negotiated in this connection. diff --git a/gennghttpxfun.py b/gennghttpxfun.py index ddfba421..7e391d21 100755 --- a/gennghttpxfun.py +++ b/gennghttpxfun.py @@ -195,6 +195,7 @@ LOGVARS = [ "tls_client_fingerprint_sha1", "tls_client_subject_name", "tls_client_issuer_name", + "tls_client_serial", "backend_host", "backend_port", ] diff --git a/src/shrpx.cc b/src/shrpx.cc index a4f145b2..ac584772 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -2502,6 +2502,8 @@ Logging: certificate. * $tls_client_issuer_name: issuer name in client certificate. + * $tls_client_serial: serial number in client + certificate. * $tls_protocol: protocol for SSL/TLS connection. * $tls_session_id: session ID for SSL/TLS connection. * $tls_session_reused: "r" if SSL/TLS session was diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index f30e2522..734ce810 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -498,6 +498,15 @@ LogFragmentType log_var_lookup_token(const char *name, size_t namelen) { break; } break; + case 17: + switch (name[16]) { + case 'l': + if (util::strieq_l("tls_client_seria", name, 16)) { + return SHRPX_LOGF_TLS_CLIENT_SERIAL; + } + break; + } + break; case 18: switch (name[17]) { case 'd': diff --git a/src/shrpx_log.cc b/src/shrpx_log.cc index cc9f9da2..89559cab 100644 --- a/src/shrpx_log.cc +++ b/src/shrpx_log.cc @@ -579,6 +579,25 @@ void upstream_accesslog(const std::vector &lfv, std::tie(p, last) = copy(name, p, last); break; } + case SHRPX_LOGF_TLS_CLIENT_SERIAL: { + if (!lgsp.ssl) { + std::tie(p, last) = copy('-', p, last); + break; + } + auto x = SSL_get_peer_certificate(lgsp.ssl); + if (!x) { + std::tie(p, last) = copy('-', p, last); + break; + } + auto sn = tls::get_x509_serial(balloc, x); + X509_free(x); + if (sn.empty()) { + std::tie(p, last) = copy('-', p, last); + break; + } + std::tie(p, last) = copy(sn, p, last); + break; + } case SHRPX_LOGF_BACKEND_HOST: if (!downstream_addr) { std::tie(p, last) = copy('-', p, last); diff --git a/src/shrpx_log.h b/src/shrpx_log.h index 0e4e106e..6185b95a 100644 --- a/src/shrpx_log.h +++ b/src/shrpx_log.h @@ -141,6 +141,7 @@ enum LogFragmentType { SHRPX_LOGF_TLS_CLIENT_FINGERPRINT_SHA1, SHRPX_LOGF_TLS_CLIENT_FINGERPRINT_SHA256, SHRPX_LOGF_TLS_CLIENT_ISSUER_NAME, + SHRPX_LOGF_TLS_CLIENT_SERIAL, SHRPX_LOGF_TLS_CLIENT_SUBJECT_NAME, SHRPX_LOGF_BACKEND_HOST, SHRPX_LOGF_BACKEND_PORT, diff --git a/src/shrpx_mruby_module_env.cc b/src/shrpx_mruby_module_env.cc index 982e728f..70cdcea7 100644 --- a/src/shrpx_mruby_module_env.cc +++ b/src/shrpx_mruby_module_env.cc @@ -235,6 +235,30 @@ mrb_value env_get_tls_client_issuer_name(mrb_state *mrb, mrb_value self) { } } // namespace +namespace { +mrb_value env_get_tls_client_serial(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_str_new_static(mrb, "", 0); + } + + auto x = SSL_get_peer_certificate(ssl); + if (!x) { + return mrb_str_new_static(mrb, "", 0); + } + + auto &balloc = downstream->get_block_allocator(); + auto sn = tls::get_x509_serial(balloc, x); + X509_free(x); + return mrb_str_new(mrb, sn.c_str(), sn.size()); +} +} // namespace + namespace { mrb_value env_get_tls_cipher(mrb_state *mrb, mrb_value self) { auto data = static_cast(mrb->ud); @@ -348,6 +372,8 @@ void init_env_class(mrb_state *mrb, RClass *module) { env_get_tls_client_issuer_name, MRB_ARGS_NONE()); mrb_define_method(mrb, env_class, "tls_client_subject_name", 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_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 6f5327cf..bf3309c5 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -1959,6 +1959,46 @@ StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x) { return get_x509_name(balloc, X509_get_issuer_name(x)); } +StringRef get_x509_serial(BlockAllocator &balloc, X509 *x) { +#if OPENSSL_1_1_API + auto sn = X509_get0_serialNumber(x); + uint64_t r; + if (ASN1_INTEGER_get_uint64(&r, sn) != 1) { + return StringRef{}; + } + + auto iov = make_byte_ref(balloc, 16 + 7 + 1); + auto p = iov.base; + for (int i = 56; i >= 0; i -= 8) { + auto a = r >> i; + *p++ = util::LOWER_XDIGITS[(a >> 4) & 0xf]; + *p++ = util::LOWER_XDIGITS[a & 0xf]; + *p++ = ':'; + } +#else // !OPENSSL_1_1_API + auto sn = X509_get_serialNumber(x); + auto bn = BN_new(); + auto bn_d = defer(BN_free, bn); + if (!ASN1_INTEGER_to_BN(sn, bn)) { + return StringRef{}; + } + + std::array b; + auto n = BN_bn2bin(bn, b.data()); + assert(n == b.size()); + + auto iov = make_byte_ref(balloc, 16 + 7 + 1); + auto p = iov.base; + for (auto c : b) { + *p++ = util::LOWER_XDIGITS[c >> 4]; + *p++ = util::LOWER_XDIGITS[c & 0xf]; + *p++ = ':'; + } +#endif // !OPENSSL_1_1_API + *--p = '\0'; + return StringRef{iov.base, p}; +} + } // namespace tls } // namespace shrpx diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h index 17ecaf40..52ecbab1 100644 --- a/src/shrpx_tls.h +++ b/src/shrpx_tls.h @@ -284,6 +284,11 @@ StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x); // name, it returns an empty string. StringRef get_x509_issuer_name(BlockAllocator &balloc, X509 *x); +// Returns serial number of |x| in format like +// "ff:ff:ff:ff:ff:ff:ff:ff". If this function fails to get serial +// number, it returns an empty string. number +StringRef get_x509_serial(BlockAllocator &balloc, X509 *x); + } // namespace tls } // namespace shrpx