Skip to content

Commit

Permalink
Make alert behavior configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
xonatius committed Sep 25, 2018
1 parent 1f17df9 commit 8d9fd1d
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 83 deletions.
2 changes: 2 additions & 0 deletions api/s2n.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ typedef enum { S2N_STATUS_REQUEST_NONE = 0, S2N_STATUS_REQUEST_OCSP = 1 } s2n_st
extern int s2n_config_set_status_request_type(struct s2n_config *config, s2n_status_request_type type);
typedef enum { S2N_CT_SUPPORT_NONE = 0, S2N_CT_SUPPORT_REQUEST = 1 } s2n_ct_support_level;
extern int s2n_config_set_ct_support_level(struct s2n_config *config, s2n_ct_support_level level);
typedef enum { S2N_ALERT_FAIL_ON_WARNINGS = 0, S2N_ALERT_IGNORE_WARNINGS = 1 } s2n_alert_behavior;
extern int s2n_config_set_alert_behavior(struct s2n_config *config, s2n_alert_behavior alert_behavior);
extern int s2n_config_set_extension_data(struct s2n_config *config, s2n_tls_extension_type type, const uint8_t *data, uint32_t length);
extern int s2n_config_send_max_fragment_length(struct s2n_config *config, s2n_max_frag_len mfl_code);
extern int s2n_config_accept_max_fragment_length(struct s2n_config *config);
Expand Down
8 changes: 8 additions & 0 deletions docs/USAGE-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,14 @@ callback can get any ClientHello infromation from the connection and use
The callback can return 0 to continue handshake in s2n or it can return negative
value to make s2n terminate handshake early with fatal handshake failure alert.
### s2n\_config\_set\_alert\_behavior
```c
int s2n_config_set_alert_behavior(struct s2n_config *config, s2n_alert_behavior alert_behavior);
```
Sets whether or not a should terminate connection on WARNING alert from peer. `alert_behavior` can take the following values:
- `S2N_ALERT_FAIL_ON_WARNINGS` - default behavior: s2n will terminate conneciton if peer sends WARNING alert.
- `S2N_ALERT_IGNORE_WARNINGS` - with the exception of `close_notify` s2n will ignore all WARNING alerts and keep communicating with its peer.

## Client Auth Related calls
Client Auth Related API's are not recommended for normal users. Use of these API's is discouraged.

Expand Down
3 changes: 2 additions & 1 deletion error/s2n_errno.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ struct s2n_error_translation EN[] = {
{S2N_ERR_ENCRYPT_DECRYPT_KEY_SELECTION_FAILED, "Failed to select a key from keys in encrypt-decrypt state"},
{S2N_ERR_KEY_USED_IN_SESSION_TICKET_NOT_FOUND, "Key used in already assigned session ticket not found for decryption"},
{S2N_ERR_SENDING_NST, "Error in session ticket status encountered before sending NST"},
{S2N_ERR_INVALID_DYNAMIC_THRESHOLD, "invalid dynamic record threshold"}
{S2N_ERR_INVALID_DYNAMIC_THRESHOLD, "invalid dynamic record threshold"},
{S2N_ERR_INVALID_ARGUMENT, "invalid argument provided into a function call"},
};

const char *s2n_strerror(int error, const char *lang)
Expand Down
1 change: 1 addition & 0 deletions error/s2n_errno.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ typedef enum {
S2N_ERR_KEY_USED_IN_SESSION_TICKET_NOT_FOUND,
S2N_ERR_SENDING_NST,
S2N_ERR_INVALID_DYNAMIC_THRESHOLD,
S2N_ERR_INVALID_ARGUMENT,
} s2n_error;

#define S2N_DEBUG_STR_LEN 128
Expand Down
219 changes: 139 additions & 80 deletions tests/unit/s2n_self_talk_alerts_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct alert_ctx {
uint8_t code;
};

int mock_client(int writefd, int readfd, int expect_failure)
int mock_client(int writefd, int readfd, s2n_alert_behavior alert_behavior, int expect_failure)
{
struct s2n_connection *conn;
struct s2n_config *config;
Expand All @@ -57,6 +57,7 @@ int mock_client(int writefd, int readfd, int expect_failure)
conn = s2n_connection_new(S2N_CLIENT);
config = s2n_config_new();
s2n_config_disable_x509_verification(config);
s2n_config_set_alert_behavior(config, alert_behavior);
s2n_connection_set_config(conn, config);

s2n_connection_set_read_fd(conn, readfd);
Expand Down Expand Up @@ -145,122 +146,180 @@ int main(int argc, char **argv)

EXPECT_SUCCESS(setenv("S2N_ENABLE_CLIENT_MODE", "1", 0));

EXPECT_NOT_NULL(config = s2n_config_new());

EXPECT_NOT_NULL(cert_chain_pem = malloc(S2N_MAX_TEST_PEM_SIZE));
EXPECT_NOT_NULL(private_key_pem = malloc(S2N_MAX_TEST_PEM_SIZE));
EXPECT_SUCCESS(s2n_read_test_pem(S2N_DEFAULT_TEST_CERT_CHAIN, cert_chain_pem, S2N_MAX_TEST_PEM_SIZE));
EXPECT_SUCCESS(s2n_read_test_pem(S2N_DEFAULT_TEST_PRIVATE_KEY, private_key_pem, S2N_MAX_TEST_PEM_SIZE));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key(config, cert_chain_pem, private_key_pem));

/* Test that we ignore Warning Alerts */
/* Create a pipe */
EXPECT_SUCCESS(pipe(server_to_client));
EXPECT_SUCCESS(pipe(client_to_server));
/* Test that we ignore Warning Alerts in S2N_ALERT_IGNORE_WARNINGS mode */
{
EXPECT_NOT_NULL(config = s2n_config_new());
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key(config, cert_chain_pem, private_key_pem));

/* Set up the callback to send an alert after receiving ClientHello */
struct alert_ctx warning_alert = {.write_fd = server_to_client[1], .invoked = 0, .level = TLS_ALERT_LEVEL_WARNING, .code = TLS_ALERT_UNRECOGNIZED_NAME};
EXPECT_SUCCESS(s2n_config_set_client_hello_cb(config, client_hello_send_alert, &warning_alert));
/* Create a pipe */
EXPECT_SUCCESS(pipe(server_to_client));
EXPECT_SUCCESS(pipe(client_to_server));

/* Create a child process */
pid = fork();
if (pid == 0) {
/* This is the child process, close the read end of the pipe */
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));
/* Set up the callback to send an alert after receiving ClientHello */
struct alert_ctx warning_alert = {.write_fd = server_to_client[1], .invoked = 0, .level = TLS_ALERT_LEVEL_WARNING, .code = TLS_ALERT_UNRECOGNIZED_NAME};
EXPECT_SUCCESS(s2n_config_set_client_hello_cb(config, client_hello_send_alert, &warning_alert));

mock_client(client_to_server[1], server_to_client[0], 0);
}
/* Create a child process */
pid = fork();
if (pid == 0) {
/* This is the child process, close the read end of the pipe */
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));

/* This is the parent */
EXPECT_SUCCESS(close(client_to_server[1]));
EXPECT_SUCCESS(close(server_to_client[0]));
mock_client(client_to_server[1], server_to_client[0], S2N_ALERT_IGNORE_WARNINGS, 0);
}

EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER));
EXPECT_SUCCESS(s2n_connection_set_config(conn, config));
/* This is the parent */
EXPECT_SUCCESS(close(client_to_server[1]));
EXPECT_SUCCESS(close(server_to_client[0]));

/* Set up the connection to read from the fd */
EXPECT_SUCCESS(s2n_connection_set_read_fd(conn, client_to_server[0]));
EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, server_to_client[1]));
EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER));
EXPECT_SUCCESS(s2n_connection_set_config(conn, config));

/* Negotiate the handshake. */
EXPECT_SUCCESS(s2n_negotiate(conn, &blocked));
/* Set up the connection to read from the fd */
EXPECT_SUCCESS(s2n_connection_set_read_fd(conn, client_to_server[0]));
EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, server_to_client[1]));

/* Ensure that callback was invoked */
EXPECT_EQUAL(warning_alert.invoked, 1);
/* Negotiate the handshake. */
EXPECT_SUCCESS(s2n_negotiate(conn, &blocked));

for (int i = 1; i < 0xffff; i += 100) {
char * ptr = buffer;
int size = i;
/* Ensure that callback was invoked */
EXPECT_EQUAL(warning_alert.invoked, 1);

do {
int bytes_read = 0;
EXPECT_SUCCESS(bytes_read = s2n_recv(conn, ptr, size, &blocked));
for (int i = 1; i < 0xffff; i += 100) {
char * ptr = buffer;
int size = i;

size -= bytes_read;
ptr += bytes_read;
} while(size);
do {
int bytes_read = 0;
EXPECT_SUCCESS(bytes_read = s2n_recv(conn, ptr, size, &blocked));

for (int j = 0; j < i; j++) {
EXPECT_EQUAL(buffer[j], 33);
size -= bytes_read;
ptr += bytes_read;
} while(size);

for (int j = 0; j < i; j++) {
EXPECT_EQUAL(buffer[j], 33);
}
}

EXPECT_SUCCESS(s2n_shutdown(conn, &blocked));
EXPECT_SUCCESS(s2n_connection_free(conn));
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));

/* Clean up */
EXPECT_EQUAL(waitpid(-1, &status, 0), pid);
EXPECT_EQUAL(status, 0);
EXPECT_SUCCESS(s2n_config_free(config));
}

EXPECT_SUCCESS(s2n_shutdown(conn, &blocked));
EXPECT_SUCCESS(s2n_connection_free(conn));
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));
/* Test that we don't ignore Fatal Alerts in S2N_ALERT_IGNORE_WARNINGS mode */
{
EXPECT_NOT_NULL(config = s2n_config_new());
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key(config, cert_chain_pem, private_key_pem));

/* Create a pipe */
EXPECT_SUCCESS(pipe(server_to_client));
EXPECT_SUCCESS(pipe(client_to_server));

/* Set up the callback to send an alert after receiving ClientHello */
struct alert_ctx fatal_alert = {.write_fd = server_to_client[1], .invoked = 0, .level = TLS_ALERT_LEVEL_FATAL, .code = TLS_ALERT_UNRECOGNIZED_NAME};
EXPECT_SUCCESS(s2n_config_set_client_hello_cb(config, client_hello_send_alert, &fatal_alert));

/* Clean up */
EXPECT_EQUAL(waitpid(-1, &status, 0), pid);
EXPECT_EQUAL(status, 0);
/* Create a child process */
pid = fork();
if (pid == 0) {
/* This is the child process, close the read end of the pipe */
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));

/* Test that we don't ignore Fatal Alerts */
/* Create a pipe */
EXPECT_SUCCESS(pipe(server_to_client));
EXPECT_SUCCESS(pipe(client_to_server));
mock_client(client_to_server[1], server_to_client[0], S2N_ALERT_IGNORE_WARNINGS, 1);
}

/* This is the parent */
EXPECT_SUCCESS(close(client_to_server[1]));
EXPECT_SUCCESS(close(server_to_client[0]));

EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER));
EXPECT_SUCCESS(s2n_connection_set_config(conn, config));

/* Set up the connection to read from the fd */
EXPECT_SUCCESS(s2n_connection_set_read_fd(conn, client_to_server[0]));
EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, server_to_client[1]));

/* Set up the callback to send an alert after receiving ClientHello */
struct alert_ctx fatal_alert = {.write_fd = server_to_client[1], .invoked = 0, .level = TLS_ALERT_LEVEL_FATAL, .code = TLS_ALERT_UNRECOGNIZED_NAME};
EXPECT_SUCCESS(s2n_config_set_client_hello_cb(config, client_hello_send_alert, &fatal_alert));
/* Negotiate the handshake. */
EXPECT_FAILURE(s2n_negotiate(conn, &blocked));

/* Create a child process */
pid = fork();
if (pid == 0) {
/* This is the child process, close the read end of the pipe */
/* Ensure that callback was invoked */
EXPECT_EQUAL(fatal_alert.invoked, 1);

EXPECT_SUCCESS(s2n_connection_free(conn));
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));

mock_client(client_to_server[1], server_to_client[0], 1);
/* Clean up */
EXPECT_EQUAL(waitpid(-1, &status, 0), pid);
EXPECT_EQUAL(status, 0);
EXPECT_SUCCESS(s2n_config_free(config));
}

/* This is the parent */
EXPECT_SUCCESS(close(client_to_server[1]));
EXPECT_SUCCESS(close(server_to_client[0]));
/* Test that we don't ignore Warning Alerts in S2N_ALERT_FAIL_ON_WARNINGS mode */
{
EXPECT_NOT_NULL(config = s2n_config_new());
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key(config, cert_chain_pem, private_key_pem));

/* Create a pipe */
EXPECT_SUCCESS(pipe(server_to_client));
EXPECT_SUCCESS(pipe(client_to_server));

/* Set up the callback to send an alert after receiving ClientHello */
struct alert_ctx warning_alert = {.write_fd = server_to_client[1], .invoked = 0, .level = TLS_ALERT_LEVEL_WARNING, .code = TLS_ALERT_UNRECOGNIZED_NAME};
EXPECT_SUCCESS(s2n_config_set_client_hello_cb(config, client_hello_send_alert, &warning_alert));

EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER));
EXPECT_SUCCESS(s2n_connection_set_config(conn, config));
/* Create a child process */
pid = fork();
if (pid == 0) {
/* This is the child process, close the read end of the pipe */
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));

/* Set up the connection to read from the fd */
EXPECT_SUCCESS(s2n_connection_set_read_fd(conn, client_to_server[0]));
EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, server_to_client[1]));
mock_client(client_to_server[1], server_to_client[0], S2N_ALERT_FAIL_ON_WARNINGS, 1);
}

/* This is the parent */
EXPECT_SUCCESS(close(client_to_server[1]));
EXPECT_SUCCESS(close(server_to_client[0]));

/* Negotiate the handshake. */
EXPECT_FAILURE(s2n_negotiate(conn, &blocked));
EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER));
EXPECT_SUCCESS(s2n_connection_set_config(conn, config));

/* Ensure that callback was invoked */
EXPECT_EQUAL(fatal_alert.invoked, 1);
/* Set up the connection to read from the fd */
EXPECT_SUCCESS(s2n_connection_set_read_fd(conn, client_to_server[0]));
EXPECT_SUCCESS(s2n_connection_set_write_fd(conn, server_to_client[1]));

EXPECT_SUCCESS(s2n_connection_free(conn));
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));
/* Negotiate the handshake. */
EXPECT_FAILURE(s2n_negotiate(conn, &blocked));

/* Clean up */
EXPECT_EQUAL(waitpid(-1, &status, 0), pid);
EXPECT_EQUAL(status, 0);
/* Ensure that callback was invoked */
EXPECT_EQUAL(warning_alert.invoked, 1);

EXPECT_SUCCESS(s2n_connection_free(conn));
EXPECT_SUCCESS(close(client_to_server[0]));
EXPECT_SUCCESS(close(server_to_client[1]));

/* Clean up */
EXPECT_EQUAL(waitpid(-1, &status, 0), pid);
EXPECT_EQUAL(status, 0);
EXPECT_SUCCESS(s2n_config_free(config));
}

/* Shutdown */
EXPECT_SUCCESS(s2n_config_free(config));
free(cert_chain_pem);
free(private_key_pem);

Expand Down
5 changes: 3 additions & 2 deletions tls/s2n_alerts.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ int s2n_process_alert_fragment(struct s2n_connection *conn)
return 0;
}

/* Ignore warning-level alerts */
if (conn->alert_in_data[0] == S2N_TLS_ALERT_LEVEL_WARNING) {
/* Ignore warning-level alerts if we're in warning-tolerant mode */
if (conn->config->alert_behavior == S2N_ALERT_IGNORE_WARNINGS &&
conn->alert_in_data[0] == S2N_TLS_ALERT_LEVEL_WARNING) {
return 0;
}

Expand Down
17 changes: 17 additions & 0 deletions tls/s2n_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ static int s2n_config_init(struct s2n_config *config)
config->cache_delete_data = NULL;
config->ct_type = S2N_CT_SUPPORT_NONE;
config->mfl_code = S2N_TLS_MAX_FRAG_LEN_EXT_NONE;
config->alert_behavior = S2N_ALERT_FAIL_ON_WARNINGS;
config->accept_mfl = 0;
config->session_state_lifetime_in_nanos = S2N_STATE_LIFETIME_IN_NANOS;
config->use_tickets = 0;
Expand Down Expand Up @@ -363,6 +364,22 @@ int s2n_config_set_ct_support_level(struct s2n_config *config, s2n_ct_support_le
return 0;
}

int s2n_config_set_alert_behavior(struct s2n_config *config, s2n_alert_behavior alert_behavior)
{
notnull_check(config);

switch (alert_behavior) {
case S2N_ALERT_FAIL_ON_WARNINGS:
case S2N_ALERT_IGNORE_WARNINGS:
config->alert_behavior = alert_behavior;
break;
default:
S2N_ERROR(S2N_ERR_INVALID_ARGUMENT);
}

return 0;
}

int s2n_config_set_verify_host_callback(struct s2n_config *config, s2n_verify_host_fn verify_host_fn, void *data)
{
notnull_check(config);
Expand Down
2 changes: 2 additions & 0 deletions tls/s2n_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ struct s2n_config {

s2n_cert_auth_type client_cert_auth_type;

s2n_alert_behavior alert_behavior;

/* Return TRUE if the host should be trusted, If FALSE this will likely be called again for every host/alternative name
* in the certificate. If any respond TRUE. If none return TRUE, the cert will be considered untrusted. */
uint8_t (*verify_host) (const char *host_name, size_t host_name_len, void *data);
Expand Down

0 comments on commit 8d9fd1d

Please sign in to comment.