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

randombit / botan / 6430144375

06 Oct 2023 09:53AM UTC coverage: 91.693% (+0.005%) from 91.688%
6430144375

Pull #3732

github

web-flow
Merge 870e00833 into 30ecb8719
Pull Request #3732: [TLS 1.3] Test Hybrid Schemes Online

79955 of 87199 relevant lines covered (91.69%)

8639434.86 hits per line

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

76.0
/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
*
8
* Botan is released under the Simplified BSD License (see license.txt)
9
*/
10

11
#include "cli.h"
12

13
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(BOTAN_TARGET_OS_HAS_SOCKETS)
14

15
   #include <botan/hex.h>
16
   #include <botan/ocsp.h>
17
   #include <botan/tls_callbacks.h>
18
   #include <botan/tls_client.h>
19
   #include <botan/tls_policy.h>
20
   #include <botan/tls_session_manager_memory.h>
21
   #include <botan/x509path.h>
22
   #include <fstream>
23

24
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
25
      #include <botan/tls_session_manager_sqlite.h>
26
   #endif
27

28
   #include <memory>
29
   #include <string>
30

31
   #include "socket_utils.h"
32
   #include "tls_helpers.h"
33

34
namespace Botan_CLI {
35

36
class TLS_Client;
37

38
namespace {
39

40
class Callbacks : public Botan::TLS::Callbacks {
7✔
41
   public:
42
      Callbacks(TLS_Client& client_command) : m_client_command(client_command), m_peer_closed(false) {}
7✔
43

44
      std::ostream& output();
45
      bool flag_set(const std::string& flag_name) const;
46
      void send(std::span<const uint8_t> buffer);
47

48
      int peer_closed() const { return m_peer_closed; }
×
49

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

60
         Botan::Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(),
5✔
61
                                                          policy.minimum_signature_strength());
15✔
62

63
         auto ocsp_timeout = std::chrono::milliseconds(1000);
5✔
64

65
         Botan::Path_Validation_Result result = Botan::x509_path_validate(
5✔
66
            cert_chain, restrictions, trusted_roots, hostname, usage, tls_current_timestamp(), ocsp_timeout, ocsp);
5✔
67

68
         output() << "Certificate validation status: " << result.result_string() << "\n";
10✔
69
         if(result.successful_validation()) {
5✔
70
            auto status = result.all_statuses();
5✔
71

72
            if(!status.empty() && status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD)) {
10✔
73
               output() << "Valid OCSP response for this server\n";
×
74
            }
75
         }
5✔
76
      }
5✔
77

78
      void tls_session_activated() override { output() << "Handshake complete\n"; }
7✔
79

80
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
7✔
81
         output() << "Handshake complete, " << session.version().to_string() << " using "
7✔
82
                  << session.ciphersuite().to_string();
28✔
83

84
         if(const auto& psk = session.external_psk_identity()) {
7✔
85
            output() << " (utilized PSK identity: " << maybe_hex_encode(psk.value()) << ")";
4✔
86
         }
87

88
         output() << std::endl;
7✔
89

90
         if(const auto& session_id = session.session_id(); !session_id.empty()) {
7✔
91
            output() << "Session ID " << Botan::hex_encode(session_id.get()) << "\n";
14✔
92
         }
93

94
         if(const auto& session_ticket = session.session_ticket()) {
7✔
95
            output() << "Session ticket " << Botan::hex_encode(session_ticket->get()) << "\n";
×
96
         }
97

98
         if(flag_set("print-certs")) {
14✔
99
            const std::vector<Botan::X509_Certificate>& certs = session.peer_certs();
100

101
            for(size_t i = 0; i != certs.size(); ++i) {
×
102
               output() << "Certificate " << i + 1 << "/" << certs.size() << "\n";
×
103
               output() << certs[i].to_string();
×
104
               output() << certs[i].PEM_encode();
×
105
            }
106
         }
107
      }
7✔
108

109
      void tls_emit_data(std::span<const uint8_t> buf) override {
32✔
110
         if(flag_set("debug")) {
64✔
111
            output() << "<< " << Botan::hex_encode(buf) << "\n";
×
112
         }
113

114
         send(buf);
64✔
115
      }
32✔
116

117
      void tls_alert(Botan::TLS::Alert alert) override { output() << "Alert: " << alert.type_string() << "\n"; }
×
118

119
      void tls_record_received(uint64_t /*seq_no*/, std::span<const uint8_t> buf) override {
10✔
120
         for(const auto c : buf) {
1,606✔
121
            output() << c;
1,596✔
122
         }
123
      }
10✔
124

125
      std::vector<uint8_t> tls_sign_message(const Botan::Private_Key& key,
×
126
                                            Botan::RandomNumberGenerator& rng,
127
                                            const std::string_view padding,
128
                                            Botan::Signature_Format format,
129
                                            const std::vector<uint8_t>& msg) override {
130
         output() << "Performing client authentication\n";
×
131
         return Botan::TLS::Callbacks::tls_sign_message(key, rng, padding, format, msg);
×
132
      }
133

134
      bool tls_peer_closed_connection() override {
×
135
         m_peer_closed = true;
×
136
         return Botan::TLS::Callbacks::tls_peer_closed_connection();
×
137
      }
138

139
   private:
140
      TLS_Client& m_client_command;
141
      bool m_peer_closed;
142
};
143

144
}  // namespace
145

146
class TLS_Client final : public Command {
147
   public:
148
      TLS_Client() :
8✔
149
            Command(
150
               "tls_client host --port=443 --print-certs --policy=default "
151
               "--skip-system-cert-store --trusted-cas= --tls-version=default "
152
               "--session-db= --session-db-pass= --next-protocols= --type=tcp "
153
               "--client-cert= --client-cert-key= --psk= --psk-identity= --psk-prf=SHA-256 --debug") {
16✔
154
         init_sockets();
8✔
155
      }
8✔
156

157
      ~TLS_Client() override { stop_sockets(); }
8✔
158

159
      TLS_Client(const TLS_Client& other) = delete;
160
      TLS_Client(TLS_Client&& other) = delete;
161
      TLS_Client& operator=(const TLS_Client& other) = delete;
162
      TLS_Client& operator=(TLS_Client&& other) = delete;
163

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

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

168
      void go() override {
7✔
169
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
7✔
170

171
         auto callbacks = std::make_shared<Callbacks>(*this);
7✔
172

173
         const std::string sessions_db = get_arg("session-db");
7✔
174
         const std::string host = get_arg("host");
7✔
175
         const uint16_t port = get_arg_u16("port");
7✔
176
         const std::string transport = get_arg("type");
7✔
177
         const std::string next_protos = get_arg("next-protocols");
7✔
178
         const bool use_system_cert_store = flag_set("skip-system-cert-store") == false;
7✔
179
         const std::string trusted_CAs = get_arg("trusted-cas");
7✔
180
         const auto tls_version = get_arg("tls-version");
7✔
181

182
         if(!sessions_db.empty()) {
7✔
183
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
184
            const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
×
185
            session_mgr.reset(
×
186
               new Botan::TLS::Session_Manager_SQLite(sessions_passphrase, rng_as_shared(), sessions_db));
×
187
   #else
188
            error_output() << "Ignoring session DB file, sqlite not enabled\n";
189
   #endif
190
         }
×
191

192
         if(!session_mgr) {
7✔
193
            session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());
21✔
194
         }
195

196
         auto policy = load_tls_policy(get_arg("policy"));
14✔
197

198
         if(transport != "tcp" && transport != "udp") {
7✔
199
            throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS");
×
200
         }
201

202
         const std::vector<std::string> protocols_to_offer = Command::split_on(next_protos, ',');
7✔
203

204
         if(!policy) {
7✔
205
            policy = std::make_shared<Botan::TLS::Policy>();
×
206
         }
207

208
         const bool use_tcp = (transport == "tcp");
7✔
209
         Botan::TLS::Protocol_Version version = policy->latest_supported_version(!use_tcp);
7✔
210

211
         if(tls_version != "default") {
7✔
212
            if(tls_version == "1.2") {
7✔
213
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V12 : Botan::TLS::Protocol_Version::DTLS_V12;
2✔
214
            } else if(tls_version == "1.3") {
5✔
215
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V13 : Botan::TLS::Protocol_Version::DTLS_V13;
5✔
216
            } else {
217
               error_output() << "Unknown TLS protocol version " << tls_version << '\n';
×
218
            }
219
         }
220

221
         struct sockaddr_storage addrbuf;
7✔
222
         std::string hostname;
7✔
223
         if(!host.empty() && inet_pton(AF_INET, host.c_str(), &addrbuf) != 1 &&
14✔
224
            inet_pton(AF_INET6, host.c_str(), &addrbuf) != 1) {
7✔
225
            hostname = host;
7✔
226
         }
227

228
         m_sockfd = connect_to_host(host, port, use_tcp);
7✔
229

230
         const auto client_crt_path = get_arg_maybe("client-cert");
7✔
231
         const auto client_key_path = get_arg_maybe("client-cert-key");
7✔
232

233
         auto psk = [this]() -> std::optional<Botan::secure_vector<uint8_t>> {
14✔
234
            auto psk_hex = get_arg_maybe("psk");
7✔
235
            if(psk_hex) {
7✔
236
               return Botan::hex_decode_locked(psk_hex.value());
2✔
237
            } else {
238
               return {};
7✔
239
            }
240
         }();
14✔
241
         const std::optional<std::string> psk_identity = get_arg_maybe("psk-identity");
7✔
242
         const std::optional<std::string> psk_prf = get_arg_maybe("psk-prf");
7✔
243

244
         auto creds = std::make_shared<Basic_Credentials_Manager>(use_system_cert_store,
7✔
245
                                                                  trusted_CAs,
246
                                                                  client_crt_path,
247
                                                                  client_key_path,
248
                                                                  std::move(psk),
7✔
249
                                                                  psk_identity,
250
                                                                  psk_prf);
7✔
251

252
         Botan::TLS::Client client(callbacks,
7✔
253
                                   session_mgr,
254
                                   creds,
255
                                   policy,
256
                                   rng_as_shared(),
7✔
257
                                   Botan::TLS::Server_Information(hostname, port),
7✔
258
                                   version,
259
                                   protocols_to_offer);
28✔
260

261
         bool first_active = true;
7✔
262
         bool we_closed = false;
7✔
263

264
         while(!client.is_closed()) {
34✔
265
            fd_set readfds;
266
            FD_ZERO(&readfds);
578✔
267
            FD_SET(m_sockfd, &readfds);
34✔
268

269
            if(client.is_active()) {
34✔
270
               FD_SET(STDIN_FILENO, &readfds);
21✔
271
               if(first_active && !protocols_to_offer.empty()) {
21✔
272
                  std::string app = client.application_protocol();
×
273
                  if(!app.empty()) {
×
274
                     output() << "Server choose protocol: " << client.application_protocol() << "\n";
×
275
                  }
276
                  first_active = false;
×
277
               }
×
278
            }
279

280
            struct timeval timeout = {1, 0};
34✔
281

282
            ::select(static_cast<int>(m_sockfd + 1), &readfds, nullptr, nullptr, &timeout);
34✔
283

284
            if(FD_ISSET(m_sockfd, &readfds)) {
34✔
285
               uint8_t buf[4 * 1024] = {0};
22✔
286

287
               ssize_t got = ::read(m_sockfd, buf, sizeof(buf));
22✔
288

289
               if(got == 0) {
22✔
290
                  output() << "EOF on socket\n";
×
291
                  break;
×
292
               } else if(got == -1) {
22✔
293
                  output() << "Socket error: " << errno << " " << err_to_string(errno) << "\n";
×
294
                  continue;
×
295
               }
296

297
               if(flag_set("debug")) {
22✔
298
                  output() << ">> " << Botan::hex_encode(buf, got) << "\n";
×
299
               }
300

301
               client.received_data(buf, got);
22✔
302
            }
303

304
            if(FD_ISSET(STDIN_FILENO, &readfds)) {
34✔
305
               uint8_t buf[1024] = {0};
14✔
306
               ssize_t got = read(STDIN_FILENO, buf, sizeof(buf));
14✔
307

308
               if(got == 0) {
14✔
309
                  output() << "EOF on stdin\n";
7✔
310
                  client.close();
7✔
311
                  we_closed = true;
7✔
312
                  break;
7✔
313
               } else if(got == -1) {
7✔
314
                  output() << "Stdin error: " << errno << " " << err_to_string(errno) << "\n";
×
315
                  continue;
×
316
               }
317

318
               if(got == 2 && buf[1] == '\n') {
7✔
319
                  char cmd = buf[0];
×
320

321
                  if(cmd == 'R' || cmd == 'r') {
×
322
                     output() << "Client initiated renegotiation\n";
×
323
                     client.renegotiate(cmd == 'R');
×
324
                  } else if(cmd == 'Q') {
×
325
                     output() << "Client initiated close\n";
×
326
                     client.close();
×
327
                     we_closed = true;
328
                  }
329
               } else {
330
                  client.send(buf, got);
14✔
331
               }
332
            }
333

334
            if(client.timeout_check()) {
27✔
335
               output() << "Timeout detected\n";
×
336
            }
337
         }
338

339
         set_return_code((we_closed || callbacks->peer_closed()) ? 0 : 1);
7✔
340

341
         ::close(m_sockfd);
7✔
342
      }
42✔
343

344
   public:
345
      using Command::flag_set;
346
      using Command::output;
347

348
      void send(std::span<const uint8_t> buf) const {
32✔
349
         while(!buf.empty()) {
64✔
350
            ssize_t sent = ::send(m_sockfd, buf.data(), buf.size(), MSG_NOSIGNAL);
32✔
351

352
            if(sent == -1) {
32✔
353
               if(errno == EINTR) {
×
354
                  sent = 0;
355
               } else {
356
                  throw CLI_Error("Socket write failed errno=" + std::to_string(errno));
×
357
               }
358
            }
359

360
            buf = buf.subspan(sent);
32✔
361
         }
362
      }
32✔
363

364
   private:
365
      static socket_type connect_to_host(const std::string& host, uint16_t port, bool tcp) {
7✔
366
         addrinfo hints;
7✔
367
         Botan::clear_mem(&hints, 1);
7✔
368
         hints.ai_family = AF_UNSPEC;
7✔
369
         hints.ai_socktype = tcp ? SOCK_STREAM : SOCK_DGRAM;
7✔
370
         addrinfo *res, *rp = nullptr;
7✔
371

372
         if(::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res) != 0) {
7✔
373
            throw CLI_Error("getaddrinfo failed for " + host);
×
374
         }
375

376
         socket_type fd = 0;
7✔
377

378
         for(rp = res; rp != nullptr; rp = rp->ai_next) {
14✔
379
            fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
14✔
380

381
            if(fd == invalid_socket()) {
14✔
382
               continue;
×
383
            }
384

385
            if(::connect(fd, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen)) != 0) {
14✔
386
               ::close(fd);
7✔
387
               continue;
7✔
388
            }
389

390
            break;
391
         }
392

393
         ::freeaddrinfo(res);
7✔
394

395
         if(rp == nullptr)  // no address succeeded
7✔
396
         {
397
            throw CLI_Error("connect failed");
×
398
         }
399

400
         return fd;
7✔
401
      }
402

403
      static void dgram_socket_write(int sockfd, const uint8_t buf[], size_t length) {
404
         auto r = ::send(sockfd, buf, length, MSG_NOSIGNAL);
405

406
         if(r == -1) {
407
            throw CLI_Error("Socket write failed errno=" + std::to_string(errno));
408
         }
409
      }
410

411
      socket_type m_sockfd = invalid_socket();
412
};
413

414
namespace {
415

416
std::ostream& Callbacks::output() {
1,631✔
417
   return m_client_command.output();
1,631✔
418
}
419

420
bool Callbacks::flag_set(const std::string& flag_name) const {
39✔
421
   return m_client_command.flag_set(flag_name);
39✔
422
}
423

424
void Callbacks::send(std::span<const uint8_t> buffer) {
32✔
425
   m_client_command.send(buffer);
32✔
426
}
427

428
}  // namespace
429

430
BOTAN_REGISTER_COMMAND("tls_client", TLS_Client);
8✔
431

432
}  // namespace Botan_CLI
433

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