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