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