From 9f80a82c1a8120b0dc710fd5b471fb5c3444efee Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 29 Oct 2017 00:25:20 +0900 Subject: [PATCH] nghttpx: Add client fingerprint and subject name to mruby env --- doc/nghttpx.h2r | 8 +++++ src/shrpx_mruby_module_env.cc | 60 +++++++++++++++++++++++++++++++++++ src/shrpx_tls.cc | 31 ++++++++++++++++++ src/shrpx_tls.h | 9 ++++++ 4 files changed, 108 insertions(+) diff --git a/doc/nghttpx.h2r b/doc/nghttpx.h2r index 86bf80e0..a1dc59f5 100644 --- a/doc/nghttpx.h2r +++ b/doc/nghttpx.h2r @@ -368,6 +368,14 @@ respectively. Return the TLS SNI value which client sent in this connection. + .. rb:attr_reader:: tls_client_fingerprint + + Return the SHA-256 fingerprint of a client certificate. + + .. rb:attr_reader:: tls_client_subject_name + + Return the subject name of a client certificate. + .. rb:class:: Request Object to represent request from client. The modification to diff --git a/src/shrpx_mruby_module_env.cc b/src/shrpx_mruby_module_env.cc index 078a4751..ba220e1a 100644 --- a/src/shrpx_mruby_module_env.cc +++ b/src/shrpx_mruby_module_env.cc @@ -34,6 +34,7 @@ #include "shrpx_mruby.h" #include "shrpx_mruby_module.h" #include "shrpx_log.h" +#include "shrpx_tls.h" namespace shrpx { @@ -140,6 +141,61 @@ mrb_value env_get_tls_sni(mrb_state *mrb, mrb_value self) { } } // namespace +namespace { +mrb_value env_get_tls_client_fingerprint(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); + } + + // Fingerprint is SHA-256, so we need 32 bytes buffer. + std::array buf; + auto slen = tls::get_x509_fingerprint(buf.data(), buf.size(), x); + if (slen == -1) { + mrb_raise(mrb, E_RUNTIME_ERROR, "could not compute client fingerprint"); + } + + // TODO Use template version of format_hex + auto &balloc = downstream->get_block_allocator(); + auto f = util::format_hex(balloc, + StringRef{std::begin(buf), std::begin(buf) + slen}); + return mrb_str_new(mrb, f.c_str(), f.size()); +} +} // namespace + +namespace { +mrb_value env_get_tls_client_subject_name(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 name = tls::get_x509_subject_name(balloc, x); + return mrb_str_new(mrb, name.c_str(), name.size()); +} +} // namespace + void init_env_class(mrb_state *mrb, RClass *module) { auto env_class = mrb_define_class_under(mrb, module, "Env", mrb->object_class); @@ -159,6 +215,10 @@ void init_env_class(mrb_state *mrb, RClass *module) { MRB_ARGS_NONE()); mrb_define_method(mrb, env_class, "tls_sni", env_get_tls_sni, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_fingerprint", + env_get_tls_client_fingerprint, MRB_ARGS_NONE()); + mrb_define_method(mrb, env_class, "tls_client_subject_name", + env_get_tls_client_subject_name, MRB_ARGS_NONE()); } } // namespace mruby diff --git a/src/shrpx_tls.cc b/src/shrpx_tls.cc index 9224ec3b..806ae103 100644 --- a/src/shrpx_tls.cc +++ b/src/shrpx_tls.cc @@ -1920,6 +1920,37 @@ int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp, return 0; } +ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, X509 *x) { + assert(dstlen >= 32); + unsigned int len = dstlen; + if (X509_digest(x, EVP_sha256(), dst, &len) != 1) { + return -1; + } + return len; +} + +StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x) { + auto nm = X509_get_subject_name(x); + + auto b = BIO_new(BIO_s_mem()); + if (!b) { + return StringRef{}; + } + + // Not documented, but it seems that X509_NAME_print_ex returns the + // number of bytes written into b. + auto slen = X509_NAME_print_ex(b, nm, 0, XN_FLAG_RFC2253); + if (slen <= 0) { + return StringRef{}; + } + + auto iov = make_byte_ref(balloc, slen + 1); + BIO_read(b, iov.base, slen); + BIO_free(b); + iov.base[slen] = '\0'; + return StringRef{iov.base, static_cast(slen)}; +} + } // namespace tls } // namespace shrpx diff --git a/src/shrpx_tls.h b/src/shrpx_tls.h index 20be9607..6fd8d2b6 100644 --- a/src/shrpx_tls.h +++ b/src/shrpx_tls.h @@ -269,6 +269,15 @@ int proto_version_from_string(const StringRef &v); int verify_ocsp_response(SSL_CTX *ssl_ctx, const uint8_t *ocsp_resp, size_t ocsp_resplen); +// Stores SHA-256 fingerprint of |x| in |dst| of length |dstlen|. +// |dstlen| must be larger than 32 bytes. This function returns the +// number of bytes written in |dst|, or -1. +ssize_t get_x509_fingerprint(uint8_t *dst, size_t dstlen, X509 *x); + +// Returns subject name of |x|. If this function fails to get subject +// name, it returns an empty string. +StringRef get_x509_subject_name(BlockAllocator &balloc, X509 *x); + } // namespace tls } // namespace shrpx