1/* 2 * nghttp2 - HTTP/2 C Library 3 * 4 * Copyright (c) 2013 Tatsuhiro Tsujikawa 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be 15 * included in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 */ 25#include "shrpx_tls_test.h" 26 27#include <CUnit/CUnit.h> 28 29#include "shrpx_tls.h" 30#include "shrpx_log.h" 31#include "util.h" 32#include "template.h" 33 34using namespace nghttp2; 35 36namespace shrpx { 37 38void test_shrpx_tls_create_lookup_tree(void) { 39 auto tree = std::make_unique<tls::CertLookupTree>(); 40 41 constexpr StringRef hostnames[] = { 42 StringRef::from_lit("example.com"), // 0 43 StringRef::from_lit("www.example.org"), // 1 44 StringRef::from_lit("*www.example.org"), // 2 45 StringRef::from_lit("xy*.host.domain"), // 3 46 StringRef::from_lit("*yy.host.domain"), // 4 47 StringRef::from_lit("nghttp2.sourceforge.net"), // 5 48 StringRef::from_lit("sourceforge.net"), // 6 49 StringRef::from_lit("sourceforge.net"), // 7, duplicate 50 StringRef::from_lit("*.foo.bar"), // 8, oo.bar is suffix of *.foo.bar 51 StringRef::from_lit("oo.bar") // 9 52 }; 53 auto num = array_size(hostnames); 54 55 for (size_t idx = 0; idx < num; ++idx) { 56 tree->add_cert(hostnames[idx], idx); 57 } 58 59 tree->dump(); 60 61 CU_ASSERT(0 == tree->lookup(hostnames[0])); 62 CU_ASSERT(1 == tree->lookup(hostnames[1])); 63 CU_ASSERT(2 == tree->lookup(StringRef::from_lit("2www.example.org"))); 64 CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("www2.example.org"))); 65 CU_ASSERT(3 == tree->lookup(StringRef::from_lit("xy1.host.domain"))); 66 // Does not match *yy.host.domain, because * must match at least 1 67 // character. 68 CU_ASSERT(-1 == tree->lookup(StringRef::from_lit("yy.host.domain"))); 69 CU_ASSERT(4 == tree->lookup(StringRef::from_lit("xyy.host.domain"))); 70 CU_ASSERT(-1 == tree->lookup(StringRef{})); 71 CU_ASSERT(5 == tree->lookup(hostnames[5])); 72 CU_ASSERT(6 == tree->lookup(hostnames[6])); 73 static constexpr char h6[] = "pdylay.sourceforge.net"; 74 for (int i = 0; i < 7; ++i) { 75 CU_ASSERT(-1 == tree->lookup(StringRef{h6 + i, str_size(h6) - i})); 76 } 77 CU_ASSERT(8 == tree->lookup(StringRef::from_lit("x.foo.bar"))); 78 CU_ASSERT(9 == tree->lookup(hostnames[9])); 79 80 constexpr StringRef names[] = { 81 StringRef::from_lit("rab"), // 1 82 StringRef::from_lit("zab"), // 2 83 StringRef::from_lit("zzub"), // 3 84 StringRef::from_lit("ab") // 4 85 }; 86 num = array_size(names); 87 88 tree = std::make_unique<tls::CertLookupTree>(); 89 for (size_t idx = 0; idx < num; ++idx) { 90 tree->add_cert(names[idx], idx); 91 } 92 for (size_t i = 0; i < num; ++i) { 93 CU_ASSERT((ssize_t)i == tree->lookup(names[i])); 94 } 95} 96 97// We use cfssl to generate key pairs. 98// 99// CA self-signed key pairs generation: 100// 101// $ cfssl genkey -initca ca.nghttp2.org.csr.json | 102// cfssljson -bare ca.nghttp2.org 103// 104// Create CSR: 105// 106// $ cfssl genkey test.nghttp2.org.csr.json | cfssljson -bare test.nghttp2.org 107// $ cfssl genkey test.example.com.csr.json | cfssljson -bare test.example.com 108// 109// Sign CSR: 110// 111// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem 112// -config=ca-config.json -profile=server test.nghttp2.org.csr | 113// cfssljson -bare test.nghttp2.org 114// 115// $ cfssl sign -ca ca.nghttp2.org.pem -ca-key ca.nghttp2.org-key.pem 116// -config=ca-config.json -profile=server test.example.com.csr | 117// cfssljson -bare test.example.com 118// 119void test_shrpx_tls_cert_lookup_tree_add_ssl_ctx(void) { 120 int rv; 121 122 static constexpr char nghttp2_certfile[] = 123 NGHTTP2_SRC_DIR "/test.nghttp2.org.pem"; 124 auto nghttp2_ssl_ctx = SSL_CTX_new(TLS_server_method()); 125 auto nghttp2_ssl_ctx_del = defer(SSL_CTX_free, nghttp2_ssl_ctx); 126 auto nghttp2_tls_ctx_data = std::make_unique<tls::TLSContextData>(); 127 nghttp2_tls_ctx_data->cert_file = nghttp2_certfile; 128 SSL_CTX_set_app_data(nghttp2_ssl_ctx, nghttp2_tls_ctx_data.get()); 129 rv = SSL_CTX_use_certificate_chain_file(nghttp2_ssl_ctx, nghttp2_certfile); 130 131 CU_ASSERT(1 == rv); 132 133 static constexpr char examples_certfile[] = 134 NGHTTP2_SRC_DIR "/test.example.com.pem"; 135 auto examples_ssl_ctx = SSL_CTX_new(TLS_server_method()); 136 auto examples_ssl_ctx_del = defer(SSL_CTX_free, examples_ssl_ctx); 137 auto examples_tls_ctx_data = std::make_unique<tls::TLSContextData>(); 138 examples_tls_ctx_data->cert_file = examples_certfile; 139 SSL_CTX_set_app_data(examples_ssl_ctx, examples_tls_ctx_data.get()); 140 rv = SSL_CTX_use_certificate_chain_file(examples_ssl_ctx, examples_certfile); 141 142 CU_ASSERT(1 == rv); 143 144 tls::CertLookupTree tree; 145 std::vector<std::vector<SSL_CTX *>> indexed_ssl_ctx; 146 147 rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx, 148 nghttp2_ssl_ctx); 149 150 CU_ASSERT(0 == rv); 151 152 rv = tls::cert_lookup_tree_add_ssl_ctx(&tree, indexed_ssl_ctx, 153 examples_ssl_ctx); 154 155 CU_ASSERT(0 == rv); 156 157 CU_ASSERT(-1 == tree.lookup(StringRef::from_lit("not-used.nghttp2.org"))); 158 CU_ASSERT(0 == tree.lookup(StringRef::from_lit("test.nghttp2.org"))); 159 CU_ASSERT(1 == tree.lookup(StringRef::from_lit("w.test.nghttp2.org"))); 160 CU_ASSERT(2 == tree.lookup(StringRef::from_lit("www.test.nghttp2.org"))); 161 CU_ASSERT(3 == tree.lookup(StringRef::from_lit("test.example.com"))); 162} 163 164template <size_t N, size_t M> 165bool tls_hostname_match_wrapper(const char (&pattern)[N], 166 const char (&hostname)[M]) { 167 return tls::tls_hostname_match(StringRef{pattern, N}, StringRef{hostname, M}); 168} 169 170void test_shrpx_tls_tls_hostname_match(void) { 171 CU_ASSERT(tls_hostname_match_wrapper("example.com", "example.com")); 172 CU_ASSERT(tls_hostname_match_wrapper("example.com", "EXAMPLE.com")); 173 174 // check wildcard 175 CU_ASSERT(tls_hostname_match_wrapper("*.example.com", "www.example.com")); 176 CU_ASSERT(tls_hostname_match_wrapper("*w.example.com", "www.example.com")); 177 CU_ASSERT(tls_hostname_match_wrapper("www*.example.com", "www1.example.com")); 178 CU_ASSERT( 179 tls_hostname_match_wrapper("www*.example.com", "WWW12.EXAMPLE.com")); 180 // at least 2 dots are required after '*' 181 CU_ASSERT(!tls_hostname_match_wrapper("*.com", "example.com")); 182 CU_ASSERT(!tls_hostname_match_wrapper("*", "example.com")); 183 // '*' must be in left most label 184 CU_ASSERT( 185 !tls_hostname_match_wrapper("blog.*.example.com", "blog.my.example.com")); 186 // prefix is wrong 187 CU_ASSERT( 188 !tls_hostname_match_wrapper("client*.example.com", "server.example.com")); 189 // '*' must match at least one character 190 CU_ASSERT(!tls_hostname_match_wrapper("www*.example.com", "www.example.com")); 191 192 CU_ASSERT(!tls_hostname_match_wrapper("example.com", "nghttp2.org")); 193 CU_ASSERT(!tls_hostname_match_wrapper("www.example.com", "example.com")); 194 CU_ASSERT(!tls_hostname_match_wrapper("example.com", "www.example.com")); 195} 196 197static X509 *load_cert(const char *path) { 198 auto f = fopen(path, "r"); 199 auto cert = PEM_read_X509(f, nullptr, nullptr, nullptr); 200 201 fclose(f); 202 203 return cert; 204} 205 206static Address parse_addr(const char *ipaddr) { 207 addrinfo hints{}; 208 209 hints.ai_family = AF_UNSPEC; 210 hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; 211 212 addrinfo *res = nullptr; 213 214 auto rv = getaddrinfo(ipaddr, "443", &hints, &res); 215 216 CU_ASSERT(0 == rv); 217 CU_ASSERT(nullptr != res); 218 219 Address addr; 220 addr.len = res->ai_addrlen; 221 memcpy(&addr.su, res->ai_addr, res->ai_addrlen); 222 223 freeaddrinfo(res); 224 225 return addr; 226} 227 228void test_shrpx_tls_verify_numeric_hostname(void) { 229 { 230 // Successful IPv4 address match in SAN 231 static constexpr char ipaddr[] = "127.0.0.1"; 232 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); 233 auto addr = parse_addr(ipaddr); 234 auto rv = 235 tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); 236 237 CU_ASSERT(0 == rv); 238 239 X509_free(cert); 240 } 241 242 { 243 // Successful IPv6 address match in SAN 244 static constexpr char ipaddr[] = "::1"; 245 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); 246 auto addr = parse_addr(ipaddr); 247 auto rv = 248 tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); 249 250 CU_ASSERT(0 == rv); 251 252 X509_free(cert); 253 } 254 255 { 256 // Unsuccessful IPv4 address match in SAN 257 static constexpr char ipaddr[] = "192.168.0.127"; 258 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); 259 auto addr = parse_addr(ipaddr); 260 auto rv = 261 tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); 262 263 CU_ASSERT(-1 == rv); 264 265 X509_free(cert); 266 } 267 268 { 269 // CommonName is not used if SAN is available 270 static constexpr char ipaddr[] = "192.168.0.1"; 271 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/ipaddr.crt"); 272 auto addr = parse_addr(ipaddr); 273 auto rv = 274 tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); 275 276 CU_ASSERT(-1 == rv); 277 278 X509_free(cert); 279 } 280 281 { 282 // Successful IPv4 address match in CommonName 283 static constexpr char ipaddr[] = "127.0.0.1"; 284 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan_ip.crt"); 285 auto addr = parse_addr(ipaddr); 286 auto rv = 287 tls::verify_numeric_hostname(cert, StringRef::from_lit(ipaddr), &addr); 288 289 CU_ASSERT(0 == rv); 290 291 X509_free(cert); 292 } 293} 294 295void test_shrpx_tls_verify_dns_hostname(void) { 296 { 297 // Successful exact DNS name match in SAN 298 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); 299 auto rv = tls::verify_dns_hostname( 300 cert, StringRef::from_lit("nghttp2.example.com")); 301 302 CU_ASSERT(0 == rv); 303 304 X509_free(cert); 305 } 306 307 { 308 // Successful wildcard DNS name match in SAN 309 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); 310 auto rv = tls::verify_dns_hostname( 311 cert, StringRef::from_lit("www.nghttp2.example.com")); 312 313 CU_ASSERT(0 == rv); 314 315 X509_free(cert); 316 } 317 318 { 319 // CommonName is not used if SAN is available. 320 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/verify_hostname.crt"); 321 auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost")); 322 323 CU_ASSERT(-1 == rv); 324 325 X509_free(cert); 326 } 327 328 { 329 // Successful DNS name match in CommonName 330 auto cert = load_cert(NGHTTP2_SRC_DIR "/testdata/nosan.crt"); 331 auto rv = tls::verify_dns_hostname(cert, StringRef::from_lit("localhost")); 332 333 CU_ASSERT(0 == rv); 334 335 X509_free(cert); 336 } 337} 338 339} // namespace shrpx 340