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 "-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, "Allow early data", 125 }, 126 { 127 "-tls13-variant", kOptionalArgument, 128 "Enable the specified experimental TLS 1.3 variant", 129 }, 130 { 131 "-ed25519", kBooleanArgument, "Advertise Ed25519 support", 132 }, 133 { 134 "-http-tunnel", kOptionalArgument, 135 "An HTTP proxy server to tunnel the TCP connection through", 136 }, 137 { 138 "", kOptionalArgument, "", 139 }, 140 }; 141 142 static bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file) { 143 bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file())); 144 if (!bio || !BIO_read_filename(bio.get(), file.c_str())) { 145 return nullptr; 146 } 147 bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, 148 nullptr, nullptr)); 149 return pkey; 150 } 151 152 static int NextProtoSelectCallback(SSL* ssl, uint8_t** out, uint8_t* outlen, 153 const uint8_t* in, unsigned inlen, void* arg) { 154 *out = reinterpret_cast<uint8_t *>(arg); 155 *outlen = strlen(reinterpret_cast<const char *>(arg)); 156 return SSL_TLSEXT_ERR_OK; 157 } 158 159 static FILE *g_keylog_file = nullptr; 160 161 static void KeyLogCallback(const SSL *ssl, const char *line) { 162 fprintf(g_keylog_file, "%s\n", line); 163 fflush(g_keylog_file); 164 } 165 166 static bssl::UniquePtr<BIO> session_out; 167 static bssl::UniquePtr<SSL_SESSION> resume_session; 168 169 static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) { 170 if (session_out) { 171 if (!PEM_write_bio_SSL_SESSION(session_out.get(), session) || 172 BIO_flush(session_out.get()) <= 0) { 173 fprintf(stderr, "Error while saving session:\n"); 174 ERR_print_errors_cb(PrintErrorCallback, stderr); 175 return 0; 176 } 177 } 178 resume_session = bssl::UniquePtr<SSL_SESSION>(session); 179 return 1; 180 } 181 182 static bool WaitForSession(SSL *ssl, int sock) { 183 fd_set read_fds; 184 FD_ZERO(&read_fds); 185 186 if (!SocketSetNonBlocking(sock, true)) { 187 return false; 188 } 189 190 while (!resume_session) { 191 FD_SET(sock, &read_fds); 192 int ret = select(sock + 1, &read_fds, NULL, NULL, NULL); 193 if (ret <= 0) { 194 perror("select"); 195 return false; 196 } 197 198 uint8_t buffer[512]; 199 int ssl_ret = SSL_read(ssl, buffer, sizeof(buffer)); 200 201 if (ssl_ret <= 0) { 202 int ssl_err = SSL_get_error(ssl, ssl_ret); 203 if (ssl_err == SSL_ERROR_WANT_READ) { 204 continue; 205 } 206 fprintf(stderr, "Error while reading: %d\n", ssl_err); 207 ERR_print_errors_cb(PrintErrorCallback, stderr); 208 return false; 209 } 210 } 211 212 return true; 213 } 214 215 static bool DoConnection(SSL_CTX *ctx, 216 std::map<std::string, std::string> args_map, 217 bool (*cb)(SSL *ssl, int sock)) { 218 int sock = -1; 219 if (args_map.count("-http-tunnel") != 0) { 220 if (!Connect(&sock, args_map["-http-tunnel"]) || 221 !DoHTTPTunnel(sock, args_map["-connect"])) { 222 return false; 223 } 224 } else if (!Connect(&sock, args_map["-connect"])) { 225 return false; 226 } 227 228 if (args_map.count("-starttls") != 0) { 229 const std::string& starttls = args_map["-starttls"]; 230 if (starttls == "smtp") { 231 if (!DoSMTPStartTLS(sock)) { 232 return false; 233 } 234 } else { 235 fprintf(stderr, "Unknown value for -starttls: %s\n", starttls.c_str()); 236 return false; 237 } 238 } 239 240 bssl::UniquePtr<BIO> bio(BIO_new_socket(sock, BIO_CLOSE)); 241 bssl::UniquePtr<SSL> ssl(SSL_new(ctx)); 242 243 if (args_map.count("-server-name") != 0) { 244 SSL_set_tlsext_host_name(ssl.get(), args_map["-server-name"].c_str()); 245 } 246 247 if (args_map.count("-session-in") != 0) { 248 bssl::UniquePtr<BIO> in(BIO_new_file(args_map["-session-in"].c_str(), 249 "rb")); 250 if (!in) { 251 fprintf(stderr, "Error reading session\n"); 252 ERR_print_errors_cb(PrintErrorCallback, stderr); 253 return false; 254 } 255 bssl::UniquePtr<SSL_SESSION> session(PEM_read_bio_SSL_SESSION(in.get(), 256 nullptr, nullptr, nullptr)); 257 if (!session) { 258 fprintf(stderr, "Error reading session\n"); 259 ERR_print_errors_cb(PrintErrorCallback, stderr); 260 return false; 261 } 262 SSL_set_session(ssl.get(), session.get()); 263 } 264 265 if (resume_session) { 266 SSL_set_session(ssl.get(), resume_session.get()); 267 } 268 269 SSL_set_bio(ssl.get(), bio.get(), bio.get()); 270 bio.release(); 271 272 int ret = SSL_connect(ssl.get()); 273 if (ret != 1) { 274 int ssl_err = SSL_get_error(ssl.get(), ret); 275 fprintf(stderr, "Error while connecting: %d\n", ssl_err); 276 ERR_print_errors_cb(PrintErrorCallback, stderr); 277 return false; 278 } 279 280 if (args_map.count("-early-data") != 0 && SSL_in_early_data(ssl.get())) { 281 int ed_size = args_map["-early-data"].size(); 282 int ssl_ret = SSL_write(ssl.get(), args_map["-early-data"].data(), ed_size); 283 if (ssl_ret <= 0) { 284 int ssl_err = SSL_get_error(ssl.get(), ssl_ret); 285 fprintf(stderr, "Error while writing: %d\n", ssl_err); 286 ERR_print_errors_cb(PrintErrorCallback, stderr); 287 return false; 288 } else if (ssl_ret != ed_size) { 289 fprintf(stderr, "Short write from SSL_write.\n"); 290 return false; 291 } 292 } 293 294 fprintf(stderr, "Connected.\n"); 295 PrintConnectionInfo(ssl.get()); 296 297 return cb(ssl.get(), sock); 298 } 299 300 bool Client(const std::vector<std::string> &args) { 301 if (!InitSocketLibrary()) { 302 return false; 303 } 304 305 std::map<std::string, std::string> args_map; 306 307 if (!ParseKeyValueArguments(&args_map, args, kArguments)) { 308 PrintUsage(kArguments); 309 return false; 310 } 311 312 bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(SSLv23_client_method())); 313 314 const char *keylog_file = getenv("SSLKEYLOGFILE"); 315 if (keylog_file) { 316 g_keylog_file = fopen(keylog_file, "a"); 317 if (g_keylog_file == nullptr) { 318 perror("fopen"); 319 return false; 320 } 321 SSL_CTX_set_keylog_callback(ctx.get(), KeyLogCallback); 322 } 323 324 if (args_map.count("-cipher") != 0 && 325 !SSL_CTX_set_strict_cipher_list(ctx.get(), args_map["-cipher"].c_str())) { 326 fprintf(stderr, "Failed setting cipher list\n"); 327 return false; 328 } 329 330 if (args_map.count("-curves") != 0 && 331 !SSL_CTX_set1_curves_list(ctx.get(), args_map["-curves"].c_str())) { 332 fprintf(stderr, "Failed setting curves list\n"); 333 return false; 334 } 335 336 uint16_t max_version = TLS1_3_VERSION; 337 if (args_map.count("-max-version") != 0 && 338 !VersionFromString(&max_version, args_map["-max-version"])) { 339 fprintf(stderr, "Unknown protocol version: '%s'\n", 340 args_map["-max-version"].c_str()); 341 return false; 342 } 343 344 if (!SSL_CTX_set_max_proto_version(ctx.get(), max_version)) { 345 return false; 346 } 347 348 if (args_map.count("-min-version") != 0) { 349 uint16_t version; 350 if (!VersionFromString(&version, args_map["-min-version"])) { 351 fprintf(stderr, "Unknown protocol version: '%s'\n", 352 args_map["-min-version"].c_str()); 353 return false; 354 } 355 if (!SSL_CTX_set_min_proto_version(ctx.get(), version)) { 356 return false; 357 } 358 } 359 360 if (args_map.count("-select-next-proto") != 0) { 361 const std::string &proto = args_map["-select-next-proto"]; 362 if (proto.size() > 255) { 363 fprintf(stderr, "Bad NPN protocol: '%s'\n", proto.c_str()); 364 return false; 365 } 366 // |SSL_CTX_set_next_proto_select_cb| is not const-correct. 367 SSL_CTX_set_next_proto_select_cb(ctx.get(), NextProtoSelectCallback, 368 const_cast<char *>(proto.c_str())); 369 } 370 371 if (args_map.count("-alpn-protos") != 0) { 372 const std::string &alpn_protos = args_map["-alpn-protos"]; 373 std::vector<uint8_t> wire; 374 size_t i = 0; 375 while (i <= alpn_protos.size()) { 376 size_t j = alpn_protos.find(',', i); 377 if (j == std::string::npos) { 378 j = alpn_protos.size(); 379 } 380 size_t len = j - i; 381 if (len > 255) { 382 fprintf(stderr, "Invalid ALPN protocols: '%s'\n", alpn_protos.c_str()); 383 return false; 384 } 385 wire.push_back(static_cast<uint8_t>(len)); 386 wire.resize(wire.size() + len); 387 OPENSSL_memcpy(wire.data() + wire.size() - len, alpn_protos.data() + i, 388 len); 389 i = j + 1; 390 } 391 if (SSL_CTX_set_alpn_protos(ctx.get(), wire.data(), wire.size()) != 0) { 392 return false; 393 } 394 } 395 396 if (args_map.count("-fallback-scsv") != 0) { 397 SSL_CTX_set_mode(ctx.get(), SSL_MODE_SEND_FALLBACK_SCSV); 398 } 399 400 if (args_map.count("-ocsp-stapling") != 0) { 401 SSL_CTX_enable_ocsp_stapling(ctx.get()); 402 } 403 404 if (args_map.count("-signed-certificate-timestamps") != 0) { 405 SSL_CTX_enable_signed_cert_timestamps(ctx.get()); 406 } 407 408 if (args_map.count("-channel-id-key") != 0) { 409 bssl::UniquePtr<EVP_PKEY> pkey = 410 LoadPrivateKey(args_map["-channel-id-key"]); 411 if (!pkey || !SSL_CTX_set1_tls_channel_id(ctx.get(), pkey.get())) { 412 return false; 413 } 414 } 415 416 if (args_map.count("-false-start") != 0) { 417 SSL_CTX_set_mode(ctx.get(), SSL_MODE_ENABLE_FALSE_START); 418 } 419 420 if (args_map.count("-key") != 0) { 421 const std::string &key = args_map["-key"]; 422 if (!SSL_CTX_use_PrivateKey_file(ctx.get(), key.c_str(), 423 SSL_FILETYPE_PEM)) { 424 fprintf(stderr, "Failed to load private key: %s\n", key.c_str()); 425 return false; 426 } 427 const std::string &cert = 428 args_map.count("-cert") != 0 ? args_map["-cert"] : key; 429 if (!SSL_CTX_use_certificate_chain_file(ctx.get(), cert.c_str())) { 430 fprintf(stderr, "Failed to load cert chain: %s\n", cert.c_str()); 431 return false; 432 } 433 } 434 435 SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_CLIENT); 436 SSL_CTX_sess_set_new_cb(ctx.get(), NewSessionCallback); 437 438 if (args_map.count("-session-out") != 0) { 439 session_out.reset(BIO_new_file(args_map["-session-out"].c_str(), "wb")); 440 if (!session_out) { 441 fprintf(stderr, "Error while opening %s:\n", 442 args_map["-session-out"].c_str()); 443 ERR_print_errors_cb(PrintErrorCallback, stderr); 444 return false; 445 } 446 } 447 448 if (args_map.count("-grease") != 0) { 449 SSL_CTX_set_grease_enabled(ctx.get(), 1); 450 } 451 452 if (args_map.count("-root-certs") != 0) { 453 if (!SSL_CTX_load_verify_locations( 454 ctx.get(), args_map["-root-certs"].c_str(), nullptr)) { 455 fprintf(stderr, "Failed to load root certificates.\n"); 456 ERR_print_errors_cb(PrintErrorCallback, stderr); 457 return false; 458 } 459 SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER, nullptr); 460 } 461 462 if (args_map.count("-early-data") != 0) { 463 SSL_CTX_set_early_data_enabled(ctx.get(), 1); 464 } 465 466 if (args_map.count("-tls13-variant") != 0) { 467 SSL_CTX_set_tls13_variant(ctx.get(), 468 static_cast<enum tls13_variant_t>( 469 atoi(args_map["-tls13-variant"].c_str()))); 470 } 471 472 if (args_map.count("-ed25519") != 0) { 473 SSL_CTX_set_ed25519_enabled(ctx.get(), 1); 474 } 475 476 if (args_map.count("-test-resumption") != 0) { 477 if (args_map.count("-session-in") != 0) { 478 fprintf(stderr, 479 "Flags -session-in and -test-resumption are incompatible.\n"); 480 return false; 481 } 482 483 if (!DoConnection(ctx.get(), args_map, &WaitForSession)) { 484 return false; 485 } 486 } 487 488 return DoConnection(ctx.get(), args_map, &TransferData); 489 } 490