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

randombit / botan / 21848380424

10 Feb 2026 01:47AM UTC coverage: 91.634% (+1.6%) from 90.069%
21848380424

push

github

web-flow
Merge pull request #5296 from randombit/jack/tls-header-patrol

Various changes to reduce header dependencies in TLS

104002 of 113497 relevant lines covered (91.63%)

11230277.53 hits per line

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

88.72
/src/cli/tls_http_server.cpp
1
/*
2
* (C) 2014,2015,2017,2019,2023 Jack Lloyd
3
* (C) 2016 Matthias Gierlings
4
* (C) 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
5
*
6
* This implementation is roughly based on this BSL-licensed example
7
* by Klemens D. Morgenstern:
8
*   www.boost.org/doc/libs/1_83_0/libs/beast/example/http/server/awaitable/http_server_awaitable.cpp
9
*
10
* Botan is released under the Simplified BSD License (see license.txt)
11
*/
12

13
#include "cli.h"
14

15
#if defined(BOTAN_HAS_TLS_ASIO_STREAM)
16
   #include <botan/asio_compat.h>
17
#endif
18

19
#if defined(BOTAN_FOUND_COMPATIBLE_BOOST_ASIO_VERSION)
20
   #include <boost/asio/awaitable.hpp>
21
#endif
22

23
// If your boost version is too old, this might not be defined despite
24
// your toolchain supporting C++20 co_await.
25
#if defined(BOOST_ASIO_HAS_CO_AWAIT) && defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
26

27
   #include <ctime>
28
   #include <iomanip>
29
   #include <memory>
30
   #include <string>
31
   #include <thread>
32
   #include <vector>
33

34
   #include <boost/asio/co_spawn.hpp>
35
   #include <boost/asio/ip/tcp.hpp>
36
   #include <boost/asio/use_awaitable.hpp>
37
   #include <boost/beast/http.hpp>
38

39
   #include <botan/asio_stream.h>
40
   #include <botan/tls_ciphersuite.h>
41
   #include <botan/tls_messages.h>
42
   #include <botan/tls_session_manager_memory.h>
43
   #include <botan/version.h>
44
   #include <botan/internal/fmt.h>
45

46
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
47
      #include <botan/tls_session_manager_sqlite.h>
48
   #endif
49

50
   #if defined(BOTAN_HAS_OS_UTILS)
51
      #include <botan/internal/os_utils.h>
52
   #endif
53

54
   #include "tls_helpers.h"
55

56
namespace Botan_CLI {
57

58
namespace {
59

60
namespace beast = boost::beast;    // from <boost/beast.hpp>
61
namespace http = beast::http;      // from <boost/beast/http.hpp>
62
namespace net = boost::asio;       // from <boost/asio.hpp>
63
using tcp = boost::asio::ip::tcp;  // from <boost/asio/ip/tcp.hpp>
64

65
using tcp_stream = typename beast::tcp_stream::rebind_executor<
66
   net::use_awaitable_t<>::executor_with_default<net::any_io_executor>>::other;
67

68
class Logger final {
69
   private:
70
      std::string timestamp() const {
5✔
71
   #if defined(BOTAN_HAS_OS_UTILS)
72
         return Botan::OS::format_time(std::time(nullptr), "%c");
10✔
73
   #else
74
         return std::to_string(std::time(nullptr));
75
   #endif
76
      }
77

78
   public:
79
      Logger(std::ostream& out, std::ostream& err) : m_out(out), m_err(err) {}
1✔
80

81
      void log(std::string_view out) {
5✔
82
         const std::scoped_lock lk(m_mutex);
5✔
83
         m_out << Botan::fmt("[{}] {}", timestamp(), out) << "\n";
15✔
84
      }
5✔
85

86
      void error(std::string_view err) {
×
87
         const std::scoped_lock lk(m_mutex);
×
88
         m_err << Botan::fmt("[{}] {}", timestamp(), err) << "\n";
×
89
      }
×
90

91
      void flush() {
1✔
92
         const std::scoped_lock lk(m_mutex);
1✔
93
         m_out.flush();
1✔
94
         m_err.flush();
1✔
95
      }
1✔
96

97
   private:
98
      std::mutex m_mutex;
99
      std::ostream& m_out;
100
      std::ostream& m_err;
101
};
102

103
class TlsHttpCallbacks final : public Botan::TLS::StreamCallbacks {
4✔
104
   public:
105
      void tls_session_activated() override {
4✔
106
         std::ostringstream strm;
4✔
107

108
         strm << "TLS negotiation with " << Botan::version_string() << " test server\n\n";
8✔
109

110
         m_connection_summary = strm.str();
4✔
111
      }
4✔
112

113
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
4✔
114
         std::ostringstream strm;
4✔
115

116
         strm << "Version: " << session.version().to_string() << "\n";
8✔
117
         strm << "Ciphersuite: " << session.ciphersuite().to_string() << "\n";
8✔
118
         if(const auto& session_id = session.session_id(); !session_id.empty()) {
4✔
119
            strm << "SessionID: " << Botan::hex_encode(session_id.get()) << "\n";
8✔
120
         }
121
         if(!session.server_info().hostname().empty()) {
8✔
122
            strm << "SNI: " << session.server_info().hostname() << "\n";
12✔
123
         }
124

125
         m_session_summary = strm.str();
4✔
126
      }
4✔
127

128
      void tls_inspect_handshake_msg(const Botan::TLS::Handshake_Message& message) override {
34✔
129
         if(message.type() == Botan::TLS::Handshake_Type::ClientHello) {
34✔
130
            const Botan::TLS::Client_Hello& client_hello = dynamic_cast<const Botan::TLS::Client_Hello&>(message);
6✔
131

132
            std::ostringstream strm;
6✔
133

134
            strm << "Client random: " << Botan::hex_encode(client_hello.random()) << "\n";
6✔
135

136
            strm << "Client offered following ciphersuites:\n";
6✔
137
            for(const uint16_t suite_id : client_hello.ciphersuites()) {
126✔
138
               const auto ciphersuite = Botan::TLS::Ciphersuite::by_id(suite_id);
120✔
139

140
               strm << " - 0x" << std::hex << std::setfill('0') << std::setw(4) << suite_id << std::dec
120✔
141
                    << std::setfill(' ') << std::setw(0) << " ";
120✔
142

143
               if(ciphersuite && ciphersuite->valid()) {
120✔
144
                  strm << ciphersuite->to_string() << "\n";
342✔
145
               } else if(suite_id == 0x00FF) {
6✔
146
                  strm << "Renegotiation SCSV\n";
6✔
147
               } else {
148
                  strm << "Unknown ciphersuite\n";
×
149
               }
150
            }
151

152
            m_chello_summary = strm.str();
6✔
153
         }
6✔
154
      }
34✔
155

156
      std::string summary() const {
2✔
157
         BOTAN_STATE_CHECK(!m_connection_summary.empty() && !m_session_summary.empty() && !m_chello_summary.empty());
2✔
158
         return m_connection_summary + m_session_summary + m_chello_summary;
4✔
159
      }
160

161
   private:
162
      std::string m_chello_summary;
163
      std::string m_connection_summary;
164
      std::string m_session_summary;
165
};
166

167
std::string summarize_request(const Botan::TLS::Stream<tcp_stream>& tls_stream,
2✔
168
                              const http::request<http::string_body>& req) {
169
   std::ostringstream strm;
2✔
170

171
   const auto& remote = tls_stream.next_layer().socket().remote_endpoint();
2✔
172

173
   strm << "Client " << remote.address().to_string() << " requested " << req.method_string() << " " << req.target()
4✔
174
        << "\n";
8✔
175

176
   if(std::distance(req.begin(), req.end()) > 0) {
4✔
177
      strm << "Client HTTP headers:\n";
2✔
178
      for(const auto& header : req) {
8✔
179
         strm << " " << header.name_string() << ": " << header.value() << "\n";
6✔
180
      }
181
   }
182

183
   return strm.str();
4✔
184
}
2✔
185

186
auto make_final_completion_handler(const std::shared_ptr<Logger>& logger, const std::string& context) {
5✔
187
   return [=](std::exception_ptr e) {
5✔
188
      if(e) {
5✔
189
         try {
×
190
            std::rethrow_exception(std::move(e));
×
191
         } catch(const std::exception& ex) {
×
192
            logger->error(Botan::fmt("{}: {}", context, ex.what()));
×
193
         }
×
194
      }
195
   };
10✔
196
}
197

198
std::shared_ptr<http::response<http::string_body>> handle_response(const http::request<http::string_body>& req,
4✔
199
                                                                   const std::shared_ptr<TlsHttpCallbacks>& callbacks,
200
                                                                   const Botan::TLS::Stream<tcp_stream>& tls_stream,
201
                                                                   const std::shared_ptr<Logger>& logger) {
202
   logger->log(Botan::fmt("{} {}", req.method_string(), req.target()));
12✔
203

204
   auto [status_code, msg] = [&]() -> std::tuple<http::status, std::string> {
4✔
205
      if(req.method() != http::verb::get) {
4✔
206
         return {http::status::method_not_allowed, "Unsupported HTTP verb\n"};
2✔
207
      } else if(req.target() == "/" || req.target() == "/status") {
4✔
208
         return {http::status::ok, callbacks->summary() + summarize_request(tls_stream, req)};
4✔
209
      } else {
210
         return {http::status::not_found, "Not found\n"};
×
211
      }
212
   }();
4✔
213

214
   auto response = std::make_shared<http::response<http::string_body>>(status_code, req.version());
4✔
215
   response->body() = msg;
4✔
216
   response->set(http::field::content_type, "text/plain");
4✔
217
   response->keep_alive(req.keep_alive());
4✔
218
   response->prepare_payload();
4✔
219

220
   return response;
4✔
221
}
4✔
222

223
net::awaitable<void> do_session(tcp_stream stream,
12✔
224
                                std::shared_ptr<Botan::TLS::Context> tls_ctx,
225
                                std::shared_ptr<Logger> logger) {
226
   // This buffer is required to persist across reads
227
   beast::flat_buffer buffer;
228

229
   // Set up Botan's TLS stack
230
   auto callbacks = std::make_shared<TlsHttpCallbacks>();
231
   Botan::TLS::Stream<tcp_stream> tls_stream(std::move(stream), std::move(tls_ctx), callbacks);
232

233
   std::exception_ptr protocol_exception;
234

235
   try {
236
      // Perform a TLS handshake with the peer
237
      co_await tls_stream.async_handshake(Botan::TLS::Connection_Side::Server);
238

239
      while(true) {
240
         // Set the timeout.
241
         tls_stream.next_layer().expires_after(std::chrono::seconds(30));
242

243
         // Read a request
244
         http::request<http::string_body> req;
245
         co_await http::async_read(tls_stream, buffer, req);
246

247
         // Handle the request
248
         auto response = handle_response(req, callbacks, tls_stream, logger);
249

250
         // Send the response
251
         co_await http::async_write(tls_stream, *response);
252

253
         // Determine if we should close the connection
254
         if(!response->keep_alive()) {
255
            // This means we should close the connection, usually because
256
            // the response indicated the "Connection: close" semantic.
257
            break;
258
         }
259
      }
260
   } catch(boost::system::system_error& se) {
261
      if(se.code() != http::error::end_of_stream) {
262
         // Something went wrong during the communication, as good citizens we
263
         // try to shutdown the connection gracefully, anyway. The protocol
264
         // exception is kept for later re-throw.
265
         protocol_exception = std::current_exception();
266
      }
267
   }
268

269
   try {
270
      // Shut down the connection gracefully. It gives the stream a chance to
271
      // flush remaining send buffers and/or close the connection gracefully. If
272
      // the communication above failed this may or may not be successful.
273
      co_await tls_stream.async_shutdown();
274
      tls_stream.next_layer().close();
275
   } catch(const std::exception&) {
276
      // if the protocol interaction above produced an exception the shutdown
277
      // was "best effort" anyway and we swallow the secondary exception that
278
      // happened during shutdown.
279
      if(!protocol_exception) {
280
         throw;
281
      }
282
   }
283

284
   if(protocol_exception) {
285
      std::rethrow_exception(protocol_exception);
286
   }
287

288
   // At this point the connection is closed gracefully
289
   // we ignore the error because the client might have
290
   // dropped the connection already.
291
}
8✔
292

293
net::awaitable<void> do_listen(tcp::endpoint endpoint,
2✔
294
                               std::shared_ptr<Botan::TLS::Context> tls_ctx,
295
                               size_t max_clients,
296
                               std::shared_ptr<Logger> logger) {
297
   auto acceptor = net::use_awaitable.as_default_on(tcp::acceptor(co_await net::this_coro::executor));
298
   acceptor.open(endpoint.protocol());
299
   acceptor.set_option(net::socket_base::reuse_address(true));
300
   acceptor.bind(endpoint);
301
   acceptor.listen(net::socket_base::max_listen_connections);
302

303
   // If max_clients is zero in the beginning, we'll serve forever
304
   // otherwise we'll count down and stop eventually.
305

306
   const bool run_forever = (max_clients == 0);
307

308
   logger->log(Botan::fmt("Listening for new connections on {}:{}", endpoint.address().to_string(), endpoint.port()));
309
   logger->flush();
310

311
   auto done = [&] {
312
      if(run_forever) {
313
         return false;
314
      } else {
315
         return max_clients-- == 0;
316
      }
317
   };
318

319
   while(!done()) {
320
      boost::asio::co_spawn(acceptor.get_executor(),
321
                            do_session(tcp_stream(co_await acceptor.async_accept()), tls_ctx, logger),
322
                            make_final_completion_handler(logger, "Session"));
323
   }
324
}
2✔
325

326
}  // namespace
327

328
class TLS_HTTP_Server final : public Command {
329
   public:
330
      TLS_HTTP_Server() :
2✔
331
            Command(
332
               "tls_http_server server_cert server_key "
333
               "--port=443 --policy=default --threads=0 --max-clients=0 "
334
               "--session-db= --session-db-pass=") {}
4✔
335

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

338
      std::string description() const override { return "Provides a simple HTTP server"; }
1✔
339

340
      size_t thread_count() const {
1✔
341
         if(const size_t t = get_arg_sz("threads")) {
1✔
342
            return t;
343
         }
344
   #if defined(BOTAN_HAS_OS_UTILS)
345
         if(const size_t t = Botan::OS::get_cpu_available()) {
1✔
346
            return t;
1✔
347
         }
348
   #endif
349
         return 2;
350
      }
351

352
      void go() override {
1✔
353
         const uint16_t listen_port = get_arg_u16("port");
1✔
354

355
         const std::string server_crt = get_arg("server_cert");
1✔
356
         const std::string server_key = get_arg("server_key");
1✔
357

358
         const size_t num_threads = thread_count();
1✔
359
         const size_t max_clients = get_arg_sz("max-clients");
1✔
360

361
         auto creds = std::make_shared<Basic_Credentials_Manager>(server_crt, server_key);
1✔
362

363
         auto policy = load_tls_policy(get_arg("policy"));
2✔
364

365
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
1✔
366

367
         const std::string sessions_db = get_arg("session-db");
1✔
368

369
         if(!sessions_db.empty()) {
1✔
370
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
371
            const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
×
372
            session_mgr =
×
373
               std::make_shared<Botan::TLS::Session_Manager_SQLite>(sessions_passphrase, rng_as_shared(), sessions_db);
×
374
   #else
375
            throw CLI_Error_Unsupported("Sqlite3 support not available");
376
   #endif
377
         }
×
378

379
         if(!session_mgr) {
1✔
380
            session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());
3✔
381
         }
382

383
         auto logger = std::make_shared<Logger>(output(), error_output());
1✔
384

385
         net::io_context io{static_cast<int>(num_threads)};
1✔
386
         auto address = net::ip::make_address("0.0.0.0");
1✔
387
         boost::asio::co_spawn(
2✔
388
            io,
389
            do_listen(tcp::endpoint{address, listen_port},
2✔
390
                      std::make_shared<Botan::TLS::Context>(creds, rng_as_shared(), session_mgr, policy),
3✔
391
                      max_clients,
392
                      logger),
393
            make_final_completion_handler(logger, "Acceptor"));
2✔
394

395
         std::vector<std::shared_ptr<std::thread>> threads;
1✔
396

397
         // run forever... first thread is main calling io.run below
398
         for(size_t i = 2; i <= num_threads; ++i) {
4✔
399
            threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); }));
6✔
400
         }
401

402
         io.run();
1✔
403

404
         for(auto& thread : threads) {
4✔
405
            thread->join();
3✔
406
         }
407
      }
5✔
408
};
409

410
BOTAN_REGISTER_COMMAND("tls_http_server", TLS_HTTP_Server);
2✔
411

412
}  // namespace Botan_CLI
413

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

© 2026 Coveralls, Inc