| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License |
| |
| #include <stdio.h> |
| |
| #include <map> |
| #include <string> |
| #include <vector> |
| |
| #include <process/clock.hpp> |
| #include <process/future.hpp> |
| #include <process/gtest.hpp> |
| #include <process/http.hpp> |
| #include <process/io.hpp> |
| #include <process/network.hpp> |
| #include <process/socket.hpp> |
| #include <process/subprocess.hpp> |
| |
| #include <process/ssl/gtest.hpp> |
| #include <process/ssl/utilities.hpp> |
| |
| #include <stout/foreach.hpp> |
| #include <stout/gtest.hpp> |
| #include <stout/nothing.hpp> |
| #include <stout/option.hpp> |
| #include <stout/os.hpp> |
| #include <stout/try.hpp> |
| |
| #include "openssl.hpp" |
| |
| using std::map; |
| using std::string; |
| using std::vector; |
| |
| // We only run these tests if we have configured with '--enable-ssl'. |
| #ifdef USE_SSL_SOCKET |
| |
| #if OPENSSL_VERSION_NUMBER >= 0x10002000L |
| #define HOSTNAME_VALIDATION_OPENSSL |
| #endif |
| |
| namespace http = process::http; |
| namespace io = process::io; |
| namespace network = process::network; |
| namespace openssl = network::openssl; |
| #ifndef __WINDOWS__ |
| namespace unix = process::network::unix; |
| #endif // __WINDOWS__ |
| |
| using network::inet::Address; |
| using network::inet::Socket; |
| |
| using network::internal::SocketImpl; |
| |
| using process::Clock; |
| using process::Failure; |
| using process::Future; |
| using process::Subprocess; |
| |
| |
| // Wait for a subprocess and test the status code for the following |
| // conditions of 'expected_status': |
| // 1. 'None' = Anything but '0'. |
| // 2. 'Some' = the value of 'expected_status'. |
| // Returns Nothing if the resulting status code matches the |
| // expectation otherwise a Failure with the output of the subprocess. |
| // TODO(jmlvanre): Turn this into a generally useful abstraction for |
| // gtest where we can have a more straigtforward 'expected_status'. |
| Future<Nothing> await_subprocess( |
| const Subprocess& subprocess, |
| const Option<int>& expected_status = None()) |
| { |
| // Dup the pipe fd of the subprocess so we can read the output if |
| // needed. |
| Try<int_fd> dup = os::dup(subprocess.out().get()); |
| if (dup.isError()) { |
| return Failure(dup.error()); |
| } |
| |
| int_fd out = dup.get(); |
| |
| // Once we get the status of the process. |
| return subprocess.status() |
| .then([=](const Option<int>& status) -> Future<Nothing> { |
| // If the status is not set, fail out. |
| if (status.isNone()) { |
| return Failure("Subprocess status is none"); |
| } |
| |
| // If the status is not what we expect then fail out with the |
| // output of the subprocess. The failure message will include |
| // the assertion failures of the subprocess. |
| if ((expected_status.isSome() && status.get() != expected_status.get()) || |
| (expected_status.isNone() && status.get() == 0)) { |
| return io::read(out) |
| .then([](const string& output) -> Future<Nothing> { |
| return Failure("\n[++++++++++] Subprocess output.\n" + output + |
| "[++++++++++]\n"); |
| }); |
| } |
| |
| // If the subprocess ran successfully then return nothing. |
| return Nothing(); |
| }).onAny([=]() { |
| os::close(out); |
| }); |
| } |
| |
| |
| // The SSL protocols that we support through configuration flags. |
| static const vector<string> protocols = { |
| // OpenSSL can be compiled with SSLV3 disabled completely, so we |
| // conditionally test for this protocol. |
| #ifndef OPENSSL_NO_SSL3 |
| "LIBPROCESS_SSL_ENABLE_SSL_V3", |
| #endif |
| "LIBPROCESS_SSL_ENABLE_TLS_V1_0", |
| "LIBPROCESS_SSL_ENABLE_TLS_V1_1", |
| "LIBPROCESS_SSL_ENABLE_TLS_V1_2", |
| // On some platforms, we need to build against OpenSSL versions that |
| // do not support TLS 1.3 yet. |
| #ifdef SSL_OP_NO_TLSv1_3 |
| "LIBPROCESS_SSL_ENABLE_TLS_V1_3", |
| #endif |
| }; |
| |
| static const vector<string> hostname_validation_schemes = { |
| "legacy", |
| #ifdef HOSTNAME_VALIDATION_OPENSSL |
| "openssl", |
| #endif |
| }; |
| |
| |
| // Ensure that we can't create an SSL socket when SSL is not enabled. |
| TEST(SSL, Disabled) |
| { |
| os::setenv("LIBPROCESS_SSL_ENABLED", "false"); |
| openssl::reinitialize(); |
| EXPECT_ERROR(Socket::create(SocketImpl::Kind::SSL)); |
| } |
| |
| |
| // Test a basic back-and-forth communication using the 'ssl-client' |
| // subprocess. |
| TEST_F(SSLTest, SSLSocket) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}, |
| server.get(), |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } |
| |
| |
| // Ensure that a POLL based socket can't connect to an SSL based |
| // socket, even when SSL is enabled. |
| TEST_F(SSLTest, NonSSLSocket) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}, |
| server.get(), |
| false); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| |
| |
| class SSLTestStringParameter |
| : public SSLTest, |
| public ::testing::WithParamInterface<std::string> {}; |
| |
| |
| INSTANTIATE_TEST_CASE_P(HostnameValidationScheme, |
| SSLTestStringParameter, |
| ::testing::ValuesIn(hostname_validation_schemes)); |
| |
| |
| // Ensure that a certificate that was not generated using the |
| // certificate authority is still allowed to communicate as long as |
| // the LIBPROCESS_SSL_VERIFY_SERVER_CERT and LIBPROCESS_SSL_REQUIRE_CLIENT_CERT |
| // flags are disabled. |
| TEST_P(SSLTestStringParameter, NoVerifyBadCA) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "false"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", scrap_key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", scrap_certificate_path().string()}, |
| {"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "false"}, |
| {"LIBPROCESS_SSL_CA_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}}, |
| server.get(), |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } |
| |
| |
| // Ensure that a client certificate that was not generated using the |
| // certificate authority is NOT allowed to communicate when the |
| // LIBPROCESS_SSL_REQUIRE_CLIENT_CERT flag is enabled. |
| // |
| // NOTE: We cannot run this test with the 'legacy' hostname |
| // validation scheme due to MESOS-9867. |
| #ifdef HOSTNAME_VALIDATION_OPENSSL |
| TEST_F(SSLTest, RequireBadCA) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", scrap_key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", scrap_certificate_path().string()}, |
| {"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "false"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}}, |
| server.get(), |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| #endif // HOSTNAME_VALIDATION_OPENSSL |
| |
| |
| // Ensure that a server certificate that was not generated using the |
| // certificate authority is NOT allowed to communicate when the |
| // LIBPROCESS_SSL_VERIFY_SERVER_CERT flag is enabled. |
| TEST_P(SSLTestStringParameter, VerifyBadCA) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", scrap_key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", scrap_certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "false"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}}); |
| ASSERT_SOME(server); |
| |
| Try<std::string> hostname = net::getHostname(process::address().ip); |
| ASSERT_SOME(hostname); |
| |
| Try<Address> address = server->address(); |
| ASSERT_SOME(address); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}}, |
| *hostname, |
| address->ip, |
| address->port, |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| |
| |
| // Ensure that a certificate that WAS generated using the certificate |
| // authority IS allowed to communicate when the |
| // LIBPROCESS_SSL_VERIFY_SERVER_CERT and LIBPROCESS_SSL_REQUIRE_CLIENT_CERT |
| // flags are enabled. |
| // |
| // NOTE: If this test is failing for the 'legacy' scheme, subsequent |
| // tests may be affected due to MESOS-9867. |
| TEST_P(SSLTestStringParameter, VerifyCertificate) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CA_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}}); |
| ASSERT_SOME(server); |
| |
| Try<std::string> hostname = net::getHostname(process::address().ip); |
| ASSERT_SOME(hostname); |
| |
| Try<Address> address = server->address(); |
| ASSERT_SOME(address); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CA_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", GetParam()}}, |
| *hostname, |
| address->ip, |
| address->port, |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } |
| |
| |
| // Ensure that a server presenting a valid certificate with a not matching |
| // hostname is NOT allowed to communicate when the |
| // LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME flag is set to 'openssl'. |
| #ifdef HOSTNAME_VALIDATION_OPENSSL |
| TEST_F(SSLTest, HostnameMismatch) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CA_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}}); |
| ASSERT_SOME(server); |
| |
| Try<Address> address = server->address(); |
| ASSERT_SOME(address); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CA_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"}, |
| {"LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "openssl"}}, |
| "invalid.example.org", |
| address->ip, |
| address->port, |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| #endif // HOSTNAME_VALIDATION_OPENSSL |
| |
| |
| // Ensure that a server that attempts to present no certificate at all |
| // is NOT allowed to communicate when the LIBPROCESS_SSL_VERIFY_SERVER_CERT |
| // flag is enabled in the client. |
| TEST_F(SSLTest, NoAnonymousCipherIfVerify) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| // ADH stands for "Anonymous Diffie-Hellman", and is the only |
| // anonymous cipher supported by OpenSSL out of the box. |
| {"LIBPROCESS_SSL_CIPHERS", "ADH-AES256-SHA"}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_VERIFY_SERVER_CERT", "true"}, |
| {"LIBPROCESS_SSL_CIPHERS", "ADH-AES256-SHA"}}, |
| server.get(), |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| |
| |
| // Ensure that key exchange using ECDHE algorithm works. |
| TEST_F(SSLTest, ECDHESupport) |
| { |
| // Set up the default server environment variables. |
| map<string, string> server_environment = { |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CIPHERS", |
| "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:" |
| "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA"} |
| }; |
| |
| // Set up the default client environment variables. |
| map<string, string> client_environment = { |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CIPHERS", |
| "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:" |
| "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA"} |
| }; |
| |
| // Set up the server. |
| Try<Socket> server = setup_server(server_environment); |
| ASSERT_SOME(server); |
| |
| // Launch the client. |
| Try<Subprocess> client = |
| launch_client(client_environment, server.get(), true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } |
| |
| |
| // Ensure we can communicate between a POLL based socket and an SSL |
| // socket if 'SSL_SUPPORT_DOWNGRADE' is enabled. |
| TEST_F(SSLTest, ValidDowngrade) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "false"}}, |
| server.get(), |
| false); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } |
| |
| |
| // Ensure we CANNOT communicate between a POLL based socket and an |
| // SSL socket if 'SSL_SUPPORT_DOWNGRADE' is not enabled. |
| TEST_F(SSLTest, NoValidDowngrade) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "false"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "false"}}, |
| server.get(), |
| false); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| |
| |
| // For each protocol: ensure we can communicate between a POLL based |
| // socket and an SSL socket if 'SSL_SUPPORT_DOWNGRADE' is enabled. |
| TEST_F(SSLTest, ValidDowngradeEachProtocol) |
| { |
| // For each protocol. |
| foreach (const string& server_protocol, protocols) { |
| LOG(INFO) << "Testing server protocol '" << server_protocol << "'\n"; |
| |
| // Set up the default server environment variables. |
| map<string, string> server_environment = { |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()} |
| }; |
| |
| // Disable all protocols except for the one we're testing. |
| foreach (const string& protocol, protocols) { |
| server_environment.emplace( |
| protocol, |
| stringify(protocol == server_protocol)); |
| } |
| |
| // Set up the server. |
| Try<Socket> server = setup_server(server_environment); |
| ASSERT_SOME(server); |
| |
| // Launch the client with a POLL socket. |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "false"}}, |
| server.get(), |
| false); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } |
| } |
| |
| |
| // For each protocol: ensure we CANNOT communicate between a POLL |
| // based socket and an SSL socket if 'SSL_SUPPORT_DOWNGRADE' is not |
| // enabled. |
| TEST_F(SSLTest, NoValidDowngradeEachProtocol) |
| { |
| // For each protocol. |
| foreach (const string& server_protocol, protocols) { |
| LOG(INFO) << "Testing server protocol '" << server_protocol << "'\n"; |
| |
| // Set up the default server environment variables. |
| map<string, string> server_environment = { |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "false"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()} |
| }; |
| |
| // Disable all protocols except for the one we're testing. |
| foreach (const string& protocol, protocols) { |
| server_environment.emplace( |
| protocol, |
| stringify(protocol == server_protocol)); |
| } |
| |
| // Set up the server. |
| Try<Socket> server = setup_server(server_environment); |
| ASSERT_SOME(server); |
| |
| // Launch the client with a POLL socket. |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "false"}}, |
| server.get(), |
| false); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| } |
| |
| |
| // Verify that the 'peer()' address call works correctly. |
| TEST_F(SSLTest, PeerAddress) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| ASSERT_SOME(server); |
| |
| const Try<Socket> client_create = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(client_create); |
| |
| Socket client = client_create.get(); |
| |
| Future<Socket> socket = server->accept(); |
| |
| const Try<Address> server_address = server->address(); |
| ASSERT_SOME(server_address); |
| |
| // Pass `None()` as hostname because this test is still |
| // using the 'legacy' hostname validation scheme. |
| const Future<Nothing> connect = client.connect( |
| server_address.get(), |
| openssl::create_tls_client_config(None())); |
| |
| AWAIT_ASSERT_READY(socket); |
| AWAIT_ASSERT_READY(connect); |
| |
| const Try<Address> socket_address = socket->address(); |
| ASSERT_SOME(socket_address); |
| |
| // Ensure the client thinks its peer is the server. |
| ASSERT_SOME_EQ(socket_address.get(), client.peer()); |
| |
| // Ensure the client has an address, and that the server thinks its |
| // peer is the client. |
| ASSERT_SOME(client.address()); |
| ASSERT_SOME_EQ(client.address().get(), socket->peer()); |
| } |
| |
| |
| // Basic Https GET test. |
| TEST_F(SSLTest, HTTPSGet) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| |
| ASSERT_SOME(server); |
| ASSERT_SOME(server->address()); |
| |
| Try<std::string> serverHostname = server->address()->lookup_hostname(); |
| ASSERT_SOME(serverHostname); |
| |
| Future<Socket> socket = server->accept(); |
| |
| // Create URL from server hostname and port. |
| const http::URL url( |
| "https", serverHostname.get(), server->address()->port); |
| |
| // Send GET request. |
| Future<http::Response> response = http::get(url); |
| |
| AWAIT_ASSERT_READY(socket); |
| |
| // Construct response and send(server side). |
| const string buffer = |
| string("HTTP/1.1 200 OK\r\n") + |
| "Content-Length : " + |
| stringify(data.length()) + "\r\n" + |
| "\r\n" + |
| data; |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer)); |
| |
| AWAIT_ASSERT_READY(response); |
| AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response); |
| ASSERT_EQ(data, response->body); |
| } |
| |
| |
| // Basic Https POST test. |
| TEST_F(SSLTest, HTTPSPost) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| |
| ASSERT_SOME(server); |
| ASSERT_SOME(server->address()); |
| |
| Try<std::string> serverHostname = server->address()->lookup_hostname(); |
| ASSERT_SOME(serverHostname); |
| |
| Future<Socket> socket = server->accept(); |
| |
| // Create URL from server hostname and port. |
| const http::URL url( |
| "https", serverHostname.get(), server->address()->port); |
| |
| // Send POST request. |
| Future<http::Response> response = |
| http::post(url, None(), "payload", "text/plain"); |
| |
| AWAIT_ASSERT_READY(socket); |
| |
| // Construct response and send(server side). |
| const string buffer = |
| string("HTTP/1.1 200 OK\r\n") + |
| "Content-Length : " + |
| stringify(data.length()) + "\r\n" + |
| "\r\n" + |
| data; |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(buffer)); |
| |
| AWAIT_ASSERT_READY(response); |
| AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response); |
| ASSERT_EQ(data, response->body); |
| } |
| |
| |
| // This test ensures that if an SSL connection never sends any |
| // data (i.e. no handshake or downgrade completes), it will not |
| // impact our ability to accept additional connections. |
| // This test was added due to MESOS-5340. |
| TEST_F(SSLTest, SilentSocket) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| |
| ASSERT_SOME(server); |
| ASSERT_SOME(server->address()); |
| |
| Try<std::string> serverHostname = server->address()->lookup_hostname(); |
| ASSERT_SOME(serverHostname); |
| |
| Future<Socket> socket = server->accept(); |
| |
| // We initiate a connection on which we will not send |
| // any data. This means the socket on the server will |
| // not complete the SSL handshake, nor be downgraded. |
| // As a result, we expect that the server will not see |
| // an accepted socket for this connection. |
| Try<Socket> connection = Socket::create(SocketImpl::Kind::POLL); |
| ASSERT_SOME(connection); |
| connection->connect(server->address().get()); |
| |
| // Note that settling libprocess is not sufficient |
| // for ensuring socket events are processed. |
| Clock::pause(); |
| Clock::settle(); |
| Clock::resume(); |
| |
| ASSERT_TRUE(socket.isPending()); |
| |
| // Now send an HTTP GET request, it should complete |
| // without getting blocked by the socket above |
| // undergoing the SSL handshake. |
| const http::URL url( |
| "https", |
| serverHostname.get(), |
| server->address()->port); |
| |
| Future<http::Response> response = http::get(url); |
| |
| AWAIT_READY(socket); |
| |
| // Send the response from the server. |
| const string buffer = string() + |
| "HTTP/1.1 200 OK\r\n" + |
| "Content-Length: " + stringify(data.length()) + "\r\n" + |
| "\r\n" + |
| data; |
| AWAIT_READY(Socket(socket.get()).send(buffer)); |
| |
| AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response); |
| EXPECT_EQ(data, response->body); |
| } |
| |
| |
| // A copy of the SilentSocket test to ensure that the issue |
| // also is not present with downgrade support enabled. This |
| // was added due to MESOS-10114. |
| TEST_F(SSLTest, SilentSocketWithDowngrade) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_SUPPORT_DOWNGRADE", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| |
| ASSERT_SOME(server); |
| ASSERT_SOME(server->address()); |
| |
| Try<std::string> serverHostname = server->address()->lookup_hostname(); |
| ASSERT_SOME(serverHostname); |
| |
| Future<Socket> socket = server->accept(); |
| |
| // We initiate a connection on which we will not send |
| // any data. This means the socket on the server will |
| // not complete the SSL handshake, nor be downgraded. |
| // As a result, we expect that the server will not see |
| // an accepted socket for this connection. |
| Try<Socket> connection = Socket::create(SocketImpl::Kind::POLL); |
| ASSERT_SOME(connection); |
| connection->connect(server->address().get()); |
| |
| // Note that settling libprocess is not sufficient |
| // for ensuring socket events are processed. |
| Clock::pause(); |
| Clock::settle(); |
| Clock::resume(); |
| |
| ASSERT_TRUE(socket.isPending()); |
| |
| // Now send an HTTP GET request, it should complete |
| // without getting blocked by the socket above |
| // undergoing the SSL handshake. |
| const http::URL url( |
| "https", |
| serverHostname.get(), |
| server->address()->port); |
| |
| Future<http::Response> response = http::get(url); |
| |
| AWAIT_READY(socket); |
| |
| // Send the response from the server. |
| const string buffer = string() + |
| "HTTP/1.1 200 OK\r\n" + |
| "Content-Length: " + stringify(data.length()) + "\r\n" + |
| "\r\n" + |
| data; |
| AWAIT_READY(Socket(socket.get()).send(buffer)); |
| |
| AWAIT_EXPECT_RESPONSE_STATUS_EQ(http::OK().status, response); |
| EXPECT_EQ(data, response->body); |
| } |
| |
| |
| // This test was added due to an OOM issue: MESOS-7934. |
| TEST_F(SSLTest, ShutdownThenSend) |
| { |
| Clock::pause(); |
| |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}}); |
| |
| ASSERT_SOME(server); |
| ASSERT_SOME(server->address()); |
| |
| Future<Socket> socket = server->accept(); |
| |
| Clock::settle(); |
| EXPECT_TRUE(socket.isPending()); |
| |
| Try<Socket> client = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(client); |
| |
| // Pass `None()` as hostname because this test is still |
| // using the 'legacy' hostname validation scheme. |
| AWAIT_ASSERT_READY(client->connect( |
| server->address().get(), |
| openssl::create_tls_client_config(None()))); |
| |
| AWAIT_ASSERT_READY(socket); |
| |
| EXPECT_SOME(Socket(socket.get()).shutdown()); |
| |
| // This send should fail now that the socket is shut down. |
| AWAIT_FAILED(Socket(socket.get()).send("Hello World")); |
| } |
| |
| |
| #endif // USE_SSL_SOCKET |
| |
| |
| class SSLVerifyIPAddTest : public SSLTest, |
| public ::testing::WithParamInterface<const char*> {}; |
| |
| |
| INSTANTIATE_TEST_CASE_P(SSLVerifyIPAdd, |
| SSLVerifyIPAddTest, |
| ::testing::Values("false", "true")); |
| |
| |
| // Test a basic back-and-forth communication within the same OS |
| // process. |
| TEST_P(SSLVerifyIPAddTest, BasicSameProcess) |
| { |
| os::setenv("LIBPROCESS_SSL_ENABLED", "true"); |
| os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string()); |
| os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string()); |
| os::setenv("LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"); |
| os::setenv("LIBPROCESS_SSL_CA_DIR", os::getcwd()); |
| os::setenv("LIBPROCESS_SSL_CA_FILE", certificate_path().string()); |
| os::setenv("LIBPROCESS_SSL_VERIFY_IPADD", GetParam()); |
| os::setenv("LIBPROCESS_SSL_HOSTNAME_VALIDATION_SCHEME", "legacy"); |
| |
| openssl::reinitialize(); |
| |
| Try<Socket> server = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(server); |
| |
| Try<Socket> client = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(client); |
| |
| // We need to explicitly bind to the address advertised by libprocess so the |
| // certificate we create in this test fixture can be verified. |
| ASSERT_SOME(server->bind(Address(net::IP(process::address().ip), 0))); |
| ASSERT_SOME(server->listen(BACKLOG)); |
| |
| Try<Address> address = server->address(); |
| ASSERT_SOME(address); |
| |
| Future<Socket> accept = server->accept(); |
| |
| // Pass `None()` as hostname because this test is still |
| // using the 'legacy' hostname validation scheme. |
| AWAIT_ASSERT_READY(client->connect( |
| address.get(), |
| openssl::create_tls_client_config(None()))); |
| |
| // Wait for the server to have accepted the client connection. |
| AWAIT_ASSERT_READY(accept); |
| |
| Socket socket = accept.get(); |
| |
| // Send a message from the client to the server. |
| const string data = "Hello World!"; |
| AWAIT_ASSERT_READY(client->send(data)); |
| |
| // Verify the server received the message. |
| AWAIT_ASSERT_EQ(data, socket.recv(data.size())); |
| |
| // Send the message back from the server to the client. |
| AWAIT_ASSERT_READY(socket.send(data)); |
| |
| // Verify the client received the message. |
| AWAIT_ASSERT_EQ(data, client->recv(data.size())); |
| } |
| |
| |
| #ifndef __WINDOWS__ |
| TEST_P(SSLVerifyIPAddTest, BasicSameProcessUnix) |
| { |
| os::setenv("LIBPROCESS_SSL_ENABLED", "true"); |
| os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string()); |
| os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string()); |
| // NOTE: we must set LIBPROCESS_SSL_REQUIRE_CLIENT_CERT to false because we |
| // don't have a hostname or IP to verify! |
| os::setenv("LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "false"); |
| os::setenv("LIBPROCESS_SSL_CA_DIR", os::getcwd()); |
| os::setenv("LIBPROCESS_SSL_CA_FILE", certificate_path().string()); |
| os::setenv("LIBPROCESS_SSL_VERIFY_IPADD", GetParam()); |
| |
| openssl::reinitialize(); |
| |
| Try<unix::Socket> server = unix::Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(server); |
| |
| Try<unix::Socket> client = unix::Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(client); |
| |
| // Use a path in the temporary directory so it gets cleaned up. |
| string path = path::join(sandbox.get(), "socket"); |
| |
| Try<unix::Address> address = unix::Address::create(path); |
| ASSERT_SOME(address); |
| |
| ASSERT_SOME(server->bind(address.get())); |
| ASSERT_SOME(server->listen(BACKLOG)); |
| |
| Future<unix::Socket> accept = server->accept(); |
| |
| // Pass `None()` as hostname because this test is still |
| // using the 'legacy' hostname validation scheme. |
| AWAIT_ASSERT_READY(client->connect( |
| address.get(), |
| openssl::create_tls_client_config(None()))); |
| |
| // Wait for the server to have accepted the client connection. |
| AWAIT_ASSERT_READY(accept); |
| |
| unix::Socket socket = accept.get(); |
| |
| // Send a message from the client to the server. |
| const string data = "Hello World!"; |
| AWAIT_ASSERT_READY(client->send(data)); |
| |
| // Verify the server received the message. |
| AWAIT_ASSERT_EQ(data, socket.recv(data.size())); |
| |
| // Send the message back from the server to the client. |
| AWAIT_ASSERT_READY(socket.send(data)); |
| |
| // Verify the client received the message. |
| AWAIT_ASSERT_EQ(data, client->recv(data.size())); |
| } |
| #endif // __WINDOWS__ |
| |
| |
| // Ensure that a certificate that WAS generated using the certificate |
| // authority IS allowed to communicate when the |
| // LIBPROCESS_SSL_REQUIRE_CLIENT_CERT flag is enabled. |
| TEST_P(SSLVerifyIPAddTest, RequireCertificate) |
| { |
| Try<Socket> server = setup_server({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CA_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}, |
| {"LIBPROCESS_SSL_VERIFY_IPADD", GetParam()}}); |
| ASSERT_SOME(server); |
| |
| Try<Subprocess> client = launch_client({ |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_CA_FILE", certificate_path().string()}, |
| {"LIBPROCESS_SSL_REQUIRE_CLIENT_CERT", "true"}, |
| {"LIBPROCESS_SSL_VERIFY_IPADD", GetParam()}}, |
| server.get(), |
| true); |
| ASSERT_SOME(client); |
| |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } |
| |
| |
| class SSLProtocolTest |
| : public SSLTest, |
| public ::testing::WithParamInterface<std::tuple<string, string>> {}; |
| |
| |
| INSTANTIATE_TEST_CASE_P( |
| SSLProtocol, |
| SSLProtocolTest, |
| ::testing::Combine( |
| ::testing::ValuesIn(protocols), ::testing::ValuesIn(protocols))); |
| |
| |
| // Test all the combinations of protocols. Ensure that they can only |
| // communicate if the opposing end allows the given protocol, and not |
| // otherwise. |
| TEST_P(SSLProtocolTest, Mismatch) |
| { |
| const string& server_protocol = std::get<0>(GetParam()); |
| const string& client_protocol = std::get<1>(GetParam()); |
| |
| LOG(INFO) << "Testing server protocol '" << server_protocol |
| << "' with client protocol '" << client_protocol << "'\n"; |
| |
| // Set up the default server environment variables. |
| map<string, string> server_environment = { |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()} |
| }; |
| |
| // Set up the default client environment variables. |
| map<string, string> client_environment = { |
| {"LIBPROCESS_SSL_ENABLED", "true"}, |
| {"LIBPROCESS_SSL_KEY_FILE", key_path().string()}, |
| {"LIBPROCESS_SSL_CERT_FILE", certificate_path().string()}, |
| }; |
| |
| // Disable all protocols except for the one we're testing. |
| foreach (const string& protocol, protocols) { |
| server_environment.emplace( |
| protocol, |
| stringify(protocol == server_protocol)); |
| |
| client_environment.emplace( |
| protocol, |
| stringify(protocol == client_protocol)); |
| } |
| |
| // Set up the server. |
| Try<Socket> server = setup_server(server_environment); |
| ASSERT_SOME(server); |
| |
| // Launch the client. |
| Try<Subprocess> client = |
| launch_client(client_environment, server.get(), true); |
| ASSERT_SOME(client); |
| |
| if (server_protocol == client_protocol) { |
| // If the protocols are the same, it is valid. |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_READY(socket); |
| |
| // TODO(jmlvanre): Remove const copy. |
| AWAIT_ASSERT_EQ(data, Socket(socket.get()).recv()); |
| AWAIT_ASSERT_READY(Socket(socket.get()).send(data)); |
| |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), 0)); |
| } else { |
| // If the protocols are NOT the same, it is invalid. |
| Future<Socket> socket = server->accept(); |
| AWAIT_ASSERT_FAILED(socket); |
| |
| // Pass `None()` as hostname because this test is still |
| // using the 'legacy' hostname validation scheme. |
| AWAIT_ASSERT_READY(await_subprocess(client.get(), None())); |
| } |
| } |
| |
| |
| // Verify that we can make a connection using a custom SSL context, |
| // and that the specified `verify` and `configure_socket` callbacks |
| // are called. |
| TEST_F(SSLTest, CustomSSLContext) |
| { |
| static bool verify_called; |
| static bool configure_socket_called; |
| |
| os::setenv("LIBPROCESS_SSL_ENABLED", "true"); |
| os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string()); |
| os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string()); |
| |
| openssl::reinitialize(); |
| |
| verify_called = false; |
| configure_socket_called = false; |
| |
| SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method()); |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr); |
| |
| openssl::TLSClientConfig config( |
| None(), |
| ctx, |
| [](SSL*, const network::Address&, const Option<std::string>&) |
| -> Try<Nothing> |
| { |
| configure_socket_called = true; |
| return Nothing(); |
| }, |
| [](const SSL* const, const Option<std::string>&, const Option<net::IP>&) |
| -> Try<Nothing> |
| { |
| verify_called = true; |
| return Nothing(); |
| }); |
| |
| Try<Socket> client = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(client); |
| |
| Try<Socket> server = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(server); |
| |
| server->listen(1); |
| Try<Address> address = server->address(); |
| ASSERT_SOME(address); |
| |
| Future<Socket> socket = server->accept(); |
| Future<Nothing> connected = client->connect(*address, config); |
| |
| AWAIT_READY(socket); |
| AWAIT_READY(connected); |
| |
| EXPECT_TRUE(verify_called); |
| EXPECT_TRUE(configure_socket_called); |
| } |
| |
| |
| // Ensures that `connect()` fails if the passed |
| // `configure_socket` callback returns an error. |
| TEST_F(SSLTest, CustomSSLContextConfigureSocketFails) |
| { |
| os::setenv("LIBPROCESS_SSL_ENABLED", "true"); |
| os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string()); |
| os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string()); |
| |
| openssl::reinitialize(); |
| |
| SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method()); |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr); |
| |
| openssl::TLSClientConfig config( |
| None(), |
| ctx, |
| [](SSL*, const network::Address&, const Option<std::string>&) |
| -> Try<Nothing> |
| { |
| return Error("Configure socket."); |
| }, |
| [](const SSL* const, const Option<std::string>&, const Option<net::IP>&) |
| -> Try<Nothing> |
| { |
| return Nothing(); |
| }); |
| |
| Try<Socket> client = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(client); |
| |
| Try<Socket> server = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(server); |
| |
| server->listen(1); |
| Try<Address> address = server->address(); |
| ASSERT_SOME(address); |
| |
| Future<Socket> socket = server->accept(); |
| Future<Nothing> connected = client->connect(*address, config); |
| |
| AWAIT_ASSERT_FAILED(connected); |
| } |
| |
| |
| // Ensures that `connect()` fails if the passed |
| // `verify` callback returns an error. |
| TEST_F(SSLTest, CustomSSLContextVerifyFails) |
| { |
| os::setenv("LIBPROCESS_SSL_ENABLED", "true"); |
| os::setenv("LIBPROCESS_SSL_KEY_FILE", key_path().string()); |
| os::setenv("LIBPROCESS_SSL_CERT_FILE", certificate_path().string()); |
| |
| openssl::reinitialize(); |
| |
| SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method()); |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr); |
| |
| openssl::TLSClientConfig config( |
| None(), |
| ctx, |
| [](SSL*, const network::Address&, const Option<std::string>&) |
| -> Try<Nothing> |
| { |
| return Nothing(); |
| }, |
| [](const SSL* const, const Option<std::string>&, const Option<net::IP>&) |
| -> Try<Nothing> |
| { |
| return Error("Verify failed."); |
| }); |
| |
| Try<Socket> client = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(client); |
| |
| Try<Socket> server = Socket::create(SocketImpl::Kind::SSL); |
| ASSERT_SOME(server); |
| |
| server->listen(1); |
| Try<Address> address = server->address(); |
| ASSERT_SOME(address); |
| |
| Future<Socket> socket = server->accept(); |
| Future<Nothing> connected = client->connect(*address, config); |
| |
| AWAIT_ASSERT_FAILED(connected); |
| } |