xref: /aosp_15_r20/external/boringssl/src/tool/client.cc (revision 8fb009dc861624b67b6cdb62ea21f0f22d0c584b)
1 /* Copyright (c) 2014, Google Inc.
2  *
3  * Permission to use, copy, modify, and/or distribute this software for any
4  * purpose with or without fee is hereby granted, provided that the above
5  * copyright notice and this permission notice appear in all copies.
6  *
7  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
14 
15 #include <openssl/base.h>
16 
17 #include <stdio.h>
18 
19 #if !defined(OPENSSL_WINDOWS)
20 #include <sys/select.h>
21 #else
22 OPENSSL_MSVC_PRAGMA(warning(push, 3))
23 #include <winsock2.h>
24 OPENSSL_MSVC_PRAGMA(warning(pop))
25 #endif
26 
27 #include <openssl/err.h>
28 #include <openssl/pem.h>
29 #include <openssl/ssl.h>
30 
31 #include "../crypto/internal.h"
32 #include "internal.h"
33 #include "transport_common.h"
34 
35 
36 static const struct argument kArguments[] = {
37     {
38         "-connect", kRequiredArgument,
39         "The hostname and port of the server to connect to, e.g. foo.com:443",
40     },
41     {
42         "-cipher", kOptionalArgument,
43         "An OpenSSL-style cipher suite string that configures the offered "
44         "ciphers",
45     },
46     {
47         "-curves", kOptionalArgument,
48         "An OpenSSL-style ECDH curves list that configures the offered curves",
49     },
50     {
51         "-sigalgs", kOptionalArgument,
52         "An OpenSSL-style signature algorithms list that configures the "
53         "signature algorithm preferences",
54     },
55     {
56         "-max-version", kOptionalArgument,
57         "The maximum acceptable protocol version",
58     },
59     {
60         "-min-version", kOptionalArgument,
61         "The minimum acceptable protocol version",
62     },
63     {
64         "-server-name", kOptionalArgument, "The server name to advertise",
65     },
66     {
67         "-ech-grease", kBooleanArgument, "Enable ECH GREASE",
68     },
69     {
70         "-ech-config-list", kOptionalArgument,
71         "Path to file containing serialized ECHConfigs",
72     },
73     {
74         "-select-next-proto", kOptionalArgument,
75         "An NPN protocol to select if the server supports NPN",
76     },
77     {
78         "-alpn-protos", kOptionalArgument,
79         "A comma-separated list of ALPN protocols to advertise",
80     },
81     {
82         "-fallback-scsv", kBooleanArgument, "Enable FALLBACK_SCSV",
83     },
84     {
85         "-ocsp-stapling", kBooleanArgument,
86         "Advertise support for OCSP stabling",
87     },
88     {
89         "-signed-certificate-timestamps", kBooleanArgument,
90         "Advertise support for signed certificate timestamps",
91     },
92     {
93         "-channel-id-key", kOptionalArgument,
94         "The key to use for signing a channel ID",
95     },
96     {
97         "-false-start", kBooleanArgument, "Enable False Start",
98     },
99     {
100         "-session-in", kOptionalArgument,
101         "A file containing a session to resume.",
102     },
103     {
104         "-session-out", kOptionalArgument,
105         "A file to write the negotiated session to.",
106     },
107     {
108         "-key", kOptionalArgument,
109         "PEM-encoded file containing the private key.",
110     },
111     {
112         "-cert", kOptionalArgument,
113         "PEM-encoded file containing the leaf certificate and optional "
114         "certificate chain. This is taken from the -key argument if this "
115         "argument is not provided.",
116     },
117     {
118         "-starttls", kOptionalArgument,
119         "A STARTTLS mini-protocol to run before the TLS handshake. Supported"
120         " values: 'smtp'",
121     },
122     {
123         "-grease", kBooleanArgument, "Enable GREASE",
124     },
125     {
126         "-permute-extensions",
127         kBooleanArgument,
128         "Permute extensions in handshake messages",
129     },
130     {
131         "-test-resumption", kBooleanArgument,
132         "Connect to the server twice. The first connection is closed once a "
133         "session is established. The second connection offers it.",
134     },
135     {
136         "-root-certs", kOptionalArgument,
137         "A filename containing one or more PEM root certificates. Implies that "
138         "verification is required.",
139     },
140     {
141         "-root-cert-dir", kOptionalArgument,
142         "A directory containing one or more root certificate PEM files in "
143         "OpenSSL's hashed-directory format. Implies that verification is "
144         "required.",
145     },
146     {
147         "-early-data", kOptionalArgument, "Enable early data. The argument to "
148         "this flag is the early data to send or if it starts with '@', the "
149         "file to read from for early data.",
150     },
151     {
152         "-http-tunnel", kOptionalArgument,
153         "An HTTP proxy server to tunnel the TCP connection through",
154     },
155     {
156         "-renegotiate-freely", kBooleanArgument,
157         "Allow renegotiations from the peer.",
158     },
159     {
160         "-debug", kBooleanArgument,
161         "Print debug information about the handshake",
162     },
163     {
164         "", kOptionalArgument, "",
165     },
166 };
167 
LoadPrivateKey(const std::string & file)168 static bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file) {
169   bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
170   if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
171     return nullptr;
172   }
173   bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr,
174                                  nullptr, nullptr));
175   return pkey;
176 }
177 
NextProtoSelectCallback(SSL * ssl,uint8_t ** out,uint8_t * outlen,const uint8_t * in,unsigned inlen,void * arg)178 static int NextProtoSelectCallback(SSL* ssl, uint8_t** out, uint8_t* outlen,
179                                    const uint8_t* in, unsigned inlen, void* arg) {
180   *out = reinterpret_cast<uint8_t *>(arg);
181   *outlen = strlen(reinterpret_cast<const char *>(arg));
182   return SSL_TLSEXT_ERR_OK;
183 }
184 
185 static FILE *g_keylog_file = nullptr;
186 
KeyLogCallback(const SSL * ssl,const char * line)187 static void KeyLogCallback(const SSL *ssl, const char *line) {
188   fprintf(g_keylog_file, "%s\n", line);
189   fflush(g_keylog_file);
190 }
191 
192 static bssl::UniquePtr<BIO> session_out;
193 static bssl::UniquePtr<SSL_SESSION> resume_session;
194 
NewSessionCallback(SSL * ssl,SSL_SESSION * session)195 static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) {
196   if (session_out) {
197     if (!PEM_write_bio_SSL_SESSION(session_out.get(), session) ||
198         BIO_flush(session_out.get()) <= 0) {
199       fprintf(stderr, "Error while saving session:\n");
200       ERR_print_errors_fp(stderr);
201       return 0;
202     }
203   }
204   resume_session = bssl::UniquePtr<SSL_SESSION>(session);
205   return 1;
206 }
207 
WaitForSession(SSL * ssl,int sock)208 static bool WaitForSession(SSL *ssl, int sock) {
209   fd_set read_fds;
210   FD_ZERO(&read_fds);
211 
212   if (!SocketSetNonBlocking(sock, true)) {
213     return false;
214   }
215 
216   while (!resume_session) {
217 #if defined(OPENSSL_WINDOWS)
218     // Windows sockets are really of type SOCKET, not int, but everything here
219     // casts them to ints. Clang gets unhappy about signed values as a result.
220     //
221     // TODO(davidben): Keep everything as the appropriate platform type.
222     FD_SET(static_cast<SOCKET>(sock), &read_fds);
223 #else
224     FD_SET(sock, &read_fds);
225 #endif
226     int ret = select(sock + 1, &read_fds, NULL, NULL, NULL);
227     if (ret <= 0) {
228       perror("select");
229       return false;
230     }
231 
232     uint8_t buffer[512];
233     int ssl_ret = SSL_read(ssl, buffer, sizeof(buffer));
234 
235     if (ssl_ret <= 0) {
236       int ssl_err = SSL_get_error(ssl, ssl_ret);
237       if (ssl_err == SSL_ERROR_WANT_READ) {
238         continue;
239       }
240       PrintSSLError(stderr, "Error while reading", ssl_err, ssl_ret);
241       return false;
242     }
243   }
244 
245   return true;
246 }
247 
DoConnection(SSL_CTX * ctx,std::map<std::string,std::string> args_map,bool (* cb)(SSL * ssl,int sock))248 static bool DoConnection(SSL_CTX *ctx,
249                          std::map<std::string, std::string> args_map,
250                          bool (*cb)(SSL *ssl, int sock)) {
251   int sock = -1;
252   if (args_map.count("-http-tunnel") != 0) {
253     if (!Connect(&sock, args_map["-http-tunnel"]) ||
254         !DoHTTPTunnel(sock, args_map["-connect"])) {
255       return false;
256     }
257   } else if (!Connect(&sock, args_map["-connect"])) {
258     return false;
259   }
260 
261   if (args_map.count("-starttls") != 0) {
262     const std::string& starttls = args_map["-starttls"];
263     if (starttls == "smtp") {
264       if (!DoSMTPStartTLS(sock)) {
265         return false;
266       }
267     } else {
268       fprintf(stderr, "Unknown value for -starttls: %s\n", starttls.c_str());
269       return false;
270     }
271   }
272 
273   bssl::UniquePtr<BIO> bio(BIO_new_socket(sock, BIO_CLOSE));
274   bssl::UniquePtr<SSL> ssl(SSL_new(ctx));
275 
276   if (args_map.count("-server-name") != 0) {
277     SSL_set_tlsext_host_name(ssl.get(), args_map["-server-name"].c_str());
278   }
279 
280   if (args_map.count("-ech-grease") != 0) {
281     SSL_set_enable_ech_grease(ssl.get(), 1);
282   }
283 
284   if (args_map.count("-ech-config-list") != 0) {
285     const char *filename = args_map["-ech-config-list"].c_str();
286     ScopedFILE f(fopen(filename, "rb"));
287     std::vector<uint8_t> data;
288     if (f == nullptr || !ReadAll(&data, f.get())) {
289       fprintf(stderr, "Error reading %s.\n", filename);
290       return false;
291     }
292     if (!SSL_set1_ech_config_list(ssl.get(), data.data(), data.size())) {
293       fprintf(stderr, "Error setting ECHConfigList\n");
294       return false;
295     }
296   }
297 
298   if (args_map.count("-session-in") != 0) {
299     bssl::UniquePtr<BIO> in(BIO_new_file(args_map["-session-in"].c_str(),
300                                          "rb"));
301     if (!in) {
302       fprintf(stderr, "Error reading session\n");
303       ERR_print_errors_fp(stderr);
304       return false;
305     }
306     bssl::UniquePtr<SSL_SESSION> session(PEM_read_bio_SSL_SESSION(in.get(),
307                                          nullptr, nullptr, nullptr));
308     if (!session) {
309       fprintf(stderr, "Error reading session\n");
310       ERR_print_errors_fp(stderr);
311       return false;
312     }
313     SSL_set_session(ssl.get(), session.get());
314   }
315 
316   if (args_map.count("-renegotiate-freely") != 0) {
317     SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely);
318   }
319 
320   if (resume_session) {
321     SSL_set_session(ssl.get(), resume_session.get());
322   }
323 
324   SSL_set_bio(ssl.get(), bio.get(), bio.get());
325   bio.release();
326 
327   int ret = SSL_connect(ssl.get());
328   if (ret != 1) {
329     int ssl_err = SSL_get_error(ssl.get(), ret);
330     PrintSSLError(stderr, "Error while connecting", ssl_err, ret);
331     return false;
332   }
333 
334   if (args_map.count("-early-data") != 0 && SSL_in_early_data(ssl.get())) {
335     std::string early_data = args_map["-early-data"];
336     if (early_data.size() > 0 && early_data[0] == '@') {
337       const char *filename = early_data.c_str() + 1;
338       std::vector<uint8_t> data;
339       ScopedFILE f(fopen(filename, "rb"));
340       if (f == nullptr || !ReadAll(&data, f.get())) {
341         fprintf(stderr, "Error reading %s.\n", filename);
342         return false;
343       }
344       early_data = std::string(data.begin(), data.end());
345     }
346     if (!early_data.empty()) {
347       int ed_size = early_data.size();
348       int ssl_ret = SSL_write(ssl.get(), early_data.data(), ed_size);
349       if (ssl_ret <= 0) {
350         int ssl_err = SSL_get_error(ssl.get(), ssl_ret);
351         PrintSSLError(stderr, "Error while writing", ssl_err, ssl_ret);
352         return false;
353       } else if (ssl_ret != ed_size) {
354         fprintf(stderr, "Short write from SSL_write.\n");
355         return false;
356       }
357     }
358   }
359 
360   fprintf(stderr, "Connected.\n");
361   bssl::UniquePtr<BIO> bio_stderr(BIO_new_fp(stderr, BIO_NOCLOSE));
362   PrintConnectionInfo(bio_stderr.get(), ssl.get());
363 
364   return cb(ssl.get(), sock);
365 }
366 
InfoCallback(const SSL * ssl,int type,int value)367 static void InfoCallback(const SSL *ssl, int type, int value) {
368   switch (type) {
369     case SSL_CB_HANDSHAKE_START:
370       fprintf(stderr, "Handshake started.\n");
371       break;
372     case SSL_CB_HANDSHAKE_DONE:
373       fprintf(stderr, "Handshake done.\n");
374       break;
375     case SSL_CB_CONNECT_LOOP:
376       fprintf(stderr, "Handshake progress: %s\n", SSL_state_string_long(ssl));
377       break;
378   }
379 }
380 
Client(const std::vector<std::string> & args)381 bool Client(const std::vector<std::string> &args) {
382   if (!InitSocketLibrary()) {
383     return false;
384   }
385 
386   std::map<std::string, std::string> args_map;
387 
388   if (!ParseKeyValueArguments(&args_map, args, kArguments)) {
389     PrintUsage(kArguments);
390     return false;
391   }
392 
393   bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
394 
395   const char *keylog_file = getenv("SSLKEYLOGFILE");
396   if (keylog_file) {
397     g_keylog_file = fopen(keylog_file, "a");
398     if (g_keylog_file == nullptr) {
399       perror("fopen");
400       return false;
401     }
402     SSL_CTX_set_keylog_callback(ctx.get(), KeyLogCallback);
403   }
404 
405   if (args_map.count("-cipher") != 0 &&
406       !SSL_CTX_set_strict_cipher_list(ctx.get(), args_map["-cipher"].c_str())) {
407     fprintf(stderr, "Failed setting cipher list\n");
408     return false;
409   }
410 
411   if (args_map.count("-curves") != 0 &&
412       !SSL_CTX_set1_curves_list(ctx.get(), args_map["-curves"].c_str())) {
413     fprintf(stderr, "Failed setting curves list\n");
414     return false;
415   }
416 
417   if (args_map.count("-sigalgs") != 0 &&
418       !SSL_CTX_set1_sigalgs_list(ctx.get(), args_map["-sigalgs"].c_str())) {
419     fprintf(stderr, "Failed setting signature algorithms list\n");
420     return false;
421   }
422 
423   uint16_t max_version = TLS1_3_VERSION;
424   if (args_map.count("-max-version") != 0 &&
425       !VersionFromString(&max_version, args_map["-max-version"])) {
426     fprintf(stderr, "Unknown protocol version: '%s'\n",
427             args_map["-max-version"].c_str());
428     return false;
429   }
430 
431   if (!SSL_CTX_set_max_proto_version(ctx.get(), max_version)) {
432     return false;
433   }
434 
435   if (args_map.count("-min-version") != 0) {
436     uint16_t version;
437     if (!VersionFromString(&version, args_map["-min-version"])) {
438       fprintf(stderr, "Unknown protocol version: '%s'\n",
439               args_map["-min-version"].c_str());
440       return false;
441     }
442     if (!SSL_CTX_set_min_proto_version(ctx.get(), version)) {
443       return false;
444     }
445   }
446 
447   if (args_map.count("-select-next-proto") != 0) {
448     const std::string &proto = args_map["-select-next-proto"];
449     if (proto.size() > 255) {
450       fprintf(stderr, "Bad NPN protocol: '%s'\n", proto.c_str());
451       return false;
452     }
453     // |SSL_CTX_set_next_proto_select_cb| is not const-correct.
454     SSL_CTX_set_next_proto_select_cb(ctx.get(), NextProtoSelectCallback,
455                                      const_cast<char *>(proto.c_str()));
456   }
457 
458   if (args_map.count("-alpn-protos") != 0) {
459     const std::string &alpn_protos = args_map["-alpn-protos"];
460     std::vector<uint8_t> wire;
461     size_t i = 0;
462     while (i <= alpn_protos.size()) {
463       size_t j = alpn_protos.find(',', i);
464       if (j == std::string::npos) {
465         j = alpn_protos.size();
466       }
467       size_t len = j - i;
468       if (len > 255) {
469         fprintf(stderr, "Invalid ALPN protocols: '%s'\n", alpn_protos.c_str());
470         return false;
471       }
472       wire.push_back(static_cast<uint8_t>(len));
473       wire.resize(wire.size() + len);
474       OPENSSL_memcpy(wire.data() + wire.size() - len, alpn_protos.data() + i,
475                      len);
476       i = j + 1;
477     }
478     if (SSL_CTX_set_alpn_protos(ctx.get(), wire.data(), wire.size()) != 0) {
479       return false;
480     }
481   }
482 
483   if (args_map.count("-fallback-scsv") != 0) {
484     SSL_CTX_set_mode(ctx.get(), SSL_MODE_SEND_FALLBACK_SCSV);
485   }
486 
487   if (args_map.count("-ocsp-stapling") != 0) {
488     SSL_CTX_enable_ocsp_stapling(ctx.get());
489   }
490 
491   if (args_map.count("-signed-certificate-timestamps") != 0) {
492     SSL_CTX_enable_signed_cert_timestamps(ctx.get());
493   }
494 
495   if (args_map.count("-channel-id-key") != 0) {
496     bssl::UniquePtr<EVP_PKEY> pkey =
497         LoadPrivateKey(args_map["-channel-id-key"]);
498     if (!pkey || !SSL_CTX_set1_tls_channel_id(ctx.get(), pkey.get())) {
499       return false;
500     }
501   }
502 
503   if (args_map.count("-false-start") != 0) {
504     SSL_CTX_set_mode(ctx.get(), SSL_MODE_ENABLE_FALSE_START);
505   }
506 
507   if (args_map.count("-key") != 0) {
508     const std::string &key = args_map["-key"];
509     if (!SSL_CTX_use_PrivateKey_file(ctx.get(), key.c_str(),
510                                      SSL_FILETYPE_PEM)) {
511       fprintf(stderr, "Failed to load private key: %s\n", key.c_str());
512       return false;
513     }
514     const std::string &cert =
515         args_map.count("-cert") != 0 ? args_map["-cert"] : key;
516     if (!SSL_CTX_use_certificate_chain_file(ctx.get(), cert.c_str())) {
517       fprintf(stderr, "Failed to load cert chain: %s\n", cert.c_str());
518       return false;
519     }
520   }
521 
522   SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_CLIENT);
523   SSL_CTX_sess_set_new_cb(ctx.get(), NewSessionCallback);
524 
525   if (args_map.count("-session-out") != 0) {
526     session_out.reset(BIO_new_file(args_map["-session-out"].c_str(), "wb"));
527     if (!session_out) {
528       fprintf(stderr, "Error while opening %s:\n",
529               args_map["-session-out"].c_str());
530       ERR_print_errors_fp(stderr);
531       return false;
532     }
533   }
534 
535   if (args_map.count("-grease") != 0) {
536     SSL_CTX_set_grease_enabled(ctx.get(), 1);
537   }
538 
539   if (args_map.count("-permute-extensions") != 0) {
540     SSL_CTX_set_permute_extensions(ctx.get(), 1);
541   }
542 
543   if (args_map.count("-root-certs") != 0) {
544     if (!SSL_CTX_load_verify_locations(
545             ctx.get(), args_map["-root-certs"].c_str(), nullptr)) {
546       fprintf(stderr, "Failed to load root certificates.\n");
547       ERR_print_errors_fp(stderr);
548       return false;
549     }
550     SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr);
551   }
552 
553   if (args_map.count("-root-cert-dir") != 0) {
554     if (!SSL_CTX_load_verify_locations(
555             ctx.get(), nullptr, args_map["-root-cert-dir"].c_str())) {
556       fprintf(stderr, "Failed to load root certificates.\n");
557       ERR_print_errors_fp(stderr);
558       return false;
559     }
560     SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr);
561   }
562 
563   if (args_map.count("-early-data") != 0) {
564     SSL_CTX_set_early_data_enabled(ctx.get(), 1);
565   }
566 
567   if (args_map.count("-debug") != 0) {
568     SSL_CTX_set_info_callback(ctx.get(), InfoCallback);
569   }
570 
571   if (args_map.count("-test-resumption") != 0) {
572     if (args_map.count("-session-in") != 0) {
573       fprintf(stderr,
574               "Flags -session-in and -test-resumption are incompatible.\n");
575       return false;
576     }
577 
578     if (!DoConnection(ctx.get(), args_map, &WaitForSession)) {
579       return false;
580     }
581   }
582 
583   return DoConnection(ctx.get(), args_map, &TransferData);
584 }
585