diff --git a/bin/common.c b/bin/common.c index 9e4a1f739a8..155acfd07a3 100644 --- a/bin/common.c +++ b/bin/common.c @@ -376,6 +376,10 @@ int s2n_set_common_server_config(int max_early_data, struct s2n_config *config, int s2n_setup_server_connection(struct s2n_connection *conn, int fd, struct s2n_config *config, struct conn_settings settings) { + if (settings.deserialize_in) { + GUARD_RETURN(s2n_connection_deserialize_in(conn, settings.deserialize_in), "Failed to deserialize file"); + } + if (settings.self_service_blinding) { s2n_connection_set_blinding(conn, S2N_SELF_SERVICE_BLINDING); } @@ -529,3 +533,30 @@ int wait_for_shutdown(struct s2n_connection *conn, int fd) } return S2N_SUCCESS; } + +int s2n_connection_serialize_out(struct s2n_connection *conn, const char *file_path) +{ + uint32_t serialize_length = 0; + GUARD_RETURN(s2n_connection_serialization_length(conn, &serialize_length), "Failed to get serialized connection length"); + uint8_t *mem = malloc(serialize_length); + GUARD_RETURN_NULL(mem); + GUARD_RETURN(s2n_connection_serialize(conn, mem, serialize_length), "Failed to get serialized connection"); + GUARD_RETURN(write_array_to_file(file_path, mem, serialize_length), "Failed to write serialized connection to file"); + free(mem); + + return 0; +} + +int s2n_connection_deserialize_in(struct s2n_connection *conn, const char *file_path) +{ + size_t deserialize_length = 0; + GUARD_RETURN(get_file_size(file_path, &deserialize_length), "Failed to read deserialize-in file size"); + ENSURE_RETURN(deserialize_length <= UINT32_MAX, "deserialize-in file size is too large"); + uint8_t *mem = malloc(deserialize_length); + GUARD_RETURN_NULL(mem); + GUARD_RETURN(load_file_to_array(file_path, mem, deserialize_length), "Failed to read deserialize-in file"); + GUARD_RETURN(s2n_connection_deserialize(conn, mem, (uint32_t) deserialize_length), "Failed to deserialize connection"); + free(mem); + + return 0; +} diff --git a/bin/common.h b/bin/common.h index 8ea154dc1cc..d0abe04e818 100644 --- a/bin/common.h +++ b/bin/common.h @@ -35,6 +35,22 @@ } \ } while (0) +#define ENSURE_EXIT(x, msg) \ + do { \ + if (!(x)) { \ + fprintf(stderr, "%s\n", msg); \ + exit(1); \ + } \ + } while (0) + +#define GUARD_RETURN_NULL(x) \ + do { \ + if (x == NULL) { \ + fprintf(stderr, "NULL pointer encountered\n"); \ + return -1; \ + } \ + } while (0) + #define GUARD_RETURN(x, msg) \ do { \ if ((x) < 0) { \ @@ -43,6 +59,14 @@ } \ } while (0) +#define ENSURE_RETURN(x, msg) \ + do { \ + if (!(x)) { \ + fprintf(stderr, "%s\n", msg); \ + return -1; \ + } \ + } while (0) + #define S2N_MAX_PSK_LIST_LENGTH 10 #define MAX_KEY_LEN 32 #define MAX_VAL_LEN 255 @@ -74,6 +98,8 @@ struct conn_settings { int max_conns; const char *ca_dir; const char *ca_file; + const char *serialize_out; + const char *deserialize_in; char *psk_optarg_list[S2N_MAX_PSK_LIST_LENGTH]; size_t psk_list_len; }; @@ -130,3 +156,5 @@ int s2n_setup_external_psk_list(struct s2n_connection *conn, char *psk_optarg_li uint8_t unsafe_verify_host(const char *host_name, size_t host_name_len, void *data); int s2n_setup_server_connection(struct s2n_connection *conn, int fd, struct s2n_config *config, struct conn_settings settings); int s2n_set_common_server_config(int max_early_data, struct s2n_config *config, struct conn_settings conn_settings, const char *cipher_prefs, const char *session_ticket_key_file_path); +int s2n_connection_serialize_out(struct s2n_connection *conn, const char *file_path); +int s2n_connection_deserialize_in(struct s2n_connection *conn, const char *file_path); diff --git a/bin/s2nc.c b/bin/s2nc.c index 3afd29ea78b..1c7421d2df4 100644 --- a/bin/s2nc.c +++ b/bin/s2nc.c @@ -44,6 +44,8 @@ #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 /* * s2nc is an example client that uses many s2n-tls APIs. @@ -292,6 +294,8 @@ int main(int argc, char *const *argv) 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; @@ -340,6 +344,8 @@ int main(int argc, char *const *argv) { "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' }, @@ -419,6 +425,22 @@ int main(int argc, char *const *argv) 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; @@ -629,6 +651,11 @@ int main(int argc, char *const *argv) 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) { @@ -636,6 +663,10 @@ int main(int argc, char *const *argv) 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"); @@ -684,8 +715,7 @@ int main(int argc, char *const *argv) } } - /* See echo.c */ - if (negotiate(conn, sockfd) != 0) { + if (!deserialize_in && negotiate(conn, sockfd) != 0) { /* Error is printed in negotiate */ S2N_ERROR_PRESERVE_ERRNO(); } @@ -746,7 +776,11 @@ int main(int argc, char *const *argv) GUARD_EXIT(renegotiate(conn, sockfd, reneg_ctx.wait), "Renegotiation failed"); } - GUARD_EXIT(wait_for_shutdown(conn, sockfd), "Error closing connection"); + 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"); diff --git a/bin/s2nd.c b/bin/s2nd.c index 6e6d29a9e70..886c50b3e35 100644 --- a/bin/s2nd.c +++ b/bin/s2nd.c @@ -131,7 +131,9 @@ static char default_private_key[] = "ggF9KQ0xWz7Km3GXv5+bwM5bcgt1A/s6sZCimXuj3Fle3RqOTF0=" "-----END RSA PRIVATE KEY-----"; -#define OPT_BUFFERED_SEND 1000 +#define OPT_BUFFERED_SEND 1000 +#define OPT_SERIALIZE_OUT 1001 +#define OPT_DESERIALIZE_IN 1002 void usage() { @@ -221,9 +223,9 @@ int handle_connection(int fd, struct s2n_config *config, struct conn_settings se S2N_ERROR_PRESERVE_ERRNO(); } - s2n_setup_server_connection(conn, fd, config, settings); + GUARD_EXIT(s2n_setup_server_connection(conn, fd, config, settings), "Error setting up connection"); - if (negotiate(conn, fd) != S2N_SUCCESS) { + if (!settings.deserialize_in && negotiate(conn, fd) != S2N_SUCCESS) { if (settings.mutual_auth) { if (!s2n_connection_client_cert_used(conn)) { print_s2n_error("Error: Mutual Auth was required, but not negotiated"); @@ -243,7 +245,11 @@ int handle_connection(int fd, struct s2n_config *config, struct conn_settings se echo(conn, fd, &stop_echo); } - GUARD_RETURN(wait_for_shutdown(conn, fd), "Error closing connection"); + if (settings.serialize_out) { + GUARD_RETURN(s2n_connection_serialize_out(conn, settings.serialize_out), "Error serializing connection"); + } else { + GUARD_RETURN(wait_for_shutdown(conn, fd), "Error closing connection"); + } GUARD_RETURN(s2n_connection_wipe(conn), "Error wiping connection"); @@ -307,6 +313,8 @@ int main(int argc, char *const *argv) { "ca-file", required_argument, 0, 't' }, { "insecure", no_argument, 0, 'i' }, { "stk-file", required_argument, 0, 'a' }, + { "serialize-out", required_argument, 0, OPT_SERIALIZE_OUT }, + { "deserialize-in", required_argument, 0, OPT_DESERIALIZE_IN }, { "no-session-ticket", no_argument, 0, 'T' }, { "corked-io", no_argument, 0, 'C' }, { "max-conns", optional_argument, 0, 'X' }, @@ -426,6 +434,22 @@ int main(int argc, char *const *argv) send_buffer_size = (uint32_t) send_buffer_size_scanned_value; break; } + /* The serialize_out and deserialize_in options are not documented + * in the usage section as they are not intended to work correctly + * using s2nd 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 s2nd alone. + */ + case OPT_SERIALIZE_OUT: + conn_settings.serialize_out = optarg; + break; + case OPT_DESERIALIZE_IN: + conn_settings.deserialize_in = optarg; + break; case 'A': alpn = optarg; break; @@ -616,6 +640,11 @@ int main(int argc, char *const *argv) GUARD_EXIT(s2n_config_set_npn(config, 1), "Error setting npn support"); } + if (conn_settings.serialize_out) { + GUARD_EXIT(s2n_config_set_serialization_version(config, S2N_SERIALIZED_CONN_V1), + "Error setting serialized version"); + } + FILE *key_log_file = NULL; if (key_log_path) {