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

randombit / botan / 31146488627

06 Aug 2026 03:47PM UTC coverage: 89.698% (+0.009%) from 89.689%
31146488627

push

github

web-flow
Merge pull request #5807 from fcasal/audit-fix/python-pksign-unicode-length

Use the encoded byte length in Python PKSign.update

119395 of 133108 relevant lines covered (89.7%)

10474838.25 hits per line

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

73.28
/src/cli/tls_client.cpp
1
/*
2
* (C) 2014,2015 Jack Lloyd
3
*     2016 Matthias Gierlings
4
*     2017 René Korthaus, Rohde & Schwarz Cybersecurity
5
*     2022 René Meusel, Hannes Rantzsch - neXenio GmbH
6
*     2023 René Meusel, Rohde & Schwarz Cybersecurity
7
*     2026 René Meusel, Rohde & Schwarz Networks and Cybersecurity
8
*
9
* Botan is released under the Simplified BSD License (see license.txt)
10
*/
11

12
#include "cli.h"
13

14
#include <botan/internal/stl_util.h>
15
#include <botan/internal/target_info.h>
16

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

19
   #include <botan/hex.h>
20
   #include <botan/ocsp.h>
21
   #include <botan/tls_callbacks.h>
22
   #include <botan/tls_client.h>
23
   #include <botan/tls_exceptn.h>
24
   #include <botan/tls_policy.h>
25
   #include <botan/tls_session_manager_memory.h>
26
   #include <botan/x509path.h>
27

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

32
   #include <memory>
33
   #include <string>
34

35
   #include "socket_utils.h"
36
   #include "tls_helpers.h"
37

38
namespace Botan_CLI {
39

40
class TLS_Client;
41

42
namespace {
43

44
class Callbacks : public Botan::TLS::Callbacks {
10✔
45
   public:
46
      explicit Callbacks(TLS_Client& client_command) : m_client_command(client_command), m_peer_closed(false) {}
10✔
47

48
      std::ostream& output();
49
      bool flag_set(const std::string& flag_name) const;
50
      std::string get_arg(const std::string& arg_name) const;
51
      void send(std::span<const uint8_t> buffer);
52

53
      bool peer_closed() const { return m_peer_closed; }
1✔
54

55
      void tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& cert_chain,
7✔
56
                                 const std::vector<std::optional<Botan::OCSP::Response>>& ocsp,
57
                                 const std::vector<Botan::Certificate_Store*>& trusted_roots,
58
                                 Botan::Usage_Type usage,
59
                                 std::string_view hostname,
60
                                 const Botan::TLS::Policy& policy) override {
61
         if(cert_chain.empty()) {
7✔
62
            throw Botan::Invalid_Argument("Certificate chain was empty");
×
63
         }
64

65
         // As a diagnostic tool we want to attempt OCSP but still connect if
66
         // the responder was unavailable or the certs have no OCSP URL
67
         const Botan::Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(),
7✔
68
                                                                policy.minimum_signature_strength(),
7✔
69
                                                                /* ocsp_all_intermediates */ false,
70
                                                                std::chrono::hours(24 * 7),
7✔
71
                                                                /* trusted_ocsp_responders */ nullptr,
72
                                                                /* ignore_trusted_root_time_range */ false,
73
                                                                /* require_self_signed_trust_anchors */ true,
74
                                                                /* accept_ocsp_softfail */ true);
14✔
75

76
         auto ocsp_timeout = std::chrono::milliseconds(1000);
7✔
77

78
         const std::string checked_name = flag_set("skip-hostname-check") ? "" : std::string(hostname);
21✔
79

80
         const Botan::Path_Validation_Result result = Botan::x509_path_validate(
7✔
81
            cert_chain, restrictions, trusted_roots, checked_name, usage, tls_current_timestamp(), ocsp_timeout, ocsp);
7✔
82

83
         if(result.successful_validation()) {
7✔
84
            output() << "Certificate validation status: " << result.result_string() << "\n";
14✔
85
            const auto& status = result.all_statuses();
7✔
86

87
            if(!status.empty() && status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD)) {
14✔
88
               output() << "Valid OCSP response for this server\n";
×
89
            }
90

91
            if(!result.no_warnings()) {
7✔
92
               output() << "Certificate validation warnings: " << result.warnings_string() << "\n";
21✔
93
            }
94
         } else {
95
            if(flag_set("ignore-cert-error")) {
×
96
               output() << "Certificate validation status: " << result.result_string() << "\n";
×
97
            } else {
98
               throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::BadCertificate,
×
99
                                               "Certificate validation failure: " + result.result_string());
×
100
            }
101
         }
102
      }
7✔
103

104
      void tls_verify_raw_public_key(const Botan::Public_Key& raw_public_key,
×
105
                                     Botan::Usage_Type /* usage */,
106
                                     std::string_view /* hostname */,
107
                                     const Botan::TLS::Policy& /* policy */) override {
108
         const auto fingerprint = raw_public_key.fingerprint_public("SHA-256");
×
109
         const auto trusted = (fingerprint == get_arg("trusted-pubkey-sha256"));
×
110
         output() << "Raw Public Key (" << fingerprint
×
111
                  << ") validation status: " << (trusted ? "trusted" : "NOT trusted") << "\n";
×
112
      }
×
113

114
      void tls_session_activated() override { output() << "Handshake complete\n"; }
9✔
115

116
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
9✔
117
         output() << "Handshake complete, " << session.version().to_string() << "\n";
18✔
118

119
         if(const auto& psk = session.external_psk_identity()) {
9✔
120
            output() << "Utilized PSK identity: " << maybe_hex_encode(psk.value()) << "\n";
6✔
121
         }
122

123
         output() << "Negotiated ciphersuite " << session.ciphersuite().to_string() << "\n";
18✔
124

125
         if(auto kex_params = session.kex_parameters()) {
9✔
126
            output() << "Key exchange using " << *kex_params << "\n";
6✔
127
         }
×
128

129
         if(const auto& session_id = session.session_id(); !session_id.empty()) {
9✔
130
            output() << "Session ID " << Botan::hex_encode(session_id.get()) << "\n";
18✔
131
         }
132

133
         if(const auto& session_ticket = session.session_ticket()) {
9✔
134
            output() << "Session ticket " << Botan::hex_encode(session_ticket->get()) << "\n";
×
135
         }
136

137
         if(flag_set("print-certs")) {
18✔
138
            const std::vector<Botan::X509_Certificate>& certs = session.peer_certs();
139

140
            for(size_t i = 0; i != certs.size(); ++i) {
×
141
               output() << "Certificate " << i + 1 << "/" << certs.size() << "\n";
×
142
               output() << certs[i].to_string();
×
143
               output() << certs[i].PEM_encode();
×
144
            }
145
         }
146
         output() << std::flush;
9✔
147
      }
9✔
148

149
      void tls_emit_data(std::span<const uint8_t> buf) override {
54✔
150
         if(flag_set("debug")) {
108✔
151
            output() << "<< " << Botan::hex_encode(buf) << "\n";
×
152
         }
153

154
         send(buf);
108✔
155
      }
54✔
156

157
      void tls_alert(Botan::TLS::Alert alert) override { output() << "Alert: " << alert.type_string() << "\n"; }
3✔
158

159
      void tls_record_received(uint64_t /*seq_no*/, std::span<const uint8_t> buf) override {
12✔
160
         for(const auto c : buf) {
2,064✔
161
            output() << c;
2,052✔
162
         }
163
         output() << std::flush;
12✔
164
      }
12✔
165

166
      std::vector<uint8_t> tls_sign_message(const Botan::Private_Key& key,
×
167
                                            Botan::RandomNumberGenerator& rng,
168
                                            const std::string_view padding,
169
                                            Botan::Signature_Format format,
170
                                            const std::vector<uint8_t>& msg) override {
171
         output() << "Performing client authentication\n";
×
172
         return Botan::TLS::Callbacks::tls_sign_message(key, rng, padding, format, msg);
×
173
      }
174

175
      bool tls_peer_closed_connection() override {
×
176
         m_peer_closed = true;
×
177
         return Botan::TLS::Callbacks::tls_peer_closed_connection();
×
178
      }
179

180
   private:
181
      TLS_Client& m_client_command;
182
      bool m_peer_closed;
183
};
184

185
}  // namespace
186

187
class TLS_Client final : public Command {
188
   public:
189
      TLS_Client() :
11✔
190
            Command(
191
               "tls_client host --port=443 --print-certs --policy=default "
192
               "--skip-system-cert-store --trusted-cas= --trusted-pubkey-sha256= "
193
               "--skip-hostname-check --ignore-cert-error "
194
               "--tls-version=default --session-db= --session-db-pass= "
195
               "--next-protocols= --type=tcp --client-cert= --client-cert-key= "
196
               "--psk= --psk-identity= --psk-prf=SHA-256 --debug") {
22✔
197
         init_sockets();
11✔
198
      }
11✔
199

200
      ~TLS_Client() override {
11✔
201
         shutdown_socket();
11✔
202
         stop_sockets();
11✔
203
      }
11✔
204

205
      TLS_Client(const TLS_Client& other) = delete;
206
      TLS_Client(TLS_Client&& other) = delete;
207
      TLS_Client& operator=(const TLS_Client& other) = delete;
208
      TLS_Client& operator=(TLS_Client&& other) = delete;
209

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

212
      std::string description() const override { return "Connect to a host using TLS/DTLS"; }
1✔
213

214
      void go() override {
10✔
215
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
10✔
216

217
         auto callbacks = std::make_shared<Callbacks>(*this);
10✔
218

219
         const std::string sessions_db = get_arg("session-db");
10✔
220
         const std::string host = get_arg("host");
10✔
221
         const uint16_t port = get_arg_u16("port");
10✔
222
         const std::string transport = get_arg("type");
10✔
223
         const std::string next_protos = get_arg("next-protocols");
10✔
224
         const bool use_system_cert_store = !flag_set("skip-system-cert-store");
10✔
225
         const std::string trusted_CAs = get_arg("trusted-cas");
10✔
226
         const auto tls_version = get_arg("tls-version");
10✔
227

228
         if(!sessions_db.empty()) {
10✔
229
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
230
            const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
×
231
            session_mgr =
×
232
               std::make_shared<Botan::TLS::Session_Manager_SQLite>(sessions_passphrase, rng_as_shared(), sessions_db);
×
233
   #else
234
            error_output() << "Ignoring session DB file, sqlite not enabled\n";
235
   #endif
236
         }
×
237

238
         if(!session_mgr) {
10✔
239
            session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());
30✔
240
         }
241

242
         auto policy = load_tls_policy(get_arg("policy"));
20✔
243

244
         if(transport != "tcp" && transport != "udp") {
10✔
245
            throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS");
×
246
         }
247

248
         const std::vector<std::string> protocols_to_offer = Command::split_on(next_protos, ',');
10✔
249

250
         if(!policy) {
10✔
251
            policy = std::make_shared<Botan::TLS::Policy>();
×
252
         }
253

254
         const bool use_tcp = (transport == "tcp");
10✔
255
         Botan::TLS::Protocol_Version version = policy->latest_supported_version(!use_tcp);
10✔
256

257
         if(tls_version != "default") {
10✔
258
            if(tls_version == "1.2") {
10✔
259
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V12 : Botan::TLS::Protocol_Version::DTLS_V12;
3✔
260
            } else if(tls_version == "1.3") {
7✔
261
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V13 : Botan::TLS::Protocol_Version::DTLS_V13;
7✔
262
            } else {
263
               error_output() << "Unknown TLS protocol version " << tls_version << '\n';
×
264
            }
265
         }
266

267
         m_sockfd = connect_to_host(host, port, use_tcp);
10✔
268

269
         const auto client_crt_path = get_arg_maybe("client-cert");
10✔
270
         const auto client_key_path = get_arg_maybe("client-cert-key");
10✔
271

272
         auto psk = [this]() -> std::optional<Botan::secure_vector<uint8_t>> {
×
273
            auto psk_hex = get_arg_maybe("psk");
10✔
274
            if(psk_hex) {
10✔
275
               return Botan::hex_decode_locked(psk_hex.value());
2✔
276
            } else {
277
               return {};
8✔
278
            }
279
         }();
20✔
280
         const std::optional<std::string> psk_identity = get_arg_maybe("psk-identity");
10✔
281
         const std::optional<std::string> psk_prf = get_arg_maybe("psk-prf");
10✔
282

283
         auto creds = std::make_shared<Basic_Credentials_Manager>(use_system_cert_store,
10✔
284
                                                                  trusted_CAs,
285
                                                                  client_crt_path,
286
                                                                  client_key_path,
287
                                                                  std::move(psk),
288
                                                                  psk_identity,
289
                                                                  psk_prf);
10✔
290

291
         Botan::TLS::Client client(callbacks,
10✔
292
                                   session_mgr,
293
                                   creds,
294
                                   policy,
295
                                   rng_as_shared(),
10✔
296
                                   Botan::TLS::Server_Information(host, port),
10✔
297
                                   version,
298
                                   protocols_to_offer);
40✔
299

300
         bool first_active = true;
10✔
301
         bool we_closed = false;
10✔
302

303
         while(!client.is_closed()) {
62✔
304
            fd_set readfds;
305
            FD_ZERO(&readfds);
1,037✔
306
            FD_SET(m_sockfd, &readfds);
61✔
307

308
            if(client.is_active()) {
61✔
309
               FD_SET(STDIN_FILENO, &readfds);
33✔
310
               if(first_active && !protocols_to_offer.empty()) {
33✔
311
                  const std::string app = client.application_protocol();
×
312
                  if(!app.empty()) {
×
313
                     output() << "Server choose protocol: " << client.application_protocol() << "\n";
×
314
                  }
315
                  first_active = false;
×
316
               }
×
317
            }
318

319
            struct timeval timeout = {1, 0};
61✔
320

321
            ::select(static_cast<int>(m_sockfd + 1), &readfds, nullptr, nullptr, &timeout);
61✔
322

323
            if(FD_ISSET(m_sockfd, &readfds)) {
61✔
324
               uint8_t buf[4 * 1024] = {0};
43✔
325

326
               const ssize_t got = ::read(m_sockfd, buf, sizeof(buf));
43✔
327

328
               if(got == 0) {
43✔
329
                  output() << "EOF on socket\n";
×
330
                  break;
×
331
               } else if(got == -1) {
43✔
332
                  output() << "Socket error: " << errno << " " << err_to_string(errno) << "\n";
×
333
                  continue;
×
334
               }
335

336
               if(flag_set("debug")) {
43✔
337
                  output() << ">> " << Botan::hex_encode(buf, got) << "\n";
×
338
               }
339

340
               client.received_data(buf, got);
43✔
341
            }
342

343
            if(FD_ISSET(STDIN_FILENO, &readfds)) {
61✔
344
               uint8_t buf[1024] = {0};
18✔
345
               const ssize_t got = read(STDIN_FILENO, buf, sizeof(buf));
18✔
346

347
               if(got == 0) {
18✔
348
                  output() << "EOF on stdin\n";
9✔
349
                  client.close();
9✔
350
                  we_closed = true;
9✔
351
                  break;
9✔
352
               } else if(got == -1) {
9✔
353
                  output() << "Stdin error: " << errno << " " << err_to_string(errno) << "\n";
×
354
                  continue;
×
355
               }
356

357
               if(got == 2 && buf[1] == '\n') {
9✔
358
                  const char cmd = buf[0];
×
359

360
                  if(cmd == 'R' || cmd == 'r') {
×
361
                     output() << "Client initiated renegotiation\n";
×
362
                     client.renegotiate(cmd == 'R');
×
363
                  } else if(cmd == 'Q') {
×
364
                     output() << "Client initiated close\n";
×
365
                     client.close();
×
366
                     we_closed = true;
367
                  }
368
               } else {
369
                  client.send(buf, got);
9✔
370
               }
371
            }
372

373
            if(client.timeout_check()) {
52✔
374
               output() << "Timeout detected\n";
×
375
            }
376
         }
377

378
         set_return_code((we_closed || callbacks->peer_closed()) ? 0 : 1);
19✔
379

380
         shutdown_socket();
10✔
381
      }
62✔
382

383
   public:
384
      using Command::flag_set;
385
      using Command::get_arg;
386
      using Command::output;
387

388
      void send(std::span<const uint8_t> buf) const {
54✔
389
         while(!buf.empty()) {
108✔
390
            ssize_t sent = ::send(m_sockfd, buf.data(), buf.size(), MSG_NOSIGNAL);
54✔
391

392
            if(sent == -1) {
54✔
393
               if(errno == EINTR) {
×
394
                  sent = 0;
395
               } else {
396
                  throw CLI_Error("Socket write failed errno=" + std::to_string(errno));
×
397
               }
398
            }
399

400
            buf = buf.subspan(sent);
54✔
401
         }
402
      }
54✔
403

404
   private:
405
      static socket_type connect_to_host(const std::string& host, uint16_t port, bool tcp) {
10✔
406
         addrinfo hints{};
10✔
407
         hints.ai_family = AF_UNSPEC;
10✔
408
         hints.ai_socktype = tcp ? SOCK_STREAM : SOCK_DGRAM;
10✔
409

410
         unique_addr_info_ptr res = nullptr;
10✔
411

412
         if(::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, Botan::out_ptr(res)) != 0) {
10✔
413
            throw CLI_Error("getaddrinfo failed for " + host);
×
414
         }
415

416
         socket_type fd = 0;
10✔
417
         bool success = false;
10✔
418

419
         for(const addrinfo* rp = res.get(); rp != nullptr; rp = rp->ai_next) {
20✔
420
            fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
20✔
421

422
            if(fd == invalid_socket()) {
20✔
423
               continue;
×
424
            }
425

426
            if(fd >= FD_SETSIZE) {
20✔
427
               ::close(fd);
×
428
               throw CLI_Error("Socket descriptor exceeds FD_SETSIZE; select() would be unsafe");
×
429
            }
430

431
            if(::connect(fd, rp->ai_addr, rp->ai_addrlen) != 0) {
20✔
432
               ::close(fd);
10✔
433
               continue;
10✔
434
            }
435

436
            success = true;
437
            break;
438
         }
439

440
         if(!success) {
10✔
441
            // no address succeeded
442
            throw CLI_Error("Connecting to host failed");
×
443
         }
444

445
         return fd;
10✔
446
      }
10✔
447

448
      static void dgram_socket_write(int sockfd, const uint8_t buf[], size_t length) {
449
         auto r = ::send(sockfd, buf, length, MSG_NOSIGNAL);
450

451
         if(r == -1) {
452
            throw CLI_Error("Socket write failed errno=" + std::to_string(errno));
453
         }
454
      }
455

456
      void shutdown_socket() {
21✔
457
         if(m_sockfd == invalid_socket()) {
21✔
458
            return;
11✔
459
         }
460

461
         // Signal that we are done writing so pending alert records are
462
         // delivered with a FIN rather than lost to a RST.
463
         ::shutdown(m_sockfd, SHUT_WR);
10✔
464

465
         // Drain unread incoming data; if the receive buffer is non-empty when
466
         // we close(), the kernel sends RST which discards our outgoing data
467
         // (including any alert we sent).
468
         char buf[256];
10✔
469
         while(::read(m_sockfd, buf, sizeof(buf)) > 0) {}
29✔
470
         ::close(m_sockfd);
10✔
471

472
         m_sockfd = invalid_socket();
10✔
473
      }
474

475
      socket_type m_sockfd = invalid_socket();
476

477
      using unique_addr_info_ptr = std::unique_ptr<addrinfo, decltype([](addrinfo* p) {
10✔
478
                                                      if(p != nullptr) {
10✔
479
                                                         ::freeaddrinfo(p);
10✔
480
                                                      }
481
                                                   })>;
10✔
482
};
483

484
namespace {
485

486
std::ostream& Callbacks::output() {
2,132✔
487
   return m_client_command.output();
2,132✔
488
}
489

490
bool Callbacks::flag_set(const std::string& flag_name) const {
70✔
491
   return m_client_command.flag_set(flag_name);
70✔
492
}
493

494
std::string Callbacks::get_arg(const std::string& arg_name) const {
×
495
   return m_client_command.get_arg(arg_name);
×
496
}
497

498
void Callbacks::send(std::span<const uint8_t> buffer) {
54✔
499
   m_client_command.send(buffer);
54✔
500
}
501

502
}  // namespace
503

504
BOTAN_REGISTER_COMMAND("tls_client", TLS_Client);
11✔
505

506
}  // namespace Botan_CLI
507

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