1 /*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License").
5 * You may not use this file except in compliance with the License.
6 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15
16 #include <sys/param.h>
17 #include <sys/socket.h>
18 #include <netdb.h>
19
20 #include <unistd.h>
21 #include <getopt.h>
22 #include <fcntl.h>
23
24 #include <s2n.h>
25 #include "common.h"
26 #include <error/s2n_errno.h>
27
28 #include "tls/s2n_connection.h"
29
usage()30 void usage()
31 {
32 fprintf(stderr, "usage: s2nc [options] host [port]\n");
33 fprintf(stderr, " host: hostname or IP address to connect to\n");
34 fprintf(stderr, " port: port to connect to\n");
35 fprintf(stderr, "\n Options:\n\n");
36 fprintf(stderr, " -a [protocols]\n");
37 fprintf(stderr, " --alpn [protocols]\n");
38 fprintf(stderr, " Sets the application protocols supported by this client, as a comma separated list.\n");
39 fprintf(stderr, " -c [version_string]\n");
40 fprintf(stderr, " --ciphers [version_string]\n");
41 fprintf(stderr, " Set the cipher preference version string. Defaults to \"default\". See USAGE-GUIDE.md\n");
42 fprintf(stderr, " -e,--echo\n");
43 fprintf(stderr, " Listen to stdin after TLS Connection is established and echo it to the Server\n");
44 fprintf(stderr, " -h,--help\n");
45 fprintf(stderr, " Display this message and quit.\n");
46 fprintf(stderr, " -n [server name]\n");
47 fprintf(stderr, " --name [server name]\n");
48 fprintf(stderr, " Sets the SNI server name header for this client. If not specified, the host value is used.\n");
49 fprintf(stderr, "\n");
50 fprintf(stderr, " -s,--status\n");
51 fprintf(stderr, " Request the OCSP status of the remote server certificate\n");
52 fprintf(stderr, " -m,--mfl\n");
53 fprintf(stderr, " Request maximum fragment length from: 512, 1024, 2048, 4096\n");
54 fprintf(stderr, " -f,--ca-file [file path]\n");
55 fprintf(stderr, " Location of trust store CA file (PEM format). If neither -f or -d are specified. System defaults will be used.\n");
56 fprintf(stderr, " -d,--ca-dir [directory path]\n");
57 fprintf(stderr, " Directory containing hashed trusted certs. If neither -f or -d are specified. System defaults will be used.\n");
58 fprintf(stderr, " -i,--insecure\n");
59 fprintf(stderr, " Turns off certification validation altogether.\n");
60 fprintf(stderr, " -l,--cert [file path]\n");
61 fprintf(stderr, " Path to a PEM encoded certificate. Optional. Will only be used for client auth\n");
62 fprintf(stderr, " -k,--key [file path]\n");
63 fprintf(stderr, " Path to a PEM encoded private key that matches cert. Will only be used for client auth\n");
64 fprintf(stderr, " -r,--reconnect\n");
65 fprintf(stderr, " Drop and re-make the connection using Session ticket. If session ticket is disabled, then re-make the connection using Session-ID \n");
66 fprintf(stderr, " -T,--no-session-ticket \n");
67 fprintf(stderr, " Disable session ticket for resumption.\n");
68 fprintf(stderr, " -D,--dynamic\n");
69 fprintf(stderr, " Set dynamic record resize threshold\n");
70 fprintf(stderr, " -t,--timeout\n");
71 fprintf(stderr, " Set dynamic record timeout threshold\n");
72 fprintf(stderr, " -C,--corked-io\n");
73 fprintf(stderr, " Turn on corked io\n");
74 fprintf(stderr, " -B,--non-blocking\n");
75 fprintf(stderr, " Set the non-blocking flag on the connection's socket.\n");
76 fprintf(stderr, " -L --key-log <path>\n");
77 fprintf(stderr, " Enable NSS key logging into the provided path\n");
78 fprintf(stderr, " -P --psk <psk-identity,psk-secret,psk-hmac-alg> \n"
79 " A comma-separated list of psk parameters in this order: psk_identity, psk_secret and psk_hmac_alg.\n"
80 " Note that the maximum number of permitted psks is 10, the psk-secret is hex-encoded, and whitespace is not allowed before or after the commas.\n"
81 " Ex: --psk psk_id,psk_secret,SHA256 --psk shared_id,shared_secret,SHA384.\n");
82 fprintf(stderr, " -E ,--early-data <file path>\n");
83 fprintf(stderr, " Sends data in file path as early data to the server. Early data will only be sent if s2nc receives a session ticket and resumes a session.");
84 fprintf(stderr, "\n");
85 exit(1);
86 }
87
88
89
90 size_t session_state_length = 0;
91 uint8_t *session_state = NULL;
test_session_ticket_cb(struct s2n_connection * conn,void * ctx,struct s2n_session_ticket * ticket)92 static int test_session_ticket_cb(struct s2n_connection *conn, void *ctx, struct s2n_session_ticket *ticket)
93 {
94 GUARD_EXIT_NULL(conn);
95 GUARD_EXIT_NULL(ticket);
96
97 GUARD_EXIT(s2n_session_ticket_get_data_len(ticket, &session_state_length), "Error getting ticket length ");
98 session_state = realloc(session_state, session_state_length);
99 if(session_state == NULL) {
100 print_s2n_error("Error getting new session state");
101 exit(1);
102 }
103 GUARD_EXIT(s2n_session_ticket_get_data(ticket, session_state_length, session_state), "Error getting ticket data");
104
105 bool *session_ticket_recv = (bool *)ctx;
106 *session_ticket_recv = 1;
107
108 return S2N_SUCCESS;
109 }
110
setup_s2n_config(struct s2n_config * config,const char * cipher_prefs,s2n_status_request_type type,struct verify_data * unsafe_verify_data,const char * host,const char * alpn_protocols,uint16_t mfl_value)111 static void setup_s2n_config(struct s2n_config *config, const char *cipher_prefs, s2n_status_request_type type,
112 struct verify_data *unsafe_verify_data, const char *host, const char *alpn_protocols, uint16_t mfl_value) {
113
114 if (config == NULL) {
115 print_s2n_error("Error getting new config");
116 exit(1);
117 }
118
119 GUARD_EXIT(s2n_config_set_cipher_preferences(config, cipher_prefs), "Error setting cipher prefs");
120
121 GUARD_EXIT(s2n_config_set_status_request_type(config, type), "OCSP validation is not supported by the linked libCrypto implementation. It cannot be set.");
122
123 if (s2n_config_set_verify_host_callback(config, unsafe_verify_host, unsafe_verify_data) < 0) {
124 print_s2n_error("Error setting host name verification function.");
125 }
126
127 if (type == S2N_STATUS_REQUEST_OCSP) {
128 if(s2n_config_set_check_stapled_ocsp_response(config, 1)) {
129 print_s2n_error("OCSP validation is not supported by the linked libCrypto implementation. It cannot be set.");
130 }
131 }
132
133 unsafe_verify_data->trusted_host = host;
134
135 if (alpn_protocols) {
136 /* Count the number of commas, this tells us how many protocols there
137 are in the list */
138 const char *ptr = alpn_protocols;
139 int protocol_count = 1;
140 while (*ptr) {
141 if (*ptr == ',') {
142 protocol_count++;
143 }
144 ptr++;
145 }
146
147 char **protocols = malloc(sizeof(char *) * protocol_count);
148 if (!protocols) {
149 fprintf(stderr, "Error allocating memory\n");
150 exit(1);
151 }
152
153 const char *next = alpn_protocols;
154 int idx = 0;
155 int length = 0;
156 ptr = alpn_protocols;
157 while (*ptr) {
158 if (*ptr == ',') {
159 protocols[idx] = malloc(length + 1);
160 if (!protocols[idx]) {
161 fprintf(stderr, "Error allocating memory\n");
162 exit(1);
163 }
164 memcpy(protocols[idx], next, length);
165 protocols[idx][length] = '\0';
166 length = 0;
167 idx++;
168 ptr++;
169 next = ptr;
170 } else {
171 length++;
172 ptr++;
173 }
174 }
175 if (ptr != next) {
176 protocols[idx] = malloc(length + 1);
177 if (!protocols[idx]) {
178 fprintf(stderr, "Error allocating memory\n");
179 exit(1);
180 }
181 memcpy(protocols[idx], next, length);
182 protocols[idx][length] = '\0';
183 }
184
185 GUARD_EXIT(s2n_config_set_protocol_preferences(config, (const char *const *)protocols, protocol_count), "Failed to set protocol preferences");
186
187 while (protocol_count) {
188 protocol_count--;
189 free(protocols[protocol_count]);
190 }
191 free(protocols);
192 }
193
194 uint8_t mfl_code = 0;
195 if (mfl_value > 0) {
196 switch(mfl_value) {
197 case 512:
198 mfl_code = S2N_TLS_MAX_FRAG_LEN_512;
199 break;
200 case 1024:
201 mfl_code = S2N_TLS_MAX_FRAG_LEN_1024;
202 break;
203 case 2048:
204 mfl_code = S2N_TLS_MAX_FRAG_LEN_2048;
205 break;
206 case 4096:
207 mfl_code = S2N_TLS_MAX_FRAG_LEN_4096;
208 break;
209 default:
210 fprintf(stderr, "Invalid maximum fragment length value\n");
211 exit(1);
212 }
213 }
214
215 GUARD_EXIT(s2n_config_send_max_fragment_length(config, mfl_code), "Error setting maximum fragment length");
216 }
217
main(int argc,char * const * argv)218 int main(int argc, char *const *argv)
219 {
220 struct addrinfo hints, *ai_list, *ai;
221 int r, sockfd = 0;
222 bool session_ticket_recv = 0;
223 /* Optional args */
224 const char *alpn_protocols = NULL;
225 const char *server_name = NULL;
226 const char *ca_file = NULL;
227 const char *ca_dir = NULL;
228 const char *client_cert = NULL;
229 const char *client_key = NULL;
230 bool client_cert_input = false;
231 bool client_key_input = false;
232 uint16_t mfl_value = 0;
233 uint8_t insecure = 0;
234 int reconnect = 0;
235 uint8_t session_ticket = 1;
236 s2n_status_request_type type = S2N_STATUS_REQUEST_NONE;
237 uint32_t dyn_rec_threshold = 0;
238 uint8_t dyn_rec_timeout = 0;
239 /* required args */
240 const char *cipher_prefs = "default";
241 const char *host = NULL;
242 struct verify_data unsafe_verify_data;
243 const char *port = "443";
244 int echo_input = 0;
245 int use_corked_io = 0;
246 uint8_t non_blocking = 0;
247 const char *key_log_path = NULL;
248 FILE *key_log_file = NULL;
249 char *psk_optarg_list[S2N_MAX_PSK_LIST_LENGTH];
250 size_t psk_list_len = 0;
251 char *early_data = NULL;
252
253 static struct option long_options[] = {
254 {"alpn", required_argument, 0, 'a'},
255 {"ciphers", required_argument, 0, 'c'},
256 {"echo", no_argument, 0, 'e'},
257 {"help", no_argument, 0, 'h'},
258 {"name", required_argument, 0, 'n'},
259 {"status", no_argument, 0, 's'},
260 {"mfl", required_argument, 0, 'm'},
261 {"ca-file", required_argument, 0, 'f'},
262 {"ca-dir", required_argument, 0, 'd'},
263 {"cert", required_argument, 0, 'l'},
264 {"key", required_argument, 0, 'k'},
265 {"insecure", no_argument, 0, 'i'},
266 {"reconnect", no_argument, 0, 'r'},
267 {"no-session-ticket", no_argument, 0, 'T'},
268 {"dynamic", required_argument, 0, 'D'},
269 {"timeout", required_argument, 0, 't'},
270 {"corked-io", no_argument, 0, 'C'},
271 {"tls13", no_argument, 0, '3'},
272 {"non-blocking", no_argument, 0, 'B'},
273 {"key-log", required_argument, 0, 'L'},
274 {"psk", required_argument, 0, 'P'},
275 {"early-data", required_argument, 0, 'E'},
276 { 0 },
277 };
278
279 while (1) {
280 int option_index = 0;
281 int c = getopt_long(argc, argv, "a:c:ehn:m:sf:d:l:k:D:t:irTCBL:P:E:", long_options, &option_index);
282 if (c == -1) {
283 break;
284 }
285 switch (c) {
286 case 'a':
287 alpn_protocols = optarg;
288 break;
289 case 'C':
290 use_corked_io = 1;
291 break;
292 case 'c':
293 cipher_prefs = optarg;
294 break;
295 case 'e':
296 echo_input = 1;
297 break;
298 case 'h':
299 usage();
300 break;
301 case 'n':
302 server_name = optarg;
303 break;
304 case 's':
305 type = S2N_STATUS_REQUEST_OCSP;
306 break;
307 case 'm':
308 mfl_value = (uint16_t) atoi(optarg);
309 break;
310 case 'f':
311 ca_file = optarg;
312 break;
313 case 'd':
314 ca_dir = optarg;
315 break;
316 case 'l':
317 client_cert = load_file_to_cstring(optarg);
318 client_cert_input = true;
319 break;
320 case 'k':
321 client_key = load_file_to_cstring(optarg);
322 client_key_input = true;
323 break;
324 case 'i':
325 insecure = 1;
326 break;
327 case 'r':
328 reconnect = 5;
329 break;
330 case 'T':
331 session_ticket = 0;
332 break;
333 case 't':
334 dyn_rec_timeout = (uint8_t) MIN(255, atoi(optarg));
335 break;
336 case 'D':
337 errno = 0;
338 dyn_rec_threshold = strtoul(optarg, 0, 10);
339 if (errno == ERANGE) {
340 dyn_rec_threshold = 0;
341 }
342 break;
343 case '3':
344 /* Do nothing -- this argument is deprecated. */
345 break;
346 case 'B':
347 non_blocking = 1;
348 break;
349 case 'L':
350 key_log_path = optarg;
351 break;
352 case 'P':
353 if (psk_list_len >= S2N_MAX_PSK_LIST_LENGTH) {
354 fprintf(stderr, "Error setting psks, maximum number of psks permitted is 10.\n");
355 exit(1);
356 }
357 psk_optarg_list[psk_list_len++] = optarg;
358 break;
359 case 'E':
360 early_data = load_file_to_cstring(optarg);
361 GUARD_EXIT_NULL(early_data);
362 break;
363 case '?':
364 default:
365 usage();
366 break;
367 }
368 }
369
370 if (optind < argc) {
371 host = argv[optind++];
372 }
373
374 /* cppcheck-suppress duplicateCondition */
375 if (optind < argc) {
376 port = argv[optind++];
377 }
378
379 if (!host) {
380 usage();
381 }
382
383 if (!server_name) {
384 server_name = host;
385 }
386
387 memset(&hints, 0, sizeof(hints));
388
389 hints.ai_family = AF_UNSPEC;
390 hints.ai_socktype = SOCK_STREAM;
391
392 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
393 fprintf(stderr, "Error disabling SIGPIPE\n");
394 exit(1);
395 }
396
397 GUARD_EXIT(s2n_init(), "Error running s2n_init()");
398
399 if ((r = getaddrinfo(host, port, &hints, &ai_list)) != 0) {
400 fprintf(stderr, "error: %s\n", gai_strerror(r));
401 exit(1);
402 }
403
404 do {
405 int connected = 0;
406 for (ai = ai_list; ai != NULL; ai = ai->ai_next) {
407 if ((sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) {
408 continue;
409 }
410
411 if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) == -1) {
412 close(sockfd);
413 continue;
414 }
415
416 connected = 1;
417 /* connect() succeeded */
418 break;
419 }
420
421 if (connected == 0) {
422 fprintf(stderr, "Failed to connect to %s:%s\n", host, port);
423 exit(1);
424 }
425
426 if (non_blocking) {
427 int flags = fcntl(sockfd, F_GETFL, 0);
428 if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) {
429 fprintf(stderr, "fcntl error: %s\n", strerror(errno));
430 exit(1);
431 }
432 }
433
434 struct s2n_config *config = s2n_config_new();
435 setup_s2n_config(config, cipher_prefs, type, &unsafe_verify_data, host, alpn_protocols, mfl_value);
436
437 if (client_cert_input != client_key_input) {
438 print_s2n_error("Client cert/key pair must be given.");
439 }
440
441 if (client_cert_input) {
442 struct s2n_cert_chain_and_key *chain_and_key = s2n_cert_chain_and_key_new();
443 GUARD_EXIT(s2n_cert_chain_and_key_load_pem(chain_and_key, client_cert, client_key), "Error getting certificate/key");
444 GUARD_EXIT(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key), "Error setting certificate/key");
445 }
446
447 if (ca_file || ca_dir) {
448 GUARD_EXIT(s2n_config_wipe_trust_store(config), "Error wiping trust store");
449 if (s2n_config_set_verification_ca_location(config, ca_file, ca_dir) < 0) {
450 print_s2n_error("Error setting CA file for trust store.");
451 }
452 }
453 else if (insecure) {
454 GUARD_EXIT(s2n_config_disable_x509_verification(config), "Error disabling X.509 validation");
455 }
456
457 if (session_ticket) {
458 GUARD_EXIT(s2n_config_set_session_tickets_onoff(config, 1), "Error enabling session tickets");
459 GUARD_EXIT(s2n_config_set_session_ticket_cb(config, test_session_ticket_cb, &session_ticket_recv), "Error setting session ticket callback");
460 session_ticket_recv = 0;
461 }
462
463 if (key_log_path) {
464 key_log_file = fopen(key_log_path, "a");
465 GUARD_EXIT(key_log_file == NULL ? S2N_FAILURE : S2N_SUCCESS, "Failed to open key log file");
466 GUARD_EXIT(
467 s2n_config_set_key_log_cb(
468 config,
469 key_log_callback,
470 (void *)key_log_file
471 ),
472 "Failed to set key log callback"
473 );
474 }
475
476 struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT);
477
478 if (conn == NULL) {
479 print_s2n_error("Error getting new connection");
480 exit(1);
481 }
482
483 GUARD_EXIT(s2n_connection_set_config(conn, config), "Error setting configuration");
484
485 GUARD_EXIT(s2n_set_server_name(conn, server_name), "Error setting server name");
486
487 GUARD_EXIT(s2n_connection_set_fd(conn, sockfd) , "Error setting file descriptor");
488
489 GUARD_EXIT(s2n_connection_set_client_auth_type(conn, S2N_CERT_AUTH_OPTIONAL), "Error setting ClientAuth optional");
490
491 if (use_corked_io) {
492 GUARD_EXIT(s2n_connection_use_corked_io(conn), "Error setting corked io");
493 }
494
495 /* Update session state in connection if exists */
496 if (session_state_length > 0) {
497 GUARD_EXIT(s2n_connection_set_session(conn, session_state, session_state_length), "Error setting session state in connection");
498 }
499
500 GUARD_EXIT(s2n_setup_external_psk_list(conn, psk_optarg_list, psk_list_len), "Error setting external psk list");
501
502 if (early_data) {
503 if (!session_ticket) {
504 print_s2n_error("Early data can only be used with session tickets.");
505 exit(1);
506 }
507 /* Send early data if we have a received a session ticket from the server */
508 if (session_state_length) {
509 uint32_t early_data_length = strlen(early_data);
510 GUARD_EXIT(early_data_send(conn, (uint8_t *)early_data, early_data_length), "Error sending early data");
511 }
512 }
513
514 /* See echo.c */
515 if (negotiate(conn, sockfd) != 0) {
516 /* Error is printed in negotiate */
517 S2N_ERROR_PRESERVE_ERRNO();
518 }
519
520 printf("Connected to %s:%s\n", host, port);
521
522 /* Save session state from connection if reconnect is enabled. */
523 if (reconnect > 0) {
524 if (conn->actual_protocol_version >= S2N_TLS13) {
525 if (!session_ticket) {
526 print_s2n_error("s2nc can only reconnect in TLS1.3 with session tickets.");
527 exit(1);
528 }
529 GUARD_EXIT(echo(conn, sockfd, &session_ticket_recv), "Error calling echo");
530 } else {
531 if (!session_ticket && s2n_connection_get_session_id_length(conn) <= 0) {
532 print_s2n_error("Endpoint sent empty session id so cannot resume session");
533 exit(1);
534 }
535 free(session_state);
536 session_state_length = s2n_connection_get_session_length(conn);
537 session_state = calloc(session_state_length, sizeof(uint8_t));
538 GUARD_EXIT_NULL(session_state);
539 if (s2n_connection_get_session(conn, session_state, session_state_length) != session_state_length) {
540 print_s2n_error("Error getting serialized session state");
541 exit(1);
542 }
543 }
544 }
545
546 if (dyn_rec_threshold > 0 && dyn_rec_timeout > 0) {
547 s2n_connection_set_dynamic_record_threshold(conn, dyn_rec_threshold, dyn_rec_timeout);
548 }
549
550 GUARD_EXIT(s2n_connection_free_handshake(conn), "Error freeing handshake memory after negotiation");
551
552 if (echo_input == 1) {
553 bool stop_echo = false;
554 fflush(stdout);
555 fflush(stderr);
556 echo(conn, sockfd, &stop_echo);
557 }
558
559 /* The following call can block on receiving a close_notify if we initiate the shutdown or if the */
560 /* peer fails to send a close_notify. */
561 /* TODO: However, we should expect to receive a close_notify from the peer and shutdown gracefully. */
562 /* Please see tracking issue for more detail: https://github.com/aws/s2n-tls/issues/2692 */
563 s2n_blocked_status blocked;
564 int shutdown_rc = s2n_shutdown(conn, &blocked);
565 if (shutdown_rc == -1 && blocked != S2N_BLOCKED_ON_READ) {
566 fprintf(stderr, "Unexpected error during shutdown: '%s'\n", s2n_strerror(s2n_errno, "NULL"));
567 exit(1);
568 }
569
570 GUARD_EXIT(s2n_connection_free(conn), "Error freeing connection");
571
572 GUARD_EXIT(s2n_config_free(config), "Error freeing configuration");
573
574 close(sockfd);
575 reconnect--;
576
577 } while (reconnect >= 0);
578
579 if (key_log_file) {
580 fclose(key_log_file);
581 }
582
583 GUARD_EXIT(s2n_cleanup(), "Error running s2n_cleanup()");
584
585 free(early_data);
586 free(session_state);
587 freeaddrinfo(ai_list);
588 return 0;
589 }
590