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

randombit / botan / 5088545471

26 May 2023 08:27AM UTC coverage: 91.672% (-0.02%) from 91.688%
5088545471

Pull #3549

github

Pull Request #3549: SPHINCS+

78521 of 85654 relevant lines covered (91.67%)

12148878.33 hits per line

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

77.27
/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/tls_callbacks.h>
16
#include <botan/tls_client.h>
17
#include <botan/tls_policy.h>
18
#include <botan/tls_session_manager_memory.h>
19
#include <botan/x509path.h>
20
#include <botan/ocsp.h>
21
#include <botan/hex.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
class Callbacks : public Botan::TLS::Callbacks
4✔
40
   {
41
   public:
42
      Callbacks(TLS_Client& client_command)
4✔
43
         : m_client_command(client_command) {}
4✔
44

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

49
      void tls_verify_cert_chain(
3✔
50
         const std::vector<Botan::X509_Certificate>& cert_chain,
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
         {
57
         if(cert_chain.empty())
3✔
58
            {
59
            throw Botan::Invalid_Argument("Certificate chain was empty");
×
60
            }
61

62
         Botan::Path_Validation_Restrictions restrictions(
3✔
63
            policy.require_cert_revocation_info(),
3✔
64
            policy.minimum_signature_strength());
9✔
65

66
         auto ocsp_timeout = std::chrono::milliseconds(1000);
3✔
67

68
         Botan::Path_Validation_Result result = Botan::x509_path_validate(
3✔
69
               cert_chain,
70
               restrictions,
71
               trusted_roots,
72
               hostname,
73
               usage,
74
               tls_current_timestamp(),
75
               ocsp_timeout,
76
               ocsp);
3✔
77

78
         output() << "Certificate validation status: " << result.result_string() << "\n";
6✔
79
         if(result.successful_validation())
3✔
80
            {
81
            auto status = result.all_statuses();
3✔
82

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

90
      void tls_session_activated() override
4✔
91
         {
92
         output() << "Handshake complete\n";
4✔
93
         }
4✔
94

95
      void tls_session_established(const Botan::TLS::Session_Summary& session) override
4✔
96
         {
97
         output() << "Handshake complete, " << session.version().to_string()
4✔
98
                  << " using " << session.ciphersuite().to_string() << "\n";
16✔
99

100
         if(const auto& session_id = session.session_id(); !session_id.empty())
4✔
101
            {
102
            output() << "Session ID " << Botan::hex_encode(session_id.get()) << "\n";
8✔
103
            }
104

105
         if(const auto& session_ticket = session.session_ticket())
4✔
106
            {
107
            output() << "Session ticket " << Botan::hex_encode(session_ticket->get()) << "\n";
×
108
            }
109

110
         if(flag_set("print-certs"))
8✔
111
            {
112
            const std::vector<Botan::X509_Certificate>& certs = session.peer_certs();
113

114
            for(size_t i = 0; i != certs.size(); ++i)
×
115
               {
116
               output() << "Certificate " << i + 1 << "/" << certs.size() << "\n";
×
117
               output() << certs[i].to_string();
×
118
               output() << certs[i].PEM_encode();
×
119
               }
120
            }
121
         }
4✔
122

123
      void tls_emit_data(std::span<const uint8_t> buf) override
20✔
124
         {
125
         if(flag_set("debug"))
40✔
126
            {
127
            output() << "<< " << Botan::hex_encode(buf) << "\n";
×
128
            }
129

130
         send(buf);
40✔
131
         }
20✔
132

133
      void tls_alert(Botan::TLS::Alert alert) override
×
134
         {
135
         output() << "Alert: " << alert.type_string() << "\n";
×
136
         }
×
137

138
      void tls_record_received(uint64_t /*seq_no*/, std::span<const uint8_t> buf) override
7✔
139
         {
140
         for(const auto c : buf)
919✔
141
            {
142
            output() << c;
912✔
143
            }
144
         }
7✔
145

146
      std::vector<uint8_t> tls_sign_message(
×
147
         const Botan::Private_Key& key,
148
         Botan::RandomNumberGenerator& rng,
149
         const std::string_view padding,
150
         Botan::Signature_Format format,
151
         const std::vector<uint8_t>& msg) override
152
         {
153
         output() << "Performing client authentication\n";
×
154
         return Botan::TLS::Callbacks::tls_sign_message(key, rng, padding, format, msg);
×
155
         }
156

157
   private:
158
      TLS_Client& m_client_command;
159
   };
160

161
}
162

163
class TLS_Client final : public Command
164
   {
165
   public:
166
      TLS_Client()
5✔
167
         : Command("tls_client host --port=443 --print-certs --policy=default "
5✔
168
                   "--skip-system-cert-store --trusted-cas= --tls-version=default "
169
                   "--session-db= --session-db-pass= --next-protocols= --type=tcp "
170
                   "--client-cert= --client-cert-key= --psk= --psk-identity= --debug")
10✔
171
         {
172
         init_sockets();
5✔
173
         }
5✔
174

175
      ~TLS_Client() override
5✔
176
         {
5✔
177
         stop_sockets();
5✔
178
         }
5✔
179

180
      TLS_Client(const TLS_Client& other) = delete;
181
      TLS_Client(TLS_Client&& other) = delete;
182
      TLS_Client& operator=(const TLS_Client& other) = delete;
183
      TLS_Client& operator=(TLS_Client&& other) = delete;
184

185
      std::string group() const override
1✔
186
         {
187
         return "tls";
1✔
188
         }
189

190
      std::string description() const override
1✔
191
         {
192
         return "Connect to a host using TLS/DTLS";
1✔
193
         }
194

195
      void go() override
4✔
196
         {
197
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
4✔
198

199
         auto callbacks = std::make_shared<Callbacks>(*this);
4✔
200

201
         const std::string sessions_db = get_arg("session-db");
4✔
202
         const std::string host = get_arg("host");
4✔
203
         const uint16_t port = get_arg_u16("port");
4✔
204
         const std::string transport = get_arg("type");
4✔
205
         const std::string next_protos = get_arg("next-protocols");
4✔
206
         const bool use_system_cert_store = flag_set("skip-system-cert-store") == false;
4✔
207
         const std::string trusted_CAs = get_arg("trusted-cas");
4✔
208
         const auto tls_version = get_arg("tls-version");
4✔
209

210
         if(!sessions_db.empty())
4✔
211
            {
212
#if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
213
            const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
×
214
            session_mgr.reset(new Botan::TLS::Session_Manager_SQLite(sessions_passphrase, rng_as_shared(), sessions_db));
×
215
#else
216
            error_output() << "Ignoring session DB file, sqlite not enabled\n";
217
#endif
218
            }
×
219

220
         if(!session_mgr)
4✔
221
            {
222
            session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());
12✔
223
            }
224

225
         auto policy = load_tls_policy(get_arg("policy"));
8✔
226

227
         if(transport != "tcp" && transport != "udp")
4✔
228
            {
229
            throw CLI_Usage_Error("Invalid transport type '" + transport + "' for TLS");
×
230
            }
231

232
         const std::vector<std::string> protocols_to_offer = Command::split_on(next_protos, ',');
4✔
233

234
         if(!policy)
4✔
235
            {
236
            policy = std::make_shared<Botan::TLS::Policy>();
×
237
            }
238

239
         const bool use_tcp = (transport == "tcp");
4✔
240
         Botan::TLS::Protocol_Version version = policy->latest_supported_version(!use_tcp);
4✔
241

242
         if(tls_version != "default") {
4✔
243
            if(tls_version == "1.2") {
4✔
244
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V12 : Botan::TLS::Protocol_Version::DTLS_V12;
2✔
245
            } else if (tls_version == "1.3") {
2✔
246
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V13 : Botan::TLS::Protocol_Version::DTLS_V13;
2✔
247
            } else {
248
               error_output() << "Unknown TLS protocol version " << tls_version << '\n';
×
249
            }
250
         }
251

252
         struct sockaddr_storage addrbuf;
4✔
253
         std::string hostname;
4✔
254
         if(!host.empty() &&
8✔
255
               inet_pton(AF_INET, host.c_str(), &addrbuf) != 1 &&
8✔
256
               inet_pton(AF_INET6, host.c_str(), &addrbuf) != 1)
4✔
257
            {
258
            hostname = host;
4✔
259
            }
260

261
         m_sockfd = connect_to_host(host, port, use_tcp);
4✔
262

263
         const auto client_crt_path = get_arg_maybe("client-cert");
4✔
264
         const auto client_key_path = get_arg_maybe("client-cert-key");
4✔
265

266
         const auto psk = [this]() -> std::optional<Botan::SymmetricKey> {
8✔
267
            auto psk_hex = get_arg_maybe("psk");
4✔
268
            if(psk_hex)
4✔
269
               return Botan::SymmetricKey(Botan::hex_decode_locked(psk_hex.value()));
1✔
270
            else
271
               return {};
4✔
272
         }();
8✔
273
         const std::optional<std::string> psk_identity = get_arg_maybe("psk-identity");
4✔
274

275
         auto creds = std::make_shared<Basic_Credentials_Manager>(use_system_cert_store, trusted_CAs, client_crt_path, client_key_path, psk, psk_identity);
4✔
276

277
         Botan::TLS::Client client(callbacks, session_mgr, creds, policy, rng_as_shared(),
8✔
278
                                   Botan::TLS::Server_Information(hostname, port),
4✔
279
                                   version, protocols_to_offer);
16✔
280

281
         bool first_active = true;
4✔
282

283
         while(!client.is_closed())
23✔
284
            {
285
            fd_set readfds;
286
            FD_ZERO(&readfds);
391✔
287
            FD_SET(m_sockfd, &readfds);
23✔
288

289
            if(client.is_active())
23✔
290
               {
291
               FD_SET(STDIN_FILENO, &readfds);
13✔
292
               if(first_active && !protocols_to_offer.empty())
13✔
293
                  {
294
                  std::string app = client.application_protocol();
×
295
                  if(!app.empty())
×
296
                     {
297
                     output() << "Server choose protocol: " << client.application_protocol() << "\n";
×
298
                     }
299
                  first_active = false;
×
300
                  }
×
301
               }
302

303
            struct timeval timeout = { 1, 0 };
23✔
304

305
            ::select(static_cast<int>(m_sockfd + 1), &readfds, nullptr, nullptr, &timeout);
23✔
306

307
            if(FD_ISSET(m_sockfd, &readfds))
23✔
308
               {
309
               uint8_t buf[4 * 1024] = { 0 };
16✔
310

311
               ssize_t got = ::read(m_sockfd, buf, sizeof(buf));
16✔
312

313
               if(got == 0)
16✔
314
                  {
315
                  output() << "EOF on socket\n";
×
316
                  break;
×
317
                  }
318
               else if(got == -1)
16✔
319
                  {
320
                  output() << "Socket error: " << errno << " " << err_to_string(errno) << "\n";
×
321
                  continue;
×
322
                  }
323

324
               if(flag_set("debug"))
16✔
325
                  {
326
                  output() << ">> " << Botan::hex_encode(buf, got) << "\n";
×
327
                  }
328

329
               client.received_data(buf, got);
16✔
330
               }
331

332
            if(FD_ISSET(STDIN_FILENO, &readfds))
23✔
333
               {
334
               uint8_t buf[1024] = { 0 };
8✔
335
               ssize_t got = read(STDIN_FILENO, buf, sizeof(buf));
8✔
336

337
               if(got == 0)
8✔
338
                  {
339
                  output() << "EOF on stdin\n";
4✔
340
                  client.close();
4✔
341
                  break;
4✔
342
                  }
343
               else if(got == -1)
4✔
344
                  {
345
                  output() << "Stdin error: " << errno << " " << err_to_string(errno) << "\n";
×
346
                  continue;
×
347
                  }
348

349
               if(got == 2 && buf[1] == '\n')
4✔
350
                  {
351
                  char cmd = buf[0];
×
352

353
                  if(cmd == 'R' || cmd == 'r')
×
354
                     {
355
                     output() << "Client initiated renegotiation\n";
×
356
                     client.renegotiate(cmd == 'R');
×
357
                     }
358
                  else if(cmd == 'Q')
×
359
                     {
360
                     output() << "Client initiated close\n";
×
361
                     client.close();
×
362
                     }
363
                  }
364
               else
365
                  {
366
                  client.send(buf, got);
8✔
367
                  }
368
               }
369

370
            if(client.timeout_check())
19✔
371
               {
372
               output() << "Timeout detected\n";
×
373
               }
374
            }
375

376
         ::close(m_sockfd);
4✔
377
         }
24✔
378

379
   public:
380
      using Command::output;
381
      using Command::flag_set;
382

383
      void send(std::span<const uint8_t> buf) const
20✔
384
         {
385
         while(!buf.empty())
40✔
386
            {
387
            ssize_t sent = ::send(m_sockfd, buf.data(), buf.size(), MSG_NOSIGNAL);
20✔
388

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

401
            buf = buf.subspan(sent);
20✔
402
            }
403
         }
20✔
404

405
   private:
406
      static socket_type connect_to_host(const std::string& host, uint16_t port, bool tcp)
4✔
407
         {
408
         addrinfo hints;
4✔
409
         Botan::clear_mem(&hints, 1);
4✔
410
         hints.ai_family = AF_UNSPEC;
4✔
411
         hints.ai_socktype = tcp ? SOCK_STREAM : SOCK_DGRAM;
4✔
412
         addrinfo* res, *rp = nullptr;
4✔
413

414
         if(::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res) != 0)
4✔
415
            {
416
            throw CLI_Error("getaddrinfo failed for " + host);
×
417
            }
418

419
         socket_type fd = 0;
4✔
420

421
         for(rp = res; rp != nullptr; rp = rp->ai_next)
8✔
422
            {
423
            fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
8✔
424

425
            if(fd == invalid_socket())
8✔
426
               {
427
               continue;
×
428
               }
429

430
            if(::connect(fd, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen)) != 0)
8✔
431
               {
432
               ::close(fd);
4✔
433
               continue;
4✔
434
               }
435

436
            break;
437
            }
438

439
         ::freeaddrinfo(res);
4✔
440

441
         if(rp == nullptr) // no address succeeded
4✔
442
            {
443
            throw CLI_Error("connect failed");
×
444
            }
445

446
         return fd;
4✔
447
         }
448

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

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

459
      socket_type m_sockfd = invalid_socket();
460
   };
461

462
namespace {
463

464
std::ostream& Callbacks::output()
927✔
465
   {
466
   return m_client_command.output();
927✔
467
   }
468

469
bool Callbacks::flag_set(const std::string& flag_name) const
24✔
470
   {
471
   return m_client_command.flag_set(flag_name);
24✔
472
   }
473

474
void Callbacks::send(std::span<const uint8_t> buffer)
20✔
475
   {
476
   m_client_command.send(buffer);
20✔
477
   }
478

479
}
480

481
BOTAN_REGISTER_COMMAND("tls_client", TLS_Client);
5✔
482

483
}
484

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