/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #ifndef S2N_INTERN_LIBCRYPTO #include #include #endif #include "api/s2n.h" #include "api/unstable/npn.h" #include "api/unstable/renegotiate.h" #include "common.h" #include "crypto/s2n_libcrypto.h" #include "error/s2n_errno.h" #include "tls/s2n_connection.h" #define OPT_TICKET_IN 1000 #define OPT_TICKET_OUT 1001 #define OPT_SEND_FILE 1002 #define OPT_RENEG 1003 #define OPT_NPN 1004 #define OPT_PREFER_LOW_LATENCY 1005 #define OPT_PREFER_THROUGHPUT 1006 #define OPT_BUFFERED_SEND 1007 #define OPT_SERIALIZE_OUT 1008 #define OPT_DESERIALIZE_IN 1009 /* This should match the final cert in the s2nd default_certificate_chain */ const char default_trusted_cert[] = "-----BEGIN CERTIFICATE-----" "MIIC/jCCAeagAwIBAgIUFFjxpSf0mUsrVbyLPQhccDYfixowDQYJKoZIhvcNAQEL" "BQAwFjEUMBIGA1UEAwwLczJuVGVzdFJvb3QwIBcNMjAwMTI0MDEwODIyWhgPMjEx" "OTEyMzEwMTA4MjJaMBYxFDASBgNVBAMMC3MyblRlc3RSb290MIIBIjANBgkqhkiG" "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz3AaOAlkcxJHryCI9SfwB9q4PA53hv5tz4ZL" "be37b69v58mfP+D18cWIBHUmkmN6gWWoWZ/9hv75pxcNXW0zPn7+wOVvXLUjtmkq" "1IGT/mykhasw00viaBFAuBHZ5iLwfc4/cjUFAPVCKLmfv5Xs7TJVzWA/0mR4r1h8" "uFqqXczkVMklIbsOIrlZXz8ifQs3DpFA2FeoziEh+Pcb4c3QBPgCHFDEGyTSdqo9" "+NbS+iRlw0T6tqUOpC0DdKXo/3mJNBmy4XPahTi9zgsu7b+UVqemL7eXXf/iSr5y" "iwJKJjz+N/rLpcF1VJtF8q0fpHagzljQaN7/emjg7BplUUyLawIDAQABo0IwQDAP" "BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTDmXkyQEJ7ZciyE4KF7wAJKDxMfDAO" "BgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBAFobyhsc7mYoGaA7N4Pp" "it+MQZZNzWte5vWal/3/2V7ZGrJsgeCPwLblzzTmey85RilX6ovMQHEqT1vBFSHq" "nntMZnHkEl2QLU8XopJWR4MXK7LzjjQYaXiZhGbJbtylVSfATAa/ZzdgjBx1C8aD" "IM1+ELGCP/UHD0YEJkFoxSUwXGAXoV8I+cPDAWHC6VnC4mY8qubhx95FpX02ERnz" "1Cw2YWtntyO8P52dEJD1+0EJjtVX4Bj5wwgJHHbDkPP1IzFrR/uBC2LCjtRY+UtZ" "kfoDfWu2tslkLK7/LaC5qZyCPKnpPHLLz8gUWKlvbuejM99FTlBg/tcH+bv5x7WB" "MZ8=" "-----END CERTIFICATE-----"; /* * s2nc is an example client that uses many s2n-tls APIs. * It is intended for testing purposes only, and should not be used in production. */ void usage() { /* clang-format off */ fprintf(stderr, "s2nc is an s2n-tls client testing utility.\n"); fprintf(stderr, "It is not intended for production use.\n"); fprintf(stderr, "\n"); fprintf(stderr, "usage: s2nc [options] host [port]\n"); fprintf(stderr, " host: hostname or IP address to connect to\n"); fprintf(stderr, " port: port to connect to\n"); fprintf(stderr, "\n Options:\n\n"); fprintf(stderr, " -a [protocols]\n"); fprintf(stderr, " --alpn [protocols]\n"); fprintf(stderr, " Sets the application protocols supported by this client, as a comma separated list.\n"); fprintf(stderr, " -c [version_string]\n"); fprintf(stderr, " --ciphers [version_string]\n"); fprintf(stderr, " Set the cipher preference version string. Defaults to \"default\" \n"); fprintf(stderr, " --enter-fips-mode\n"); fprintf(stderr, " Enter libcrypto's FIPS mode. The linked version of OpenSSL must be built with the FIPS module.\n"); fprintf(stderr, " -e,--echo\n"); fprintf(stderr, " Listen to stdin after TLS Connection is established and echo it to the Server\n"); fprintf(stderr, " --send-file [file path]\n"); fprintf(stderr, " Sends the contents of the provided file to the server after connecting.\n"); fprintf(stderr, " -h,--help\n"); fprintf(stderr, " Display this message and quit.\n"); fprintf(stderr, " -n [server name]\n"); fprintf(stderr, " --name [server name]\n"); fprintf(stderr, " Sets the SNI server name header for this client. If not specified, the host value is used.\n"); fprintf(stderr, " -s,--status\n"); fprintf(stderr, " Request the OCSP status of the remote server certificate\n"); fprintf(stderr, " -m,--mfl\n"); fprintf(stderr, " Request maximum fragment length from: 512, 1024, 2048, 4096\n"); fprintf(stderr, " -f,--ca-file [file path]\n"); fprintf(stderr, " Location of trust store CA file (PEM format). If neither -f or -d are specified. System defaults will be used.\n"); fprintf(stderr, " -d,--ca-dir [directory path]\n"); fprintf(stderr, " Directory containing hashed trusted certs. If neither -f or -d are specified. System defaults will be used.\n"); fprintf(stderr, " -i,--insecure\n"); fprintf(stderr, " Turns off certification validation altogether.\n"); fprintf(stderr, " -l,--cert [file path]\n"); fprintf(stderr, " Path to a PEM encoded certificate. Optional. Will only be used for client auth\n"); fprintf(stderr, " -k,--key [file path]\n"); fprintf(stderr, " Path to a PEM encoded private key that matches cert. Will only be used for client auth\n"); fprintf(stderr, " -r,--reconnect\n"); 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"); fprintf(stderr, " -T,--no-session-ticket \n"); fprintf(stderr, " Disable session ticket for resumption.\n"); fprintf(stderr, " --ticket-out [file path]\n"); fprintf(stderr, " Path to a file where the session ticket can be stored.\n"); fprintf(stderr, " --ticket-in [file path]\n"); fprintf(stderr, " Path to session ticket file to resume connection.\n"); fprintf(stderr, " -D,--dynamic\n"); fprintf(stderr, " Set dynamic record resize threshold\n"); fprintf(stderr, " -t,--timeout\n"); fprintf(stderr, " Set dynamic record timeout threshold\n"); fprintf(stderr, " -C,--corked-io\n"); fprintf(stderr, " Turn on corked io\n"); fprintf(stderr, " -B,--non-blocking\n"); fprintf(stderr, " Set the non-blocking flag on the connection's socket.\n"); fprintf(stderr, " -L --key-log \n"); fprintf(stderr, " Enable NSS key logging into the provided path\n"); fprintf(stderr, " -P --psk \n" " A comma-separated list of psk parameters in this order: psk_identity, psk_secret and psk_hmac_alg.\n" " 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" " Ex: --psk psk_id,psk_secret,SHA256 --psk shared_id,shared_secret,SHA384.\n"); fprintf(stderr, " -E ,--early-data \n"); 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.\n"); fprintf(stderr, " --renegotiation [accept|reject|wait]\n" " accept: Accept all server requests for a new handshake\n" " reject: Reject all server requests for a new handshake\n" " wait: Wait for additional application data before accepting server requests. Intended for the integ tests.\n"); fprintf(stderr, " --npn \n"); fprintf(stderr, " Indicates support for the NPN extension. The '--alpn' option MUST be used with this option to signal the protocols supported."); fprintf(stderr, "\n"); fprintf(stderr, " --buffered-send \n"); fprintf(stderr, " Set s2n_send to buffer up to bytes before sending records over the wire.\n"); fprintf(stderr, " --prefer-low-latency\n"); fprintf(stderr, " Prefer low latency by clamping maximum outgoing record size at 1500.\n"); fprintf(stderr, " --prefer-throughput\n"); fprintf(stderr, " Prefer throughput by raising maximum outgoing record size to 16k\n"); /* clang-format on */ exit(1); } size_t session_state_length = 0; uint8_t *session_state = NULL; static int test_session_ticket_cb(struct s2n_connection *conn, void *ctx, struct s2n_session_ticket *ticket) { GUARD_EXIT_NULL(conn); GUARD_EXIT_NULL(ticket); GUARD_EXIT(s2n_session_ticket_get_data_len(ticket, &session_state_length), "Error getting ticket length "); session_state = realloc(session_state, session_state_length); if (session_state == NULL) { print_s2n_error("Error getting new session state"); exit(1); } GUARD_EXIT(s2n_session_ticket_get_data(ticket, session_state_length, session_state), "Error getting ticket data"); bool *session_ticket_recv = (bool *) ctx; *session_ticket_recv = 1; return S2N_SUCCESS; } struct reneg_req_ctx { bool do_renegotiate; bool wait; s2n_renegotiate_response response; }; static int reneg_req_cb(struct s2n_connection *conn, void *context, s2n_renegotiate_response *response) { GUARD_EXIT_NULL(conn); GUARD_EXIT_NULL(context); GUARD_EXIT_NULL(response); struct reneg_req_ctx *reneg_ctx = (struct reneg_req_ctx *) context; *response = reneg_ctx->response; if (*response == S2N_RENEGOTIATE_ACCEPT) { reneg_ctx->do_renegotiate = true; } return S2N_SUCCESS; } static void 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) { if (config == NULL) { print_s2n_error("Error getting new config"); exit(1); } /* The s2n-tls blinding security feature is disabled for testing purposes to make debugging easier. */ GUARD_EXIT(s2n_config_set_max_blinding_delay(config, 0), "Error setting blinding delay"); GUARD_EXIT(s2n_config_set_cipher_preferences(config, cipher_prefs), "Error setting cipher prefs"); GUARD_EXIT(s2n_config_set_status_request_type(config, type), "OCSP validation is not supported by the linked libCrypto implementation. It cannot be set."); if (s2n_config_set_verify_host_callback(config, unsafe_verify_host, unsafe_verify_data) < 0) { print_s2n_error("Error setting host name verification function."); } if (type == S2N_STATUS_REQUEST_OCSP) { if (s2n_config_set_check_stapled_ocsp_response(config, 1)) { print_s2n_error("OCSP validation is not supported by the linked libCrypto implementation. It cannot be set."); } } unsafe_verify_data->trusted_host = host; if (alpn_protocols) { /* Count the number of commas, this tells us how many protocols there are in the list */ const char *ptr = alpn_protocols; int protocol_count = 1; while (*ptr) { if (*ptr == ',') { protocol_count++; } ptr++; } char **protocols = malloc(sizeof(char *) * protocol_count); if (!protocols) { fprintf(stderr, "Error allocating memory\n"); exit(1); } const char *next = alpn_protocols; int idx = 0; int length = 0; ptr = alpn_protocols; while (*ptr) { if (*ptr == ',') { protocols[idx] = malloc(length + 1); if (!protocols[idx]) { fprintf(stderr, "Error allocating memory\n"); exit(1); } memmove(protocols[idx], next, length); protocols[idx][length] = '\0'; length = 0; idx++; ptr++; next = ptr; } else { length++; ptr++; } } if (ptr != next) { protocols[idx] = malloc(length + 1); if (!protocols[idx]) { fprintf(stderr, "Error allocating memory\n"); exit(1); } memmove(protocols[idx], next, length); protocols[idx][length] = '\0'; } GUARD_EXIT(s2n_config_set_protocol_preferences(config, (const char *const *) protocols, protocol_count), "Failed to set protocol preferences"); while (protocol_count) { protocol_count--; free(protocols[protocol_count]); } free(protocols); } uint8_t mfl_code = 0; if (mfl_value > 0) { switch (mfl_value) { case 512: mfl_code = S2N_TLS_MAX_FRAG_LEN_512; break; case 1024: mfl_code = S2N_TLS_MAX_FRAG_LEN_1024; break; case 2048: mfl_code = S2N_TLS_MAX_FRAG_LEN_2048; break; case 4096: mfl_code = S2N_TLS_MAX_FRAG_LEN_4096; break; default: fprintf(stderr, "Invalid maximum fragment length value\n"); exit(1); } } GUARD_EXIT(s2n_config_send_max_fragment_length(config, mfl_code), "Error setting maximum fragment length"); } int main(int argc, char *const *argv) { struct addrinfo hints, *ai_list = NULL, *ai = NULL; int r = 0, sockfd = 0; bool session_ticket_recv = 0; /* Optional args */ const char *alpn_protocols = NULL; const char *server_name = NULL; const char *ca_file = NULL; const char *ca_dir = NULL; const char *client_cert = NULL; const char *client_key = NULL; bool client_cert_input = false; bool client_key_input = false; const char *ticket_out = NULL; char *ticket_in = NULL; const char *serialize_out = NULL; const char *deserialize_in = NULL; uint16_t mfl_value = 0; uint8_t insecure = 0; int reconnect = 0; uint8_t session_ticket = 1; s2n_status_request_type type = S2N_STATUS_REQUEST_NONE; uint32_t dyn_rec_threshold = 0; uint8_t dyn_rec_timeout = 0; /* required args */ const char *cipher_prefs = "default"; int fips_mode = 0; const char *host = NULL; struct verify_data unsafe_verify_data; const char *port = "443"; bool echo_input = false; const char *send_file = NULL; int use_corked_io = 0; uint8_t non_blocking = 0; const char *key_log_path = NULL; FILE *key_log_file = NULL; char *psk_optarg_list[S2N_MAX_PSK_LIST_LENGTH]; size_t psk_list_len = 0; char *early_data = NULL; bool setup_reneg_cb = false; struct reneg_req_ctx reneg_ctx = { 0 }; bool npn = false; uint32_t send_buffer_size = 0; bool prefer_low_latency = false; bool prefer_throughput = false; static struct option long_options[] = { { "alpn", required_argument, 0, 'a' }, { "ciphers", required_argument, 0, 'c' }, { "enter-fips-mode", no_argument, NULL, 'F' }, { "echo", no_argument, 0, 'e' }, { "send-file", required_argument, 0, OPT_SEND_FILE }, { "help", no_argument, 0, 'h' }, { "name", required_argument, 0, 'n' }, { "status", no_argument, 0, 's' }, { "mfl", required_argument, 0, 'm' }, { "ca-file", required_argument, 0, 'f' }, { "ca-dir", required_argument, 0, 'd' }, { "cert", required_argument, 0, 'l' }, { "key", required_argument, 0, 'k' }, { "insecure", no_argument, 0, 'i' }, { "reconnect", no_argument, 0, 'r' }, { "ticket-out", required_argument, 0, OPT_TICKET_OUT }, { "ticket-in", required_argument, 0, OPT_TICKET_IN }, { "no-session-ticket", no_argument, 0, 'T' }, { "serialize-out", required_argument, 0, OPT_SERIALIZE_OUT }, { "deserialize-in", required_argument, 0, OPT_DESERIALIZE_IN }, { "dynamic", required_argument, 0, 'D' }, { "timeout", required_argument, 0, 't' }, { "corked-io", no_argument, 0, 'C' }, { "tls13", no_argument, 0, '3' }, { "non-blocking", no_argument, 0, 'B' }, { "key-log", required_argument, 0, 'L' }, { "psk", required_argument, 0, 'P' }, { "early-data", required_argument, 0, 'E' }, { "renegotiation", required_argument, 0, OPT_RENEG }, { "npn", no_argument, 0, OPT_NPN }, { "buffered-send", required_argument, 0, OPT_BUFFERED_SEND }, { "prefer-low-latency", no_argument, NULL, OPT_PREFER_LOW_LATENCY }, { "prefer-throughput", no_argument, NULL, OPT_PREFER_THROUGHPUT }, { 0 }, }; while (1) { int option_index = 0; int c = getopt_long(argc, argv, "a:c:ehn:m:sf:d:l:k:D:t:irTCBL:P:E:", long_options, &option_index); if (c == -1) { break; } switch (c) { case 'a': alpn_protocols = optarg; break; case 'C': use_corked_io = 1; break; case 'c': cipher_prefs = optarg; break; case 'F': fips_mode = 1; break; case 'e': echo_input = true; break; case OPT_SEND_FILE: send_file = load_file_to_cstring(optarg); break; case 'h': usage(); break; case 'n': server_name = optarg; break; case 's': type = S2N_STATUS_REQUEST_OCSP; break; case 'm': mfl_value = (uint16_t) atoi(optarg); break; case 'f': ca_file = optarg; break; case 'd': ca_dir = optarg; break; case 'l': client_cert = load_file_to_cstring(optarg); client_cert_input = true; break; case 'k': client_key = load_file_to_cstring(optarg); client_key_input = true; break; case 'i': insecure = 1; break; case 'r': reconnect = 5; break; case OPT_TICKET_OUT: ticket_out = optarg; break; case OPT_TICKET_IN: ticket_in = optarg; break; /* The serialize_out and deserialize_in options are not documented * in the usage section as they are not intended to work correctly * using s2nc by itself. s2nc and s2nd are processes which close * their TCP connection upon exit. This will cause an error if one * peer serializes and exits and the other doesn't, as serialization * depends on a continuous TCP connection with the peer. Therefore, our * only usage of this feature is in our integ test framework, * which serializes and deserializes both client and server at the * same time. Do not expect these options to work when using s2nc alone. */ case OPT_SERIALIZE_OUT: serialize_out = optarg; break; case OPT_DESERIALIZE_IN: deserialize_in = optarg; break; case 'T': session_ticket = 0; break; case 't': dyn_rec_timeout = (uint8_t) MIN(255, atoi(optarg)); break; case 'D': errno = 0; dyn_rec_threshold = strtoul(optarg, 0, 10); if (errno == ERANGE) { dyn_rec_threshold = 0; } break; case '3': /* Do nothing -- this argument is deprecated. */ break; case 'B': non_blocking = 1; break; case 'L': key_log_path = optarg; break; case 'P': if (psk_list_len >= S2N_MAX_PSK_LIST_LENGTH) { fprintf(stderr, "Error setting psks, maximum number of psks permitted is 10.\n"); exit(1); } psk_optarg_list[psk_list_len++] = optarg; break; case 'E': early_data = load_file_to_cstring(optarg); GUARD_EXIT_NULL(early_data); break; case OPT_RENEG: setup_reneg_cb = true; if (strcmp(optarg, "accept") == 0) { reneg_ctx.response = S2N_RENEGOTIATE_ACCEPT; } else if (strcmp(optarg, "reject") == 0) { reneg_ctx.response = S2N_RENEGOTIATE_REJECT; } else if (strcmp(optarg, "wait") == 0) { reneg_ctx.response = S2N_RENEGOTIATE_ACCEPT; reneg_ctx.wait = true; } else { fprintf(stderr, "Unrecognized option: %s\n", optarg); exit(1); } break; case OPT_NPN: npn = true; break; case OPT_BUFFERED_SEND: { intmax_t send_buffer_size_scanned_value = strtoimax(optarg, 0, 10); if (send_buffer_size_scanned_value > UINT32_MAX || send_buffer_size_scanned_value < 0) { fprintf(stderr, " must be a positive 32 bit value\n"); exit(1); } send_buffer_size = (uint32_t) send_buffer_size_scanned_value; break; } case OPT_PREFER_LOW_LATENCY: prefer_low_latency = true; break; case OPT_PREFER_THROUGHPUT: prefer_throughput = true; break; case '?': default: usage(); break; } } if (optind < argc) { host = argv[optind++]; } /* cppcheck-suppress duplicateCondition */ if (optind < argc) { port = argv[optind++]; } if (!host) { usage(); } if (!server_name) { server_name = host; } memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { fprintf(stderr, "Error disabling SIGPIPE\n"); exit(1); } if (fips_mode) { #ifndef S2N_INTERN_LIBCRYPTO #if defined(OPENSSL_FIPS) || defined(OPENSSL_IS_AWSLC) if (FIPS_mode_set(1) == 0) { unsigned long fips_rc = ERR_get_error(); char ssl_error_buf[256]; /* Openssl claims you need no more than 120 bytes for error strings */ fprintf(stderr, "s2nc failed to enter FIPS mode with RC: %lu; String: %s\n", fips_rc, ERR_error_string(fips_rc, ssl_error_buf)); exit(1); } printf("s2nc entered FIPS mode\n"); #else fprintf(stderr, "Error entering FIPS mode. s2nc was not built against a FIPS-capable libcrypto.\n"); exit(1); #endif #endif } if (prefer_low_latency && prefer_throughput) { fprintf(stderr, "prefer-throughput and prefer-low-latency options are mutually exclusive\n"); exit(1); } GUARD_EXIT(s2n_init(), "Error running s2n_init()"); printf("libcrypto: %s\n", s2n_libcrypto_get_version_name()); if ((r = getaddrinfo(host, port, &hints, &ai_list)) != 0) { fprintf(stderr, "error: %s\n", gai_strerror(r)); exit(1); } do { int connected = 0; for (ai = ai_list; ai != NULL; ai = ai->ai_next) { if ((sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) { continue; } if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) == -1) { close(sockfd); continue; } connected = 1; /* connect() succeeded */ break; } if (connected == 0) { fprintf(stderr, "Failed to connect to %s:%s\n", host, port); exit(1); } if (non_blocking) { int flags = fcntl(sockfd, F_GETFL, 0); if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { fprintf(stderr, "fcntl error: %s\n", strerror(errno)); exit(1); } } struct s2n_config *config = s2n_config_new(); setup_s2n_config(config, cipher_prefs, type, &unsafe_verify_data, host, alpn_protocols, mfl_value); if (send_buffer_size != 0) { GUARD_EXIT(s2n_config_set_send_buffer_size(config, send_buffer_size), "Error setting send buffer size"); } if (client_cert_input != client_key_input) { print_s2n_error("Client cert/key pair must be given."); } if (client_cert_input) { struct s2n_cert_chain_and_key *chain_and_key = s2n_cert_chain_and_key_new(); GUARD_EXIT(s2n_cert_chain_and_key_load_pem(chain_and_key, client_cert, client_key), "Error getting certificate/key"); GUARD_EXIT(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key), "Error setting certificate/key"); } GUARD_EXIT(s2n_config_add_pem_to_trust_store(config, default_trusted_cert), "Error adding default cert to trust store."); if (ca_file || ca_dir) { GUARD_EXIT(s2n_config_wipe_trust_store(config), "Error wiping trust store"); if (s2n_config_set_verification_ca_location(config, ca_file, ca_dir) < 0) { print_s2n_error("Error setting CA file for trust store."); } } else if (insecure) { GUARD_EXIT(s2n_config_disable_x509_verification(config), "Error disabling X.509 validation"); } if (session_ticket) { GUARD_EXIT(s2n_config_set_session_tickets_onoff(config, 1), "Error enabling session tickets"); GUARD_EXIT(s2n_config_set_session_ticket_cb(config, test_session_ticket_cb, &session_ticket_recv), "Error setting session ticket callback"); session_ticket_recv = 0; } if (key_log_path) { key_log_file = fopen(key_log_path, "a"); GUARD_EXIT(key_log_file == NULL ? S2N_FAILURE : S2N_SUCCESS, "Failed to open key log file"); GUARD_EXIT( s2n_config_set_key_log_cb( config, key_log_callback, (void *) key_log_file), "Failed to set key log callback"); } if (setup_reneg_cb) { GUARD_EXIT(s2n_config_set_renegotiate_request_cb(config, reneg_req_cb, &reneg_ctx), "Error setting renegotiation request callback"); } if (npn) { GUARD_EXIT(s2n_config_set_npn(config, 1), "Error setting npn support"); } if (serialize_out) { GUARD_EXIT(s2n_config_set_serialization_version(config, S2N_SERIALIZED_CONN_V1), "Error setting serialized version"); } struct s2n_connection *conn = s2n_connection_new(S2N_CLIENT); if (conn == NULL) { print_s2n_error("Error getting new connection"); exit(1); } if (deserialize_in) { GUARD_EXIT(s2n_connection_deserialize_in(conn, deserialize_in), "Failed to deserialize file"); } GUARD_EXIT(s2n_connection_set_config(conn, config), "Error setting configuration"); GUARD_EXIT(s2n_set_server_name(conn, server_name), "Error setting server name"); GUARD_EXIT(s2n_connection_set_fd(conn, sockfd), "Error setting file descriptor"); GUARD_EXIT(s2n_connection_set_client_auth_type(conn, S2N_CERT_AUTH_OPTIONAL), "Error setting ClientAuth optional"); if (use_corked_io) { GUARD_EXIT(s2n_connection_use_corked_io(conn), "Error setting corked io"); } /* Read in session ticket from previous session */ if (ticket_in) { GUARD_EXIT(get_file_size(ticket_in, &session_state_length), "Failed to read ticket-in file"); free(session_state); session_state = calloc(session_state_length, sizeof(uint8_t)); GUARD_EXIT_NULL(session_state); GUARD_EXIT(load_file_to_array(ticket_in, session_state, session_state_length), "Failed to read ticket-in file"); } /* Update session state in connection if exists */ if (session_state_length > 0) { GUARD_EXIT(s2n_connection_set_session(conn, session_state, session_state_length), "Error setting session state in connection"); } GUARD_EXIT(s2n_setup_external_psk_list(conn, psk_optarg_list, psk_list_len), "Error setting external psk list"); if (prefer_throughput) { GUARD_RETURN(s2n_connection_prefer_throughput(conn), "Error setting prefer throughput"); } if (prefer_low_latency) { GUARD_RETURN(s2n_connection_prefer_low_latency(conn), "Error setting prefer low latency"); } if (early_data) { if (!session_ticket) { print_s2n_error("Early data can only be used with session tickets."); exit(1); } /* Send early data if we have a received a session ticket from the server */ if (session_state_length) { uint32_t early_data_length = strlen(early_data); GUARD_EXIT(early_data_send(conn, (uint8_t *) early_data, early_data_length), "Error sending early data"); } } if (!deserialize_in && negotiate(conn, sockfd) != 0) { /* Error is printed in negotiate */ S2N_ERROR_PRESERVE_ERRNO(); } printf("Connected to %s:%s\n", host, port); /* Save session state from connection if reconnect is enabled. */ if (reconnect > 0 || ticket_out) { if (conn->actual_protocol_version >= S2N_TLS13) { if (!session_ticket) { print_s2n_error("s2nc can only reconnect in TLS1.3 with session tickets."); exit(1); } GUARD_EXIT(echo(conn, sockfd, &session_ticket_recv), "Error calling echo"); } else { if (!session_ticket && s2n_connection_get_session_id_length(conn) <= 0) { print_s2n_error("Endpoint sent empty session id so cannot resume session"); exit(1); } free(session_state); session_state_length = s2n_connection_get_session_length(conn); session_state = calloc(session_state_length, sizeof(uint8_t)); GUARD_EXIT_NULL(session_state); if (s2n_connection_get_session(conn, session_state, session_state_length) != session_state_length) { print_s2n_error("Error getting serialized session state"); exit(1); } } if (ticket_out) { GUARD_EXIT(write_array_to_file(ticket_out, session_state, session_state_length), "Failed to write to ticket-out file"); } } if (dyn_rec_threshold > 0 && dyn_rec_timeout > 0) { s2n_connection_set_dynamic_record_threshold(conn, dyn_rec_threshold, dyn_rec_timeout); } GUARD_EXIT(s2n_connection_free_handshake(conn), "Error freeing handshake memory after negotiation"); if (send_file != NULL) { printf("Sending file contents:\n%s\n", send_file); unsigned long send_file_len = strlen(send_file); s2n_blocked_status blocked = S2N_NOT_BLOCKED; send_data(conn, sockfd, send_file, send_file_len, &blocked); } while (echo_input) { fflush(stdout); fflush(stderr); echo(conn, sockfd, &reneg_ctx.do_renegotiate); if (!reneg_ctx.do_renegotiate) { break; } reneg_ctx.do_renegotiate = false; GUARD_EXIT(renegotiate(conn, sockfd, reneg_ctx.wait), "Renegotiation failed"); } if (serialize_out) { GUARD_EXIT(s2n_connection_serialize_out(conn, serialize_out), "Error serializing connection"); } else { GUARD_EXIT(wait_for_shutdown(conn, sockfd), "Error closing connection"); } GUARD_EXIT(s2n_connection_free(conn), "Error freeing connection"); GUARD_EXIT(s2n_config_free(config), "Error freeing configuration"); close(sockfd); reconnect--; } while (reconnect >= 0); if (key_log_file) { fclose(key_log_file); } GUARD_EXIT(s2n_cleanup(), "Error running s2n_cleanup()"); free(early_data); free(session_state); freeaddrinfo(ai_list); return 0; }