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 22OPENSSL_MSVC_PRAGMA(warning(push, 3)) 23#include <winsock2.h> 24OPENSSL_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 36static 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 "-max-version", kOptionalArgument, 52 "The maximum acceptable protocol version", 53 }, 54 { 55 "-min-version", kOptionalArgument, 56 "The minimum acceptable protocol version", 57 }, 58 { 59 "-server-name", kOptionalArgument, "The server name to advertise", 60 }, 61 { 62 "-select-next-proto", kOptionalArgument, 63 "An NPN protocol to select if the server supports NPN", 64 }, 65 { 66 "-alpn-protos", kOptionalArgument, 67 "A comma-separated list of ALPN protocols to advertise", 68 }, 69 { 70 "-fallback-scsv", kBooleanArgument, "Enable FALLBACK_SCSV", 71 }, 72 { 73 "-ocsp-stapling", kBooleanArgument, 74 "Advertise support for OCSP stabling", 75 }, 76 { 77 "-signed-certificate-timestamps", kBooleanArgument, 78 "Advertise support for signed certificate timestamps", 79 }, 80 { 81 "-channel-id-key", kOptionalArgument, 82 "The key to use for signing a channel ID", 83 }, 84 { 85 "-false-start", kBooleanArgument, "Enable False Start", 86 }, 87 { 88 "-session-in", kOptionalArgument, 89 "A file containing a session to resume.", 90 }, 91 { 92 "-session-out", kOptionalArgument, 93 "A file to write the negotiated session to.", 94 }, 95 { 96 "-key", kOptionalArgument, 97 "PEM-encoded file containing the private key.", 98 }, 99 { 100 "-cert", kOptionalArgument, 101 "PEM-encoded file containing the leaf certificate and optional " 102 "certificate chain. This is taken from the -key argument if this " 103 "argument is not provided.", 104 }, 105 { 106 "-starttls", kOptionalArgument, 107 "A STARTTLS mini-protocol to run before the TLS handshake. Supported" 108 " values: 'smtp'", 109 }, 110 { 111 "-grease", kBooleanArgument, "Enable GREASE", 112 }, 113 { 114 "-test-resumption", kBooleanArgument, 115 "Connect to the server twice. The first connection is closed once a " 116 "session is established. The second connection offers it.", 117 }, 118 { 119 "-root-certs", kOptionalArgument, 120 "A filename containing one of more PEM root certificates. Implies that " 121 "verification is required.", 122 }, 123 { 124 "-early-data", kOptionalArgument, "Enable early data. The argument to " 125 "this flag is the early data to send or if it starts with '@', the " 126 "file to read from for early data.", 127 }, 128 { 129 "-tls13-variant", kOptionalArgument, 130 "Enable the specified experimental TLS 1.3 variant", 131 }, 132 { 133 "-ed25519", kBooleanArgument, "Advertise Ed25519 support", 134 }, 135 { 136 "-http-tunnel", kOptionalArgument, 137 "An HTTP proxy server to tunnel the TCP connection through", 138 }, 139 { 140 "-renegotiate-freely", kBooleanArgument, 141 "Allow renegotiations from the peer.", 142 }, 143 { 144 "-debug", kBooleanArgument, 145 "Print debug information about the handshake", 146 }, 147 { 148 "", kOptionalArgument, "", 149 }, 150}; 151 152static bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file) { 153 bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file())); 154 if (!bio || !BIO_read_filename(bio.get(), file.c_str())) { 155 return nullptr; 156 } 157 bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, 158 nullptr, nullptr)); 159 return pkey; 160} 161 162static int NextProtoSelectCallback(SSL* ssl, uint8_t** out, uint8_t* outlen, 163 const uint8_t* in, unsigned inlen, void* arg) { 164 *out = reinterpret_cast<uint8_t *>(arg); 165 *outlen = strlen(reinterpret_cast<const char *>(arg)); 166 return SSL_TLSEXT_ERR_OK; 167} 168 169static FILE *g_keylog_file = nullptr; 170 171static void KeyLogCallback(const SSL *ssl, const char *line) { 172 fprintf(g_keylog_file, "%s\n", line); 173 fflush(g_keylog_file); 174} 175 176static bssl::UniquePtr<BIO> session_out; 177static bssl::UniquePtr<SSL_SESSION> resume_session; 178 179static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) { 180 if (session_out) { 181 if (!PEM_write_bio_SSL_SESSION(session_out.get(), session) || 182 BIO_flush(session_out.get()) <= 0) { 183 fprintf(stderr, "Error while saving session:\n"); 184 ERR_print_errors_cb(PrintErrorCallback, stderr); 185 return 0; 186 } 187 } 188 resume_session = bssl::UniquePtr<SSL_SESSION>(session); 189 return 1; 190} 191 192static bool WaitForSession(SSL *ssl, int sock) { 193 fd_set read_fds; 194 FD_ZERO(&read_fds); 195 196 if (!SocketSetNonBlocking(sock, true)) { 197 return false; 198 } 199 200 while (!resume_session) { 201#if defined(OPENSSL_WINDOWS) 202 // Windows sockets are really of type SOCKET, not int, but everything here 203 // casts them to ints. Clang gets unhappy about signed values as a result. 204 // 205 // TODO(davidben): Keep everything as the appropriate platform type. 206 FD_SET(static_cast<SOCKET>(sock), &read_fds); 207#else 208 FD_SET(sock, &read_fds); 209#endif 210 int ret = select(sock + 1, &read_fds, NULL, NULL, NULL); 211 if (ret <= 0) { 212 perror("select"); 213 return false; 214 } 215 216 uint8_t buffer[512]; 217 int ssl_ret = SSL_read(ssl, buffer, sizeof(buffer)); 218 219 if (ssl_ret <= 0) { 220 int ssl_err = SSL_get_error(ssl, ssl_ret); 221 if (ssl_err == SSL_ERROR_WANT_READ) { 222 continue; 223 } 224 fprintf(stderr, "Error while reading: %d\n", ssl_err); 225 ERR_print_errors_cb(PrintErrorCallback, stderr); 226 return false; 227 } 228 } 229 230 return true; 231} 232 233static bool DoConnection(SSL_CTX *ctx, 234 std::map<std::string, std::string> args_map, 235 bool (*cb)(SSL *ssl, int sock)) { 236 int sock = -1; 237 if (args_map.count("-http-tunnel") != 0) { 238 if (!Connect(&sock, args_map["-http-tunnel"]) || 239 !DoHTTPTunnel(sock, args_map["-connect"])) { 240 return false; 241 } 242 } else if (!Connect(&sock, args_map["-connect"])) { 243 return false; 244 } 245 246 if (args_map.count("-starttls") != 0) { 247 const std::string& starttls = args_map["-starttls"]; 248 if (starttls == "smtp") { 249 if (!DoSMTPStartTLS(sock)) { 250 return false; 251 } 252 } else { 253 fprintf(stderr, "Unknown value for -starttls: %s\n", starttls.c_str()); 254 return false; 255 } 256 } 257 258 bssl::UniquePtr<BIO> bio(BIO_new_socket(sock, BIO_CLOSE)); 259 bssl::UniquePtr<SSL> ssl(SSL_new(ctx)); 260 261 if (args_map.count("-server-name") != 0) { 262 SSL_set_tlsext_host_name(ssl.get(), args_map["-server-name"].c_str()); 263 } 264 265 if (args_map.count("-session-in") != 0) { 266 bssl::UniquePtr<BIO> in(BIO_new_file(args_map["-session-in"].c_str(), 267 "rb")); 268 if (!in) { 269 fprintf(stderr, "Error reading session\n"); 270 ERR_print_errors_cb(PrintErrorCallback, stderr); 271 return false; 272 } 273 bssl::UniquePtr<SSL_SESSION> session(PEM_read_bio_SSL_SESSION(in.get(), 274 nullptr, nullptr, nullptr)); 275 if (!session) { 276 fprintf(stderr, "Error reading session\n"); 277 ERR_print_errors_cb(PrintErrorCallback, stderr); 278 return false; 279 } 280 SSL_set_session(ssl.get(), session.get()); 281 } 282 283 if (args_map.count("-renegotiate-freely") != 0) { 284 SSL_set_renegotiate_mode(ssl.get(), ssl_renegotiate_freely); 285 } 286 287 if (resume_session) { 288 SSL_set_session(ssl.get(), resume_session.get()); 289 } 290 291 SSL_set_bio(ssl.get(), bio.get(), bio.get()); 292 bio.release(); 293 294 int ret = SSL_connect(ssl.get()); 295 if (ret != 1) { 296 int ssl_err = SSL_get_error(ssl.get(), ret); 297 fprintf(stderr, "Error while connecting: %d\n", ssl_err); 298 ERR_print_errors_cb(PrintErrorCallback, stderr); 299 return false; 300 } 301 302 if (args_map.count("-early-data") != 0 && SSL_in_early_data(ssl.get())) { 303 std::string early_data = args_map["-early-data"]; 304 if (early_data.size() > 0 && early_data[0] == '@') { 305 const char *filename = early_data.c_str() + 1; 306 std::vector<uint8_t> data; 307 ScopedFILE f(fopen(filename, "rb")); 308 if (f == nullptr || !ReadAll(&data, f.get())) { 309 fprintf(stderr, "Error reading %s.\n", filename); 310 return false; 311 } 312 early_data = std::string(data.begin(), data.end()); 313 } 314 int ed_size = early_data.size(); 315 int ssl_ret = SSL_write(ssl.get(), early_data.data(), ed_size); 316 if (ssl_ret <= 0) { 317 int ssl_err = SSL_get_error(ssl.get(), ssl_ret); 318 fprintf(stderr, "Error while writing: %d\n", ssl_err); 319 ERR_print_errors_cb(PrintErrorCallback, stderr); 320 return false; 321 } else if (ssl_ret != ed_size) { 322 fprintf(stderr, "Short write from SSL_write.\n"); 323 return false; 324 } 325 } 326 327 fprintf(stderr, "Connected.\n"); 328 bssl::UniquePtr<BIO> bio_stderr(BIO_new_fp(stderr, BIO_NOCLOSE)); 329 PrintConnectionInfo(bio_stderr.get(), ssl.get()); 330 331 return cb(ssl.get(), sock); 332} 333 334static bool GetTLS13Variant(tls13_variant_t *out, const std::string &in) { 335 if (in == "draft23") { 336 *out = tls13_default; 337 return true; 338 } 339 return false; 340} 341 342static void InfoCallback(const SSL *ssl, int type, int value) { 343 switch (type) { 344 case SSL_CB_HANDSHAKE_START: 345 fprintf(stderr, "Handshake started.\n"); 346 break; 347 case SSL_CB_HANDSHAKE_DONE: 348 fprintf(stderr, "Handshake done.\n"); 349 break; 350 case SSL_CB_CONNECT_LOOP: 351 fprintf(stderr, "Handshake progress: %s\n", SSL_state_string_long(ssl)); 352 break; 353 } 354} 355 356bool Client(const std::vector<std::string> &args) { 357 if (!InitSocketLibrary()) { 358 return false; 359 } 360 361 std::map<std::string, std::string> args_map; 362 363 if (!ParseKeyValueArguments(&args_map, args, kArguments)) { 364 PrintUsage(kArguments); 365 return false; 366 } 367 368 bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method())); 369 370 const char *keylog_file = getenv("SSLKEYLOGFILE"); 371 if (keylog_file) { 372 g_keylog_file = fopen(keylog_file, "a"); 373 if (g_keylog_file == nullptr) { 374 perror("fopen"); 375 return false; 376 } 377 SSL_CTX_set_keylog_callback(ctx.get(), KeyLogCallback); 378 } 379 380 if (args_map.count("-cipher") != 0 && 381 !SSL_CTX_set_strict_cipher_list(ctx.get(), args_map["-cipher"].c_str())) { 382 fprintf(stderr, "Failed setting cipher list\n"); 383 return false; 384 } 385 386 if (args_map.count("-curves") != 0 && 387 !SSL_CTX_set1_curves_list(ctx.get(), args_map["-curves"].c_str())) { 388 fprintf(stderr, "Failed setting curves list\n"); 389 return false; 390 } 391 392 uint16_t max_version = TLS1_3_VERSION; 393 if (args_map.count("-max-version") != 0 && 394 !VersionFromString(&max_version, args_map["-max-version"])) { 395 fprintf(stderr, "Unknown protocol version: '%s'\n", 396 args_map["-max-version"].c_str()); 397 return false; 398 } 399 400 if (!SSL_CTX_set_max_proto_version(ctx.get(), max_version)) { 401 return false; 402 } 403 404 if (args_map.count("-min-version") != 0) { 405 uint16_t version; 406 if (!VersionFromString(&version, args_map["-min-version"])) { 407 fprintf(stderr, "Unknown protocol version: '%s'\n", 408 args_map["-min-version"].c_str()); 409 return false; 410 } 411 if (!SSL_CTX_set_min_proto_version(ctx.get(), version)) { 412 return false; 413 } 414 } 415 416 if (args_map.count("-select-next-proto") != 0) { 417 const std::string &proto = args_map["-select-next-proto"]; 418 if (proto.size() > 255) { 419 fprintf(stderr, "Bad NPN protocol: '%s'\n", proto.c_str()); 420 return false; 421 } 422 // |SSL_CTX_set_next_proto_select_cb| is not const-correct. 423 SSL_CTX_set_next_proto_select_cb(ctx.get(), NextProtoSelectCallback, 424 const_cast<char *>(proto.c_str())); 425 } 426 427 if (args_map.count("-alpn-protos") != 0) { 428 const std::string &alpn_protos = args_map["-alpn-protos"]; 429 std::vector<uint8_t> wire; 430 size_t i = 0; 431 while (i <= alpn_protos.size()) { 432 size_t j = alpn_protos.find(',', i); 433 if (j == std::string::npos) { 434 j = alpn_protos.size(); 435 } 436 size_t len = j - i; 437 if (len > 255) { 438 fprintf(stderr, "Invalid ALPN protocols: '%s'\n", alpn_protos.c_str()); 439 return false; 440 } 441 wire.push_back(static_cast<uint8_t>(len)); 442 wire.resize(wire.size() + len); 443 OPENSSL_memcpy(wire.data() + wire.size() - len, alpn_protos.data() + i, 444 len); 445 i = j + 1; 446 } 447 if (SSL_CTX_set_alpn_protos(ctx.get(), wire.data(), wire.size()) != 0) { 448 return false; 449 } 450 } 451 452 if (args_map.count("-fallback-scsv") != 0) { 453 SSL_CTX_set_mode(ctx.get(), SSL_MODE_SEND_FALLBACK_SCSV); 454 } 455 456 if (args_map.count("-ocsp-stapling") != 0) { 457 SSL_CTX_enable_ocsp_stapling(ctx.get()); 458 } 459 460 if (args_map.count("-signed-certificate-timestamps") != 0) { 461 SSL_CTX_enable_signed_cert_timestamps(ctx.get()); 462 } 463 464 if (args_map.count("-channel-id-key") != 0) { 465 bssl::UniquePtr<EVP_PKEY> pkey = 466 LoadPrivateKey(args_map["-channel-id-key"]); 467 if (!pkey || !SSL_CTX_set1_tls_channel_id(ctx.get(), pkey.get())) { 468 return false; 469 } 470 } 471 472 if (args_map.count("-false-start") != 0) { 473 SSL_CTX_set_mode(ctx.get(), SSL_MODE_ENABLE_FALSE_START); 474 } 475 476 if (args_map.count("-key") != 0) { 477 const std::string &key = args_map["-key"]; 478 if (!SSL_CTX_use_PrivateKey_file(ctx.get(), key.c_str(), 479 SSL_FILETYPE_PEM)) { 480 fprintf(stderr, "Failed to load private key: %s\n", key.c_str()); 481 return false; 482 } 483 const std::string &cert = 484 args_map.count("-cert") != 0 ? args_map["-cert"] : key; 485 if (!SSL_CTX_use_certificate_chain_file(ctx.get(), cert.c_str())) { 486 fprintf(stderr, "Failed to load cert chain: %s\n", cert.c_str()); 487 return false; 488 } 489 } 490 491 SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_CLIENT); 492 SSL_CTX_sess_set_new_cb(ctx.get(), NewSessionCallback); 493 494 if (args_map.count("-session-out") != 0) { 495 session_out.reset(BIO_new_file(args_map["-session-out"].c_str(), "wb")); 496 if (!session_out) { 497 fprintf(stderr, "Error while opening %s:\n", 498 args_map["-session-out"].c_str()); 499 ERR_print_errors_cb(PrintErrorCallback, stderr); 500 return false; 501 } 502 } 503 504 if (args_map.count("-grease") != 0) { 505 SSL_CTX_set_grease_enabled(ctx.get(), 1); 506 } 507 508 if (args_map.count("-root-certs") != 0) { 509 if (!SSL_CTX_load_verify_locations( 510 ctx.get(), args_map["-root-certs"].c_str(), nullptr)) { 511 fprintf(stderr, "Failed to load root certificates.\n"); 512 ERR_print_errors_cb(PrintErrorCallback, stderr); 513 return false; 514 } 515 SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr); 516 } 517 518 if (args_map.count("-early-data") != 0) { 519 SSL_CTX_set_early_data_enabled(ctx.get(), 1); 520 } 521 522 if (args_map.count("-tls13-variant") != 0) { 523 tls13_variant_t variant; 524 if (!GetTLS13Variant(&variant, args_map["-tls13-variant"])) { 525 fprintf(stderr, "Unknown TLS 1.3 variant: %s\n", 526 args_map["-tls13-variant"].c_str()); 527 return false; 528 } 529 SSL_CTX_set_tls13_variant(ctx.get(), variant); 530 } 531 532 if (args_map.count("-ed25519") != 0) { 533 SSL_CTX_set_ed25519_enabled(ctx.get(), 1); 534 } 535 536 if (args_map.count("-debug") != 0) { 537 SSL_CTX_set_info_callback(ctx.get(), InfoCallback); 538 } 539 540 if (args_map.count("-test-resumption") != 0) { 541 if (args_map.count("-session-in") != 0) { 542 fprintf(stderr, 543 "Flags -session-in and -test-resumption are incompatible.\n"); 544 return false; 545 } 546 547 if (!DoConnection(ctx.get(), args_map, &WaitForSession)) { 548 return false; 549 } 550 } 551 552 return DoConnection(ctx.get(), args_map, &TransferData); 553} 554