-
Notifications
You must be signed in to change notification settings - Fork 718
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: calculation of session ticket age #5001
base: main
Are you sure you want to change the base?
Conversation
* checked the chosen psk keying material expiration.
Why not? |
I will do it in this PR. |
* add reference check for conn->config * makes the s2n_generate_ticket_lifetime function more readable * fix the chosen_psk logic
tls/s2n_server_new_session_ticket.c
Outdated
uint32_t session_lifetime_in_secs = conn->config->session_state_lifetime_in_nanos / ONE_SEC_IN_NANOS; | ||
struct s2n_psk *chosen_psk = conn->psk_params.chosen_psk; | ||
uint32_t psk_keying_material_lifetime_in_secs = UINT32_MAX; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, not really following this. Why are you setting it to UINT32_MAX?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally, I want to set UINT32_MAX as the default for the PSK keying material lifetime. If PSK doesn't exist, then it wouldn't be updated. Subsequently, it will always be larger than the rest variables. This method is hard to understand, and I will improve this logic.
* assert unsigned integer subtractions * fix testing: ensure that keying material expiration is one week after current time
* use s2n_stuffer_reserve_uint32 to skip the session ticket lifetime calculation. * add s2n_stuffer_reserve_uint32 to s2n_stuff.h
uint32_t key_and_session_min_lifetime = MIN(key_lifetime_in_secs, session_lifetime_in_secs); | ||
uint32_t key_session_and_psk_keying_material_min_lifetime = key_and_session_min_lifetime; | ||
struct s2n_psk *chosen_psk = conn->psk_params.chosen_psk; | ||
if (chosen_psk && chosen_psk->type == S2N_PSK_TYPE_RESUMPTION) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to incorporate the server_keying_material_lifetime in this calculation? I'm honestly not sure.
uint8_t selected_ticket_key_name[S2N_TICKET_KEY_NAME_LEN] = { 0 }; | ||
EXPECT_NOT_NULL(memcpy(selected_ticket_key_name, ticket_key_name1, s2n_array_len(ticket_key_name1))); | ||
struct s2n_ticket_key *key = s2n_find_ticket_key(server_config, selected_ticket_key_name); | ||
uint64_t ticket_key_age_in_nanos = server_conn->ticket_fields.current_time - key->intro_timestamp; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you already have the intro timestamp in key_intro_time? What's the difference between that value and key->intro_timestamp?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between that value and key->intro_timestamp?
The difference is that key_intro_time
variable is recording the intro time in seconds. That variable is passed into s2n_config_add_ticket_crypto_key()
and the the intro time is then converted into nanoseconds in that function. This is how the function handles this:
Lines 940 to 943 in efb3500
int s2n_config_add_ticket_crypto_key(struct s2n_config *config, | |
const uint8_t *name, uint32_t name_len, | |
uint8_t *key, uint32_t key_len, | |
uint64_t intro_time_in_seconds_from_epoch) |
Lines 1015 to 1021 in efb3500
if (intro_time_in_seconds_from_epoch == 0) { | |
uint64_t now = 0; | |
POSIX_GUARD_RESULT(s2n_config_wall_clock(config, &now)); | |
session_ticket_key->intro_timestamp = now; | |
} else { | |
session_ticket_key->intro_timestamp = (intro_time_in_seconds_from_epoch * ONE_SEC_IN_NANOS); | |
} |
We need the key_intro_time in nanoseconds, and that's why I queried the key and acquire the intro time from the key.
I figured out another way to handle this is to simply convert key_intro_time
from seconds to nanoseconds by doing:
key_intro_time * ONE_SEC_IN_NANOS
That will potentially improve the readability. I think I can do that simple calculation for every elifible key_intro_time query in this test. There are some cases later in this test that still needs to query the key in order to get the key intro time, because they didn't record the key_intro_time
variable.
uint32_t key_lifetime_in_secs = | ||
(S2N_TICKET_ENCRYPT_DECRYPT_KEY_LIFETIME_IN_NANOS + S2N_TICKET_DECRYPT_KEY_LIFETIME_IN_NANOS) / ONE_SEC_IN_NANOS; | ||
(S2N_TICKET_ENCRYPT_DECRYPT_KEY_LIFETIME_IN_NANOS + S2N_TICKET_DECRYPT_KEY_LIFETIME_IN_NANOS + key->intro_timestamp - wall_clock_time_stamp) / ONE_SEC_IN_NANOS; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have any idea if key->intro_timestamp - wall_clock_time_stamp is a constant value? Or maybe since we're removing precision with the conversion to seconds it becomes constant?
I'm curious because if we're not careful this could introduce flakiness to this test, where the test "sometimes" fails because the timing was off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe since we're removing precision with the conversion to seconds it becomes constant?
It is constant because we are removing precision with conversion to seconds. However, since we have stored the current timestamp in s2n_connection->ticket_fields
, we should use that attribute to query current time stamp every time after a s2n_serialize_resumption_state()
is called.
Hence, a more precise way to do this calculation is to do:
S2N_TICKET_ENCRYPT_DECRYPT_KEY_LIFETIME_IN_NANOS + S2N_TICKET_DECRYPT_KEY_LIFETIME_IN_NANOS +key->intro_timestamp - conn->ticket_fields.current_time
EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&stuffer, 0)); | ||
EXPECT_SUCCESS(s2n_connection_set_io_stuffers(&stuffer, &stuffer, conn)); | ||
s2n_blocked_status blocked = 0; | ||
EXPECT_OK(s2n_tls13_server_nst_send(conn, &blocked)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for zero tickets lives in s2n_tls13_server_nst_write, not s2n_tls13_server_nst_send, so I would expect a test for s2n_tls13_server_nst_write.
EXPECT_OK(s2n_generate_ticket_lifetime(conn, key->intro_timestamp, current_time, &min_lifetime)); | ||
EXPECT_EQUAL(min_lifetime, ONE_WEEK_IN_SEC); | ||
|
||
/* Test: PSK Keying Material has shortest lifetime */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is very confusing. If you're saying psk keying material has the shortest lifetime, it should be smaller than one week. Otherwise we could be just catching one week case.
struct s2n_connection *conn = NULL; | ||
EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER)); | ||
EXPECT_NOT_NULL(config = s2n_config_new()); | ||
|
||
EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need to set up a resumption test key for this test. You control the inputs to s2n_generate_ticket_lifetime. Just provide the function with the inputs that you want for testing.
Release Summary:
Fix the improper calculation of session ticket lifetime.
Resolved issues:
Resolve issue #4583. Resolve issue #2756.
Description of changes:
Our current calculations of session ticket lifetimes are incorrect, please refers to the relevant issues for more details. The session ticket age should not exceed the user defined ticket age, remaining lifetime of encrypt decrypt key, remaining lifetime of PSK keying material, and one week.
s2n_generate_ticket_lifetime()
function:uint64_t key_intro_time
, anduint64_t current_time
tos2n_generate_ticket_lifetime()
, so that the function is aware of when the key is introduced and what the current time is.chosen_psk->keying_material_expiration
in as2n_connection
struct to calculate the remaining lifetime of the PSK.uint64_t *key_intro_time
parameter tos2n_resume_encrypt_session_ticket()
:s2n_resume_encrypt_session_ticket()
function. We can retrieve thekey_intro_time
from that function without moving the location where we calleds2n_get_ticket_encrypt_decrypt_key()
.s2n_resume_decrypt_session()
:s2n-tls/tls/s2n_resume.c
Lines 892 to 893 in a439c1a
s2n-tls/tls/s2n_resume.c
Lines 959 to 960 in a439c1a
key_intro_time
, then they can passNULL
into that parameter.uint64_t current_time
intostruct s2n_ticket_fields
.s2n_connection
fromtls13_ticket_fields
toticket_fields
, since both TLS1.2 and TLS1.3 have access to that variable.now
will have consistent time stamp.Call-outs
section.s2n_stuffer_reserve_uint32()
to skip the section wheresession_ticket_lifetime_in_secs
is calculated.s2n_resume_encrypt_session_ticket()
function call is finished, since it records thekey_intro_time
. Since the ticket is serialized, we need to uses2n_stuffer_reserve_uint32()
to skip that section, and then come back to write the session ticket lifetime withs2n_stuffer_write_reservation()
. Refer to this comment for more details.s2n_generate_ticket_lifetime()
function:s2n_resumption_test_ticket_key_setup()
to set up STEK for the connection.s2n_find_ticket_key()
with the key's name to acquire the actuals2n_ticket_key
.s2n_session_ticket_tests.c
:s2n-tls/tests/unit/s2n_session_ticket_test.c
Lines 817 to 821 in 1c38961
Call-outs:
s2n_generate_ticket_lifetime()
for shortest remaining PSK keying material lifetime. I set the PSK keying material expiration to be half a week, and reuse the previous configurations.s2n_server_new_session_ticket_test.c
, I didn't record realtime time stamp for two test cases whereSession state has shortest lifetime
andBoth session state and decrypt key have longer lifetimes than a week
.s2n_resume_encrypt_session_ticket_function()
, which is to pass bothkey
andcurrent_time
parameter into all functions involved. However, that will damaged the overall readability of the code. Such changes can be found in this feature branch.Testing:
S2nIntegrationV2SmallBatch
test requires this PR to be updated with main. I have tested it on a back up branch. The test should succeed after the update. Here is the test result.S2nIntegrationV2SmallBatch
job failed, theLinters / validate start_codebuild.sh
failed as well. However, that job should pass after this PR branch is updated to main.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.