Add |SSL_set1_host| and |SSL_set_hostflags|. This allows code that uses OpenSSL's suggested pattern for 1.1.0 [1] to work. [1] https://wiki.openssl.org/index.php/Hostname_validation Change-Id: I6d1b983074d5ad8645400cef887c1cc20f7bf2a1 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50645 Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index ca7ae14..29d825b 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h
@@ -2459,6 +2459,15 @@ OPENSSL_EXPORT int (*SSL_get_verify_callback(const SSL *ssl))( int ok, X509_STORE_CTX *store_ctx); +// SSL_set1_host sets a DNS name that will be required to be present in the +// verified leaf certificate. +// +// Note: unless _some_ name checking is performed, certificate validation is +// ineffective. Simply checking that a host has some certificate from a CA is +// rarely meaningful—you have to check that the CA believed that the host was +// who you expect to be talking to. +OPENSSL_EXPORT int SSL_set1_host(SSL *ssl, const char *hostname); + // SSL_CTX_set_verify_depth sets the maximum depth of a certificate chain // accepted in verification. This number does not include the leaf, so a depth // of 1 allows the leaf and one CA certificate. @@ -2632,6 +2641,11 @@ const uint16_t *prefs, size_t num_prefs); +// SSL_set_hostflags calls |X509_VERIFY_PARAM_set_hostflags| on the +// |X509_VERIFY_PARAM| associated with this |SSL*|. The |flags| argument +// should be one of the |X509_CHECK_*| constants. +OPENSSL_EXPORT void SSL_set_hostflags(SSL *ssl, unsigned flags); + // Client certificate CA list. //
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index 4169671..6ef80cb 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -39,6 +39,7 @@ #include <openssl/ssl.h> #include <openssl/rand.h> #include <openssl/x509.h> +#include <openssl/x509v3.h> #include "internal.h" #include "../crypto/internal.h" @@ -1498,6 +1499,8 @@ struct ClientConfig { SSL_SESSION *session = nullptr; std::string servername; + std::string verify_hostname; + unsigned hostflags = 0; bool early_data = false; }; @@ -1520,6 +1523,12 @@ !SSL_set_tlsext_host_name(client.get(), config.servername.c_str())) { return false; } + if (!config.verify_hostname.empty()) { + if (!SSL_set1_host(client.get(), config.verify_hostname.c_str())) { + return false; + } + SSL_set_hostflags(client.get(), config.hostflags); + } SSL_set_shed_handshake_config(client.get(), shed_handshake_config); SSL_set_shed_handshake_config(server.get(), shed_handshake_config); @@ -8002,5 +8011,84 @@ } } +TEST(SSLTest, HostMatching) { + static const char kCertPEM[] = + "-----BEGIN CERTIFICATE-----\n" + "MIIB9jCCAZ2gAwIBAgIQeudG9R61BOxUvWkeVhU5DTAKBggqhkjOPQQDAjApMRAw\n" + "DgYDVQQKEwdBY21lIENvMRUwEwYDVQQDEwxleGFtcGxlMy5jb20wHhcNMjExMjA2\n" + "MjA1NjU2WhcNMjIxMjA2MjA1NjU2WjApMRAwDgYDVQQKEwdBY21lIENvMRUwEwYD\n" + "VQQDEwxleGFtcGxlMy5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS7l2VO\n" + "Bl2TjVm9WfGk24+hMbVFUNB+RVHWbCvFvNZAoWiIJ2z34RLGInyZvCZ8xLAvsuaW\n" + "ULDDaoeDl1M0t4Hmo4GmMIGjMA4GA1UdDwEB/wQEAwIChDATBgNVHSUEDDAKBggr\n" + "BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTTJWurcc1t+VPQBko3\n" + "Gsw6cbcWSTBMBgNVHREERTBDggxleGFtcGxlMS5jb22CDGV4YW1wbGUyLmNvbYIP\n" + "YSouZXhhbXBsZTQuY29tgg4qLmV4YW1wbGU1LmNvbYcEAQIDBDAKBggqhkjOPQQD\n" + "AgNHADBEAiAAv0ljHJGrgyzZDkG6XvNZ5ewxRfnXcZuD0Y7E4giCZgIgNK1qjilu\n" + "5DyVbfKeeJhOCtGxqE1dWLXyJBnoRomSYBY=\n" + "-----END CERTIFICATE-----\n"; + bssl::UniquePtr<X509> cert(CertFromPEM(kCertPEM)); + ASSERT_TRUE(cert); + + static const char kKeyPEM[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQghsaSZhUzZAcQlLyJ\n" + "MDuy7WPdyqNsAX9rmEP650LF/q2hRANCAAS7l2VOBl2TjVm9WfGk24+hMbVFUNB+\n" + "RVHWbCvFvNZAoWiIJ2z34RLGInyZvCZ8xLAvsuaWULDDaoeDl1M0t4Hm\n" + "-----END PRIVATE KEY-----\n"; + bssl::UniquePtr<EVP_PKEY> key(KeyFromPEM(kKeyPEM)); + ASSERT_TRUE(key); + + bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method())); + ASSERT_TRUE(server_ctx); + ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx.get(), cert.get())); + ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx.get(), key.get())); + + bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method())); + ASSERT_TRUE(client_ctx); + ASSERT_TRUE(X509_STORE_add_cert(SSL_CTX_get_cert_store(client_ctx.get()), + cert.get())); + SSL_CTX_set_verify(client_ctx.get(), + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + nullptr); + + struct TestCase { + std::string hostname; + unsigned flags; + bool should_match; + }; + std::vector<TestCase> kTests = { + // These two names are present as SANs in the certificate. + {"example1.com", 0, true}, + {"example2.com", 0, true}, + // This is the CN of the certificate, but that shouldn't matter if a SAN + // extension is present. + {"example3.com", 0, false}, + // a*.example4.com is a SAN, which is invalid because partial wildcards + // aren't a thing except for the OpenSSL verifier. + {"abc.example4.com", 0, true}, + // ... but they can be turned off. + {"abc.example4.com", X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS, false}, + // *.example5.com is a SAN in the certificate, which is a normal and valid + // wildcard. + {"abc.example5.com", 0, true}, + // This name is not present. + {"notexample1.com", 0, false}, + // The IPv4 address 1.2.3.4 is a SAN, but that shouldn't match against a + // hostname that happens to be its textual representation. + {"1.2.3.4", 0, false}, + }; + + bssl::UniquePtr<SSL> client, server; + ClientConfig config; + for (const TestCase &test : kTests) { + SCOPED_TRACE(test.hostname); + config.verify_hostname = test.hostname; + config.hostflags = test.flags; + EXPECT_EQ(test.should_match, + ConnectClientAndServer(&client, &server, client_ctx.get(), + server_ctx.get(), config)); + } +} + } // namespace BSSL_NAMESPACE_END
diff --git a/ssl/ssl_x509.cc b/ssl/ssl_x509.cc index 26eb90a..5533c7f 100644 --- a/ssl/ssl_x509.cc +++ b/ssl/ssl_x509.cc
@@ -1304,6 +1304,23 @@ return set_cert_store(&ssl->config->cert->verify_store, store, 1); } +int SSL_set1_host(SSL *ssl, const char *hostname) { + check_ssl_x509_method(ssl); + if (!ssl->config) { + return 0; + } + return X509_VERIFY_PARAM_set1_host(ssl->config->param, hostname, + strlen(hostname)); +} + +void SSL_set_hostflags(SSL *ssl, unsigned flags) { + check_ssl_x509_method(ssl); + if (!ssl->config) { + return; + } + X509_VERIFY_PARAM_set_hostflags(ssl->config->param, flags); +} + int SSL_alert_from_verify_result(long result) { switch (result) { case X509_V_ERR_CERT_CHAIN_TOO_LONG: