blob: b9f1c13be057a54792cc43b035047d6e2eb2c1fc [file] [log] [blame]
Adam Langleyaacec172014-06-20 12:00:00 -07001/* 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
David Benjamind28f59c2015-11-17 22:32:50 -050017#include <stdio.h>
18
Adam Langleyaacec172014-06-20 12:00:00 -070019#include <openssl/err.h>
David Benjamin05709232015-03-23 19:01:33 -040020#include <openssl/pem.h>
Adam Langleyaacec172014-06-20 12:00:00 -070021#include <openssl/ssl.h>
22
23#include "internal.h"
Dave Tapuskab8a824d2014-12-10 19:09:52 -050024#include "transport_common.h"
Adam Langleyaacec172014-06-20 12:00:00 -070025
26
27static const struct argument kArguments[] = {
28 {
David Benjamin05709232015-03-23 19:01:33 -040029 "-connect", kRequiredArgument,
Adam Langleyaacec172014-06-20 12:00:00 -070030 "The hostname and port of the server to connect to, e.g. foo.com:443",
31 },
32 {
David Benjamin05709232015-03-23 19:01:33 -040033 "-cipher", kOptionalArgument,
Adam Langley5f51c252014-10-28 15:45:39 -070034 "An OpenSSL-style cipher suite string that configures the offered ciphers",
35 },
36 {
David Benjamin05709232015-03-23 19:01:33 -040037 "-max-version", kOptionalArgument,
38 "The maximum acceptable protocol version",
39 },
40 {
41 "-min-version", kOptionalArgument,
42 "The minimum acceptable protocol version",
43 },
44 {
45 "-server-name", kOptionalArgument,
46 "The server name to advertise",
47 },
48 {
49 "-select-next-proto", kOptionalArgument,
50 "An NPN protocol to select if the server supports NPN",
51 },
52 {
53 "-alpn-protos", kOptionalArgument,
54 "A comma-separated list of ALPN protocols to advertise",
55 },
56 {
57 "-fallback-scsv", kBooleanArgument,
58 "Enable FALLBACK_SCSV",
59 },
60 {
61 "-ocsp-stapling", kBooleanArgument,
62 "Advertise support for OCSP stabling",
63 },
64 {
65 "-signed-certificate-timestamps", kBooleanArgument,
66 "Advertise support for signed certificate timestamps",
67 },
68 {
69 "-channel-id-key", kOptionalArgument,
70 "The key to use for signing a channel ID",
71 },
72 {
David Benjamin1043ac02015-06-04 15:26:04 -040073 "-false-start", kBooleanArgument,
74 "Enable False Start",
75 },
David Benjamin621f95a2015-08-28 15:08:34 -040076 { "-session-in", kOptionalArgument,
77 "A file containing a session to resume.",
78 },
79 { "-session-out", kOptionalArgument,
80 "A file to write the negotiated session to.",
81 },
David Benjamin1043ac02015-06-04 15:26:04 -040082 {
David Benjamin86e412d2015-12-02 19:34:58 -050083 "-key", kOptionalArgument,
84 "Private-key file to use (default is no client certificate)",
85 },
86 {
Adam Langley403c52a2016-07-07 14:52:02 -070087 "-starttls", kOptionalArgument,
88 "A STARTTLS mini-protocol to run before the TLS handshake. Supported"
89 " values: 'smtp'",
90 },
91 {
David Benjamin65ac9972016-09-02 21:35:25 -040092 "-grease", kBooleanArgument,
93 "Enable GREASE",
94 },
95 {
David Benjamin05709232015-03-23 19:01:33 -040096 "", kOptionalArgument, "",
Adam Langleyaacec172014-06-20 12:00:00 -070097 },
98};
99
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700100static bssl::UniquePtr<EVP_PKEY> LoadPrivateKey(const std::string &file) {
101 bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_file()));
David Benjamin05709232015-03-23 19:01:33 -0400102 if (!bio || !BIO_read_filename(bio.get(), file.c_str())) {
103 return nullptr;
104 }
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700105 bssl::UniquePtr<EVP_PKEY> pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr,
106 nullptr, nullptr));
David Benjamin05709232015-03-23 19:01:33 -0400107 return pkey;
108}
109
David Benjamin05709232015-03-23 19:01:33 -0400110static int NextProtoSelectCallback(SSL* ssl, uint8_t** out, uint8_t* outlen,
111 const uint8_t* in, unsigned inlen, void* arg) {
112 *out = reinterpret_cast<uint8_t *>(arg);
113 *outlen = strlen(reinterpret_cast<const char *>(arg));
114 return SSL_TLSEXT_ERR_OK;
115}
116
David Benjamind28f59c2015-11-17 22:32:50 -0500117static FILE *g_keylog_file = nullptr;
118
119static void KeyLogCallback(const SSL *ssl, const char *line) {
120 fprintf(g_keylog_file, "%s\n", line);
121 fflush(g_keylog_file);
122}
123
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700124static bssl::UniquePtr<BIO> session_out;
Steven Valdez7b689f62016-08-02 15:59:26 -0400125
126static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) {
127 if (session_out) {
128 if (!PEM_write_bio_SSL_SESSION(session_out.get(), session) ||
129 BIO_flush(session_out.get()) <= 0) {
130 fprintf(stderr, "Error while saving session:\n");
131 ERR_print_errors_cb(PrintErrorCallback, stderr);
132 return 0;
133 }
134 }
135
136 return 0;
137}
138
Adam Langleyaacec172014-06-20 12:00:00 -0700139bool Client(const std::vector<std::string> &args) {
Brian Smith33970e62015-01-27 22:32:08 -0800140 if (!InitSocketLibrary()) {
141 return false;
142 }
143
Adam Langleyaacec172014-06-20 12:00:00 -0700144 std::map<std::string, std::string> args_map;
145
146 if (!ParseKeyValueArguments(&args_map, args, kArguments)) {
147 PrintUsage(kArguments);
148 return false;
149 }
150
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700151 bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(SSLv23_client_method()));
Adam Langleyaacec172014-06-20 12:00:00 -0700152
David Benjamin859ec3c2014-09-02 16:29:36 -0400153 const char *keylog_file = getenv("SSLKEYLOGFILE");
154 if (keylog_file) {
David Benjamind28f59c2015-11-17 22:32:50 -0500155 g_keylog_file = fopen(keylog_file, "a");
156 if (g_keylog_file == nullptr) {
157 perror("fopen");
David Benjamin859ec3c2014-09-02 16:29:36 -0400158 return false;
159 }
David Benjamind28f59c2015-11-17 22:32:50 -0500160 SSL_CTX_set_keylog_callback(ctx.get(), KeyLogCallback);
David Benjamin859ec3c2014-09-02 16:29:36 -0400161 }
162
Dave Tapuskab8a824d2014-12-10 19:09:52 -0500163 if (args_map.count("-cipher") != 0 &&
David Benjamin05709232015-03-23 19:01:33 -0400164 !SSL_CTX_set_cipher_list(ctx.get(), args_map["-cipher"].c_str())) {
Dave Tapuskab8a824d2014-12-10 19:09:52 -0500165 fprintf(stderr, "Failed setting cipher list\n");
166 return false;
Adam Langley5f51c252014-10-28 15:45:39 -0700167 }
168
David Benjamin05709232015-03-23 19:01:33 -0400169 if (args_map.count("-max-version") != 0) {
170 uint16_t version;
171 if (!VersionFromString(&version, args_map["-max-version"])) {
172 fprintf(stderr, "Unknown protocol version: '%s'\n",
173 args_map["-max-version"].c_str());
174 return false;
175 }
David Benjamine4706902016-09-20 15:12:23 -0400176 if (!SSL_CTX_set_max_proto_version(ctx.get(), version)) {
David Benjamin2dc02042016-09-19 19:57:37 -0400177 return false;
178 }
David Benjamin05709232015-03-23 19:01:33 -0400179 }
180
181 if (args_map.count("-min-version") != 0) {
182 uint16_t version;
183 if (!VersionFromString(&version, args_map["-min-version"])) {
184 fprintf(stderr, "Unknown protocol version: '%s'\n",
185 args_map["-min-version"].c_str());
186 return false;
187 }
David Benjamine4706902016-09-20 15:12:23 -0400188 if (!SSL_CTX_set_min_proto_version(ctx.get(), version)) {
David Benjamin2dc02042016-09-19 19:57:37 -0400189 return false;
190 }
David Benjamin05709232015-03-23 19:01:33 -0400191 }
192
193 if (args_map.count("-select-next-proto") != 0) {
194 const std::string &proto = args_map["-select-next-proto"];
195 if (proto.size() > 255) {
196 fprintf(stderr, "Bad NPN protocol: '%s'\n", proto.c_str());
197 return false;
198 }
199 // |SSL_CTX_set_next_proto_select_cb| is not const-correct.
200 SSL_CTX_set_next_proto_select_cb(ctx.get(), NextProtoSelectCallback,
201 const_cast<char *>(proto.c_str()));
202 }
203
204 if (args_map.count("-alpn-protos") != 0) {
205 const std::string &alpn_protos = args_map["-alpn-protos"];
206 std::vector<uint8_t> wire;
207 size_t i = 0;
208 while (i <= alpn_protos.size()) {
209 size_t j = alpn_protos.find(',', i);
210 if (j == std::string::npos) {
211 j = alpn_protos.size();
212 }
213 size_t len = j - i;
214 if (len > 255) {
215 fprintf(stderr, "Invalid ALPN protocols: '%s'\n", alpn_protos.c_str());
216 return false;
217 }
218 wire.push_back(static_cast<uint8_t>(len));
219 wire.resize(wire.size() + len);
220 memcpy(wire.data() + wire.size() - len, alpn_protos.data() + i, len);
221 i = j + 1;
222 }
223 if (SSL_CTX_set_alpn_protos(ctx.get(), wire.data(), wire.size()) != 0) {
224 return false;
225 }
226 }
227
228 if (args_map.count("-fallback-scsv") != 0) {
229 SSL_CTX_set_mode(ctx.get(), SSL_MODE_SEND_FALLBACK_SCSV);
230 }
231
232 if (args_map.count("-ocsp-stapling") != 0) {
233 SSL_CTX_enable_ocsp_stapling(ctx.get());
234 }
235
236 if (args_map.count("-signed-certificate-timestamps") != 0) {
237 SSL_CTX_enable_signed_cert_timestamps(ctx.get());
238 }
239
240 if (args_map.count("-channel-id-key") != 0) {
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700241 bssl::UniquePtr<EVP_PKEY> pkey =
242 LoadPrivateKey(args_map["-channel-id-key"]);
David Benjamin05709232015-03-23 19:01:33 -0400243 if (!pkey || !SSL_CTX_set1_tls_channel_id(ctx.get(), pkey.get())) {
244 return false;
245 }
David Benjamin05709232015-03-23 19:01:33 -0400246 }
247
David Benjamin1043ac02015-06-04 15:26:04 -0400248 if (args_map.count("-false-start") != 0) {
249 SSL_CTX_set_mode(ctx.get(), SSL_MODE_ENABLE_FALSE_START);
250 }
251
David Benjamin86e412d2015-12-02 19:34:58 -0500252 if (args_map.count("-key") != 0) {
253 const std::string &key = args_map["-key"];
254 if (!SSL_CTX_use_PrivateKey_file(ctx.get(), key.c_str(), SSL_FILETYPE_PEM)) {
255 fprintf(stderr, "Failed to load private key: %s\n", key.c_str());
256 return false;
257 }
258 if (!SSL_CTX_use_certificate_chain_file(ctx.get(), key.c_str())) {
259 fprintf(stderr, "Failed to load cert chain: %s\n", key.c_str());
260 return false;
261 }
262 }
263
Steven Valdez7b689f62016-08-02 15:59:26 -0400264 if (args_map.count("-session-out") != 0) {
265 session_out.reset(BIO_new_file(args_map["-session-out"].c_str(), "wb"));
266 if (!session_out) {
David Benjamin70728842016-09-06 18:18:52 -0400267 fprintf(stderr, "Error while opening %s:\n",
268 args_map["-session-out"].c_str());
Steven Valdez7b689f62016-08-02 15:59:26 -0400269 ERR_print_errors_cb(PrintErrorCallback, stderr);
270 return false;
271 }
David Benjamin70728842016-09-06 18:18:52 -0400272 SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_CLIENT);
Steven Valdez7b689f62016-08-02 15:59:26 -0400273 SSL_CTX_sess_set_new_cb(ctx.get(), NewSessionCallback);
274 }
275
David Benjamin65ac9972016-09-02 21:35:25 -0400276 if (args_map.count("-grease") != 0) {
277 SSL_CTX_set_grease_enabled(ctx.get(), 1);
278 }
279
Adam Langleybbb42ff2014-06-23 11:25:49 -0700280 int sock = -1;
Adam Langleyaacec172014-06-20 12:00:00 -0700281 if (!Connect(&sock, args_map["-connect"])) {
282 return false;
283 }
284
Adam Langley403c52a2016-07-07 14:52:02 -0700285 if (args_map.count("-starttls") != 0) {
286 const std::string& starttls = args_map["-starttls"];
287 if (starttls == "smtp") {
288 if (!DoSMTPStartTLS(sock)) {
289 return false;
290 }
291 } else {
292 fprintf(stderr, "Unknown value for -starttls: %s\n", starttls.c_str());
293 return false;
294 }
295 }
296
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700297 bssl::UniquePtr<BIO> bio(BIO_new_socket(sock, BIO_CLOSE));
298 bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
Adam Langleyaacec172014-06-20 12:00:00 -0700299
David Benjamin05709232015-03-23 19:01:33 -0400300 if (args_map.count("-server-name") != 0) {
301 SSL_set_tlsext_host_name(ssl.get(), args_map["-server-name"].c_str());
302 }
303
David Benjamin621f95a2015-08-28 15:08:34 -0400304 if (args_map.count("-session-in") != 0) {
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700305 bssl::UniquePtr<BIO> in(BIO_new_file(args_map["-session-in"].c_str(),
306 "rb"));
David Benjamin621f95a2015-08-28 15:08:34 -0400307 if (!in) {
308 fprintf(stderr, "Error reading session\n");
309 ERR_print_errors_cb(PrintErrorCallback, stderr);
310 return false;
311 }
Matt Braithwaited17d74d2016-08-17 20:10:28 -0700312 bssl::UniquePtr<SSL_SESSION> session(PEM_read_bio_SSL_SESSION(in.get(),
313 nullptr, nullptr, nullptr));
David Benjamin621f95a2015-08-28 15:08:34 -0400314 if (!session) {
315 fprintf(stderr, "Error reading session\n");
316 ERR_print_errors_cb(PrintErrorCallback, stderr);
317 return false;
318 }
319 SSL_set_session(ssl.get(), session.get());
320 }
321
David Benjamin05709232015-03-23 19:01:33 -0400322 SSL_set_bio(ssl.get(), bio.get(), bio.get());
323 bio.release();
324
325 int ret = SSL_connect(ssl.get());
Adam Langleyaacec172014-06-20 12:00:00 -0700326 if (ret != 1) {
David Benjamin05709232015-03-23 19:01:33 -0400327 int ssl_err = SSL_get_error(ssl.get(), ret);
Adam Langleyaacec172014-06-20 12:00:00 -0700328 fprintf(stderr, "Error while connecting: %d\n", ssl_err);
329 ERR_print_errors_cb(PrintErrorCallback, stderr);
330 return false;
331 }
332
333 fprintf(stderr, "Connected.\n");
David Benjamin05709232015-03-23 19:01:33 -0400334 PrintConnectionInfo(ssl.get());
Adam Langleyaacec172014-06-20 12:00:00 -0700335
David Benjamin05709232015-03-23 19:01:33 -0400336 bool ok = TransferData(ssl.get(), sock);
Adam Langleyaacec172014-06-20 12:00:00 -0700337
Adam Langleyaacec172014-06-20 12:00:00 -0700338 return ok;
339}