diff --git a/src/brpc/channel.cpp b/src/brpc/channel.cpp index a8caeaf953..83fc37b077 100644 --- a/src/brpc/channel.cpp +++ b/src/brpc/channel.cpp @@ -39,6 +39,7 @@ #include "brpc/policy/esp_authenticator.h" #include "brpc/transport_factory.h" #include "brpc/details/controller_private_accessor.h" +#include "brpc/details/ssl_helper.h" namespace brpc { @@ -128,7 +129,11 @@ static ChannelSignature ComputeChannelSignature(const ChannelOptions& opt) { buf.push_back('|'); buf.append((char*)&verify.verify_depth, sizeof(verify.verify_depth)); buf.push_back('|'); + buf.append((char*)&verify.verify_mode, sizeof(verify.verify_mode)); + buf.push_back('|'); buf.append(verify.ca_file_path); + buf.push_back('|'); + buf.append(verify.expected_peer_name); } else { // All disabled ChannelSSLOptions are the same } @@ -324,6 +329,26 @@ static int CreateSocketSSLContext(const ChannelOptions& options, return 0; } +static void SetHttpsPeerName(const std::string& host, + ChannelOptions* options) { + ChannelSSLOptions* ssl = options->mutable_ssl_options(); + if (ssl->sni_name.empty()) { + ssl->sni_name = host; + } + VerifyOptions& verify = ssl->verify; + if (verify.verify_depth > 0 && + verify.verify_mode != VerifyMode::VERIFY_NONE && + verify.expected_peer_name.empty()) { + if (SupportsPeerNameVerification()) { + verify.expected_peer_name = host; + } else { + LOG_ONCE(WARNING) + << "The TLS backend does not support server identity " + << "verification; only the certificate chain will be verified"; + } + } +} + int Channel::Init(butil::EndPoint server_addr_and_port, const ChannelOptions* options) { return InitSingle(server_addr_and_port, "", options); @@ -339,13 +364,12 @@ int Channel::InitSingle(const butil::EndPoint& server_addr_and_port, } int* port_out = raw_port == -1 ? &raw_port: NULL; ParseURL(raw_server_address, &_scheme, &_service_name, port_out); + const std::string host = _service_name; if (raw_port != -1) { _service_name.append(":").append(std::to_string(raw_port)); } if (_options.protocol == brpc::PROTOCOL_HTTP && _scheme == "https") { - if (_options.mutable_ssl_options()->sni_name.empty()) { - _options.mutable_ssl_options()->sni_name = _service_name; - } + SetHttpsPeerName(host, &_options); } const int port = server_addr_and_port.port; if (port < 0) { @@ -392,13 +416,12 @@ int Channel::Init(const char* ns_url, } int raw_port = -1; ParseURL(ns_url, &_scheme, &_service_name, &raw_port); + const std::string host = _service_name; if (raw_port != -1) { _service_name.append(":").append(std::to_string(raw_port)); } if (_options.protocol == brpc::PROTOCOL_HTTP && _scheme == "https") { - if (_options.mutable_ssl_options()->sni_name.empty()) { - _options.mutable_ssl_options()->sni_name = _service_name; - } + SetHttpsPeerName(host, &_options); } butil::EndPoint client_endpoint; if (!_options.client_host.empty() && diff --git a/src/brpc/details/mesalink_ssl_helper.cpp b/src/brpc/details/mesalink_ssl_helper.cpp index afc3861359..aa83fa6c35 100644 --- a/src/brpc/details/mesalink_ssl_helper.cpp +++ b/src/brpc/details/mesalink_ssl_helper.cpp @@ -34,6 +34,10 @@ namespace brpc { +bool SupportsPeerNameVerification() { + return false; +} + static const char* const PEM_START = "-----BEGIN"; static bool IsPemString(const std::string& input) { @@ -240,6 +244,10 @@ static int LoadCertificate(SSL_CTX* ctx, static int SetSSLOptions(SSL_CTX* ctx, const std::string& ciphers, int protocols, const VerifyOptions& verify) { + if (!verify.expected_peer_name.empty()) { + LOG(ERROR) << "Expected peer name verification is not supported by MesaLink"; + return -1; + } if (verify.verify_depth > 0) { std::string cafile = verify.ca_file_path; if (!cafile.empty()) { diff --git a/src/brpc/details/ssl_helper.cpp b/src/brpc/details/ssl_helper.cpp index 0e31f14b30..52246980aa 100644 --- a/src/brpc/details/ssl_helper.cpp +++ b/src/brpc/details/ssl_helper.cpp @@ -23,6 +23,7 @@ #ifndef USE_MESALINK #include // recv +#include // inet_pton #include // pthread_once #include // fopen #include // getenv @@ -39,6 +40,14 @@ namespace brpc { +bool SupportsPeerNameVerification() { +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER >= 0x10002000L + return true; +#else + return false; +#endif +} + #ifndef OPENSSL_NO_DH static DH* g_dh_1024 = NULL; static DH* g_dh_2048 = NULL; @@ -464,8 +473,12 @@ static int SetSSLOptions(SSL_CTX* ctx, const std::string& ciphers, return -1; } - // TODO: Verify the CNAME in certificate matches the requesting host if (verify.verify_depth > 0) { + if (!verify.expected_peer_name.empty() && + verify.verify_mode == VerifyMode::VERIFY_NONE) { + LOG(ERROR) << "Expected peer name requires peer verification"; + return -1; + } if (verify.verify_mode == VerifyMode::VERIFY_FAIL_IF_NO_PEER_CERT) { SSL_CTX_set_verify(ctx, (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT), NULL); @@ -493,7 +506,36 @@ static int SetSSLOptions(SSL_CTX* ctx, const std::string& ciphers, return -1; } } + if (!verify.expected_peer_name.empty()) { +#if defined(OPENSSL_IS_BORINGSSL) || OPENSSL_VERSION_NUMBER >= 0x10002000L + X509_VERIFY_PARAM* param = SSL_CTX_get0_param(ctx); + unsigned char address[sizeof(struct in6_addr)]; + int rc = 0; + if (inet_pton(AF_INET, verify.expected_peer_name.c_str(), address) == 1 || + inet_pton(AF_INET6, verify.expected_peer_name.c_str(), + address) == 1) { + rc = X509_VERIFY_PARAM_set1_ip_asc( + param, verify.expected_peer_name.c_str()); + } else { + rc = X509_VERIFY_PARAM_set1_host( + param, verify.expected_peer_name.c_str(), 0); + } + if (rc != 1) { + LOG(ERROR) << "Fail to set expected peer name " + << verify.expected_peer_name << ": " + << SSLError(ERR_get_error()); + return -1; + } +#else + LOG(ERROR) << "Expected peer name verification requires OpenSSL 1.0.2+"; + return -1; +#endif + } } else { + if (!verify.expected_peer_name.empty()) { + LOG(ERROR) << "Expected peer name requires peer verification"; + return -1; + } SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); } diff --git a/src/brpc/details/ssl_helper.h b/src/brpc/details/ssl_helper.h index a9b8736bfa..815285c23d 100644 --- a/src/brpc/details/ssl_helper.h +++ b/src/brpc/details/ssl_helper.h @@ -56,6 +56,8 @@ enum SSLProtocol { TLSv1_3 = 1 << 4, }; +bool SupportsPeerNameVerification(); + struct FreeSSLCTX { inline void operator()(SSL_CTX* ctx) const { if (ctx != NULL) { diff --git a/src/brpc/ssl_options.h b/src/brpc/ssl_options.h index 3fdb1e85bb..6edc74f222 100644 --- a/src/brpc/ssl_options.h +++ b/src/brpc/ssl_options.h @@ -66,6 +66,10 @@ struct VerifyOptions { // If empty, use the system default CA files // Default: "" std::string ca_file_path; + + // Set the expected DNS name or IP address in the peer's certificate + // Default: "" + std::string expected_peer_name; }; // SSL options at client side diff --git a/test/brpc_channel_unittest.cpp b/test/brpc_channel_unittest.cpp index 903402a78e..6f4540d6a2 100644 --- a/test/brpc_channel_unittest.cpp +++ b/test/brpc_channel_unittest.cpp @@ -2425,10 +2425,27 @@ TEST_F(ChannelTest, parse_hostname) { ASSERT_EQ(0, channel.Init("http://www.baidu.com:8888", "rr", &opt)); ASSERT_EQ("www.baidu.com:8888", channel._service_name); + opt.mutable_ssl_options()->verify.verify_mode = brpc::VerifyMode::VERIFY_PEER; + opt.mutable_ssl_options()->verify.verify_depth = 1; + opt.mutable_ssl_options()->verify.ca_file_path = "cert1.crt"; ASSERT_EQ(0, channel.Init("https://www.baidu.com", &opt)); ASSERT_EQ("www.baidu.com", channel._service_name); +#if defined(USE_MESALINK) || \ + (!defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_NUMBER < 0x10002000L) + ASSERT_TRUE(channel._options.ssl_options().verify.expected_peer_name.empty()); +#else + ASSERT_EQ("www.baidu.com", + channel._options.ssl_options().verify.expected_peer_name); +#endif ASSERT_EQ(0, channel.Init("https://www.baidu.com:443", &opt)); ASSERT_EQ("www.baidu.com:443", channel._service_name); +#if defined(USE_MESALINK) || \ + (!defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_NUMBER < 0x10002000L) + ASSERT_TRUE(channel._options.ssl_options().verify.expected_peer_name.empty()); +#else + ASSERT_EQ("www.baidu.com", + channel._options.ssl_options().verify.expected_peer_name); +#endif ASSERT_EQ(0, channel.Init("https://www.baidu.com", 443, &opt)); ASSERT_EQ("www.baidu.com:443", channel._service_name); ASSERT_EQ(0, channel.Init("https://www.baidu.com:1443", &opt)); diff --git a/test/brpc_ssl_unittest.cpp b/test/brpc_ssl_unittest.cpp index 6512eea4a6..a6f7affc7c 100644 --- a/test/brpc_ssl_unittest.cpp +++ b/test/brpc_ssl_unittest.cpp @@ -229,6 +229,79 @@ TEST_F(SSLTest, force_ssl) { ASSERT_EQ(0, server.Join()); } +void CallServerWithExpectedPeerName(int port, const char* server_address, + const char* expected_peer_name, + bool expect_success) { + brpc::Channel channel; + brpc::ChannelOptions options; + options.protocol = brpc::PROTOCOL_HTTP; + options.mutable_ssl_options()->verify.verify_mode = + brpc::VerifyMode::VERIFY_PEER; + options.mutable_ssl_options()->verify.verify_depth = 1; + options.mutable_ssl_options()->verify.ca_file_path = "cert1.crt"; + if (expected_peer_name != NULL) { + options.mutable_ssl_options()->verify.expected_peer_name = expected_peer_name; + } + std::string url = server_address; + url.append(":").append(std::to_string(port)); + ASSERT_EQ(0, channel.Init(url.c_str(), &options)); + + test::EchoRequest req; + test::EchoResponse res; + req.set_message(EXP_REQUEST); + brpc::Controller cntl; + test::EchoService_Stub stub(&channel); + stub.Echo(&cntl, &req, &res, NULL); + if (expect_success) { + EXPECT_FALSE(cntl.Failed()) << cntl.ErrorText(); + EXPECT_EQ(EXP_RESPONSE, res.message()); + } else { + EXPECT_TRUE(cntl.Failed()); + } +} + +TEST_F(SSLTest, verify_peer_name) { + const int port = 8613; + brpc::Server server; + brpc::ServerOptions server_options; + brpc::CertInfo cert; + cert.certificate = "cert1.crt"; + cert.private_key = "cert1.key"; + server_options.mutable_ssl_options()->default_cert = cert; + + EchoServiceImpl echo_svc; + ASSERT_EQ(0, server.AddService( + &echo_svc, brpc::SERVER_DOESNT_OWN_SERVICE)); + ASSERT_EQ(0, server.Start(port, &server_options)); + + CallServerWithExpectedPeerName(port, "https://localhost", NULL, true); + CallServerWithExpectedPeerName(port, "https://127.0.0.1", NULL, false); + CallServerWithExpectedPeerName( + port, "https://localhost", "wrong.local", false); + + ASSERT_EQ(0, server.Stop(0)); + ASSERT_EQ(0, server.Join()); +} + +TEST_F(SSLTest, expected_peer_name_requires_peer_verification) { + brpc::ChannelSSLOptions options; + options.verify.expected_peer_name = "localhost"; + EXPECT_EQ(NULL, brpc::CreateClientSSLContext(options)); + + options.verify.verify_depth = 1; + options.verify.verify_mode = brpc::VerifyMode::VERIFY_NONE; + EXPECT_EQ(NULL, brpc::CreateClientSSLContext(options)); +} + +TEST_F(SSLTest, peer_name_verification_capability) { +#if defined(USE_MESALINK) || \ + (!defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_NUMBER < 0x10002000L) + EXPECT_FALSE(brpc::SupportsPeerNameVerification()); +#else + EXPECT_TRUE(brpc::SupportsPeerNameVerification()); +#endif +} + void ProcessResponse(brpc::InputMessageBase* msg_base) { brpc::DestroyingPtr msg( static_cast(msg_base));