• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 39195077976

18 Oct 2026 05:18PM UTC coverage: 92.258% (+0.06%) from 92.195%
39195077976

push

github

randombit
Update release notes [ci skip]

137358 of 148884 relevant lines covered (92.26%)

8967647.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.3
/src/cli/tls_proxy.cpp
1
/*
2
* TLS Server Proxy
3
* (C) 2014,2015,2019 Jack Lloyd
4
* (C) 2016 Matthias Gierlings
5
* (C) 2023 René Meusel, Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9

10
#include "cli.h"
11

12
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_BOOST_ASIO) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
13

14
   #include <atomic>
15
   #include <iostream>
16
   #include <string>
17
   #include <thread>
18
   #include <utility>
19
   #include <vector>
20

21
   #include <boost/asio.hpp>
22
   #include <boost/bind/bind.hpp>
23

24
   #include <botan/pkcs8.h>
25
   #include <botan/rng.h>
26
   #include <botan/tls_callbacks.h>
27
   #include <botan/tls_server.h>
28
   #include <botan/tls_session_manager_memory.h>
29

30
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
31
      #include <botan/tls_session_manager_sqlite.h>
32
   #endif
33

34
   #if defined(BOTAN_HAS_OS_UTILS)
35
      #include <botan/internal/os_utils.h>
36
   #endif
37

38
   #include "tls_helpers.h"
39

40
namespace Botan_CLI {
41

42
namespace {
43

44
// NOLINTBEGIN(*-avoid-endl,*-avoid-bind)
45

46
using boost::asio::ip::tcp;
47

48
template <typename T>
49
boost::asio::io_context& get_io_service(T& s) {
4 ✔
50
   #if BOOST_VERSION >= 107000
51
   //NOLINTNEXTLINE(*-type-static-cast-downcast)
52
   return static_cast<boost::asio::io_context&>((s).get_executor().context());
4 ✔
53
   #else
54
   return s.get_io_service();
55
   #endif
56
}
57

58
void log_info(const std::string& msg) {
1 ✔
59
   std::cout << msg << std::endl;
1 ✔
60
}
1 ✔
61

62
void log_exception(const char* where, const std::exception& e) {
×
63
   std::cout << where << ' ' << e.what() << std::endl;
×
64
}
×
65

66
void log_error(const char* where, const boost::system::error_code& error) {
8 ✔
67
   std::cout << where << ' ' << error.message() << std::endl;
16 ✔
68
}
8 ✔
69

70
void log_error(const char* msg) {
×
71
   std::cout << msg << std::endl;
×
72
}
×
73

74
void log_binary_message(const char* where, const uint8_t buf[], size_t buf_len) {
75
   BOTAN_UNUSED(where, buf, buf_len);
33 ✔
76
   //std::cout << where << ' ' << Botan::hex_encode(buf, buf_len) << std::endl;
77
}
78

79
void log_text_message(const char* where, const uint8_t buf[], size_t buf_len) {
80
   BOTAN_UNUSED(where, buf, buf_len);
12 ✔
81
   //const char* c = reinterpret_cast<const char*>(buf);
82
   //std::cout << where << ' ' << std::string(c, c + buf_len)  << std::endl;
83
}
84

85
class ServerStatus {
86
   public:
87
      explicit ServerStatus(size_t max_clients) : m_max_clients(max_clients), m_clients_serviced(0) {}
1 ✔
88

89
      bool should_exit() const {
4 ✔
90
         if(m_max_clients == 0) {
4 ✔
91
            return false;
92
         }
93

94
         return clients_serviced() >= m_max_clients;
4 ✔
95
      }
96

97
      void client_serviced() { m_clients_serviced++; }
4 ✔
98

99
      size_t clients_serviced() const { return m_clients_serviced.load(); }
4 ✔
100

101
   private:
102
      size_t m_max_clients;
103
      std::atomic<size_t> m_clients_serviced;
104
};
105

106
class tls_proxy_session final : public std::enable_shared_from_this<tls_proxy_session>,
107
                                public Botan::TLS::Callbacks {
108
   public:
109
      static constexpr size_t readbuf_size = 17 * 1024;
110

111
      typedef std::shared_ptr<tls_proxy_session> pointer;
112

113
      static pointer create(boost::asio::io_context& io,
4 ✔
114
                            const std::shared_ptr<Botan::TLS::Session_Manager>& session_manager,
115
                            const std::shared_ptr<Botan::Credentials_Manager>& credentials,
116
                            const std::shared_ptr<Botan::TLS::Policy>& policy,
117
                            const tcp::resolver::results_type& endpoints) {
118
         auto session = std::make_shared<tls_proxy_session>(io, endpoints);
4 ✔
119

120
         // Defer the setup of the TLS server to make use of
121
         // shared_from_this() which wouldn't work in the c'tor.
122
         session->setup(session_manager, credentials, policy);
4 ✔
123

124
         return session;
4 ✔
125
      }
×
126

127
      tcp::socket& client_socket() { return m_client_socket; }
128

129
      void start() {
4 ✔
130
         m_c2p.resize(readbuf_size);
4 ✔
131
         client_read(boost::system::error_code(), 0);  // start read loop
4 ✔
132
      }
4 ✔
133

134
      void stop() {
8 ✔
135
         if(!m_is_closed) {
8 ✔
136
            /*
137
            Don't need to talk to the server anymore
138
            Client socket is closed during write callback
139
            */
140
            m_server_socket.close();
4 ✔
141
            m_tls->close();
4 ✔
142

143
            // Need to explicitly destroy the TLS::Server object to break the
144
            // circular ownership of shared_from_this() and the shared_ptr of
145
            // this kept inside the TLS::Channel.
146
            m_tls.reset();
4 ✔
147
            m_is_closed = true;
4 ✔
148
         }
149
      }
8 ✔
150

151
      tls_proxy_session(boost::asio::io_context& io, tcp::resolver::results_type endpoints) :
4 ✔
152
            m_strand(io),
4 ✔
153
            m_server_endpoints(std::move(endpoints)),
4 ✔
154
            m_client_socket(io),
4 ✔
155
            m_server_socket(io),
8 ✔
156
            m_rng(cli_make_rng()) {}
12 ✔
157

158
   private:
159
      void setup(const std::shared_ptr<Botan::TLS::Session_Manager>& session_manager,
4 ✔
160
                 const std::shared_ptr<Botan::Credentials_Manager>& credentials,
161
                 const std::shared_ptr<Botan::TLS::Policy>& policy) {
162
         m_tls = std::make_unique<Botan::TLS::Server>(shared_from_this(), session_manager, credentials, policy, m_rng);
4 ✔
163
      }
4 ✔
164

165
      void client_read(const boost::system::error_code& error, size_t bytes_transferred) {
20 ✔
166
         if(error) {
20 ✔
167
            log_error("Read failed", error);
4 ✔
168
            stop();
4 ✔
169
            return;
4 ✔
170
         }
171

172
         if(m_is_closed) {
16 ✔
173
            log_error("Received client data after close");
×
174
            return;
×
175
         }
176

177
         try {
16 ✔
178
            if(!m_tls->is_active()) {
16 ✔
179
               log_binary_message("From client", m_c2p.data(), bytes_transferred);
180
            }
181
            m_tls->received_data(m_c2p.data(), bytes_transferred);
16 ✔
182
         } catch(Botan::Exception& e) {
×
183
            log_exception("TLS connection failed", e);
×
184
            stop();
×
185
            return;
×
186
         }
×
187

188
         m_client_socket.async_read_some(
32 ✔
189
            boost::asio::buffer(m_c2p),
16 ✔
190
            boost::asio::bind_executor(
32 ✔
191
               m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t bytes) {
192 ✔
192
                  self->client_read(ec, bytes);
16 ✔
193
               }));
194
      }
195

196
      void handle_client_write_completion(const boost::system::error_code& error) {
25 ✔
197
         if(error) {
25 ✔
198
            log_error("Client write", error);
×
199
            stop();
×
200
            return;
×
201
         }
202

203
         m_p2c.clear();
25 ✔
204

205
         if(m_p2c_pending.empty() && (!m_tls || m_tls->is_closed())) {
25 ✔
206
            m_client_socket.close();
4 ✔
207
         }
208
         tls_emit_data({});  // initiate another write if needed
25 ✔
209
      }
210

211
      void handle_server_write_completion(const boost::system::error_code& error) {
4 ✔
212
         if(error) {
4 ✔
213
            log_error("Server write", error);
×
214
            stop();
×
215
            return;
×
216
         }
217

218
         m_p2s.clear();
4 ✔
219
         proxy_write_to_server({});  // initiate another write if needed
4 ✔
220
      }
221

222
      void tls_record_received(uint64_t /*rec_no*/, std::span<const uint8_t> buf) override {
4 ✔
223
         // Immediately bounce message to server
224
         proxy_write_to_server(buf);
4 ✔
225
      }
4 ✔
226

227
      void tls_emit_data(std::span<const uint8_t> buf) override {
55 ✔
228
         if(!buf.empty()) {
55 ✔
229
            m_p2c_pending.insert(m_p2c_pending.end(), buf.begin(), buf.end());
30 ✔
230
         }
231

232
         // no write now active and we still have output pending
233
         if(m_p2c.empty() && !m_p2c_pending.empty()) {
55 ✔
234
            std::swap(m_p2c_pending, m_p2c);
25 ✔
235

236
            log_binary_message("To Client", m_p2c.data(), m_p2c.size());
25 ✔
237

238
            boost::asio::async_write(
50 ✔
239
               m_client_socket,
25 ✔
240
               boost::asio::buffer(m_p2c),
25 ✔
241
               boost::asio::bind_executor(
50 ✔
242
                  m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t /*bytes*/) {
300 ✔
243
                     self->handle_client_write_completion(ec);
25 ✔
244
                  }));
245
         }
246
      }
55 ✔
247

248
      void proxy_write_to_server(std::span<const uint8_t> buf) {
12 ✔
249
         if(!buf.empty()) {
12 ✔
250
            m_p2s_pending.insert(m_p2s_pending.end(), buf.begin(), buf.end());
4 ✔
251
         }
252

253
         // no write now active and we still have output pending
254
         if(m_p2s.empty() && !m_p2s_pending.empty()) {
12 ✔
255
            std::swap(m_p2s_pending, m_p2s);
4 ✔
256

257
            log_text_message("To Server", m_p2s.data(), m_p2s.size());
4 ✔
258

259
            boost::asio::async_write(
8 ✔
260
               m_server_socket,
4 ✔
261
               boost::asio::buffer(m_p2s),
4 ✔
262
               boost::asio::bind_executor(
8 ✔
263
                  m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t /*bytes*/) {
48 ✔
264
                     self->handle_server_write_completion(ec);
4 ✔
265
                  }));
266
         }
267
      }
12 ✔
268

269
      void server_read(const boost::system::error_code& error, size_t bytes_transferred) {
16 ✔
270
         if(error) {
16 ✔
271
            log_error("Server read failed", error);
4 ✔
272
            stop();
4 ✔
273
            return;
4 ✔
274
         }
275

276
         try {
12 ✔
277
            if(bytes_transferred > 0) {
12 ✔
278
               log_text_message("Server to client", m_s2p.data(), m_s2p.size());
8 ✔
279
               log_binary_message("Server to client", m_s2p.data(), m_s2p.size());
8 ✔
280
               m_tls->send(m_s2p.data(), bytes_transferred);
8 ✔
281
            }
282
         } catch(Botan::Exception& e) {
×
283
            log_exception("TLS connection failed", e);
×
284
            stop();
×
285
            return;
×
286
         }
×
287

288
         m_s2p.resize(readbuf_size);
12 ✔
289

290
         m_server_socket.async_read_some(
24 ✔
291
            boost::asio::buffer(m_s2p),
12 ✔
292
            boost::asio::bind_executor(
24 ✔
293
               m_strand, [self = shared_from_this()](const boost::system::error_code& ec, std::size_t bytes) {
144 ✔
294
                  self->server_read(ec, bytes);
12 ✔
295
               }));
296
      }
297

298
      void tls_session_activated() override {
4 ✔
299
         auto onConnect = [self = weak_from_this()](boost::system::error_code ec,
12 ✔
300
                                                    const tcp::resolver::results_type::iterator& /*endpoint*/) {
301
            if(ec) {
4 ✔
302
               log_error("Server connection", ec);
×
303
               return;
×
304
            }
305

306
            if(const auto ptr = self.lock()) {
4 ✔
307
               ptr->server_read(boost::system::error_code(), 0);  // start read loop
4 ✔
308
               ptr->proxy_write_to_server({});
4 ✔
309
            } else {
310
               log_error("Server connection established, but client session already closed");
×
311
               return;
×
312
            }
4 ✔
313
         };
4 ✔
314
         // Must run on the strand like every other handler touching session state
315
         async_connect(m_server_socket,
4 ✔
316
                       m_server_endpoints.begin(),
8 ✔
317
                       m_server_endpoints.end(),
4 ✔
318
                       boost::asio::bind_executor(m_strand, onConnect));
8 ✔
319
      }
4 ✔
320

321
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
4 ✔
322
         m_hostname = session.server_info().hostname();
4 ✔
323
      }
4 ✔
324

325
      void tls_alert(Botan::TLS::Alert alert) override {
×
326
         if(alert.type() == Botan::TLS::Alert::CloseNotify) {
×
327
            m_tls->close();
×
328
            return;
×
329
         }
330
      }
331

332
      boost::asio::io_context::strand m_strand;
333

334
      tcp::resolver::results_type m_server_endpoints;
335

336
      tcp::socket m_client_socket;
337
      tcp::socket m_server_socket;
338

339
      std::shared_ptr<Botan::RandomNumberGenerator> m_rng;
340
      std::unique_ptr<Botan::TLS::Server> m_tls;
341
      std::string m_hostname;
342

343
      std::vector<uint8_t> m_c2p;
344
      std::vector<uint8_t> m_p2c;
345
      std::vector<uint8_t> m_p2c_pending;
346

347
      std::vector<uint8_t> m_s2p;
348
      std::vector<uint8_t> m_p2s;
349
      std::vector<uint8_t> m_p2s_pending;
350

351
      bool m_is_closed = false;
352
};
353

354
class tls_proxy_server final {
355
   public:
356
      typedef tls_proxy_session session;
357

358
      tls_proxy_server(boost::asio::io_context& io,
1 ✔
359
                       unsigned short port,
360
                       tcp::resolver::results_type endpoints,
361
                       std::shared_ptr<Botan::Credentials_Manager> creds,
362
                       std::shared_ptr<Botan::TLS::Policy> policy,
363
                       std::shared_ptr<Botan::TLS::Session_Manager> session_mgr,
364
                       size_t max_clients) :
1 ✔
365
            m_acceptor(io, tcp::endpoint(tcp::v4(), port)),
1 ✔
366
            m_server_endpoints(std::move(endpoints)),
1 ✔
367
            m_creds(std::move(creds)),
1 ✔
368
            m_policy(std::move(policy)),
1 ✔
369
            m_session_manager(std::move(session_mgr)),
1 ✔
370
            m_status(max_clients) {
1 ✔
371
         log_info("Listening for new connections on port " + std::to_string(m_acceptor.local_endpoint().port()));
4 ✔
372
         serve_one_session();
1 ✔
373
      }
1 ✔
374

375
   private:
376
      session::pointer make_session() {
4 ✔
377
         return session::create(get_io_service(m_acceptor), m_session_manager, m_creds, m_policy, m_server_endpoints);
4 ✔
378
      }
379

380
      void serve_one_session() {
4 ✔
381
         const session::pointer new_session = make_session();
4 ✔
382

383
         m_acceptor.async_accept(
8 ✔
384
            new_session->client_socket(),
4 ✔
385
            boost::bind(&tls_proxy_server::handle_accept, this, new_session, boost::asio::placeholders::error));
12 ✔
386
      }
4 ✔
387

388
      void handle_accept(const session::pointer& new_session, const boost::system::error_code& error) {
4 ✔
389
         if(!error) {
4 ✔
390
            new_session->start();
4 ✔
391
            m_status.client_serviced();
4 ✔
392

393
            if(!m_status.should_exit()) {
4 ✔
394
               serve_one_session();
3 ✔
395
            }
396
         }
397
      }
4 ✔
398

399
      tcp::acceptor m_acceptor;
400
      tcp::resolver::results_type m_server_endpoints;
401

402
      std::shared_ptr<Botan::Credentials_Manager> m_creds;
403
      std::shared_ptr<Botan::TLS::Policy> m_policy;
404
      std::shared_ptr<Botan::TLS::Session_Manager> m_session_manager;
405
      ServerStatus m_status;
406
};
407

408
class TLS_Proxy final : public Command {
409
   public:
410
      TLS_Proxy() :
2 ✔
411
            Command(
412
               "tls_proxy listen_port target_host target_port server_cert server_key "
413
               "--policy=default --threads=0 --max-clients=0 --session-db= --session-db-pass=") {}
4 ✔
414

415
      std::string group() const override { return "tls"; }
1 ✔
416

417
      std::string description() const override { return "Proxies requests between a TLS client and a TLS server"; }
1 ✔
418

419
      size_t thread_count() const {
1 ✔
420
         if(const size_t t = get_arg_sz("threads")) {
1 ✔
421
            return t;
422
         }
423
   #if defined(BOTAN_HAS_OS_UTILS)
424
         if(const size_t t = Botan::OS::get_cpu_available()) {
1 ✔
425
            return t;
1 ✔
426
         }
427
   #endif
428
         return 2;
429
      }
430

431
      void go() override {
1 ✔
432
         const uint16_t listen_port = get_arg_u16("listen_port");
1 ✔
433
         const std::string target = get_arg("target_host");
1 ✔
434
         const std::string target_port = get_arg("target_port");
1 ✔
435

436
         const std::string server_crt = get_arg("server_cert");
1 ✔
437
         const std::string server_key = get_arg("server_key");
1 ✔
438

439
         const size_t num_threads = thread_count();
1 ✔
440
         const size_t max_clients = get_arg_sz("max-clients");
1 ✔
441

442
         const auto creds = std::make_shared<Basic_Credentials_Manager>(server_crt, server_key);
1 ✔
443

444
         const auto policy = load_tls_policy(get_arg("policy"));
2 ✔
445

446
         boost::asio::io_context io;
1 ✔
447

448
         tcp::resolver resolver(io);
1 ✔
449
         const auto server_endpoint_iterator = resolver.resolve(target, target_port);
1 ✔
450

451
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
1 ✔
452

453
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
454
         const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
2 ✔
455
         const std::string sessions_db = get_arg("session-db");
1 ✔
456

457
         if(!sessions_db.empty()) {
1 ✔
458
            session_mgr =
×
459
               std::make_shared<Botan::TLS::Session_Manager_SQLite>(sessions_passphrase, rng_as_shared(), sessions_db);
×
460
         }
461
   #endif
462
         if(!session_mgr) {
1 ✔
463
            session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());
3 ✔
464
         }
465

466
         const tls_proxy_server server(
1 ✔
467
            io, listen_port, server_endpoint_iterator, creds, policy, session_mgr, max_clients);
2 ✔
468

469
         std::vector<std::shared_ptr<std::thread>> threads;
1 ✔
470

471
         // run forever... first thread is main calling io.run below
472
         for(size_t i = 2; i <= num_threads; ++i) {
4 ✔
473
            threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); }));
6 ✔
474
         }
475

476
         io.run();
1 ✔
477

478
         for(const auto& thread : threads) {
4 ✔
479
            thread->join();
3 ✔
480
         }
481
      }
5 ✔
482
};
483

484
// NOLINTEND(*-avoid-endl,*-avoid-bind)
485

486
BOTAN_REGISTER_COMMAND("tls_proxy", TLS_Proxy);
2 ✔
487

488
}  // namespace
489

490
}  // namespace Botan_CLI
491

492
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc