Skip to content

Commit 6137643

Browse files
committed
Add TlsNegotationTest
1 parent 4399b6e commit 6137643

File tree

1 file changed

+182
-0
lines changed

1 file changed

+182
-0
lines changed

tests/TlsNegotiationTest.cc

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
#include <gtest/gtest.h>
20+
#include <pulsar/Client.h>
21+
#include <pulsar/Authentication.h>
22+
#include "lib/AsioDefines.h"
23+
#include <future>
24+
#include <thread>
25+
#include <atomic>
26+
#include "lib/LogUtils.h"
27+
#include <openssl/ssl.h>
28+
29+
#ifdef USE_ASIO
30+
#include <asio.hpp>
31+
#include <asio/ssl.hpp>
32+
#else
33+
#include <boost/asio.hpp>
34+
#include <boost/asio/ssl.hpp>
35+
#endif
36+
37+
DECLARE_LOG_OBJECT()
38+
39+
#ifndef TEST_CONF_DIR
40+
#error "TEST_CONF_DIR is not specified"
41+
#endif
42+
43+
static const std::string caPath = TEST_CONF_DIR "/cacert.pem";
44+
static const std::string clientPublicKeyPath = TEST_CONF_DIR "/client-cert.pem";
45+
static const std::string clientPrivateKeyPath = TEST_CONF_DIR "/client-key.pem";
46+
47+
using namespace pulsar;
48+
49+
class MockTlsServer {
50+
public:
51+
MockTlsServer()
52+
: acceptor_(io_context_, ASIO::ip::tcp::endpoint(ASIO::ip::tcp::v4(), 0)),
53+
ctx_(ASIO::ssl::context::sslv23) {
54+
55+
ctx_.set_options(ASIO::ssl::context::default_workarounds |
56+
ASIO::ssl::context::no_sslv2 |
57+
ASIO::ssl::context::no_sslv3);
58+
59+
ctx_.use_certificate_chain_file(clientPublicKeyPath);
60+
ctx_.use_private_key_file(clientPrivateKeyPath, ASIO::ssl::context::pem);
61+
ctx_.set_verify_mode(ASIO::ssl::context::verify_none);
62+
}
63+
64+
int getPort() const {
65+
return acceptor_.local_endpoint().port();
66+
}
67+
68+
void setTls12Only() {
69+
SSL_CTX* ssl_ctx = ctx_.native_handle();
70+
#if defined(TLS1_2_VERSION)
71+
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION);
72+
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_2_VERSION);
73+
#else
74+
LOG_WARN("TLS 1.2 not supported by OpenSSL headers");
75+
#endif
76+
}
77+
78+
void setTls13Only() {
79+
SSL_CTX* ssl_ctx = ctx_.native_handle();
80+
#if defined(TLS1_3_VERSION)
81+
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
82+
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
83+
#else
84+
LOG_WARN("TLS 1.3 not supported by OpenSSL headers");
85+
#endif
86+
}
87+
88+
bool acceptAndHandshake() {
89+
auto socket = std::make_shared<ASIO::ip::tcp::socket>(io_context_);
90+
acceptor_.accept(*socket);
91+
92+
ASIO::ssl::stream<ASIO::ip::tcp::socket&> ssl_stream(*socket, ctx_);
93+
94+
ASIO_ERROR error;
95+
ssl_stream.handshake(ASIO::ssl::stream_base::server, error);
96+
97+
if (error) {
98+
LOG_ERROR("Handshake failed: " << error.message());
99+
return false;
100+
}
101+
LOG_INFO("Handshake success!");
102+
return true;
103+
}
104+
105+
private:
106+
ASIO::io_context io_context_;
107+
ASIO::ip::tcp::acceptor acceptor_;
108+
ASIO::ssl::context ctx_;
109+
};
110+
111+
TEST(TlsNegotiationTest, testTls12) {
112+
#if !defined(TLS1_2_VERSION)
113+
return; // Skip if TLS 1.2 is not available
114+
#endif
115+
116+
MockTlsServer server;
117+
server.setTls12Only();
118+
int port = server.getPort();
119+
120+
std::promise<bool> handshakePromise;
121+
auto handshakeFuture = handshakePromise.get_future();
122+
123+
std::thread serverThread([&server, &handshakePromise]() {
124+
bool result = server.acceptAndHandshake();
125+
handshakePromise.set_value(result);
126+
});
127+
128+
std::string serviceUrl = "pulsar+ssl://localhost:" + std::to_string(port);
129+
ClientConfiguration config;
130+
config.setTlsTrustCertsFilePath(caPath);
131+
config.setTlsAllowInsecureConnection(true); // Self-signed certs match
132+
config.setValidateHostName(false);
133+
134+
Client client(serviceUrl, config);
135+
136+
// Trigger connection by creating a producer.
137+
// It will fail to create producer because mock server doesn't speak Pulsar,
138+
// but we only care about the handshake.
139+
Producer producer;
140+
client.createProducerAsync("topic", [](Result, Producer){});
141+
142+
// Wait for handshake
143+
ASSERT_TRUE(handshakeFuture.get());
144+
145+
serverThread.join();
146+
client.close();
147+
}
148+
149+
TEST(TlsNegotiationTest, testTls13) {
150+
#if !defined(TLS1_3_VERSION)
151+
LOG_INFO("Skipping TLS 1.3 test because OpenSSL does not support it");
152+
return;
153+
#endif
154+
155+
MockTlsServer server;
156+
server.setTls13Only();
157+
int port = server.getPort();
158+
159+
std::promise<bool> handshakePromise;
160+
auto handshakeFuture = handshakePromise.get_future();
161+
162+
std::thread serverThread([&server, &handshakePromise]() {
163+
bool result = server.acceptAndHandshake();
164+
handshakePromise.set_value(result);
165+
});
166+
167+
std::string serviceUrl = "pulsar+ssl://localhost:" + std::to_string(port);
168+
ClientConfiguration config;
169+
config.setTlsTrustCertsFilePath(caPath);
170+
config.setTlsAllowInsecureConnection(true);
171+
config.setValidateHostName(false);
172+
173+
Client client(serviceUrl, config);
174+
175+
client.createProducerAsync("topic", [](Result, Producer){});
176+
177+
ASSERT_TRUE(handshakeFuture.get());
178+
179+
serverThread.join();
180+
client.close();
181+
}
182+

0 commit comments

Comments
 (0)