Skip to content

Commit

Permalink
Attempts to fix flakiness in session_ticket_test (#3913)
Browse files Browse the repository at this point in the history
  • Loading branch information
maddeleine authored Apr 11, 2023
1 parent 818a092 commit 49097f4
Showing 1 changed file with 117 additions and 69 deletions.
186 changes: 117 additions & 69 deletions tests/unit/s2n_session_ticket_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#define S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS (S2N_TICKET_ENCRYPT_DECRYPT_KEY_LIFETIME_IN_NANOS + S2N_TICKET_DECRYPT_KEY_LIFETIME_IN_NANOS) / ONE_SEC_IN_NANOS
#define S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES S2N_STATE_FORMAT_LEN + S2N_SESSION_TICKET_SIZE_LEN
#define ONE_SEC_DELAY 1

#define S2N_CLOCK_SYS CLOCK_REALTIME

Expand Down Expand Up @@ -195,9 +196,11 @@ int main(int argc, char **argv)
/* Set session state lifetime for 15 hours which is equal to the default lifetime of a ticket key */
EXPECT_SUCCESS(s2n_config_set_session_state_lifetime(server_config, S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS));

/* Add one ST key */
/* Add one session ticket key with an intro time in the past so that the key is immediately valid */
POSIX_GUARD(server_config->wall_clock(server_config->sys_clock_ctx, &now));
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1), ticket_key1, s2n_array_len(ticket_key1), now / ONE_SEC_IN_NANOS));
uint64_t key_intro_time = (now / ONE_SEC_IN_NANOS) - ONE_SEC_DELAY;
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1),
ticket_key1, s2n_array_len(ticket_key1), key_intro_time));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

Expand Down Expand Up @@ -286,8 +289,11 @@ int main(int argc, char **argv)
/* Set session state lifetime for 15 hours which is equal to the default lifetime of a ticket key */
EXPECT_SUCCESS(s2n_config_set_session_state_lifetime(server_config, S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS));

/* Add one ST key */
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1), ticket_key1, s2n_array_len(ticket_key1), 0));
/* Add one session ticket key with an intro time in the past so that the key is immediately valid */
POSIX_GUARD(server_config->wall_clock(server_config->sys_clock_ctx, &now));
uint64_t key_intro_time = (now / ONE_SEC_IN_NANOS) - ONE_SEC_DELAY;
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1),
ticket_key1, s2n_array_len(ticket_key1), key_intro_time));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

Expand Down Expand Up @@ -349,8 +355,11 @@ int main(int argc, char **argv)
uint64_t mock_delay = server_config->encrypt_decrypt_key_lifetime_in_nanos;
EXPECT_SUCCESS(s2n_config_set_wall_clock(server_config, mock_nanoseconds_since_epoch, &mock_delay));

/* Add a second ST key */
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name2, s2n_array_len(ticket_key_name2), ticket_key2, s2n_array_len(ticket_key2), 0));
/* Add one session ticket key with an intro time in the past so that the key is immediately valid */
POSIX_GUARD(server_config->wall_clock(server_config->sys_clock_ctx, &now));
uint64_t key_intro_time = (now / ONE_SEC_IN_NANOS) - ONE_SEC_DELAY;
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name2, s2n_array_len(ticket_key_name2),
ticket_key2, s2n_array_len(ticket_key2), key_intro_time));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

Expand Down Expand Up @@ -439,8 +448,8 @@ int main(int argc, char **argv)
EXPECT_SUCCESS(s2n_config_free(client_config));
};

/* Client sends non-empty ST extension. Server does a
* full handshake and issues a NST because the key is not found.
/* Client sends non-empty ST extension. Server does a full handshake and issues
* a NST because the key used to encrypt the session ticket is not found.
*/
{
EXPECT_NOT_NULL(client_config = s2n_config_new());
Expand All @@ -465,7 +474,11 @@ int main(int argc, char **argv)
/* Set session state lifetime for 15 hours which is equal to the default lifetime of a ticket key */
EXPECT_SUCCESS(s2n_config_set_session_state_lifetime(server_config, S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS));

EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1), ticket_key1, s2n_array_len(ticket_key1), 0));
/* Add one session ticket key with an intro time in the past so that the key is immediately valid */
POSIX_GUARD(server_config->wall_clock(server_config->sys_clock_ctx, &now));
uint64_t key_intro_time = (now / ONE_SEC_IN_NANOS) - ONE_SEC_DELAY;
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1),
ticket_key1, s2n_array_len(ticket_key1), key_intro_time));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

Expand Down Expand Up @@ -525,8 +538,11 @@ int main(int argc, char **argv)
uint64_t mock_delay = server_config->decrypt_key_lifetime_in_nanos + server_config->encrypt_decrypt_key_lifetime_in_nanos;
EXPECT_SUCCESS(s2n_config_set_wall_clock(server_config, mock_nanoseconds_since_epoch, &mock_delay));

/* Add a second ST key */
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name2, s2n_array_len(ticket_key_name2), ticket_key2, s2n_array_len(ticket_key2), 0));
/* Add a second session ticket key with an intro time in the past so that the key is immediately valid */
POSIX_GUARD(server_config->wall_clock(server_config->sys_clock_ctx, &now));
uint64_t key_intro_time = (now / ONE_SEC_IN_NANOS) - ONE_SEC_DELAY;
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name2, s2n_array_len(ticket_key_name2),
ticket_key2, s2n_array_len(ticket_key2), key_intro_time));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

Expand Down Expand Up @@ -594,7 +610,11 @@ int main(int argc, char **argv)
/* Set session state lifetime for 15 hours which is equal to the default lifetime of a ticket key */
EXPECT_SUCCESS(s2n_config_set_session_state_lifetime(server_config, S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS));

EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1), ticket_key1, s2n_array_len(ticket_key1), 0));
/* Add a session ticket key with an intro time in the past so that the key is immediately valid */
POSIX_GUARD(server_config->wall_clock(server_config->sys_clock_ctx, &now));
uint64_t key_intro_time = (now / ONE_SEC_IN_NANOS) - ONE_SEC_DELAY;
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1),
ticket_key1, s2n_array_len(ticket_key1), key_intro_time));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

Expand Down Expand Up @@ -642,7 +662,6 @@ int main(int argc, char **argv)
EXPECT_SUCCESS(s2n_connections_set_io_pair(client_conn, server_conn, &io_pair));

/* Not enabling resumption using ST */

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
Expand Down Expand Up @@ -793,63 +812,92 @@ int main(int argc, char **argv)

/* Scenario 2: Client sends empty ST and server has multiple encrypt-decrypt keys to choose from for encrypting NST */
{
EXPECT_NOT_NULL(client_config = s2n_config_new());
EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(client_config, 1));
EXPECT_SUCCESS(s2n_config_disable_x509_verification(client_config));
EXPECT_NOT_NULL(client_conn = s2n_connection_new(S2N_CLIENT));

EXPECT_SUCCESS(s2n_connection_set_config(client_conn, client_config));

EXPECT_NOT_NULL(server_conn = s2n_connection_new(S2N_SERVER));

EXPECT_NOT_NULL(server_config = s2n_config_new());
EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(server_config, 1));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(server_config, chain_and_key));

/* Create nonblocking pipes */
EXPECT_SUCCESS(s2n_connections_set_io_pair(client_conn, server_conn, &io_pair));

/* Set session state lifetime for 15 hours which is equal to the default lifetime of a ticket key */
EXPECT_SUCCESS(s2n_config_set_session_state_lifetime(server_config, S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS));

/* Add one ST key */
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1), ticket_key1, s2n_array_len(ticket_key1), 0));

/* Add second key when the first key is very close to it's encryption peak */
uint64_t mock_delay = (server_config->encrypt_decrypt_key_lifetime_in_nanos / 2) - ONE_SEC_IN_NANOS;
EXPECT_SUCCESS(s2n_config_set_wall_clock(server_config, mock_nanoseconds_since_epoch, &mock_delay));
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name2, s2n_array_len(ticket_key_name2), ticket_key2, s2n_array_len(ticket_key2), 0));

/* Add third key when the second key is very close to it's encryption peak and
* the first key is about to transition from encrypt-decrypt state to decrypt-only state
const size_t allowed_failures = 1;
size_t failures = 0;
bool expected_key_chosen = false;

/* This test sets up three different ticket encryption keys at various times in their encryption lifetime. The test
* is meant to check that the weighted random selection algorithm correctly selects the key that is at its
* encryption peak. However the test will sometimes pick a key that is not at its encryption peak because the
* selection function uses a weighted random selection algorithm. Here we retry the test once if the key chosen
* is not the expected key.
*
* The wrong key will be chosen 0.02% of the time. This value is drawn from the weight of the expected key,
* which does not change per test run. Therefore, the probability that the test chooses the wrong key
* more than allowed_failures times is 0.0002 ^ 2 = 0.00000004, which is extremely unlikely to occur. If
* the logic changes to chose the wrong key at a higher rate, say 50% of the time, this test would fail at a
* 0.5 ^ 2 = 0.25 or 25% of the time. This rate is high enough for us to notice and investigate.
*/
mock_delay = server_config->encrypt_decrypt_key_lifetime_in_nanos - ONE_SEC_IN_NANOS;
EXPECT_SUCCESS(s2n_config_set_wall_clock(server_config, mock_nanoseconds_since_epoch, &mock_delay));
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name3, s2n_array_len(ticket_key_name3), ticket_key3, s2n_array_len(ticket_key3), 0));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));

/* Verify that the server did a full handshake and issued NST */
EXPECT_TRUE(IS_FULL_HANDSHAKE(server_conn));
EXPECT_TRUE(IS_ISSUING_NEW_SESSION_TICKET(server_conn));

/* Verify that the client received NST which is encrypted using a key which is at it's peak encryption */
serialized_session_state_length = s2n_connection_get_session_length(client_conn);
EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length);
EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2, s2n_array_len(ticket_key_name2));

/* Verify the lifetime hint from the server */
EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS);

EXPECT_SUCCESS(s2n_shutdown_test_server_and_client(server_conn, client_conn));

EXPECT_SUCCESS(s2n_connection_free(server_conn));
EXPECT_SUCCESS(s2n_connection_free(client_conn));

EXPECT_SUCCESS(s2n_config_free(server_config));
EXPECT_SUCCESS(s2n_config_free(client_config));
while (expected_key_chosen == false) {
EXPECT_TRUE(failures <= allowed_failures);

EXPECT_NOT_NULL(client_config = s2n_config_new());
EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(client_config, 1));
EXPECT_SUCCESS(s2n_config_disable_x509_verification(client_config));
EXPECT_NOT_NULL(client_conn = s2n_connection_new(S2N_CLIENT));

EXPECT_SUCCESS(s2n_connection_set_config(client_conn, client_config));

EXPECT_NOT_NULL(server_conn = s2n_connection_new(S2N_SERVER));

EXPECT_NOT_NULL(server_config = s2n_config_new());
EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(server_config, 1));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(server_config, chain_and_key));

/* Create nonblocking pipes */
EXPECT_SUCCESS(s2n_connections_set_io_pair(client_conn, server_conn, &io_pair));

/* Set session state lifetime for 15 hours which is equal to the default lifetime of a ticket key */
EXPECT_SUCCESS(s2n_config_set_session_state_lifetime(server_config, S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS));

/* Add one ST key */
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1),
ticket_key1, s2n_array_len(ticket_key1), 0));

/* Add second key when the first key is very close to it's encryption peak */
uint64_t mock_delay = (server_config->encrypt_decrypt_key_lifetime_in_nanos / 2) - ONE_SEC_IN_NANOS;
EXPECT_SUCCESS(s2n_config_set_wall_clock(server_config, mock_nanoseconds_since_epoch, &mock_delay));
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name2, s2n_array_len(ticket_key_name2),
ticket_key2, s2n_array_len(ticket_key2), 0));

/* Add third key when the second key is very close to it's encryption peak and
* the first key is about to transition from encrypt-decrypt state to decrypt-only state
*/
mock_delay = server_config->encrypt_decrypt_key_lifetime_in_nanos - ONE_SEC_IN_NANOS;
EXPECT_SUCCESS(s2n_config_set_wall_clock(server_config, mock_nanoseconds_since_epoch, &mock_delay));
EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name3, s2n_array_len(ticket_key_name3),
ticket_key3, s2n_array_len(ticket_key3), 0));

EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config));

EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));

/* Verify that the server did a full handshake and issued NST */
EXPECT_TRUE(IS_FULL_HANDSHAKE(server_conn));
EXPECT_TRUE(IS_ISSUING_NEW_SESSION_TICKET(server_conn));

/* Verify that the client received NST which is encrypted using a key which is at it's peak encryption */
serialized_session_state_length = s2n_connection_get_session_length(client_conn);
EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length),
serialized_session_state_length);
int result = memcmp(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2,
s2n_array_len(ticket_key_name2));
if (result == 0) {
expected_key_chosen = true;
} else {
failures += 1;
}
/* Verify the lifetime hint from the server */
EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS);

EXPECT_SUCCESS(s2n_shutdown_test_server_and_client(server_conn, client_conn));

EXPECT_SUCCESS(s2n_connection_free(server_conn));
EXPECT_SUCCESS(s2n_connection_free(client_conn));

EXPECT_SUCCESS(s2n_config_free(server_config));
EXPECT_SUCCESS(s2n_config_free(client_config));
}
};

/* Testing s2n_config_set_ticket_encrypt_decrypt_key_lifetime and
Expand Down

0 comments on commit 49097f4

Please sign in to comment.