nghttpx: Rewrite CertLookupTree using Router
This commit is contained in:
parent
2a4733857f
commit
f7c0d48152
|
@ -166,7 +166,7 @@ if(ENABLE_APP)
|
|||
)
|
||||
target_include_directories(nghttpx-unittest PRIVATE ${CUNIT_INCLUDE_DIRS})
|
||||
target_compile_definitions(nghttpx-unittest
|
||||
PRIVATE "-DNGHTTP2_TESTS_DIR=\"${CMAKE_SOURCE_DIR}/tests\""
|
||||
PRIVATE "-DNGHTTP2_SRC_DIR=\"${CMAKE_SOURCE_DIR}/src\""
|
||||
)
|
||||
target_link_libraries(nghttpx-unittest nghttpx_static ${CUNIT_LIBRARIES})
|
||||
if(HAVE_MRUBY)
|
||||
|
|
|
@ -22,7 +22,10 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
SUBDIRS = includes
|
||||
|
||||
EXTRA_DIST = CMakeLists.txt
|
||||
EXTRA_DIST = \
|
||||
CMakeLists.txt \
|
||||
test.example.com.pem \
|
||||
test.nghttp2.org.pem
|
||||
|
||||
bin_PROGRAMS =
|
||||
check_PROGRAMS =
|
||||
|
@ -187,7 +190,7 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
|||
template_test.cc template_test.h \
|
||||
base64_test.cc base64_test.h
|
||||
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
|
||||
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
|
||||
-DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\"
|
||||
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
|
||||
|
||||
if HAVE_MRUBY
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"signing": {
|
||||
"default": {
|
||||
"expiry": "87600h"
|
||||
},
|
||||
"profiles": {
|
||||
"server": {
|
||||
"expiry": "87600h",
|
||||
"usages": [
|
||||
"signing",
|
||||
"key encipherment",
|
||||
"server auth"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA1kVkF8QSUwW/HV9EFRPSMoiOVmYwB8vqKDtT0d6MFiKAM8/Y
|
||||
JFUq2uKlUydgT4IPE7PATvVcIj3GtL9XzPhscqYO/S0Y7scyTE2VAPmtz+StPWf2
|
||||
wZ1IQR09HrnDTc44KvYGZpefBZkD9UjbmJ9a1ZmJjJiMr3hTnKE/sxZ2+dMsnMZX
|
||||
N822cfaHyTN+T0+Tyw5vBBboCDsZzxmf+9FFIDJNs3NL34cR8EZRhpfaegapH8bt
|
||||
OJ+D+RZ2kg7E/YYkGcS6NodvTjSUFCFHpWjHCfTFhn/owBIAooCdWorh6dc8Q72l
|
||||
AodwNLXS8uuPgPqM5s4Cz57m7Zgs4OilNmIdawIDAQABAoIBAQCwqLtygLye6KD+
|
||||
RXorapEmCsJX5553/x6Klwdvg+25ni5XCWjp47IWj0DBQzi7tL5bfxrxvod8z7QR
|
||||
d6SbIMLA77px8Ima7G7CzEAqcrBkM+TFOP8P+G4HCWVH/N5SOtDCUt9KHH4Grna9
|
||||
95jdx5yreRAX8/oh/bHp9GRBcicbpwYMVWOnjTE2seEUYQOpdpYdP4bOPUvAju0l
|
||||
mwmy2/dDGmbibktN3sdHEhDodKu+Znv7nFZo0jzhlyoXse653WcvaQeZZYuojvSe
|
||||
Sr92DvPp7UaYrb4KvT7ujXiPavSV2m/4EmGtyqevUf2dZ6sfMXZjmXsjWz9txhWp
|
||||
4BgbHyHRAoGBAPqyuNj2CDD3FE7N3Hxyba8d+ZtsVUNawjq2gwOvT9NLsMstOGyH
|
||||
OCc1v4W6Sq4w1wo4nIJyY8kNZwtReaTHOPZlDgBhVvk/x8eLBu+QTMRyocRt1LoD
|
||||
8HyKxWSAnYTtCh/GUEQ37amIqvOJ5GNL+25WDzevLa5kMYWG743uxEupAoGBANrN
|
||||
c/fVxepvP0GISlLpL3aZCFGAjMrq3xUYcf/w4wPoMq6AdpIPeRVBmJ1/Uqw1FkV8
|
||||
NRKJNPE2YcMuv8iMeQlacoPd34KT9ob80EYVlMwAkeC0NK+FfiM/UteR0wB49gmi
|
||||
ugX9YlJytOP9aUgPvEGT6l+XtgGC44W1TQWe62zzAoGBAKZenNU+0UjNb6isbToZ
|
||||
Jjkkh1Vhm2PLg0I7hM6ZNTxf6r+rDtrXEajTvnocmxrmRo796r+W8immv09/jl6P
|
||||
53l8rsIJ1xIqBYai+MNa29cyy6/zw0x++MVtwnlj8SUZubJEhVgAVbRAglKEnBBZ
|
||||
iE48xnSJyKMG0uZuGePzJEmhAoGBAIOHJcNBumum3DuklikpC+MbMyjrQbdpYRjp
|
||||
TP4x7AWZO34ysxQyQPNKL1feBfCHKRA0DiNKX4zwx+vw2lDQQKIiwNwMMCPqljOn
|
||||
HfxDVOMdJJQTP+iTMrQ1iLMVceXC0QQR0glvu/8b/SlgWD19WAmDxUwZgst9xw/F
|
||||
YLuUQKmJAoGAREeTugd4hc0U/YV/BQQjSCLhl11EtVry/oQMHj8KZpIJhP7tj8lw
|
||||
hSE0+z04oMhiTeq55PYKQkTo5l6V4PW0zfpEwlKEEm0erab1G9Ddh7us47XFcKLl
|
||||
Rmk192EVZ0lQuzftsYv7dzRLiAR7yDFXwD1ELIK/uPkwBtu7wtHlq+M=
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,17 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICwjCCAaoCAQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2Eu
|
||||
bmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWRWQX
|
||||
xBJTBb8dX0QVE9IyiI5WZjAHy+ooO1PR3owWIoAzz9gkVSra4qVTJ2BPgg8Ts8BO
|
||||
9VwiPca0v1fM+Gxypg79LRjuxzJMTZUA+a3P5K09Z/bBnUhBHT0eucNNzjgq9gZm
|
||||
l58FmQP1SNuYn1rVmYmMmIyveFOcoT+zFnb50yycxlc3zbZx9ofJM35PT5PLDm8E
|
||||
FugIOxnPGZ/70UUgMk2zc0vfhxHwRlGGl9p6Bqkfxu04n4P5FnaSDsT9hiQZxLo2
|
||||
h29ONJQUIUelaMcJ9MWGf+jAEgCigJ1aiuHp1zxDvaUCh3A0tdLy64+A+ozmzgLP
|
||||
nubtmCzg6KU2Yh1rAgMBAAGgHzAdBgkqhkiG9w0BCQ4xEDAOMAwGA1UdEwQFMAMB
|
||||
Af8wDQYJKoZIhvcNAQELBQADggEBACI5v8GbOXKv38h9/tuGEwJ9uxpYEljgGt8h
|
||||
QL5lwfEifh/7A8b39b9JEzWk5hnMRCOb8J6Jc3/6nmVgtKkQ+Mceupqpwsp1gT/v
|
||||
uUoAkJE03Iuja9zLhHmy74oZ7LWOQrZ1T7Z0eGQ+5u+LBZiPKnKxmkLCQoUPTbc4
|
||||
NQ9BbKhr8OaoJ4DDvJnszcL7to6kih7SkdoNZsq4zB0/ai/cPhvoVgkYfbLH2++D
|
||||
Tcs7TqU2L7gKzqXUtHeAKM2y81ewL7QTrcYzgiW86s3NmquxZG5pq0mjD+P4BYLc
|
||||
MOdnCxKbBuE/1R29pa6+JKgc46jOa2yRgv5+8rXkkpu53Ke3FGc=
|
||||
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"CN": "ca.nghttp2.org",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"ca": {
|
||||
"expiry": "87600h"
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "AU",
|
||||
"ST": "Some-State",
|
||||
"O": "Internet Widgits Pty Ltd"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDrTCCApWgAwIBAgIUe4dvx8haIjsT3ZpNCMrl62Xk6E0wDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
|
||||
cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBeMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMRcwFQYDVQQDEw5jYS5uZ2h0dHAyLm9yZzCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBANZFZBfEElMFvx1fRBUT0jKIjlZmMAfL6ig7U9He
|
||||
jBYigDPP2CRVKtripVMnYE+CDxOzwE71XCI9xrS/V8z4bHKmDv0tGO7HMkxNlQD5
|
||||
rc/krT1n9sGdSEEdPR65w03OOCr2BmaXnwWZA/VI25ifWtWZiYyYjK94U5yhP7MW
|
||||
dvnTLJzGVzfNtnH2h8kzfk9Pk8sObwQW6Ag7Gc8Zn/vRRSAyTbNzS9+HEfBGUYaX
|
||||
2noGqR/G7Tifg/kWdpIOxP2GJBnEujaHb040lBQhR6Voxwn0xYZ/6MASAKKAnVqK
|
||||
4enXPEO9pQKHcDS10vLrj4D6jObOAs+e5u2YLODopTZiHWsCAwEAAaNjMGEwDgYD
|
||||
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNA5xVR1Zcax
|
||||
RJL9VC6pzuLmvduGMB8GA1UdIwQYMBaAFNA5xVR1ZcaxRJL9VC6pzuLmvduGMA0G
|
||||
CSqGSIb3DQEBCwUAA4IBAQCmdVfn/hUyEdvkKG7svg5d8o6BENOj8695KtWmzJjK
|
||||
zxH8J5Vy3mn89XrHQ+BOYXCDPyhs0aDS8aq3Z+HY0n9z1oAicyGzlVwZQQNX3YId
|
||||
Y2vcf7qu/2ATm/1S+mebE1/EXMUlWISKKUYXjggCwFgjDhH87Ai+A8MKScVdmqgL
|
||||
Hf+fRSzH3ToW7BCXlRl5bPAq2g+v1ALYc8wU9cT1MYm4dqAXh870LGFyUpaSWmFr
|
||||
TtX1DXBTgLp62syNlDthAvGigYFDtCa4cDM2vdTD9wpec2V9EKpfVqiRDDuYjUVX
|
||||
UXl27MvkNWnEBKCIoNv5abWXpZVG2zQdEMmUOkVuAXUC
|
||||
-----END CERTIFICATE-----
|
|
@ -72,8 +72,8 @@ int main(int argc, char *argv[]) {
|
|||
// add the tests to the suite
|
||||
if (!CU_add_test(pSuite, "ssl_create_lookup_tree",
|
||||
shrpx::test_shrpx_ssl_create_lookup_tree) ||
|
||||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
|
||||
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
|
||||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_x509",
|
||||
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_x509) ||
|
||||
!CU_add_test(pSuite, "ssl_tls_hostname_match",
|
||||
shrpx::test_shrpx_ssl_tls_hostname_match) ||
|
||||
!CU_add_test(pSuite, "http2_add_header", shrpx::test_http2_add_header) ||
|
||||
|
|
|
@ -878,4 +878,8 @@ void ConnectionHandler::send_serial_event(SerialEvent ev) {
|
|||
ev_async_send(loop_, &serial_event_asyncev_);
|
||||
}
|
||||
|
||||
SSL_CTX *ConnectionHandler::get_ssl_ctx(size_t idx) const {
|
||||
return all_ssl_ctx_[idx];
|
||||
}
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -146,6 +146,9 @@ public:
|
|||
ev_timer *w);
|
||||
void schedule_next_tls_ticket_key_memcached_get(ev_timer *w);
|
||||
SSL_CTX *create_tls_ticket_key_memcached_ssl_ctx();
|
||||
// Returns the SSL_CTX at all_ssl_ctx_[idx]. This does not perform
|
||||
// array bound checking.
|
||||
SSL_CTX *get_ssl_ctx(size_t idx) const;
|
||||
|
||||
#ifdef HAVE_NEVERBLEED
|
||||
void set_neverbleed(std::unique_ptr<neverbleed_t> nb);
|
||||
|
|
|
@ -279,6 +279,22 @@ ssize_t Router::match(const StringRef &host, const StringRef &path) const {
|
|||
return node->index;
|
||||
}
|
||||
|
||||
ssize_t Router::match(const StringRef &s) const {
|
||||
const RNode *node;
|
||||
size_t offset;
|
||||
|
||||
node = match_complete(&offset, &root_, std::begin(s), std::end(s));
|
||||
if (node == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (node->len != offset) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return node->index;
|
||||
}
|
||||
|
||||
namespace {
|
||||
const RNode *match_prefix(size_t *nread, const RNode *node, const char *first,
|
||||
const char *last) {
|
||||
|
|
|
@ -67,6 +67,9 @@ public:
|
|||
bool add_route(const StringRef &pattern, size_t index);
|
||||
// Returns the matched index of pattern. -1 if there is no match.
|
||||
ssize_t match(const StringRef &host, const StringRef &path) const;
|
||||
// Returns the matched index of pattern |s|. -1 if there is no
|
||||
// match.
|
||||
ssize_t match(const StringRef &s) const;
|
||||
// Returns the matched index of pattern if a pattern is a suffix of
|
||||
// |s|, otherwise -1. If |*last_node| is not nullptr, it specifies
|
||||
// the first node to start matching. If it is nullptr, match will
|
||||
|
|
342
src/shrpx_ssl.cc
342
src/shrpx_ssl.cc
|
@ -38,6 +38,8 @@
|
|||
#include <string>
|
||||
#include <iomanip>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
@ -58,6 +60,7 @@
|
|||
#include "shrpx_http2_session.h"
|
||||
#include "shrpx_memcached_request.h"
|
||||
#include "shrpx_memcached_dispatcher.h"
|
||||
#include "shrpx_connection_handler.h"
|
||||
#include "util.h"
|
||||
#include "ssl.h"
|
||||
#include "template.h"
|
||||
|
@ -143,16 +146,39 @@ int servername_callback(SSL *ssl, int *al, void *arg) {
|
|||
auto handler = static_cast<ClientHandler *>(conn->data);
|
||||
auto worker = handler->get_worker();
|
||||
auto cert_tree = worker->get_cert_lookup_tree();
|
||||
if (cert_tree) {
|
||||
const char *hostname = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
if (hostname) {
|
||||
auto len = strlen(hostname);
|
||||
auto ssl_ctx = cert_tree->lookup(StringRef{hostname, len});
|
||||
if (ssl_ctx) {
|
||||
if (!cert_tree) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
std::array<uint8_t, NI_MAXHOST> buf;
|
||||
|
||||
auto rawhost = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
if (rawhost == nullptr) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
auto len = strlen(rawhost);
|
||||
// NI_MAXHOST includes terminal NULL.
|
||||
if (len == 0 || len + 1 > buf.size()) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
auto end_buf = std::copy_n(rawhost, len, std::begin(buf));
|
||||
|
||||
util::inp_strlower(std::begin(buf), end_buf);
|
||||
|
||||
auto hostname = StringRef{std::begin(buf), end_buf};
|
||||
|
||||
auto idx = cert_tree->lookup(hostname);
|
||||
if (idx == -1) {
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
|
||||
auto conn_handler = worker->get_connection_handler();
|
||||
auto ssl_ctx = conn_handler->get_ssl_ctx(idx);
|
||||
|
||||
SSL_set_SSL_CTX(ssl, ssl_ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -1075,181 +1101,129 @@ int check_cert(SSL *ssl, const DownstreamAddr *addr) {
|
|||
return check_cert(ssl, &addr->addr, hostname);
|
||||
}
|
||||
|
||||
CertLookupTree::CertLookupTree() {
|
||||
root_.ssl_ctx = nullptr;
|
||||
root_.str = nullptr;
|
||||
root_.first = root_.last = 0;
|
||||
}
|
||||
CertLookupTree::CertLookupTree() {}
|
||||
|
||||
namespace {
|
||||
// The |offset| is the index in the hostname we are examining. We are
|
||||
// going to scan from |offset| in backwards.
|
||||
void cert_lookup_tree_add_cert(CertNode *node, SSL_CTX *ssl_ctx,
|
||||
const char *hostname, size_t len, int offset) {
|
||||
int i, next_len = node->next.size();
|
||||
char c = hostname[offset];
|
||||
CertNode *cn = nullptr;
|
||||
for (i = 0; i < next_len; ++i) {
|
||||
cn = node->next[i].get();
|
||||
if (cn->str[cn->first] == c) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == next_len) {
|
||||
if (c == '*') {
|
||||
// We assume hostname as wildcard hostname when first '*' is
|
||||
// encountered. Note that as per RFC 6125 (6.4.3), there are
|
||||
// some restrictions for wildcard hostname. We just ignore
|
||||
// these rules here but do the proper check when we do the
|
||||
// match.
|
||||
node->wildcard_certs.push_back({ssl_ctx, hostname, len});
|
||||
void CertLookupTree::add_cert(const StringRef &hostname, size_t idx) {
|
||||
std::array<uint8_t, NI_MAXHOST> buf;
|
||||
|
||||
// NI_MAXHOST includes terminal NULL byte
|
||||
if (hostname.empty() || hostname.size() + 1 > buf.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int j;
|
||||
auto new_node = make_unique<CertNode>();
|
||||
new_node->str = hostname;
|
||||
new_node->first = offset;
|
||||
// If wildcard is found, set the region before it because we
|
||||
// don't include it in [first, last).
|
||||
for (j = offset; j >= 0 && hostname[j] != '*'; --j)
|
||||
;
|
||||
new_node->last = j;
|
||||
if (j == -1) {
|
||||
new_node->ssl_ctx = ssl_ctx;
|
||||
auto wildcard_it = std::find(std::begin(hostname), std::end(hostname), '*');
|
||||
if (wildcard_it != std::end(hostname) &&
|
||||
wildcard_it + 1 != std::end(hostname)) {
|
||||
auto wildcard_prefix = StringRef{std::begin(hostname), wildcard_it};
|
||||
auto wildcard_suffix = StringRef{wildcard_it + 1, std::end(hostname)};
|
||||
|
||||
auto rev_suffix = StringRef{std::begin(buf),
|
||||
std::reverse_copy(std::begin(wildcard_suffix),
|
||||
std::end(wildcard_suffix),
|
||||
std::begin(buf))};
|
||||
|
||||
WildcardPattern *wpat;
|
||||
|
||||
if (!rev_wildcard_router_.add_route(rev_suffix,
|
||||
wildcard_patterns_.size())) {
|
||||
auto wcidx = rev_wildcard_router_.match(rev_suffix);
|
||||
|
||||
assert(wcidx != -1);
|
||||
|
||||
wpat = &wildcard_patterns_[wcidx];
|
||||
} else {
|
||||
new_node->ssl_ctx = nullptr;
|
||||
new_node->wildcard_certs.push_back({ssl_ctx, hostname, len});
|
||||
wildcard_patterns_.emplace_back();
|
||||
wpat = &wildcard_patterns_.back();
|
||||
}
|
||||
node->next.push_back(std::move(new_node));
|
||||
|
||||
auto rev_prefix = StringRef{std::begin(buf),
|
||||
std::reverse_copy(std::begin(wildcard_prefix),
|
||||
std::end(wildcard_prefix),
|
||||
std::begin(buf))};
|
||||
|
||||
wpat->rev_prefix.emplace_back(rev_prefix, idx);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int j;
|
||||
for (i = cn->first, j = offset;
|
||||
i > cn->last && j >= 0 && cn->str[i] == hostname[j]; --i, --j)
|
||||
;
|
||||
if (i == cn->last) {
|
||||
if (j == -1) {
|
||||
// If the same hostname already exists, we don't overwrite
|
||||
// exiting ssl_ctx
|
||||
if (!cn->ssl_ctx) {
|
||||
cn->ssl_ctx = ssl_ctx;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The existing hostname is a suffix of this hostname. Continue
|
||||
// matching at potion j.
|
||||
cert_lookup_tree_add_cert(cn, ssl_ctx, hostname, len, j);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
auto new_node = make_unique<CertNode>();
|
||||
new_node->ssl_ctx = cn->ssl_ctx;
|
||||
new_node->str = cn->str;
|
||||
new_node->first = i;
|
||||
new_node->last = cn->last;
|
||||
new_node->wildcard_certs.swap(cn->wildcard_certs);
|
||||
new_node->next.swap(cn->next);
|
||||
|
||||
cn->next.push_back(std::move(new_node));
|
||||
}
|
||||
|
||||
cn->last = i;
|
||||
if (j == -1) {
|
||||
// This hostname is a suffix of the existing hostname.
|
||||
cn->ssl_ctx = ssl_ctx;
|
||||
return;
|
||||
}
|
||||
|
||||
// This hostname and existing one share suffix.
|
||||
cn->ssl_ctx = nullptr;
|
||||
cert_lookup_tree_add_cert(cn, ssl_ctx, hostname, len, j);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void CertLookupTree::add_cert(SSL_CTX *ssl_ctx, const StringRef &hostname) {
|
||||
if (hostname.empty()) {
|
||||
return;
|
||||
}
|
||||
// Copy hostname
|
||||
auto host_copy = make_unique<char[]>(hostname.size() + 1);
|
||||
std::copy(std::begin(hostname), std::end(hostname), host_copy.get());
|
||||
host_copy[hostname.size()] = '\0';
|
||||
util::inp_strlower(&host_copy[0], &host_copy[0] + hostname.size());
|
||||
|
||||
cert_lookup_tree_add_cert(&root_, ssl_ctx, host_copy.get(), hostname.size(),
|
||||
hostname.size() - 1);
|
||||
|
||||
hosts_.push_back(std::move(host_copy));
|
||||
router_.add_route(hostname, idx);
|
||||
}
|
||||
|
||||
namespace {
|
||||
SSL_CTX *cert_lookup_tree_lookup(CertNode *node, const StringRef &hostname,
|
||||
int offset) {
|
||||
int i, j;
|
||||
for (i = node->first, j = offset;
|
||||
i > node->last && j >= 0 && node->str[i] == util::lowcase(hostname[j]);
|
||||
--i, --j)
|
||||
;
|
||||
if (i != node->last) {
|
||||
return nullptr;
|
||||
}
|
||||
if (j == -1) {
|
||||
if (node->ssl_ctx) {
|
||||
// exact match
|
||||
return node->ssl_ctx;
|
||||
}
|
||||
ssize_t CertLookupTree::lookup(const StringRef &hostname) {
|
||||
std::array<uint8_t, NI_MAXHOST> buf;
|
||||
|
||||
// Do not perform wildcard-match because '*' must match at least
|
||||
// one character.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto &wildcert : node->wildcard_certs) {
|
||||
if (tls_hostname_match(StringRef{wildcert.hostname, wildcert.hostnamelen},
|
||||
hostname)) {
|
||||
return wildcert.ssl_ctx;
|
||||
}
|
||||
}
|
||||
auto c = util::lowcase(hostname[j]);
|
||||
for (const auto &next_node : node->next) {
|
||||
if (next_node->str[next_node->first] == c) {
|
||||
return cert_lookup_tree_lookup(next_node.get(), hostname, j);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SSL_CTX *CertLookupTree::lookup(const StringRef &hostname) {
|
||||
if (hostname.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return cert_lookup_tree_lookup(&root_, hostname, hostname.size() - 1);
|
||||
}
|
||||
|
||||
int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
|
||||
const char *certfile) {
|
||||
auto bio = BIO_new(BIO_s_file());
|
||||
if (!bio) {
|
||||
LOG(ERROR) << "BIO_new failed";
|
||||
// NI_MAXHOST includes terminal NULL byte
|
||||
if (hostname.empty() || hostname.size() + 1 > buf.size()) {
|
||||
return -1;
|
||||
}
|
||||
auto bio_deleter = defer(BIO_vfree, bio);
|
||||
if (!BIO_read_filename(bio, certfile)) {
|
||||
LOG(ERROR) << "Could not read certificate file '" << certfile << "'";
|
||||
|
||||
// Always prefer exact match
|
||||
auto idx = router_.match(hostname);
|
||||
if (idx != -1) {
|
||||
return idx;
|
||||
}
|
||||
|
||||
if (wildcard_patterns_.empty()) {
|
||||
return -1;
|
||||
}
|
||||
auto cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
||||
if (!cert) {
|
||||
LOG(ERROR) << "Could not read X509 structure from file '" << certfile
|
||||
<< "'";
|
||||
return -1;
|
||||
|
||||
ssize_t best_idx = -1;
|
||||
size_t best_prefixlen = 0;
|
||||
const RNode *last_node = nullptr;
|
||||
|
||||
auto rev_host = StringRef{
|
||||
std::begin(buf), std::reverse_copy(std::begin(hostname),
|
||||
std::end(hostname), std::begin(buf))};
|
||||
|
||||
for (;;) {
|
||||
size_t nread = 0;
|
||||
|
||||
auto wcidx =
|
||||
rev_wildcard_router_.match_prefix(&nread, &last_node, rev_host);
|
||||
if (wcidx == -1) {
|
||||
return best_idx;
|
||||
}
|
||||
auto cert_deleter = defer(X509_free, cert);
|
||||
|
||||
// '*' must match at least one byte
|
||||
if (nread == rev_host.size()) {
|
||||
return best_idx;
|
||||
}
|
||||
|
||||
rev_host = StringRef{std::begin(rev_host) + nread, std::end(rev_host)};
|
||||
|
||||
auto rev_prefix = StringRef{std::begin(rev_host) + 1, std::end(rev_host)};
|
||||
|
||||
auto &wpat = wildcard_patterns_[wcidx];
|
||||
for (auto &wprefix : wpat.rev_prefix) {
|
||||
if (!util::ends_with(rev_prefix, wprefix.prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto prefixlen =
|
||||
wprefix.prefix.size() +
|
||||
(reinterpret_cast<const uint8_t *>(&rev_host[0]) - &buf[0]);
|
||||
|
||||
// Breaking a tie with longer suffix
|
||||
if (prefixlen < best_prefixlen) {
|
||||
continue;
|
||||
}
|
||||
|
||||
best_idx = wprefix.idx;
|
||||
best_prefixlen = prefixlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CertLookupTree::dump() const {
|
||||
std::cerr << "exact:" << std::endl;
|
||||
router_.dump();
|
||||
std::cerr << "wildcard suffix (reversed):" << std::endl;
|
||||
rev_wildcard_router_.dump();
|
||||
}
|
||||
|
||||
int cert_lookup_tree_add_cert_from_x509(CertLookupTree *lt, size_t idx,
|
||||
X509 *cert) {
|
||||
std::array<uint8_t, NI_MAXHOST> buf;
|
||||
|
||||
auto altnames = static_cast<GENERAL_NAMES *>(
|
||||
X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr));
|
||||
|
@ -1285,7 +1259,15 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
|
|||
}
|
||||
|
||||
dns_found = true;
|
||||
lt->add_cert(ssl_ctx, StringRef{name, static_cast<size_t>(len)});
|
||||
|
||||
if (static_cast<size_t>(len) + 1 > buf.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto end_buf = std::copy_n(name, len, std::begin(buf));
|
||||
util::inp_strlower(std::begin(buf), end_buf);
|
||||
|
||||
lt->add_cert(StringRef{std::begin(buf), end_buf}, idx);
|
||||
}
|
||||
|
||||
// Don't bother CN if we have dNSName.
|
||||
|
@ -1309,10 +1291,14 @@ int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
|
|||
cn = StringRef{cn.c_str(), cn.size() - 1};
|
||||
}
|
||||
|
||||
lt->add_cert(ssl_ctx, cn);
|
||||
auto end_buf = std::copy(std::begin(cn), std::end(cn), std::begin(buf));
|
||||
|
||||
OPENSSL_free(const_cast<char *>(cn.c_str()));
|
||||
|
||||
util::inp_strlower(std::begin(buf), end_buf);
|
||||
|
||||
lt->add_cert(StringRef{std::begin(buf), end_buf}, idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1365,6 +1351,13 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
return ssl_ctx;
|
||||
}
|
||||
|
||||
if (ssl::cert_lookup_tree_add_cert_from_x509(
|
||||
cert_tree, all_ssl_ctx.size() - 1,
|
||||
SSL_CTX_get0_certificate(ssl_ctx)) == -1) {
|
||||
LOG(FATAL) << "Failed to add default certificate.";
|
||||
DIE();
|
||||
}
|
||||
|
||||
for (auto &keycert : tlsconf.subcerts) {
|
||||
auto ssl_ctx =
|
||||
ssl::create_ssl_context(keycert.first.c_str(), keycert.second.c_str()
|
||||
|
@ -1374,19 +1367,14 @@ SSL_CTX *setup_server_ssl_context(std::vector<SSL_CTX *> &all_ssl_ctx,
|
|||
#endif // HAVE_NEVERBLEED
|
||||
);
|
||||
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) {
|
||||
if (ssl::cert_lookup_tree_add_cert_from_x509(
|
||||
cert_tree, all_ssl_ctx.size() - 1,
|
||||
SSL_CTX_get0_certificate(ssl_ctx)) == -1) {
|
||||
LOG(FATAL) << "Failed to add sub certificate.";
|
||||
DIE();
|
||||
}
|
||||
}
|
||||
|
||||
if (ssl::cert_lookup_tree_add_cert_from_file(
|
||||
cert_tree, ssl_ctx, tlsconf.cert_file.c_str()) == -1) {
|
||||
LOG(FATAL) << "Failed to add default certificate.";
|
||||
DIE();
|
||||
}
|
||||
|
||||
return ssl_ctx;
|
||||
}
|
||||
|
||||
|
|
103
src/shrpx_ssl.h
103
src/shrpx_ssl.h
|
@ -40,6 +40,7 @@
|
|||
#endif // HAVE_NEVERBLEED
|
||||
|
||||
#include "network.h"
|
||||
#include "shrpx_router.h"
|
||||
|
||||
namespace shrpx {
|
||||
|
||||
|
@ -103,73 +104,69 @@ int check_cert(SSL *ssl, const DownstreamAddr *addr);
|
|||
void get_altnames(X509 *cert, std::vector<std::string> &dns_names,
|
||||
std::vector<std::string> &ip_addrs, std::string &common_name);
|
||||
|
||||
// CertLookupTree forms lookup tree to get SSL_CTX whose DNS or
|
||||
// commonName matches hostname in query. The tree is patricia trie
|
||||
// data structure formed from the tail of the hostname pattern. Each
|
||||
// CertNode contains part of hostname str member in range [first,
|
||||
// last) member and the next member contains the following CertNode
|
||||
// pointers ('following' means character before the current one). The
|
||||
// CertNode where a hostname pattern ends contains its SSL_CTX pointer
|
||||
// in the ssl_ctx member. For wildcard hostname pattern, we store the
|
||||
// its pattern and SSL_CTX in CertNode one before first "*" found from
|
||||
// the tail.
|
||||
//
|
||||
// When querying SSL_CTX with particular hostname, we match from its
|
||||
// tail in our lookup tree. If the query goes to the first character
|
||||
// of the hostname and current CertNode has non-NULL ssl_ctx member,
|
||||
// then it is the exact match. The ssl_ctx member is returned. Along
|
||||
// the way, if CertNode which contains non-empty wildcard_certs member
|
||||
// is encountered, wildcard hostname matching is performed against
|
||||
// them. If there is a match, its SSL_CTX is returned. If none
|
||||
// matches, query is continued to the next character.
|
||||
struct WildcardRevPrefix {
|
||||
WildcardRevPrefix(const StringRef &prefix, size_t idx)
|
||||
: prefix(std::begin(prefix), std::end(prefix)), idx(idx) {}
|
||||
|
||||
struct WildcardCert {
|
||||
SSL_CTX *ssl_ctx;
|
||||
const char *hostname;
|
||||
size_t hostnamelen;
|
||||
// "Prefix" of wildcard pattern. It is reversed from original form.
|
||||
// For example, if the original wildcard is "test*.nghttp2.org",
|
||||
// prefix would be "tset".
|
||||
ImmutableString prefix;
|
||||
// The index of SSL_CTX. See ConnectionHandler::get_ssl_ctx().
|
||||
size_t idx;
|
||||
};
|
||||
|
||||
struct CertNode {
|
||||
// list of wildcard domain name and its SSL_CTX pair, the wildcard
|
||||
// '*' appears in this position.
|
||||
std::vector<WildcardCert> wildcard_certs;
|
||||
// Next CertNode index of CertLookupTree::nodes
|
||||
std::vector<std::unique_ptr<CertNode>> next;
|
||||
// SSL_CTX for exact match
|
||||
SSL_CTX *ssl_ctx;
|
||||
const char *str;
|
||||
// [first, last) in the reverse direction in str, first >=
|
||||
// last. This indices only work for str member.
|
||||
int first, last;
|
||||
struct WildcardPattern {
|
||||
// Wildcard host sharing only suffix is probably rare, so we just do
|
||||
// linear search.
|
||||
std::vector<WildcardRevPrefix> rev_prefix;
|
||||
};
|
||||
|
||||
class CertLookupTree {
|
||||
public:
|
||||
CertLookupTree();
|
||||
|
||||
// Adds |ssl_ctx| with hostname pattern |hostname| to the lookup
|
||||
// tree.
|
||||
void add_cert(SSL_CTX *ssl_ctx, const StringRef &hostname);
|
||||
// Adds hostname pattern |hostname| to the lookup tree, associating
|
||||
// value |index|. When the queried host matches this pattern,
|
||||
// |index| is returned. We support wildcard pattern. The left most
|
||||
// '*' is considered as wildcard character, and it must match at
|
||||
// least one character. If the same pattern has been already added,
|
||||
// this function is noop.
|
||||
//
|
||||
// The caller should lower-case |hostname| since this function does
|
||||
// do that, and lookup function performs case-sensitive match.
|
||||
//
|
||||
// TODO Treat wildcard pattern described as RFC 6125.
|
||||
void add_cert(const StringRef &hostname, size_t index);
|
||||
|
||||
// Looks up SSL_CTX using the given |hostname|. If more than one
|
||||
// SSL_CTX which matches the query, it is undefined which one is
|
||||
// returned. The |hostname| must be NULL-terminated. If no
|
||||
// matching SSL_CTX found, returns NULL.
|
||||
SSL_CTX *lookup(const StringRef &hostname);
|
||||
// Looks up index using the given |hostname|. The exact match takes
|
||||
// precedence over wildcard match. For wildcard match, longest
|
||||
// match (sum of matched suffix and prefix length in bytes) is
|
||||
// preferred, breaking a tie with longer suffix.
|
||||
//
|
||||
// The caller should lower-case |hostname| since this function
|
||||
// performs case-sensitive match.
|
||||
ssize_t lookup(const StringRef &hostname);
|
||||
|
||||
// Dumps the contents of this lookup tree to stderr.
|
||||
void dump() const;
|
||||
|
||||
private:
|
||||
CertNode root_;
|
||||
// Stores pointers to copied hostname when adding hostname and
|
||||
// ssl_ctx pair.
|
||||
std::vector<std::unique_ptr<char[]>> hosts_;
|
||||
// Exact match
|
||||
Router router_;
|
||||
// Wildcard reversed suffix match. The returned index is into
|
||||
// wildcard_patterns_.
|
||||
Router rev_wildcard_router_;
|
||||
// Stores wildcard suffix patterns.
|
||||
std::vector<WildcardPattern> wildcard_patterns_;
|
||||
};
|
||||
|
||||
// Adds |ssl_ctx| to lookup tree |lt| using hostnames read from
|
||||
// |certfile|. The subjectAltNames and commonName are considered as
|
||||
// eligible hostname. This function returns 0 if it succeeds, or -1.
|
||||
// Even if no ssl_ctx is added to tree, this function returns 0.
|
||||
int cert_lookup_tree_add_cert_from_file(CertLookupTree *lt, SSL_CTX *ssl_ctx,
|
||||
const char *certfile);
|
||||
// Adds hostnames in |cert| to lookup tree |lt|. The subjectAltNames
|
||||
// and commonName are considered as eligible hostname. If there is at
|
||||
// least one dNSName in subjectAltNames, commonName is not considered.
|
||||
// This function returns 0 if it succeeds, or -1.
|
||||
int cert_lookup_tree_add_cert_from_x509(CertLookupTree *lt, size_t idx,
|
||||
X509 *cert);
|
||||
|
||||
// Returns true if |proto| is included in the
|
||||
// protocol list |protos|.
|
||||
|
|
|
@ -36,83 +36,133 @@ namespace shrpx {
|
|||
|
||||
void test_shrpx_ssl_create_lookup_tree(void) {
|
||||
auto tree = make_unique<ssl::CertLookupTree>();
|
||||
SSL_CTX *ctxs[] = {
|
||||
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
|
||||
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
|
||||
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
|
||||
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
|
||||
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method())};
|
||||
|
||||
constexpr StringRef hostnames[] = {
|
||||
StringRef::from_lit("example.com"),
|
||||
StringRef::from_lit("www.example.org"),
|
||||
StringRef::from_lit("*www.example.org"),
|
||||
StringRef::from_lit("x*.host.domain"),
|
||||
StringRef::from_lit("*yy.host.domain"),
|
||||
StringRef::from_lit("nghttp2.sourceforge.net"),
|
||||
StringRef::from_lit("sourceforge.net"),
|
||||
StringRef::from_lit("sourceforge.net"), // duplicate
|
||||
StringRef::from_lit("*.foo.bar"), // oo.bar is suffix of *.foo.bar
|
||||
StringRef::from_lit("oo.bar")};
|
||||
auto num = array_size(ctxs);
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
tree->add_cert(ctxs[i], hostnames[i]);
|
||||
StringRef::from_lit("example.com"), // 0
|
||||
StringRef::from_lit("www.example.org"), // 1
|
||||
StringRef::from_lit("*www.example.org"), // 2
|
||||
StringRef::from_lit("xy*.host.domain"), // 3
|
||||
StringRef::from_lit("*yy.host.domain"), // 4
|
||||
StringRef::from_lit("nghttp2.sourceforge.net"), // 5
|
||||
StringRef::from_lit("sourceforge.net"), // 6
|
||||
StringRef::from_lit("sourceforge.net"), // 7, duplicate
|
||||
StringRef::from_lit("*.foo.bar"), // 8, oo.bar is suffix of *.foo.bar
|
||||
StringRef::from_lit("oo.bar") // 9
|
||||
};
|
||||
auto num = array_size(hostnames);
|
||||
|
||||
for (size_t idx = 0; idx < num; ++idx) {
|
||||
tree->add_cert(hostnames[idx], idx);
|
||||
}
|
||||
|
||||
CU_ASSERT(ctxs[0] == tree->lookup(hostnames[0]));
|
||||
CU_ASSERT(ctxs[1] == tree->lookup(hostnames[1]));
|
||||
CU_ASSERT(ctxs[2] == tree->lookup(StringRef::from_lit("2www.example.org")));
|
||||
CU_ASSERT(nullptr == tree->lookup(StringRef::from_lit("www2.example.org")));
|
||||
CU_ASSERT(ctxs[3] == tree->lookup(StringRef::from_lit("x1.host.domain")));
|
||||
tree->dump();
|
||||
|
||||
CU_ASSERT(0 == tree->lookup(hostnames[0]));
|
||||
CU_ASSERT(1 == tree->lookup(hostnames[1]));
|
||||
CU_ASSERT(2 == tree->lookup(StringRef::from_lit("2www.example.org")));
|
||||
CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("www2.example.org")));
|
||||
CU_ASSERT(3 == tree->lookup(StringRef::from_lit("xy1.host.domain")));
|
||||
// Does not match *yy.host.domain, because * must match at least 1
|
||||
// character.
|
||||
CU_ASSERT(nullptr == tree->lookup(StringRef::from_lit("yy.Host.domain")));
|
||||
CU_ASSERT(ctxs[4] == tree->lookup(StringRef::from_lit("zyy.host.domain")));
|
||||
CU_ASSERT(nullptr == tree->lookup(StringRef{}));
|
||||
CU_ASSERT(ctxs[5] == tree->lookup(hostnames[5]));
|
||||
CU_ASSERT(ctxs[6] == tree->lookup(hostnames[6]));
|
||||
CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("yy.host.domain")));
|
||||
CU_ASSERT(4 == tree->lookup(StringRef::from_lit("xyy.host.domain")));
|
||||
CU_ASSERT(-1 == tree->lookup(StringRef{}));
|
||||
CU_ASSERT(5 == tree->lookup(hostnames[5]));
|
||||
CU_ASSERT(6 == tree->lookup(hostnames[6]));
|
||||
constexpr char h6[] = "pdylay.sourceforge.net";
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
CU_ASSERT(0 == tree->lookup(StringRef{h6 + i, str_size(h6) - i}));
|
||||
CU_ASSERT(-1 == tree->lookup(StringRef{h6 + i, str_size(h6) - i}));
|
||||
}
|
||||
CU_ASSERT(ctxs[8] == tree->lookup(StringRef::from_lit("x.foo.bar")));
|
||||
CU_ASSERT(ctxs[9] == tree->lookup(hostnames[9]));
|
||||
CU_ASSERT(8 == tree->lookup(StringRef::from_lit("x.foo.bar")));
|
||||
CU_ASSERT(9 == tree->lookup(hostnames[9]));
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
SSL_CTX_free(ctxs[i]);
|
||||
}
|
||||
|
||||
SSL_CTX *ctxs2[] = {
|
||||
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method()),
|
||||
SSL_CTX_new(SSLv23_method()), SSL_CTX_new(SSLv23_method())};
|
||||
constexpr StringRef names[] = {
|
||||
StringRef::from_lit("rab"), StringRef::from_lit("zab"),
|
||||
StringRef::from_lit("zzub"), StringRef::from_lit("ab")};
|
||||
num = array_size(ctxs2);
|
||||
StringRef::from_lit("rab"), // 1
|
||||
StringRef::from_lit("zab"), // 2
|
||||
StringRef::from_lit("zzub"), // 3
|
||||
StringRef::from_lit("ab") // 4
|
||||
};
|
||||
num = array_size(names);
|
||||
|
||||
tree = make_unique<ssl::CertLookupTree>();
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
tree->add_cert(ctxs2[i], names[i]);
|
||||
for (size_t idx = 0; idx < num; ++idx) {
|
||||
tree->add_cert(names[idx], idx);
|
||||
}
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
CU_ASSERT(ctxs2[i] == tree->lookup(names[i]));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
SSL_CTX_free(ctxs2[i]);
|
||||
CU_ASSERT(i == tree->lookup(names[i]));
|
||||
}
|
||||
}
|
||||
|
||||
void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void) {
|
||||
int rv;
|
||||
ssl::CertLookupTree tree;
|
||||
auto ssl_ctx = SSL_CTX_new(SSLv23_method());
|
||||
const char certfile[] = NGHTTP2_TESTS_DIR "/testdata/cacert.pem";
|
||||
rv = ssl::cert_lookup_tree_add_cert_from_file(&tree, ssl_ctx, certfile);
|
||||
CU_ASSERT(0 == rv);
|
||||
CU_ASSERT(ssl_ctx == tree.lookup(StringRef::from_lit("localhost")));
|
||||
namespace {
|
||||
X509 *load_certificate(const char *filename) {
|
||||
auto bio = BIO_new(BIO_s_file());
|
||||
if (!bio) {
|
||||
fprintf(stderr, "BIO_new() failed\n");
|
||||
return nullptr;
|
||||
}
|
||||
auto bio_deleter = defer(BIO_vfree, bio);
|
||||
if (!BIO_read_filename(bio, filename)) {
|
||||
fprintf(stderr, "Could not read certificate file '%s'\n", filename);
|
||||
return nullptr;
|
||||
}
|
||||
auto cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
||||
if (!cert) {
|
||||
fprintf(stderr, "Could not read X509 structure from file '%s'\n", filename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return cert;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// We use cfssl to generate key pairs.
|
||||
//
|
||||
// CA self-signed key pairs generation:
|
||||
//
|
||||
// $ cfssl genkey -initca ca.nghttp2.org.csr.json | \
|
||||
// cfssljson -bare ca.nghttp2.org
|
||||
//
|
||||
// Create CSR:
|
||||
//
|
||||
// $ cfssl genkey test.nghttp2.org.csr.json | cfssljson -bare test.nghttp2.org
|
||||
// $ cfssl genkey test.example.com.csr.json | cfssljson -bare test.example.com
|
||||
//
|
||||
// Sign CSR:
|
||||
//
|
||||
// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem \
|
||||
// -config=ca-config.json -profile=server test.nghttp2.org.csr | \
|
||||
// cfssljson -bare test.nghttp2.org
|
||||
//
|
||||
// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem \
|
||||
// -config=ca-config.json -profile=server test.example.com.csr | \
|
||||
// cfssljson -bare test.example.com
|
||||
//
|
||||
void test_shrpx_ssl_cert_lookup_tree_add_cert_from_x509(void) {
|
||||
int rv;
|
||||
|
||||
constexpr char nghttp2_certfile[] = NGHTTP2_SRC_DIR "/test.nghttp2.org.pem";
|
||||
auto nghttp2_cert = load_certificate(nghttp2_certfile);
|
||||
auto nghttp2_cert_deleter = defer(X509_free, nghttp2_cert);
|
||||
|
||||
constexpr char examples_certfile[] = NGHTTP2_SRC_DIR "/test.example.com.pem";
|
||||
auto examples_cert = load_certificate(examples_certfile);
|
||||
auto examples_cert_deleter = defer(X509_free, examples_cert);
|
||||
|
||||
ssl::CertLookupTree tree;
|
||||
|
||||
rv = ssl::cert_lookup_tree_add_cert_from_x509(&tree, 0, nghttp2_cert);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
rv = ssl::cert_lookup_tree_add_cert_from_x509(&tree, 1, examples_cert);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
CU_ASSERT(-1 == tree.lookup(StringRef::from_lit("not-used.nghttp2.org")));
|
||||
CU_ASSERT(0 == tree.lookup(StringRef::from_lit("test.nghttp2.org")));
|
||||
CU_ASSERT(0 == tree.lookup(StringRef::from_lit("w.test.nghttp2.org")));
|
||||
CU_ASSERT(0 == tree.lookup(StringRef::from_lit("www.test.nghttp2.org")));
|
||||
CU_ASSERT(1 == tree.lookup(StringRef::from_lit("test.example.com")));
|
||||
}
|
||||
|
||||
template <size_t N, size_t M>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
namespace shrpx {
|
||||
|
||||
void test_shrpx_ssl_create_lookup_tree(void);
|
||||
void test_shrpx_ssl_cert_lookup_tree_add_cert_from_file(void);
|
||||
void test_shrpx_ssl_cert_lookup_tree_add_cert_from_x509(void);
|
||||
void test_shrpx_ssl_tls_hostname_match(void);
|
||||
|
||||
} // namespace shrpx
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2FroLDwqVUYRlxp
|
||||
U/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpowUxvTDv/GNIHH
|
||||
XK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmIsYsO0Y4iBtwB
|
||||
M/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvcVzvSgZIzMLqS
|
||||
zvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7G/ZbX/4kUU5b
|
||||
PabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABAoIBAQCjUL69iFOs7muK
|
||||
CZGFe/iU1uxQM6XuGPN7mso3z15W07UxdlS3o6ZUSoLTONWcBxP/N4knulHJjZSY
|
||||
0LivbDYz3htic3t0kdGmxOxPVnLKRXN6ncbQTaeAE8tlBMAcWd/UH2Tlylz+Ac//
|
||||
6cV3gNJMShwUmhb3l4v3Rml0nZ6PO1pFc/Chk5L9REAV8G6rNtc9bzgmgoFucRO/
|
||||
8ce/uJrENt1Pu3vBvmz42DTGfG48v5RZ0OY4qEPawZJ7p+QYiTf6h3Eilss/AllW
|
||||
PPfQ0thdyB+yrZ3p6qb+ZUYphpGxgg6YlQxLfDKAikuo+EXwjPBPfeHhTO4kAj+h
|
||||
opDCroZhAoGBANyVFbagCWqwguE6nVPmnCaiNQUIh8b7L2CnkkLfdbPQr/KvyIjg
|
||||
Ua125bTJhe9Uk+ZBWsobQkjA0Baidiylx51pWYaxPVn5araVmkh2dqMluk2QE82X
|
||||
AWemBgKhAqCLLLMVXbrRYlxpKUm1Fc/lJ8Ig2R/MJSntTMpQhJtIejUbAoGBAMnt
|
||||
XMvlFABCoFbI9GMcteI/KkvNGQUy3OKEln/QCssnE4/XIu7LCxy6P+1lycbFy/mQ
|
||||
0bnp525sPEIIkMpi6LeAbSzYN2O3BRjNrjPcbx6Khz9DweNhRIo5qTFRszZ+pHbV
|
||||
N+9Oc9JVenwPw6EuW7uZRFKFhCHtsBFdUrWLJoSVAoGAQ3ytdwGBwA2fDW/UgL32
|
||||
mm9YT2DrwbpKJYU/X4xkw44ett6HOTGAa9ULtINPogi7c2AdeeZbIk0znSk5hLF3
|
||||
4DZCOM5zWdrQhmpBGNh9ta6uUFq7ZFRGDsMh5Z4DYsER/PyVf7neIS3ffviTYtbW
|
||||
kjNgmrTnzesXanK2D5heI28CgYEAhl+qjRTYhoPP53C7EOmeL/0QzHij2c3LKAJL
|
||||
lKqBREewwNvNp1L/BhL7T6OY7unZny48IpgBJn5oaxkAIW5IpzSTcnBAC99TSPo2
|
||||
ntRmLdDJx9PzRrkHv2Q3r1ZLCEymbV3eZyWx9ZpkdAKZkL0k1mZcDP5Eu79Ml4Ge
|
||||
9Kiw7TECgYEAh+nTKwrCUFGbe4RIVCj/QG7FVPbq5PdxJ3gILZ3/1XkhPcNRFKJS
|
||||
u5qPfA02tYEALz9KXATK1uRB/GlBM7Eap/g2GFiHpVxrw6wPpybLywJmNyNTwqiq
|
||||
eJxQ0FRzW9Kwwn1ThPY38LdFe/wvXZFOcNvGD8hHCLQRdlBR4zuTsBk=
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,17 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICpTCCAY0CAQAwYDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEZMBcGA1UEAxMQdGVz
|
||||
dC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK39
|
||||
lAbBIf8cHf+FmX1RX+bDMlnJ7P9YnRdha6Cw8KlVGEZcaVPym6yBvF/L98OHFT/8
|
||||
Tl3dcvUPlvTiytmEnAHh3hPe5h2V23KaMFMb0w7/xjSBx1yv8r2DPEdhBJ2Ue9al
|
||||
3aF7AmpL/2htnsJJLwNdPHhG5NLu0cTZiLGLDtGOIgbcATPzEpAGD31na3R4WPc9
|
||||
JeDPBgMlDrmXNLeo9M8N385I6plYBhCb3Fc70oGSMzC6ks7wvPb4l7Y9acs0IzC4
|
||||
Jrg27NJeO7kR6WV4DBlchd1LkIqT874Tuxv2W1/+JFFOWz2m3bbz2447u3ROT3xq
|
||||
yjk4TOh0vd2O0L2E1LcCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBMAUqwty7R
|
||||
/YWRrC8NuvrbSsW0r7Z7FXWxny5w5ImONCgVffc2wVydtBVQ0rfd3pDZLyu0P4sx
|
||||
4bJ/KBz67t2MsOKbCMDS7SJuFwHu9AUzaYNh455HeBOVwb6LemJDNnCtMG9DgcRv
|
||||
2BpwKqekUVTGDuUQmLibjE8qwDHw/p9k4gjQBxlfJe2sIZGs6oA/JGFJUU6ZIn8Y
|
||||
M6aazrbjWexbWCnjhiXkNa8kfKiSHzU+2ct+GY5QxI221+63bXRiAi2/LK0gaY+p
|
||||
+3vYu75F7+8oPZOfsGmYEyPz7c1jPqcwPgVDk+sdvl1MO1TGFRaFNIlRP1DhpHkj
|
||||
fuJ/id6oUHhj
|
||||
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"CN": "test.example.com",
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "AU",
|
||||
"ST": "Some-State",
|
||||
"O": "Internet Widgits Pty Ltd"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDwTCCAqmgAwIBAgIUDhKNhGRUq1TSHD6aG2k4TRR8iA0wDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
|
||||
cmcwHhcNMTYwNjI1MDkzNzAwWhcNMjYwNjIzMDkzNzAwWjBgMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMRkwFwYDVQQDExB0ZXN0LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEArf2UBsEh/xwd/4WZfVFf5sMyWcns/1idF2Fr
|
||||
oLDwqVUYRlxpU/KbrIG8X8v3w4cVP/xOXd1y9Q+W9OLK2YScAeHeE97mHZXbcpow
|
||||
UxvTDv/GNIHHXK/yvYM8R2EEnZR71qXdoXsCakv/aG2ewkkvA108eEbk0u7RxNmI
|
||||
sYsO0Y4iBtwBM/MSkAYPfWdrdHhY9z0l4M8GAyUOuZc0t6j0zw3fzkjqmVgGEJvc
|
||||
VzvSgZIzMLqSzvC89viXtj1pyzQjMLgmuDbs0l47uRHpZXgMGVyF3UuQipPzvhO7
|
||||
G/ZbX/4kUU5bPabdtvPbjju7dE5PfGrKOThM6HS93Y7QvYTUtwIDAQABo3UwczAO
|
||||
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
|
||||
ADAdBgNVHQ4EFgQUm8jn1FICope9qUce6ORQ0CtbmhYwHwYDVR0jBBgwFoAU0DnF
|
||||
VHVlxrFEkv1ULqnO4ua924YwDQYJKoZIhvcNAQELBQADggEBAD7RPz/5rAnS1MNP
|
||||
JfAj1TXZSBwlYgtmJL65yaFB6a1SNSTo15deAm/1Vl10LbmYdV4sVnGKeZjhKNk+
|
||||
bvVzetUSUS7Rh1fHtxlivJFkG1VrvPu9b416l2aKftBiaNyAWXbyjqXwLYli6Ehk
|
||||
uu6jZd0040Ggh7bY+KMSnDFDrp7Rar7OvGu9Iovs+sPdkc/iEbvwEiXdMjf3gwkT
|
||||
Wqx6br1VDLzhD83HAsFA9tt5fv6KTf91UgJnCmOi81Uo6fSEJG84g32T25gwwmCK
|
||||
q4U049aGF/f4u3QuWDsfYqNePycurAg3m5PC0wCoqvpY2u/q+PGbjWMi2PfZsF8U
|
||||
imgl/L0=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA7p6KKa3ctS+Sr/nf2uTKNtTshuDVzTsBTbaGydj8q0YDmT3n
|
||||
CnOPWXvvG1N+jJv5pcAXN2ZnV9UpGh3N5g/CaRcFTgQQ8o+NlCXYBdPIXAJ+Kkbx
|
||||
limDw3n9xIXfeL6+V2QuPNrqh6n23xwDg5boKaNkpf7X5OrjT1Ph57SEfX1op3GX
|
||||
bwkAP2+3WlxxYYs0htRq2gH97q9J4MlhHPDapi+uKGs+2b1y6Uxgf4nD5jEWdPmy
|
||||
VqeKs+fT4ja2n+3gujpdOo2lg504p50gL4zP8zhAlcqlQCmeJGL1xFzCtm2wHQo7
|
||||
6XHSWca4pJ7rxf2oIdtE7ikvgFlTVXnG1T3TEQIDAQABAoIBAQDlX/UD96MPcDmb
|
||||
e6EZ85AGgUsUpJAhBjVMlMagxTqtEVJoPj8XptoHdMD2DZ66XzztfedTU9bHcZpf
|
||||
BoNkQYXqKzzoL7Ry1leML4ymnVweRi8tSKD2bdXBVEUCYoXctc6WhzCDQxTrcBBl
|
||||
i7I9DhUB4ZTglEbIQJpdKQ8hAj/Rt55KWSxc+8X7ItSdtMrq+uz+pqg4PkysVAFS
|
||||
3aDybOqiI/2hzOvwQU4HaB48uUQwpOU6EGidt0C5nAdWOQMbS8kkCJz6UODiUfdM
|
||||
mLIyA4ygkQ45QthzrddKauUMhUd/y1SAFJOambR4ZiyA+bItomlbNq018sFx3FDr
|
||||
Uvg7nz2ZAoGBAPC6aO4W0U7vsWyL/lgC7ybFbtPJ4emj4wK/W87qx3LW1/dRgl7Q
|
||||
h6oblZTFK/oV6xA7J2/Foocz/s1ntnyIdxrtrZUYAIiBXlrWhDg9+MnnmErfXx7H
|
||||
CkRyWH6i9JbTRUeiWRNBGQ9yMkwQPc2Ckytxrh7w9M+RVCpyzUh8lX+XAoGBAP3B
|
||||
4V8cF3bVEUOk0tHshR5m2kcJ22qmUv8WUG+IdRUDb4tRpzVFC8HcKedEjk3jxkXR
|
||||
UInRSD+hLhx0HIhjZKWqJffSZI/G3U8AqoKu9+eh/xHZCah/8KW1YWNsn4rROcyZ
|
||||
5XFRiMn7psWTjLEZ17zQS4rk9g65SKc9u1wtTxeXAoGAIY3qOF2n2Tfh5D5zOnNW
|
||||
QHI+q3i1a6qzZtujgWkKWgCGY+vRn0Oz1Us5A16kbZyGgmGscpD6wZvGxXzSW/Nt
|
||||
nqxIiMKquFxH+aNzFJ/WwNXuTWlrSc/2p2nE2gn+y9MxEfYYMm3df2CskBuncbDk
|
||||
sKaM3bU6eoBIWg5cfOEYuYsCgYACB2bR59uYK6PzsoGtBAMcdx4Pq1iBxcqsF3WV
|
||||
LrYg8OIXbxOzLVYmuqfrHXU10jhnnoDSWUYGnDdOKu9/d6v6Vx3umVQMgj6Kvyqd
|
||||
2OBKjdUIQ3/8ROmbqZOZw+iSp5GavTBEc65wTv7KXZ+mWtqKu++esK32+CxIignR
|
||||
dttHCQKBgQDZUt94wj9s5p7H5LH6hxyqNr6P9JYEuYPao5l/3mOJ8N330wKuN2G+
|
||||
GUg7p/AhtQHwdoyErlsQavKZa791oCvfJiOURYa8gYU03sYsyI1tV45UexCwl40f
|
||||
oS+VQYgU16UdYo9B2petecEPNpM+mgpne3qzVUwJ5NUNURgmWpyiQw==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIDATCCAekCAQAwZDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
|
||||
ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEdMBsGA1UEAxMUbm90
|
||||
LXVzZWQubmdodHRwMi5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQDunooprdy1L5Kv+d/a5Mo21OyG4NXNOwFNtobJ2PyrRgOZPecKc49Ze+8bU36M
|
||||
m/mlwBc3ZmdX1SkaHc3mD8JpFwVOBBDyj42UJdgF08hcAn4qRvGWKYPDef3Ehd94
|
||||
vr5XZC482uqHqfbfHAODlugpo2Sl/tfk6uNPU+HntIR9fWincZdvCQA/b7daXHFh
|
||||
izSG1GraAf3ur0ngyWEc8NqmL64oaz7ZvXLpTGB/icPmMRZ0+bJWp4qz59PiNraf
|
||||
7eC6Ol06jaWDnTinnSAvjM/zOECVyqVAKZ4kYvXEXMK2bbAdCjvpcdJZxriknuvF
|
||||
/agh20TuKS+AWVNVecbVPdMRAgMBAAGgWDBWBgkqhkiG9w0BCQ4xSTBHMEUGA1Ud
|
||||
EQQ+MDyCEFRFU1QuTkdIVFRQMi5PUkeCEioudGVzdC5uZ2h0dHAyLm9yZ4IUdyp3
|
||||
LnRlc3QubmdodHRwMi5vcmcwDQYJKoZIhvcNAQELBQADggEBAIAEwnoM5moRwO5U
|
||||
eaeVCuzpxw1qQsB769GyQu+ey1aa+2BYflirv/FW+8x/uzQpCWGEgHqd5w+MXyXA
|
||||
PsyucHgKh5Ia6MUW6xxlHkkOtVtmZiH7lXWv90RNtdfHHGWnBzw8iGsk5WfEaNho
|
||||
NlPiuYLiFqA7W6jR/c4kOg3zziDlwTXaH6SWLCuDzLTb7E7nGcrWkN6moYj+QlSx
|
||||
viA4GsqDBoFgXT7cSfUzS8ZwIjrqbx7C1xkzPEt5jAiCD/UBX9ot0G+lEgCv3UQj
|
||||
Q1KkY+TO3bzMkt/kQSX2Q6plKj8D77tlDfFCjd77VC2lL3Qmzaz+M6T7uF+wyl9W
|
||||
AQJvoUg=
|
||||
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"CN": "not-used.nghttp2.org",
|
||||
"hosts": [
|
||||
"TEST.NGHTTP2.ORG",
|
||||
"*.test.nghttp2.org",
|
||||
"w*w.test.nghttp2.org"
|
||||
],
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "AU",
|
||||
"ST": "Some-State",
|
||||
"O": "Internet Widgits Pty Ltd"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIEDjCCAvagAwIBAgIUQBCY8Nre85JT1c7P+HbXUF9yzg8wDQYJKoZIhvcNAQEL
|
||||
BQAwXjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoT
|
||||
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAxMOY2EubmdodHRwMi5v
|
||||
cmcwHhcNMTYwNjI1MDkzMzAwWhcNMjYwNjIzMDkzMzAwWjBkMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMR0wGwYDVQQDExRub3QtdXNlZC5uZ2h0dHAyLm9yZzCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAO6eiimt3LUvkq/539rkyjbU7Ibg1c07
|
||||
AU22hsnY/KtGA5k95wpzj1l77xtTfoyb+aXAFzdmZ1fVKRodzeYPwmkXBU4EEPKP
|
||||
jZQl2AXTyFwCfipG8ZYpg8N5/cSF33i+vldkLjza6oep9t8cA4OW6CmjZKX+1+Tq
|
||||
409T4ee0hH19aKdxl28JAD9vt1pccWGLNIbUatoB/e6vSeDJYRzw2qYvrihrPtm9
|
||||
culMYH+Jw+YxFnT5slanirPn0+I2tp/t4Lo6XTqNpYOdOKedIC+Mz/M4QJXKpUAp
|
||||
niRi9cRcwrZtsB0KO+lx0lnGuKSe68X9qCHbRO4pL4BZU1V5xtU90xECAwEAAaOB
|
||||
vTCBujAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0T
|
||||
AQH/BAIwADAdBgNVHQ4EFgQUGlxgxowH6jrQiyyFpCbwPkCXXIYwHwYDVR0jBBgw
|
||||
FoAU0DnFVHVlxrFEkv1ULqnO4ua924YwRQYDVR0RBD4wPIIQVEVTVC5OR0hUVFAy
|
||||
Lk9SR4ISKi50ZXN0Lm5naHR0cDIub3JnghR3KncudGVzdC5uZ2h0dHAyLm9yZzAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEANCqM6ocfqOpgDEHYOOQTGFHJIptQhS3kRYAdTIo2
|
||||
G8XvGCoy+CDYe1GAUWbxE090+a1I1rsYMHcWKJnjKaCBZid7KMhyayIvrmgEsOCh
|
||||
L8iLf3bxkCoyIAmCpxJwa3LMxm2QQLtRx8AoMXWf+N8are4HY6MLNn6aP4zaTrTZ
|
||||
H+WkjKIh7WjSHtW/ro666PCXJDCCdRXljOf8v/fff3bYiLg8o70RBp7OFM0HaPtK
|
||||
wCfcLLxBeoVIncWswB6GtVUFhLeGjepDzWpuDHOdw6DtpghwSXvWFu9bRtl+x02m
|
||||
LAGfJ0kJrpYGfr9UB51NFX3aM/D3p2zxrjKwR2b59vJEcA==
|
||||
-----END CERTIFICATE-----
|
Loading…
Reference in New Issue