• 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

82.7
/src/cli/tls_server.cpp
1
/*
2
* TLS echo server using BSD sockets
3
* (C) 2014 Jack Lloyd
4
*     2017 René Korthaus, Rohde & Schwarz Cybersecurity
5
*     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
#include <botan/internal/target_info.h>
13

14
#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
15
   #include <sys/socket.h>
16
#endif
17

18
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_TARGET_OS_HAS_SOCKETS)
19

20
   #if defined(SO_MARK) || defined(SO_USER_COOKIE) || defined(SO_RTABLE)
21
      #if defined(SO_MARK)
22
         #define BOTAN_SO_SOCKETID SO_MARK
23
      #elif defined(SO_USER_COOKIE)
24
         #define BOTAN_SO_SOCKETID SO_USER_COOKIE
25
      #else
26
         #define BOTAN_SO_SOCKETID SO_RTABLE
27
      #endif
28
   #endif
29

30
   #include <botan/hex.h>
31
   #include <botan/mem_ops.h>
32
   #include <botan/rng.h>
33
   #include <botan/tls_callbacks.h>
34
   #include <botan/tls_exceptn.h>
35
   #include <botan/tls_policy.h>
36
   #include <botan/tls_server.h>
37
   #include <botan/tls_session_manager_memory.h>
38

39
   #include <chrono>
40
   #include <fstream>
41
   #include <list>
42
   #include <memory>
43

44
   #include "socket_utils.h"
45
   #include "tls_helpers.h"
46

47
namespace Botan_CLI {
48

49
class TLS_Server;
50

51
namespace {
52

53
class Callbacks : public Botan::TLS::Callbacks {
23 ✔
54
   public:
55
      explicit Callbacks(TLS_Server& server_command) : m_server_command(server_command) {}
23 ✔
56

57
      std::ostream& output();
58
      void send(std::span<const uint8_t> buffer);
59
      void push_pending_output(std::string line);
60
      std::string peer_identity() const;
61

62
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
16 ✔
63
         output() << "Handshake complete, " << session.version().to_string() << " using "
16 ✔
64
                  << session.ciphersuite().to_string();
48 ✔
65

66
         if(const auto& psk = session.external_psk_identity()) {
16 ✔
67
            output() << " (utilized PSK identity: " << maybe_hex_encode(psk.value()) << ")";
6 ✔
68
         }
69

70
         output() << std::endl;
16 ✔
71

72
         if(const auto& session_id = session.session_id(); !session_id.empty()) {
16 ✔
73
            output() << "Session ID " << Botan::hex_encode(session_id.get()) << std::endl;
16 ✔
74
         }
75

76
         if(const auto& session_ticket = session.session_ticket()) {
16 ✔
77
            output() << "Session ticket " << Botan::hex_encode(session_ticket->get()) << std::endl;
×
78
         }
79
      }
16 ✔
80

81
      void tls_record_received(uint64_t /*seq_no*/, std::span<const uint8_t> input) override {
80 ✔
82
         for(const auto uc : input) {
69,824 ✔
83
            const char c = static_cast<char>(uc);
69,744 ✔
84
            m_line_buf += c;
69,744 ✔
85
            if(c == '\n') {
69,744 ✔
86
               push_pending_output(std::exchange(m_line_buf, {}));
42 ✔
87
            }
88
         }
89

90
         // Bound the memory a client can tie up by never sending a newline
91
         if(m_line_buf.size() > max_line_length) {
80 ✔
92
            throw CLI_Error("Client sent a line longer than " + std::to_string(max_line_length) + " bytes");
3 ✔
93
         }
94
      }
79 ✔
95

96
      void tls_emit_data(std::span<const uint8_t> buf) override { send(buf); }
286 ✔
97

98
      void tls_alert(Botan::TLS::Alert alert) override { output() << "Alert: " << alert.type_string() << std::endl; }
34 ✔
99

100
      std::string tls_server_choose_app_protocol(const std::vector<std::string>& /*client_protos*/) override {
×
101
         // we ignore whatever the client sends here
102
         return "echo/0.1";
×
103
      }
104

105
      // Binds the DTLS cookie to the client's address
106
      std::string tls_peer_network_identity() override { return peer_identity(); }
4 ✔
107

108
      void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key,
3 ✔
109
                                     Botan::Usage_Type /* usage */,
110
                                     std::string_view /* hostname */,
111
                                     const Botan::TLS::Policy& /* policy */) override {
112
         const auto fingerprint = raw_public_key.fingerprint_public("SHA-256");
3 ✔
113
         const bool trusted = (fingerprint == trusted_client_pubkey_sha256());
5 ✔
114
         output() << "received Raw Public Key (" << fingerprint << ") which is "
3 ✔
115
                  << (trusted ? "trusted" : "NOT trusted") << "\n";
5 ✔
116

117
         if(!trusted) {
3 ✔
118
            throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::BadCertificate,
2 ✔
119
                                            "Client raw public key does not match --trusted-pubkey-sha256");
2 ✔
120
         }
121
      }
1 ✔
122

123
   private:
124
      static constexpr size_t max_line_length = 64 * 1024;
125

126
      std::string trusted_client_pubkey_sha256() const;
127

128
      TLS_Server& m_server_command;
129
      std::string m_line_buf;
130
};
131

132
}  // namespace
133

134
class TLS_Server final : public Command {
135
   public:
136
   #if defined(BOTAN_SO_SOCKETID)
137
      TLS_Server() :
8 ✔
138
            Command(
139
               "tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0 --trusted-pubkey-sha256= --socket-id=0")
16 ✔
140
   #else
141
      TLS_Server() :
142
            Command(
143
               "tls_server cert-or-pubkey key --port=443 --psk= --psk-identity= --psk-prf=SHA-256 --type=tcp --policy=default --dump-traces= --max-clients=0 --trusted-pubkey-sha256=")
144
   #endif
145
      {
146
         init_sockets();
8 ✔
147
      }
8 ✔
148

149
      ~TLS_Server() override { stop_sockets(); }
16 ✔
150

151
      TLS_Server(const TLS_Server& other) = delete;
152
      TLS_Server(TLS_Server&& other) = delete;
153
      TLS_Server& operator=(const TLS_Server& other) = delete;
154
      TLS_Server& operator=(TLS_Server&& other) = delete;
155

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

158
      std::string description() const override { return "Accept TLS/DTLS connections from TLS/DTLS clients"; }
1 ✔
159

160
      void go() override {
7 ✔
161
         const std::string server_cred = get_arg("cert-or-pubkey");
7 ✔
162
         const std::string server_key = get_arg("key");
7 ✔
163
         const uint16_t port = get_arg_u16("port");
14 ✔
164
         const size_t max_clients = get_arg_sz("max-clients");
7 ✔
165
         const std::string transport = get_arg("type");
7 ✔
166
         const std::string dump_traces_to = get_arg("dump-traces");
7 ✔
167
         auto psk = [this]() -> std::optional<Botan::secure_vector<uint8_t>> {
×
168
            auto psk_hex = get_arg_maybe("psk");
7 ✔
169
            if(psk_hex) {
7 ✔
170
               return Botan::hex_decode_locked(psk_hex.value());
7 ✔
171
            } else {
172
               return {};
×
173
            }
174
         }();
14 ✔
175
         const std::optional<std::string> psk_identity = get_arg_maybe("psk-identity");
7 ✔
176
         const std::optional<std::string> psk_prf = get_arg_maybe("psk-prf");
7 ✔
177

178
   #if defined(BOTAN_SO_SOCKETID)
179
         m_socket_id = static_cast<uint32_t>(get_arg_sz("socket-id"));
7 ✔
180
   #endif
181

182
         if(transport != "tcp" && transport != "udp") {
7 ✔
183
            throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS");
×
184
         }
185

186
         m_is_tcp = (transport == "tcp");
7 ✔
187

188
         const auto policy = load_tls_policy(get_arg("policy"));
14 ✔
189
         const auto session_manager =
7 ✔
190
            std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());  // TODO sqlite3
14 ✔
191
         const auto creds =
7 ✔
192
            std::make_shared<Basic_Credentials_Manager>(server_cred, server_key, std::move(psk), psk_identity, psk_prf);
7 ✔
193

194
         if(!m_is_tcp) {
7 ✔
195
            creds->set_dtls_cookie_secret(rng().random_vec(32));
2 ✔
196
         }
197

198
         const socket_type server_fd = make_server_socket(port);
7 ✔
199
         size_t clients_served = 0;
7 ✔
200

201
         output() << "Listening for new connections on " << transport << " port " << socket_port(server_fd)
7 ✔
202
                  << std::endl;
30 ✔
203

204
         while(true) {
53 ✔
205
            if(max_clients > 0 && clients_served >= max_clients) {
30 ✔
206
               break;
207
            }
208

209
            struct sockaddr_in from {};
23 ✔
210

211
            socklen_t from_len = sizeof(sockaddr_in);
23 ✔
212

213
            if(m_is_tcp) {
23 ✔
214
               m_socket = ::accept(server_fd, reinterpret_cast<struct sockaddr*>(&from), &from_len);
22 ✔
215
            } else {
216
               void* peek_buf = nullptr;
1 ✔
217
               size_t peek_len = 0;  // NOLINT(*-const-correctness)
1 ✔
218

219
   #if defined(BOTAN_TARGET_OS_IS_MACOS)
220
               // macOS handles zero size buffers differently - it will return 0 even if there's no incoming data,
221
               // and after that connect() will fail as sockaddr_in from is not initialized
222
               int dummy;
223
               peek_buf = &dummy;
224
               peek_len = sizeof(dummy);
225
   #endif
226

227
               // With a nonzero peek buffer (macOS) this returns the datagram size
228
               if(::recvfrom(server_fd,
1 ✔
229
                             static_cast<char*>(peek_buf),
230
                             static_cast<sendrecv_len_type>(peek_len),
231
                             MSG_PEEK,
232
                             reinterpret_cast<struct sockaddr*>(&from),
233
                             &from_len) < 0) {
234
                  throw CLI_Error("Could not peek next packet");
×
235
               }
236

237
               if(::connect(server_fd, reinterpret_cast<struct sockaddr*>(&from), from_len) != 0) {
1 ✔
238
                  throw CLI_Error("Could not connect UDP socket");
×
239
               }
240
               m_socket = server_fd;
1 ✔
241
            }
242

243
            m_peer_identity = format_peer(from);
23 ✔
244

245
            clients_served++;
23 ✔
246

247
            // Per-connection state: nothing buffered from a previous client may leak into this one
248
            m_pending_output.clear();
23 ✔
249
            const auto callbacks = std::make_shared<Callbacks>(*this);
23 ✔
250

251
            Botan::TLS::Server server(callbacks, session_manager, creds, policy, rng_as_shared(), m_is_tcp == false);
138 ✔
252

253
            std::unique_ptr<std::ostream> dump_stream;
23 ✔
254

255
            if(!dump_traces_to.empty()) {
23 ✔
256
               const auto now = std::chrono::system_clock::now().time_since_epoch();
×
257
               const uint64_t timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
×
258
               const std::string dump_file = dump_traces_to + "/tls_" + std::to_string(timestamp) + ".bin";
×
259
               dump_stream = std::make_unique<std::ofstream>(dump_file.c_str());
×
260
            }
×
261

262
            try {
263
               while(!server.is_closed()) {
152 ✔
264
                  try {
129 ✔
265
                     uint8_t buf[4 * 1024] = {0};
129 ✔
266
                     const ssize_t got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), sizeof(buf), 0);
129 ✔
267

268
                     if(got == -1) {
129 ✔
269
                        error_output() << "Error in socket read - " << err_to_string(errno) << std::endl;
×
270
                        break;
×
271
                     }
272

273
                     if(got == 0) {
129 ✔
274
                        error_output() << "EOF on socket" << std::endl;
×
275
                        break;
276
                     }
277

278
                     if(dump_stream) {
129 ✔
279
                        dump_stream->write(reinterpret_cast<const char*>(buf), got);
×
280
                     }
281

282
                     server.received_data(buf, got);
129 ✔
283

284
                     while(server.is_active() && !m_pending_output.empty()) {
137 ✔
285
                        const std::string output = m_pending_output.front();
14 ✔
286
                        m_pending_output.pop_front();
14 ✔
287
                        server.send(output);
14 ✔
288

289
                        if(output == "quit\n") {
14 ✔
290
                           server.close();
×
291
                        }
292
                     }
14 ✔
293
                  } catch(std::exception& e) {
6 ✔
294
                     error_output() << "Connection problem: " << e.what() << std::endl;
6 ✔
295
                     if(m_is_tcp) {
6 ✔
296
                        close_socket(m_socket);
6 ✔
297
                        m_socket = invalid_socket();
6 ✔
298
                     }
299
                  }
6 ✔
300
               }
301
            } catch(Botan::Exception& e) {
×
302
               error_output() << "Connection failed: " << e.what() << "\n";
×
303
            }
×
304

305
            if(m_is_tcp) {
23 ✔
306
               close_socket(m_socket);
22 ✔
307
               m_socket = invalid_socket();
22 ✔
308
            } else {
309
               // Reset the peer address so datagrams from the next client are
310
               // accepted; failure is harmless as the next peek rediscovers one
311
               sockaddr unspec{};
1 ✔
312
               unspec.sa_family = AF_UNSPEC;
1 ✔
313
               // NOLINTNEXTLINE(bugprone-unused-return-value)
314
               ::connect(server_fd, &unspec, sizeof(unspec));
1 ✔
315
            }
316
         }
46 ✔
317

318
         close_socket(server_fd);
14 ✔
319
      }
35 ✔
320

321
   public:
322
      using Command::flag_set;
323
      using Command::get_arg;
324
      using Command::output;
325

326
      void send(std::span<const uint8_t> buf) const {
143 ✔
327
         // A short write on the stream socket would corrupt the TLS stream
328
         while(!buf.empty()) {
286 ✔
329
            ssize_t sent = ::send(m_socket, buf.data(), static_cast<sendrecv_len_type>(buf.size()), MSG_NOSIGNAL);
143 ✔
330

331
            if(sent == -1) {
143 ✔
332
               if(errno == EINTR) {
×
333
                  sent = 0;
334
               } else {
335
                  throw CLI_Error("Socket write failed - " + err_to_string(errno));
×
336
               }
337
            }
338

339
            buf = buf.subspan(sent);
143 ✔
340
         }
341
      }
143 ✔
342

343
      void push_pending_output(std::string line) { m_pending_output.emplace_back(std::move(line)); }
14 ✔
344

345
      const std::string& peer_identity() const { return m_peer_identity; }
2 ✔
346

347
   private:
348
      static std::string format_peer(const sockaddr_in& addr) {
23 ✔
349
         char host[INET_ADDRSTRLEN] = {0};
23 ✔
350
         if(::inet_ntop(AF_INET, &addr.sin_addr, host, sizeof(host)) == nullptr) {
23 ✔
351
            return "unknown";
×
352
         }
353
         return std::string(host) + ":" + std::to_string(ntohs(addr.sin_port));
69 ✔
354
      }
355

356
      socket_type make_server_socket(uint16_t port) {
7 ✔
357
         const int type = m_is_tcp ? SOCK_STREAM : SOCK_DGRAM;
7 ✔
358

359
         const socket_type fd = ::socket(PF_INET, type, 0);
7 ✔
360
         if(fd == invalid_socket()) {
7 ✔
361
            throw CLI_Error("Unable to acquire socket");
×
362
         }
363

364
         sockaddr_in socket_info{};
7 ✔
365
         Botan::clear_mem(&socket_info, 1);
7 ✔
366
         socket_info.sin_family = AF_INET;
7 ✔
367
         socket_info.sin_port = htons(port);
7 ✔
368

369
         // FIXME: support limiting listeners
370
         socket_info.sin_addr.s_addr = INADDR_ANY;
7 ✔
371

372
         if(::bind(fd, reinterpret_cast<struct sockaddr*>(&socket_info), sizeof(struct sockaddr)) != 0) {
7 ✔
373
            close_socket(fd);
×
374
            throw CLI_Error("server bind failed");
×
375
         }
376

377
         if(m_is_tcp) {
7 ✔
378
            constexpr int backlog = std::min(100, SOMAXCONN);
6 ✔
379
            if(::listen(fd, backlog) != 0) {
6 ✔
380
               close_socket(fd);
×
381
               throw CLI_Error("listen failed");
×
382
            }
383
         }
384
         if(m_socket_id > 0) {
7 ✔
385
   #if defined(BOTAN_SO_SOCKETID)
386
            if(::setsockopt(fd,
×
387
                            SOL_SOCKET,
388
                            BOTAN_SO_SOCKETID,
389
                            reinterpret_cast<const void*>(&m_socket_id),
×
390
                            sizeof(m_socket_id)) != 0) {
391
               // Failed but not world-ending issue
392
               output() << "set socket identifier setting failed" << std::endl;
×
393
            }
394
   #endif
395
         }
396
         return fd;
7 ✔
397
      }
398

399
      socket_type m_socket = invalid_socket();
400
      bool m_is_tcp = false;
401
      uint32_t m_socket_id = 0;
402
      std::string m_peer_identity;
403
      std::list<std::string> m_pending_output;
404
};
405

406
namespace {
407

408
std::ostream& Callbacks::output() {
70 ✔
409
   return m_server_command.output();
70 ✔
410
}
411

412
void Callbacks::send(std::span<const uint8_t> buffer) {
143 ✔
413
   m_server_command.send(buffer);
143 ✔
414
}
415

416
void Callbacks::push_pending_output(std::string line) {
14 ✔
417
   m_server_command.push_pending_output(std::move(line));
28 ✔
418
}
14 ✔
419

420
std::string Callbacks::trusted_client_pubkey_sha256() const {
3 ✔
421
   return m_server_command.get_arg("trusted-pubkey-sha256");
6 ✔
422
}
423

424
std::string Callbacks::peer_identity() const {
2 ✔
425
   return m_server_command.peer_identity();
2 ✔
426
}
427

428
}  // namespace
429

430
BOTAN_REGISTER_COMMAND("tls_server", TLS_Server);
8 ✔
431

432
}  // namespace Botan_CLI
433

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