Skip to content

Commit d040039

Browse files
authored
[improve][client] Add TLSv1.3 support (#529)
1 parent 889a04b commit d040039

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

lib/ClientConnection.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,10 @@ ClientConnection::ClientConnection(const std::string& logicalAddress, const std:
207207
}
208208

209209
if (clientConfiguration.isUseTls()) {
210-
ASIO::ssl::context ctx(ASIO::ssl::context::tlsv12_client);
210+
ASIO::ssl::context ctx(ASIO::ssl::context::sslv23_client);
211+
ctx.set_options(ASIO::ssl::context::default_workarounds | ASIO::ssl::context::no_sslv2 |
212+
ASIO::ssl::context::no_sslv3 | ASIO::ssl::context::no_tlsv1 |
213+
ASIO::ssl::context::no_tlsv1_1);
211214
Url serviceUrl;
212215
Url proxyUrl;
213216
Url::parse(physicalAddress, serviceUrl);

tests/TlsNegotiationTest.cc

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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 <openssl/ssl.h>
21+
#include <pulsar/Authentication.h>
22+
#include <pulsar/Client.h>
23+
24+
#include <atomic>
25+
#include <future>
26+
#include <thread>
27+
28+
#include "lib/AsioDefines.h"
29+
#include "lib/LogUtils.h"
30+
31+
#ifdef USE_ASIO
32+
#include <asio.hpp>
33+
#include <asio/ssl.hpp>
34+
#else
35+
#include <boost/asio.hpp>
36+
#include <boost/asio/ssl.hpp>
37+
#endif
38+
39+
DECLARE_LOG_OBJECT()
40+
41+
#ifndef TEST_CONF_DIR
42+
#error "TEST_CONF_DIR is not specified"
43+
#endif
44+
45+
static const std::string caPath = TEST_CONF_DIR "/cacert.pem";
46+
static const std::string clientPublicKeyPath = TEST_CONF_DIR "/client-cert.pem";
47+
static const std::string clientPrivateKeyPath = TEST_CONF_DIR "/client-key.pem";
48+
49+
using namespace pulsar;
50+
51+
class MockTlsServer {
52+
public:
53+
MockTlsServer()
54+
: acceptor_(io_context_, ASIO::ip::tcp::endpoint(ASIO::ip::tcp::v4(), 0)),
55+
ctx_(ASIO::ssl::context::sslv23) {
56+
ctx_.set_options(ASIO::ssl::context::default_workarounds | 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 { return acceptor_.local_endpoint().port(); }
65+
66+
void setTls12Only() {
67+
SSL_CTX* ssl_ctx = ctx_.native_handle();
68+
#if defined(TLS1_2_VERSION)
69+
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION);
70+
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_2_VERSION);
71+
#else
72+
LOG_WARN("TLS 1.2 not supported by OpenSSL headers");
73+
#endif
74+
}
75+
76+
void setTls13Only() {
77+
SSL_CTX* ssl_ctx = ctx_.native_handle();
78+
#if defined(TLS1_3_VERSION)
79+
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
80+
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
81+
#else
82+
LOG_WARN("TLS 1.3 not supported by OpenSSL headers");
83+
#endif
84+
}
85+
86+
bool acceptAndHandshake() {
87+
auto socket = std::make_shared<ASIO::ip::tcp::socket>(io_context_);
88+
acceptor_.accept(*socket);
89+
90+
ASIO::ssl::stream<ASIO::ip::tcp::socket&> ssl_stream(*socket, ctx_);
91+
92+
ASIO_ERROR error;
93+
ssl_stream.handshake(ASIO::ssl::stream_base::server, error);
94+
95+
if (error) {
96+
LOG_ERROR("Handshake failed: " << error.message());
97+
return false;
98+
}
99+
LOG_INFO("Handshake success!");
100+
return true;
101+
}
102+
103+
private:
104+
ASIO::io_context io_context_;
105+
ASIO::ip::tcp::acceptor acceptor_;
106+
ASIO::ssl::context ctx_;
107+
};
108+
109+
TEST(TlsNegotiationTest, testTls12) {
110+
#if !defined(TLS1_2_VERSION)
111+
return; // Skip if TLS 1.2 is not available
112+
#endif
113+
114+
MockTlsServer server;
115+
server.setTls12Only();
116+
int port = server.getPort();
117+
118+
std::promise<bool> handshakePromise;
119+
auto handshakeFuture = handshakePromise.get_future();
120+
121+
std::thread serverThread([&server, &handshakePromise]() {
122+
bool result = server.acceptAndHandshake();
123+
handshakePromise.set_value(result);
124+
});
125+
126+
std::string serviceUrl = "pulsar+ssl://localhost:" + std::to_string(port);
127+
ClientConfiguration config;
128+
config.setTlsTrustCertsFilePath(caPath);
129+
config.setTlsAllowInsecureConnection(true); // Self-signed certs match
130+
config.setValidateHostName(false);
131+
132+
Client client(serviceUrl, config);
133+
134+
// Trigger connection by creating a producer.
135+
// It will fail to create producer because mock server doesn't speak Pulsar,
136+
// but we only care about the handshake.
137+
Producer producer;
138+
client.createProducerAsync("topic", [](Result, Producer) {});
139+
140+
// Wait for handshake
141+
ASSERT_TRUE(handshakeFuture.get());
142+
143+
serverThread.join();
144+
client.close();
145+
}
146+
147+
TEST(TlsNegotiationTest, testTls13) {
148+
#if !defined(TLS1_3_VERSION)
149+
LOG_INFO("Skipping TLS 1.3 test because OpenSSL does not support it");
150+
return;
151+
#endif
152+
153+
MockTlsServer server;
154+
server.setTls13Only();
155+
int port = server.getPort();
156+
157+
std::promise<bool> handshakePromise;
158+
auto handshakeFuture = handshakePromise.get_future();
159+
160+
std::thread serverThread([&server, &handshakePromise]() {
161+
bool result = server.acceptAndHandshake();
162+
handshakePromise.set_value(result);
163+
});
164+
165+
std::string serviceUrl = "pulsar+ssl://localhost:" + std::to_string(port);
166+
ClientConfiguration config;
167+
config.setTlsTrustCertsFilePath(caPath);
168+
config.setTlsAllowInsecureConnection(true);
169+
config.setValidateHostName(false);
170+
171+
Client client(serviceUrl, config);
172+
173+
client.createProducerAsync("topic", [](Result, Producer) {});
174+
175+
ASSERT_TRUE(handshakeFuture.get());
176+
177+
serverThread.join();
178+
client.close();
179+
}

0 commit comments

Comments
 (0)