Home | History | Annotate | Download | only in tool
      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