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

randombit / botan / 5123321399

30 May 2023 04:06PM UTC coverage: 92.213% (+0.004%) from 92.209%
5123321399

Pull #3558

github

web-flow
Merge dd72f7389 into 057bcbc35
Pull Request #3558: Add braces around all if/else statements

75602 of 81986 relevant lines covered (92.21%)

11859779.3 hits per line

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

76.22
/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
class Callbacks : public Botan::TLS::Callbacks {
4✔
40
   public:
41
      Callbacks(TLS_Client& client_command) : m_client_command(client_command) {}
4✔
42

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

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

57
         Botan::Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(),
3✔
58
                                                          policy.minimum_signature_strength());
9✔
59

60
         auto ocsp_timeout = std::chrono::milliseconds(1000);
3✔
61

62
         Botan::Path_Validation_Result result = Botan::x509_path_validate(
3✔
63
            cert_chain, restrictions, trusted_roots, hostname, usage, tls_current_timestamp(), ocsp_timeout, ocsp);
3✔
64

65
         output() << "Certificate validation status: " << result.result_string() << "\n";
6✔
66
         if(result.successful_validation()) {
3✔
67
            auto status = result.all_statuses();
3✔
68

69
            if(!status.empty() && status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD)) {
6✔
70
               output() << "Valid OCSP response for this server\n";
×
71
            }
72
         }
3✔
73
      }
3✔
74

75
      void tls_session_activated() override { output() << "Handshake complete\n"; }
4✔
76

77
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
4✔
78
         output() << "Handshake complete, " << session.version().to_string() << " using "
4✔
79
                  << session.ciphersuite().to_string() << "\n";
16✔
80

81
         if(const auto& session_id = session.session_id(); !session_id.empty()) {
4✔
82
            output() << "Session ID " << Botan::hex_encode(session_id.get()) << "\n";
8✔
83
         }
84

85
         if(const auto& session_ticket = session.session_ticket()) {
4✔
86
            output() << "Session ticket " << Botan::hex_encode(session_ticket->get()) << "\n";
×
87
         }
88

89
         if(flag_set("print-certs")) {
8✔
90
            const std::vector<Botan::X509_Certificate>& certs = session.peer_certs();
91

92
            for(size_t i = 0; i != certs.size(); ++i) {
×
93
               output() << "Certificate " << i + 1 << "/" << certs.size() << "\n";
×
94
               output() << certs[i].to_string();
×
95
               output() << certs[i].PEM_encode();
×
96
            }
97
         }
98
      }
4✔
99

100
      void tls_emit_data(std::span<const uint8_t> buf) override {
20✔
101
         if(flag_set("debug")) {
40✔
102
            output() << "<< " << Botan::hex_encode(buf) << "\n";
×
103
         }
104

105
         send(buf);
40✔
106
      }
20✔
107

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

110
      void tls_record_received(uint64_t /*seq_no*/, std::span<const uint8_t> buf) override {
7✔
111
         for(const auto c : buf) {
919✔
112
            output() << c;
912✔
113
         }
114
      }
7✔
115

116
      std::vector<uint8_t> tls_sign_message(const Botan::Private_Key& key,
×
117
                                            Botan::RandomNumberGenerator& rng,
118
                                            const std::string_view padding,
119
                                            Botan::Signature_Format format,
120
                                            const std::vector<uint8_t>& msg) override {
121
         output() << "Performing client authentication\n";
×
122
         return Botan::TLS::Callbacks::tls_sign_message(key, rng, padding, format, msg);
×
123
      }
124

125
   private:
126
      TLS_Client& m_client_command;
127
};
128

129
}  // namespace
130

131
class TLS_Client final : public Command {
132
   public:
133
      TLS_Client() :
5✔
134
            Command(
135
               "tls_client host --port=443 --print-certs --policy=default "
136
               "--skip-system-cert-store --trusted-cas= --tls-version=default "
137
               "--session-db= --session-db-pass= --next-protocols= --type=tcp "
138
               "--client-cert= --client-cert-key= --psk= --psk-identity= --debug") {
10✔
139
         init_sockets();
5✔
140
      }
5✔
141

142
      ~TLS_Client() override { stop_sockets(); }
5✔
143

144
      TLS_Client(const TLS_Client& other) = delete;
145
      TLS_Client(TLS_Client&& other) = delete;
146
      TLS_Client& operator=(const TLS_Client& other) = delete;
147
      TLS_Client& operator=(TLS_Client&& other) = delete;
148

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

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

153
      void go() override {
4✔
154
         std::shared_ptr<Botan::TLS::Session_Manager> session_mgr;
4✔
155

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

158
         const std::string sessions_db = get_arg("session-db");
4✔
159
         const std::string host = get_arg("host");
4✔
160
         const uint16_t port = get_arg_u16("port");
4✔
161
         const std::string transport = get_arg("type");
4✔
162
         const std::string next_protos = get_arg("next-protocols");
4✔
163
         const bool use_system_cert_store = flag_set("skip-system-cert-store") == false;
4✔
164
         const std::string trusted_CAs = get_arg("trusted-cas");
4✔
165
         const auto tls_version = get_arg("tls-version");
4✔
166

167
         if(!sessions_db.empty()) {
4✔
168
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
169
            const std::string sessions_passphrase = get_passphrase_arg("Session DB passphrase", "session-db-pass");
×
170
            session_mgr.reset(
×
171
               new Botan::TLS::Session_Manager_SQLite(sessions_passphrase, rng_as_shared(), sessions_db));
×
172
   #else
173
            error_output() << "Ignoring session DB file, sqlite not enabled\n";
174
   #endif
175
         }
×
176

177
         if(!session_mgr) {
4✔
178
            session_mgr = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng_as_shared());
12✔
179
         }
180

181
         auto policy = load_tls_policy(get_arg("policy"));
8✔
182

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

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

189
         if(!policy) {
4✔
190
            policy = std::make_shared<Botan::TLS::Policy>();
×
191
         }
192

193
         const bool use_tcp = (transport == "tcp");
4✔
194
         Botan::TLS::Protocol_Version version = policy->latest_supported_version(!use_tcp);
4✔
195

196
         if(tls_version != "default") {
4✔
197
            if(tls_version == "1.2") {
4✔
198
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V12 : Botan::TLS::Protocol_Version::DTLS_V12;
2✔
199
            } else if(tls_version == "1.3") {
2✔
200
               version = use_tcp ? Botan::TLS::Protocol_Version::TLS_V13 : Botan::TLS::Protocol_Version::DTLS_V13;
2✔
201
            } else {
202
               error_output() << "Unknown TLS protocol version " << tls_version << '\n';
×
203
            }
204
         }
205

206
         struct sockaddr_storage addrbuf;
4✔
207
         std::string hostname;
4✔
208
         if(!host.empty() && inet_pton(AF_INET, host.c_str(), &addrbuf) != 1 &&
8✔
209
            inet_pton(AF_INET6, host.c_str(), &addrbuf) != 1) {
4✔
210
            hostname = host;
4✔
211
         }
212

213
         m_sockfd = connect_to_host(host, port, use_tcp);
4✔
214

215
         const auto client_crt_path = get_arg_maybe("client-cert");
4✔
216
         const auto client_key_path = get_arg_maybe("client-cert-key");
4✔
217

218
         const auto psk = [this]() -> std::optional<Botan::SymmetricKey> {
8✔
219
            auto psk_hex = get_arg_maybe("psk");
4✔
220
            if(psk_hex) {
4✔
221
               return Botan::SymmetricKey(Botan::hex_decode_locked(psk_hex.value()));
1✔
222
            } else {
223
               return {};
4✔
224
            }
225
         }();
8✔
226
         const std::optional<std::string> psk_identity = get_arg_maybe("psk-identity");
4✔
227

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

231
         Botan::TLS::Client client(callbacks,
4✔
232
                                   session_mgr,
233
                                   creds,
234
                                   policy,
235
                                   rng_as_shared(),
8✔
236
                                   Botan::TLS::Server_Information(hostname, port),
4✔
237
                                   version,
238
                                   protocols_to_offer);
16✔
239

240
         bool first_active = true;
4✔
241

242
         while(!client.is_closed()) {
22✔
243
            fd_set readfds;
244
            FD_ZERO(&readfds);
374✔
245
            FD_SET(m_sockfd, &readfds);
22✔
246

247
            if(client.is_active()) {
22✔
248
               FD_SET(STDIN_FILENO, &readfds);
12✔
249
               if(first_active && !protocols_to_offer.empty()) {
12✔
250
                  std::string app = client.application_protocol();
×
251
                  if(!app.empty()) {
×
252
                     output() << "Server choose protocol: " << client.application_protocol() << "\n";
×
253
                  }
254
                  first_active = false;
×
255
               }
×
256
            }
257

258
            struct timeval timeout = {1, 0};
22✔
259

260
            ::select(static_cast<int>(m_sockfd + 1), &readfds, nullptr, nullptr, &timeout);
22✔
261

262
            if(FD_ISSET(m_sockfd, &readfds)) {
22✔
263
               uint8_t buf[4 * 1024] = {0};
15✔
264

265
               ssize_t got = ::read(m_sockfd, buf, sizeof(buf));
15✔
266

267
               if(got == 0) {
15✔
268
                  output() << "EOF on socket\n";
×
269
                  break;
×
270
               } else if(got == -1) {
15✔
271
                  output() << "Socket error: " << errno << " " << err_to_string(errno) << "\n";
×
272
                  continue;
×
273
               }
274

275
               if(flag_set("debug")) {
15✔
276
                  output() << ">> " << Botan::hex_encode(buf, got) << "\n";
×
277
               }
278

279
               client.received_data(buf, got);
15✔
280
            }
281

282
            if(FD_ISSET(STDIN_FILENO, &readfds)) {
22✔
283
               uint8_t buf[1024] = {0};
8✔
284
               ssize_t got = read(STDIN_FILENO, buf, sizeof(buf));
8✔
285

286
               if(got == 0) {
8✔
287
                  output() << "EOF on stdin\n";
4✔
288
                  client.close();
4✔
289
                  break;
4✔
290
               } else if(got == -1) {
4✔
291
                  output() << "Stdin error: " << errno << " " << err_to_string(errno) << "\n";
×
292
                  continue;
×
293
               }
294

295
               if(got == 2 && buf[1] == '\n') {
4✔
296
                  char cmd = buf[0];
×
297

298
                  if(cmd == 'R' || cmd == 'r') {
×
299
                     output() << "Client initiated renegotiation\n";
×
300
                     client.renegotiate(cmd == 'R');
×
301
                  } else if(cmd == 'Q') {
×
302
                     output() << "Client initiated close\n";
×
303
                     client.close();
×
304
                  }
305
               } else {
306
                  client.send(buf, got);
8✔
307
               }
308
            }
309

310
            if(client.timeout_check()) {
18✔
311
               output() << "Timeout detected\n";
×
312
            }
313
         }
314

315
         ::close(m_sockfd);
4✔
316
      }
24✔
317

318
   public:
319
      using Command::flag_set;
320
      using Command::output;
321

322
      void send(std::span<const uint8_t> buf) const {
20✔
323
         while(!buf.empty()) {
40✔
324
            ssize_t sent = ::send(m_sockfd, buf.data(), buf.size(), MSG_NOSIGNAL);
20✔
325

326
            if(sent == -1) {
20✔
327
               if(errno == EINTR) {
×
328
                  sent = 0;
329
               } else {
330
                  throw CLI_Error("Socket write failed errno=" + std::to_string(errno));
×
331
               }
332
            }
333

334
            buf = buf.subspan(sent);
20✔
335
         }
336
      }
20✔
337

338
   private:
339
      static socket_type connect_to_host(const std::string& host, uint16_t port, bool tcp) {
4✔
340
         addrinfo hints;
4✔
341
         Botan::clear_mem(&hints, 1);
4✔
342
         hints.ai_family = AF_UNSPEC;
4✔
343
         hints.ai_socktype = tcp ? SOCK_STREAM : SOCK_DGRAM;
4✔
344
         addrinfo *res, *rp = nullptr;
4✔
345

346
         if(::getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res) != 0) {
4✔
347
            throw CLI_Error("getaddrinfo failed for " + host);
×
348
         }
349

350
         socket_type fd = 0;
4✔
351

352
         for(rp = res; rp != nullptr; rp = rp->ai_next) {
8✔
353
            fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
8✔
354

355
            if(fd == invalid_socket()) {
8✔
356
               continue;
×
357
            }
358

359
            if(::connect(fd, rp->ai_addr, static_cast<socklen_t>(rp->ai_addrlen)) != 0) {
8✔
360
               ::close(fd);
4✔
361
               continue;
4✔
362
            }
363

364
            break;
365
         }
366

367
         ::freeaddrinfo(res);
4✔
368

369
         if(rp == nullptr)  // no address succeeded
4✔
370
         {
371
            throw CLI_Error("connect failed");
×
372
         }
373

374
         return fd;
4✔
375
      }
376

377
      static void dgram_socket_write(int sockfd, const uint8_t buf[], size_t length) {
378
         auto r = ::send(sockfd, buf, length, MSG_NOSIGNAL);
379

380
         if(r == -1) {
381
            throw CLI_Error("Socket write failed errno=" + std::to_string(errno));
382
         }
383
      }
384

385
      socket_type m_sockfd = invalid_socket();
386
};
387

388
namespace {
389

390
std::ostream& Callbacks::output() { return m_client_command.output(); }
927✔
391

392
bool Callbacks::flag_set(const std::string& flag_name) const { return m_client_command.flag_set(flag_name); }
24✔
393

394
void Callbacks::send(std::span<const uint8_t> buffer) { m_client_command.send(buffer); }
20✔
395

396
}  // namespace
397

398
BOTAN_REGISTER_COMMAND("tls_client", TLS_Client);
5✔
399

400
}  // namespace Botan_CLI
401

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