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: