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

randombit / botan / 5817837490

10 Aug 2023 06:41AM UTC coverage: 91.676% (-0.02%) from 91.7%
5817837490

push

github

web-flow
Merge pull request #3660 from FAlbertDev/fix/tls-http-alert-handling

Fix: TLS HTTP server alert handling

78523 of 85653 relevant lines covered (91.68%)

12207665.7 hits per line

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

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

9
#include "cli.h"
10

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

13
   #include <atomic>
14
   #include <fstream>
15
   #include <iomanip>
16
   #include <iostream>
17
   #include <memory>
18
   #include <string>
19
   #include <thread>
20
   #include <utility>
21
   #include <vector>
22

23
   #include <botan/internal/os_utils.h>
24
   #include <boost/asio.hpp>
25
   #include <boost/bind.hpp>
26

27
   #include <botan/hex.h>
28
   #include <botan/pkcs8.h>
29
   #include <botan/rng.h>
30
   #include <botan/tls_messages.h>
31
   #include <botan/tls_server.h>
32
   #include <botan/tls_session_manager_memory.h>
33
   #include <botan/version.h>
34
   #include <botan/x509cert.h>
35

36
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
37
      #include <botan/tls_session_manager_sqlite.h>
38
   #endif
39

40
   #include "tls_helpers.h"
41

42
namespace Botan_CLI {
43

44
namespace {
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
   return static_cast<boost::asio::io_context&>((s).get_executor().context());
8✔
52
   #else
53
   return s.get_io_service();
54
   #endif
55
}
56

57
inline void log_error(const char* msg) {
×
58
   std::cout << msg << std::endl;
×
59
}
60

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

65
class ServerStatus {
66
   public:
67
      ServerStatus(size_t max_clients) : m_max_clients(max_clients), m_clients_serviced(0) {}
1✔
68

69
      bool should_exit() const {
4✔
70
         if(m_max_clients == 0) {
4✔
71
            return false;
72
         }
73

74
         return clients_serviced() >= m_max_clients;
4✔
75
      }
76

77
      void client_serviced() { m_clients_serviced++; }
4✔
78

79
      size_t clients_serviced() const { return m_clients_serviced.load(); }
4✔
80

81
   private:
82
      size_t m_max_clients;
83
      std::atomic<size_t> m_clients_serviced;
84
};
85

86
/*
87
* This is an incomplete and highly buggy HTTP request parser. It is just
88
* barely sufficient to handle a GET request sent by a browser.
89
*/
90
class HTTP_Parser final {
8✔
91
   public:
92
      class Request {
93
         public:
94
            const std::string& verb() const { return m_verb; }
6✔
95

96
            const std::string& location() const { return m_location; }
4✔
97

98
            const std::map<std::string, std::string>& headers() const { return m_headers; }
4✔
99

100
            Request(const std::string& verb,
4✔
101
                    const std::string& location,
102
                    const std::map<std::string, std::string>& headers) :
4✔
103
                  m_verb(verb), m_location(location), m_headers(headers) {}
8✔
104

105
         private:
106
            std::string m_verb;
107
            std::string m_location;
108
            std::map<std::string, std::string> m_headers;
109
      };
110

111
      class Callbacks {
112
         public:
113
            virtual void handle_http_request(const Request& request) = 0;
114

115
            virtual ~Callbacks() = default;
×
116

117
            Callbacks() = default;
4✔
118

119
            Callbacks(const Callbacks& other) = delete;
120
            Callbacks(Callbacks&& other) = delete;
121
            Callbacks& operator=(const Callbacks& other) = delete;
122
            Callbacks& operator=(Callbacks&&) = delete;
123
      };
124

125
      HTTP_Parser(Callbacks& cb) : m_cb(cb) {}
4✔
126

127
      void consume_input(std::span<const uint8_t> buf) {
4✔
128
         m_req_buf.append(reinterpret_cast<const char*>(buf.data()), buf.size());
4✔
129

130
         std::istringstream strm(m_req_buf);
4✔
131

132
         std::string http_version;
4✔
133
         std::string verb;
4✔
134
         std::string location;
4✔
135
         std::map<std::string, std::string> headers;
4✔
136

137
         strm >> verb >> location >> http_version;
4✔
138

139
         if(verb.empty() || location.empty()) {
4✔
140
            return;
×
141
         }
142

143
         while(true) {
22✔
144
            std::string header_line;
22✔
145
            std::getline(strm, header_line);
22✔
146

147
            if(header_line == "\r") {
22✔
148
               continue;
8✔
149
            }
150

151
            auto delim = header_line.find(": ");
14✔
152
            if(delim == std::string::npos) {
14✔
153
               break;
154
            }
155

156
            const std::string hdr_name = header_line.substr(0, delim);
10✔
157
            const std::string hdr_val = header_line.substr(delim + 2, std::string::npos);
10✔
158

159
            headers[hdr_name] = hdr_val;
10✔
160

161
            if(headers.size() > 1024) {
10✔
162
               throw Botan::Invalid_Argument("Too many HTTP headers sent in request");
×
163
            }
164
         }
28✔
165

166
         if(!verb.empty() && !location.empty()) {
4✔
167
            Request req(verb, location, headers);
4✔
168
            m_cb.handle_http_request(req);
4✔
169
            m_req_buf.clear();
4✔
170
         }
4✔
171
      }
4✔
172

173
   private:
174
      Callbacks& m_cb;
175
      std::string m_req_buf;
176
};
177

178
const size_t READBUF_SIZE = 4096;
179

180
class TLS_Asio_HTTP_Session final : public std::enable_shared_from_this<TLS_Asio_HTTP_Session>,
181
                                    public Botan::TLS::Callbacks,
182
                                    public HTTP_Parser::Callbacks {
183
   public:
184
      typedef std::shared_ptr<TLS_Asio_HTTP_Session> pointer;
185

186
      static pointer create(boost::asio::io_service& io,
4✔
187
                            const std::shared_ptr<Botan::TLS::Session_Manager>& session_manager,
188
                            const std::shared_ptr<Botan::Credentials_Manager>& credentials,
189
                            const std::shared_ptr<Botan::TLS::Policy>& policy) {
190
         auto session = std::make_shared<TLS_Asio_HTTP_Session>(io);
4✔
191

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

196
         return session;
4✔
197
      }
×
198

199
      tcp::socket& client_socket() { return m_client_socket; }
200

201
      void start() {
4✔
202
         m_c2s.resize(READBUF_SIZE);
4✔
203
         client_read(boost::system::error_code(), 0);  // start read loop
4✔
204
      }
4✔
205

206
      void stop() {
4✔
207
         if(!m_tls) {
4✔
208
            // Server is already closed
209
            return;
210
         }
211

212
         m_tls->close();
4✔
213

214
         // Need to explicitly destroy the TLS::Server object to break the
215
         // circular ownership of shared_from_this() and the shared_ptr of
216
         // this kept inside the TLS::Channel.
217
         m_tls.reset();
4✔
218
      }
219

220
      TLS_Asio_HTTP_Session(boost::asio::io_service& io) : m_strand(io), m_client_socket(io), m_rng(cli_make_rng()) {}
12✔
221

222
   private:
223
      void setup(const std::shared_ptr<Botan::TLS::Session_Manager>& session_manager,
4✔
224
                 const std::shared_ptr<Botan::Credentials_Manager>& credentials,
225
                 const std::shared_ptr<Botan::TLS::Policy>& policy) {
226
         m_tls = std::make_unique<Botan::TLS::Server>(shared_from_this(), session_manager, credentials, policy, m_rng);
4✔
227
      }
4✔
228

229
      void client_read(const boost::system::error_code& error, size_t bytes_transferred) {
18✔
230
         if(error) {
18✔
231
            return stop();
2✔
232
         }
233

234
         if(!m_tls) {
16✔
235
            log_error("Received client data after close");
×
236
            return;
×
237
         }
238

239
         try {
16✔
240
            m_tls->received_data(&m_c2s[0], bytes_transferred);
16✔
241
         } catch(Botan::Exception& e) {
×
242
            log_exception("TLS connection failed", e);
×
243
            return stop();
×
244
         }
×
245
         if(m_tls->is_closed_for_reading()) {
16✔
246
            return stop();
2✔
247
         }
248

249
         m_client_socket.async_read_some(boost::asio::buffer(&m_c2s[0], m_c2s.size()),
28✔
250
                                         m_strand.wrap(boost::bind(&TLS_Asio_HTTP_Session::client_read,
28✔
251
                                                                   shared_from_this(),
28✔
252
                                                                   boost::asio::placeholders::error,
253
                                                                   boost::asio::placeholders::bytes_transferred)));
254
      }
255

256
      void handle_client_write_completion(const boost::system::error_code& error) {
22✔
257
         if(error) {
22✔
258
            return stop();
×
259
         }
260

261
         m_s2c.clear();
22✔
262

263
         if(m_s2c_pending.empty() && (!m_tls || m_tls->is_closed_for_writing())) {
22✔
264
            m_client_socket.close();
4✔
265
         }
266
         tls_emit_data({});  // initiate another write if needed
22✔
267
      }
268

269
      std::string tls_server_choose_app_protocol(const std::vector<std::string>& /*client_protos*/) override {
×
270
         return "http/1.1";
×
271
      }
272

273
      void tls_record_received(uint64_t /*rec_no*/, std::span<const uint8_t> buf) override {
4✔
274
         if(!m_http_parser) {
4✔
275
            m_http_parser = std::make_unique<HTTP_Parser>(*this);
4✔
276
         }
277

278
         m_http_parser->consume_input(buf);
4✔
279
      }
4✔
280

281
      std::string summarize_request(const HTTP_Parser::Request& request) {
2✔
282
         std::ostringstream strm;
2✔
283

284
         strm << "Client " << client_socket().remote_endpoint().address().to_string() << " requested " << request.verb()
4✔
285
              << " " << request.location() << "\n";
4✔
286

287
         if(request.headers().empty() == false) {
2✔
288
            strm << "Client HTTP headers:\n";
2✔
289
            for(const auto& kv : request.headers()) {
6✔
290
               strm << " " << kv.first << ": " << kv.second << "\n";
4✔
291
            }
292
         }
293

294
         return strm.str();
4✔
295
      }
2✔
296

297
      void handle_http_request(const HTTP_Parser::Request& request) override {
4✔
298
         if(!m_tls) {
4✔
299
            log_error("Received client data after close");
×
300
            return;
×
301
         }
302
         std::ostringstream response;
4✔
303
         if(request.verb() == "GET") {
4✔
304
            if(request.location() == "/" || request.location() == "/status") {
2✔
305
               const std::string http_summary = summarize_request(request);
2✔
306

307
               const std::string report = m_connection_summary + m_session_summary + m_chello_summary + http_summary;
4✔
308

309
               response << "HTTP/1.0 200 OK\r\n";
2✔
310
               response << "Server: " << Botan::version_string() << "\r\n";
4✔
311
               response << "Content-Type: text/plain\r\n";
2✔
312
               response << "Content-Length: " << report.size() << "\r\n";
2✔
313
               response << "\r\n";
2✔
314

315
               response << report;
2✔
316
            } else {
4✔
317
               response << "HTTP/1.0 404 Not Found\r\n\r\n";
×
318
            }
319
         } else {
320
            response << "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
2✔
321
         }
322

323
         const std::string response_str = response.str();
4✔
324
         m_tls->send(response_str);
4✔
325
         m_tls->close();
4✔
326
      }
4✔
327

328
      void tls_emit_data(std::span<const uint8_t> buf) override {
50✔
329
         if(!buf.empty()) {
50✔
330
            m_s2c_pending.insert(m_s2c_pending.end(), buf.begin(), buf.end());
28✔
331
         }
332

333
         // no write now active and we still have output pending
334
         if(m_s2c.empty() && !m_s2c_pending.empty()) {
50✔
335
            std::swap(m_s2c_pending, m_s2c);
22✔
336

337
            boost::asio::async_write(m_client_socket,
22✔
338
                                     boost::asio::buffer(&m_s2c[0], m_s2c.size()),
22✔
339
                                     m_strand.wrap(boost::bind(&TLS_Asio_HTTP_Session::handle_client_write_completion,
44✔
340
                                                               shared_from_this(),
44✔
341
                                                               boost::asio::placeholders::error)));
342
         }
343
      }
50✔
344

345
      void tls_session_activated() override {
4✔
346
         std::ostringstream strm;
4✔
347

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

350
         m_connection_summary = strm.str();
4✔
351
      }
4✔
352

353
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
4✔
354
         std::ostringstream strm;
4✔
355

356
         strm << "Version: " << session.version().to_string() << "\n";
8✔
357
         strm << "Ciphersuite: " << session.ciphersuite().to_string() << "\n";
8✔
358
         if(const auto& session_id = session.session_id(); !session_id.empty()) {
4✔
359
            strm << "SessionID: " << Botan::hex_encode(session_id.get()) << "\n";
8✔
360
         }
361
         if(!session.server_info().hostname().empty()) {
8✔
362
            strm << "SNI: " << session.server_info().hostname() << "\n";
12✔
363
         }
364

365
         m_session_summary = strm.str();
4✔
366
      }
4✔
367

368
      void tls_inspect_handshake_msg(const Botan::TLS::Handshake_Message& message) override {
34✔
369
         if(message.type() == Botan::TLS::Handshake_Type::ClientHello) {
34✔
370
            const Botan::TLS::Client_Hello& client_hello = dynamic_cast<const Botan::TLS::Client_Hello&>(message);
6✔
371

372
            std::ostringstream strm;
6✔
373

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

376
            strm << "Client offered following ciphersuites:\n";
6✔
377
            for(uint16_t suite_id : client_hello.ciphersuites()) {
74✔
378
               const auto ciphersuite = Botan::TLS::Ciphersuite::by_id(suite_id);
68✔
379

380
               strm << " - 0x" << std::hex << std::setfill('0') << std::setw(4) << suite_id << std::dec
68✔
381
                    << std::setfill(' ') << std::setw(0) << " ";
68✔
382

383
               if(ciphersuite && ciphersuite->valid()) {
68✔
384
                  strm << ciphersuite->to_string() << "\n";
186✔
385
               } else if(suite_id == 0x00FF) {
6✔
386
                  strm << "Renegotiation SCSV\n";
6✔
387
               } else {
388
                  strm << "Unknown ciphersuite\n";
×
389
               }
390
            }
391

392
            m_chello_summary = strm.str();
6✔
393
         }
6✔
394
      }
34✔
395

396
      void tls_alert(Botan::TLS::Alert alert) override {
×
397
         if(!m_tls) {
×
398
            log_error("Received client data after close");
×
399
            return;
×
400
         }
401
         if(alert.type() == Botan::TLS::Alert::CloseNotify) {
×
402
            m_tls->close();
×
403
         } else {
404
            std::cout << "Alert " << alert.type_string() << std::endl;
×
405
         }
406
      }
407

408
      boost::asio::io_service::strand m_strand;
409

410
      tcp::socket m_client_socket;
411

412
      std::shared_ptr<Botan::RandomNumberGenerator> m_rng;
413
      std::unique_ptr<Botan::TLS::Server> m_tls;
414
      std::string m_chello_summary;
415
      std::string m_connection_summary;
416
      std::string m_session_summary;
417
      std::unique_ptr<HTTP_Parser> m_http_parser;
418

419
      std::vector<uint8_t> m_c2s;
420
      std::vector<uint8_t> m_s2c;
421
      std::vector<uint8_t> m_s2c_pending;
422
};
423

424
class TLS_Asio_HTTP_Server final {
425
   public:
426
      typedef TLS_Asio_HTTP_Session session;
427

428
      TLS_Asio_HTTP_Server(boost::asio::io_service& io,
1✔
429
                           unsigned short port,
430
                           std::shared_ptr<Botan::Credentials_Manager> creds,
431
                           std::shared_ptr<Botan::TLS::Policy> policy,
432
                           std::shared_ptr<Botan::TLS::Session_Manager> session_mgr,
433
                           size_t max_clients) :
1✔
434
            m_acceptor(io, tcp::endpoint(tcp::v4(), port)),
1✔
435
            m_creds(std::move(creds)),
1✔
436
            m_policy(std::move(policy)),
1✔
437
            m_session_manager(std::move(session_mgr)),
1✔
438
            m_status(max_clients) {
1✔
439
         serve_one_session();
1✔
440
      }
1✔
441

442
   private:
443
      void serve_one_session() {
4✔
444
         auto new_session = session::create(get_io_service(m_acceptor), m_session_manager, m_creds, m_policy);
4✔
445

446
         m_acceptor.async_accept(
8✔
447
            new_session->client_socket(),
4✔
448
            boost::bind(&TLS_Asio_HTTP_Server::handle_accept, this, new_session, boost::asio::placeholders::error));
12✔
449
      }
4✔
450

451
      void handle_accept(const session::pointer& new_session, const boost::system::error_code& error) {
4✔
452
         if(!error) {
4✔
453
            new_session->start();
4✔
454
            m_status.client_serviced();
4✔
455

456
            if(!m_status.should_exit()) {
4✔
457
               serve_one_session();
3✔
458
            }
459
         }
460
      }
4✔
461

462
      tcp::acceptor m_acceptor;
463

464
      std::shared_ptr<Botan::Credentials_Manager> m_creds;
465
      std::shared_ptr<Botan::TLS::Policy> m_policy;
466
      std::shared_ptr<Botan::TLS::Session_Manager> m_session_manager;
467
      ServerStatus m_status;
468
};
469

470
}  // namespace
471

472
class TLS_HTTP_Server final : public Command {
473
   public:
474
      TLS_HTTP_Server() :
2✔
475
            Command(
476
               "tls_http_server server_cert server_key "
477
               "--port=443 --policy=default --threads=0 --max-clients=0 "
478
               "--session-db= --session-db-pass=") {}
4✔
479

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

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

484
      size_t thread_count() const {
1✔
485
         if(size_t t = get_arg_sz("threads")) {
1✔
486
            return t;
487
         }
488
         if(size_t t = Botan::OS::get_cpu_available()) {
1✔
489
            return t;
1✔
490
         }
491
         return 2;
492
      }
493

494
      void go() override {
1✔
495
         const uint16_t listen_port = get_arg_u16("port");
1✔
496

497
         const std::string server_crt = get_arg("server_cert");
1✔
498
         const std::string server_key = get_arg("server_key");
1✔
499

500
         const size_t num_threads = thread_count();
1✔
501
         const size_t max_clients = get_arg_sz("max-clients");
1✔
502

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

505
         auto policy = load_tls_policy(get_arg("policy"));
2✔
506

507
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
1✔
508

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

511
         if(!sessions_db.empty()) {
1✔
512
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
513
            const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
×
514
            session_mgr.reset(
×
515
               new Botan::TLS::Session_Manager_SQLite(sessions_passphrase, rng_as_shared(), sessions_db));
×
516
   #else
517
            throw CLI_Error_Unsupported("Sqlite3 support not available");
518
   #endif
519
         }
×
520

521
         if(!session_mgr) {
1✔
522
            session_mgr.reset(new Botan::TLS::Session_Manager_In_Memory(rng_as_shared()));
2✔
523
         }
524

525
         boost::asio::io_service io;
1✔
526

527
         TLS_Asio_HTTP_Server server(io, listen_port, creds, policy, session_mgr, max_clients);
2✔
528

529
         std::vector<std::shared_ptr<std::thread>> threads;
1✔
530

531
         // run forever... first thread is main calling io.run below
532
         for(size_t i = 2; i <= num_threads; ++i) {
2✔
533
            threads.push_back(std::make_shared<std::thread>([&io]() { io.run(); }));
2✔
534
         }
535

536
         io.run();
1✔
537

538
         for(size_t i = 0; i < threads.size(); ++i) {
2✔
539
            threads[i]->join();
1✔
540
         }
541
      }
6✔
542
};
543

544
BOTAN_REGISTER_COMMAND("tls_http_server", TLS_HTTP_Server);
2✔
545

546
}  // namespace Botan_CLI
547

548
#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

© 2025 Coveralls, Inc