diff options
author | Daiki Ueno <ueno@gnu.org> | 2018-11-12 15:54:01 +0000 |
---|---|---|
committer | Daiki Ueno <ueno@gnu.org> | 2018-11-12 15:54:01 +0000 |
commit | 868a373f915f65259c9708023ed612beb513db21 (patch) | |
tree | 54555ab056b65c644ed26253b9e5cdbe4d707fbe /lib | |
parent | 0e9e406c6d92a5cda2020ebda9bede0d3503f4bd (diff) | |
parent | 4429256c40161b088847f8e058c8a4cfb8d5b5f1 (diff) | |
download | gnutls-868a373f915f65259c9708023ed612beb513db21.tar.gz |
Merge branch 'tmp-0rtt' into 'master'
add support for 0-RTT
Closes #127
See merge request gnutls/gnutls!775
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 4 | ||||
-rw-r--r-- | lib/constate.c | 83 | ||||
-rw-r--r-- | lib/constate.h | 2 | ||||
-rw-r--r-- | lib/db.c | 61 | ||||
-rw-r--r-- | lib/errors.c | 3 | ||||
-rw-r--r-- | lib/ext/early_data.c | 49 | ||||
-rw-r--r-- | lib/ext/pre_shared_key.c | 35 | ||||
-rw-r--r-- | lib/gnutls_int.h | 24 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 175 | ||||
-rw-r--r-- | lib/handshake.c | 120 | ||||
-rw-r--r-- | lib/handshake.h | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 33 | ||||
-rw-r--r-- | lib/libgnutls.map | 16 | ||||
-rw-r--r-- | lib/record.c | 200 | ||||
-rw-r--r-- | lib/session_pack.c | 14 | ||||
-rw-r--r-- | lib/state.c | 7 | ||||
-rw-r--r-- | lib/str.h | 2 | ||||
-rw-r--r-- | lib/tls13/anti_replay.c | 221 | ||||
-rw-r--r-- | lib/tls13/anti_replay.h | 26 | ||||
-rw-r--r-- | lib/tls13/early_data.c | 101 | ||||
-rw-r--r-- | lib/tls13/early_data.h | 25 | ||||
-rw-r--r-- | lib/tls13/key_update.c | 2 | ||||
-rw-r--r-- | lib/tls13/session_ticket.c | 70 |
23 files changed, 1132 insertions, 143 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index e101ec68f2..32a8511b33 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -97,8 +97,10 @@ COBJECTS += tls13/encrypted_extensions.c tls13/encrypted_extensions.h \ tls13/hello_retry.c tls13/hello_retry.h \ tls13/session_ticket.c tls13/session_ticket.h \ tls13/certificate.c tls13/certificate.h \ + tls13/early_data.c tls13/early_data.h \ tls13/post_handshake.c \ - tls13/psk_ext_parser.c tls13/psk_ext_parser.h + tls13/psk_ext_parser.c tls13/psk_ext_parser.h \ + tls13/anti_replay.c tls13/anti_replay.h if ENABLE_PKCS11 COBJECTS += pkcs11.c pkcs11x.c pkcs11_privkey.c pkcs11_write.c pkcs11_secret.c \ diff --git a/lib/constate.c b/lib/constate.c index 456316258b..11fedab533 100644 --- a/lib/constate.c +++ b/lib/constate.c @@ -322,6 +322,61 @@ _tls13_update_keys(gnutls_session_t session, hs_stage_t stage, } static int +_tls13_set_early_keys(gnutls_session_t session, + record_parameters_st * params, + unsigned iv_size, unsigned key_size) +{ + uint8_t key_block[MAX_CIPHER_KEY_SIZE]; + uint8_t iv_block[MAX_CIPHER_IV_SIZE]; + char buf[65]; + record_state_st *early_state; + int ret; + + if (session->security_parameters.entity == GNUTLS_CLIENT && + !(session->internals.hsk_flags & HSK_TLS13_TICKET_SENT)) { + return GNUTLS_E_INVALID_REQUEST; + } + + ret = _tls13_expand_secret(session, "key", 3, NULL, 0, session->key.proto.tls13.e_ckey, key_size, key_block); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _tls13_expand_secret(session, "iv", 2, NULL, 0, session->key.proto.tls13.e_ckey, iv_size, iv_block); + if (ret < 0) + return gnutls_assert_val(ret); + + if (session->security_parameters.entity == GNUTLS_CLIENT) { + early_state = ¶ms->write; + } else { + early_state = ¶ms->read; + } + + early_state->mac_key_size = 0; + + assert(key_size <= sizeof(early_state->key)); + memcpy(early_state->key, key_block, key_size); + early_state->key_size = key_size; + + _gnutls_hard_log("INT: EARLY KEY [%d]: %s\n", + key_size, + _gnutls_bin2hex(key_block, key_size, + buf, sizeof(buf), NULL)); + + if (iv_size > 0) { + assert(iv_size <= sizeof(early_state->iv)); + memcpy(early_state->iv, iv_block, iv_size); + early_state->iv_size = iv_size; + + _gnutls_hard_log("INT: EARLY IV [%d]: %s\n", + iv_size, + _gnutls_bin2hex(iv_block, iv_size, + buf, sizeof(buf), NULL)); + } + + return 0; +} + +static int _tls13_set_keys(gnutls_session_t session, hs_stage_t stage, record_parameters_st * params, unsigned iv_size, unsigned key_size) @@ -342,7 +397,11 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage, return _tls13_update_keys(session, stage, params, iv_size, key_size); - if (stage == STAGE_HS) { + else if (stage == STAGE_EARLY) + return _tls13_set_early_keys(session, + params, iv_size, key_size); + + else if (stage == STAGE_HS) { label = HANDSHAKE_CLIENT_TRAFFIC_LABEL; label_size = sizeof(HANDSHAKE_CLIENT_TRAFFIC_LABEL)-1; hsk_len = session->internals.handshake_hash_buffer.length; @@ -551,13 +610,13 @@ _gnutls_set_cipher_suite2(gnutls_session_t session, /* Sets the next epoch to be a clone of the current one. * The keys are not cloned, only the cipher and MAC. */ -int _gnutls_epoch_dup(gnutls_session_t session) +int _gnutls_epoch_dup(gnutls_session_t session, unsigned int epoch_rel) { record_parameters_st *prev; record_parameters_st *next; int ret; - ret = _gnutls_epoch_get(session, EPOCH_READ_CURRENT, &prev); + ret = _gnutls_epoch_get(session, epoch_rel, &prev); if (ret < 0) return gnutls_assert_val(ret); @@ -621,13 +680,19 @@ int _gnutls_epoch_set_keys(gnutls_session_t session, uint16_t epoch, hs_stage_t if (ret < 0) return gnutls_assert_val(ret); - ret = _tls13_init_record_state(params->cipher->id, ¶ms->read); - if (ret < 0) - return gnutls_assert_val(ret); + if (stage != STAGE_EARLY || + session->security_parameters.entity == GNUTLS_SERVER) { + ret = _tls13_init_record_state(params->cipher->id, ¶ms->read); + if (ret < 0) + return gnutls_assert_val(ret); + } - ret = _tls13_init_record_state(params->cipher->id, ¶ms->write); - if (ret < 0) - return gnutls_assert_val(ret); + if (stage != STAGE_EARLY || + session->security_parameters.entity == GNUTLS_CLIENT) { + ret = _tls13_init_record_state(params->cipher->id, ¶ms->write); + if (ret < 0) + return gnutls_assert_val(ret); + } } else { ret = _gnutls_set_keys (session, params, hash_size, IV_size, key_size); diff --git a/lib/constate.h b/lib/constate.h index 125a48f8f2..f8e1480410 100644 --- a/lib/constate.h +++ b/lib/constate.h @@ -34,7 +34,7 @@ int _gnutls_write_connection_state_init(gnutls_session_t session); #define _gnutls_epoch_bump(session) \ (session)->security_parameters.epoch_next++ -int _gnutls_epoch_dup(gnutls_session_t session); +int _gnutls_epoch_dup(gnutls_session_t session, unsigned int epoch_rel); int _gnutls_epoch_get(gnutls_session_t session, unsigned int epoch_rel, record_parameters_st ** params_out); @@ -30,6 +30,7 @@ #include <session_pack.h> #include <datum.h> #include "ext/server_name.h" +#include <intprops.h> /** * gnutls_db_set_retrieve_function: @@ -55,6 +56,29 @@ gnutls_db_set_retrieve_function(gnutls_session_t session, } /** + * gnutls_db_set_add_function: + * @session: is a #gnutls_session_t type. + * @add_func: is the function. + * + * Sets the function that will be used to store an entry if it is not + * already present in the resumed sessions database. This function returns 0 + * if the entry is successfully stored, and a negative error code + * otherwise. In particular, if the entry is found in the database, + * it returns %GNUTLS_E_DB_ENTRY_EXISTS. + * + * The first argument to @add_func will be null unless + * gnutls_db_set_ptr() has been called. + * + * Since: 3.6.5 + **/ +void +gnutls_db_set_add_function(gnutls_session_t session, + gnutls_db_add_func add_func) +{ + session->internals.db_add_func = add_func; +} + +/** * gnutls_db_set_remove_function: * @session: is a #gnutls_session_t type. * @rem_func: is the function. @@ -155,6 +179,8 @@ unsigned gnutls_db_get_default_cache_expiration(void) * * Returns: Returns %GNUTLS_E_EXPIRED, if the database entry has * expired or 0 otherwise. + * + * Deprecated: This function is deprecated. **/ int gnutls_db_check_entry(gnutls_session_t session, @@ -166,7 +192,6 @@ gnutls_db_check_entry(gnutls_session_t session, /** * gnutls_db_check_entry_time: * @entry: is a pointer to a #gnutls_datum_t type. - * @t: is the time of the session handshake * * This function returns the time that this entry was active. * It can be used for database entry expiration. @@ -191,6 +216,40 @@ time_t gnutls_db_check_entry_time(gnutls_datum_t * entry) return t; } +/** + * gnutls_db_check_entry_expire_time: + * @entry: is a pointer to a #gnutls_datum_t type. + * + * This function returns the time that this entry will expire. + * It can be used for database entry expiration. + * + * Returns: The time this entry will expire, or zero on error. + * + * Since: 3.6.5 + **/ +time_t gnutls_db_check_entry_expire_time(gnutls_datum_t *entry) +{ + uint32_t t; + uint32_t e; + uint32_t magic; + + if (entry->size < 12) + return gnutls_assert_val(0); + + magic = _gnutls_read_uint32(entry->data); + + if (magic != PACKED_SESSION_MAGIC) + return gnutls_assert_val(0); + + t = _gnutls_read_uint32(&entry->data[4]); + e = _gnutls_read_uint32(&entry->data[8]); + + if (INT_ADD_OVERFLOW(t, e)) + return gnutls_assert_val(0); + + return t + e; +} + /* Checks if both db_store and db_retrieve functions have * been set up. */ diff --git a/lib/errors.c b/lib/errors.c index e579f46852..acdaf65bca 100644 --- a/lib/errors.c +++ b/lib/errors.c @@ -193,6 +193,7 @@ static const gnutls_error_entry error_entries[] = { ("TLS Application data were received, while expecting handshake data."), GNUTLS_E_GOT_APPLICATION_DATA), ERROR_ENTRY(N_("Error in Database backend."), GNUTLS_E_DB_ERROR), + ERROR_ENTRY(N_("The Database entry already exists."), GNUTLS_E_DB_ENTRY_EXISTS), ERROR_ENTRY(N_("The certificate type is not supported."), GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE), ERROR_ENTRY(N_ @@ -430,6 +431,8 @@ static const gnutls_error_entry error_entries[] = { GNUTLS_E_INSUFFICIENT_SECURITY), ERROR_ENTRY(N_("No common key share with peer."), GNUTLS_E_NO_COMMON_KEY_SHARE), + ERROR_ENTRY(N_("The early data were rejected."), + GNUTLS_E_EARLY_DATA_REJECTED), {NULL, NULL, 0} }; diff --git a/lib/ext/early_data.c b/lib/ext/early_data.c index daa0d8fcb7..a58e473ae1 100644 --- a/lib/ext/early_data.c +++ b/lib/ext/early_data.c @@ -39,7 +39,7 @@ const hello_ext_entry_st ext_mod_early_data = { .name = "Early Data", .tls_id = 42, .gid = GNUTLS_EXTENSION_EARLY_DATA, - .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO, + .validity = GNUTLS_EXT_FLAG_TLS | GNUTLS_EXT_FLAG_CLIENT_HELLO | GNUTLS_EXT_FLAG_EE, .parse_type = GNUTLS_EXT_MANDATORY, /* force parsing prior to EXT_TLS extensions */ .recv_func = early_data_recv_params, .send_func = early_data_send_params, @@ -54,10 +54,23 @@ early_data_recv_params(gnutls_session_t session, const uint8_t * data, size_t _data_size) { const version_entry_st *vers = get_version(session); + if (!vers || !vers->tls13_sem) return gnutls_assert_val(0); - if (session->security_parameters.entity == GNUTLS_SERVER) + + if (session->security_parameters.entity == GNUTLS_SERVER) { + /* The flag may be cleared by pre_shared_key + * extension, when replay is detected. */ + if ((session->internals.flags & GNUTLS_ENABLE_EARLY_DATA) && + /* Refuse early data when this is a second CH after HRR */ + !(session->internals.hsk_flags & HSK_HRR_SENT)) + session->internals.hsk_flags |= HSK_EARLY_DATA_ACCEPTED; + session->internals.hsk_flags |= HSK_EARLY_DATA_IN_FLIGHT; + } else { + if (_gnutls_ext_get_msg(session) == GNUTLS_EXT_FLAG_EE) + session->internals.hsk_flags |= HSK_EARLY_DATA_ACCEPTED; + } return 0; } @@ -68,10 +81,39 @@ static int early_data_send_params(gnutls_session_t session, gnutls_buffer_st * extdata) { + if (session->security_parameters.entity == GNUTLS_SERVER) { + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) + return GNUTLS_E_INT_RET_0; + } else { + if (session->internals.early_data_presend_buffer.length > 0) { + session->internals.hsk_flags |= HSK_EARLY_DATA_IN_FLIGHT; + return GNUTLS_E_INT_RET_0; + } + } + return 0; } /** + * gnutls_record_get_max_early_data_size: + * @session: is a #gnutls_session_t type. + * + * This function returns the maximum early data size in this connection. + * This property can only be set to servers. The client may be + * provided with the maximum allowed size through the "early_data" + * extension of the NewSessionTicket handshake message. + * + * Returns: The maximum early data size in this connection. + * + * Since: 3.6.5 + **/ +size_t +gnutls_record_get_max_early_data_size(gnutls_session_t session) +{ + return session->security_parameters.max_early_data_size; +} + +/** * gnutls_record_set_max_early_data_size: * @session: is a #gnutls_session_t type. * @size: is the new size @@ -93,7 +135,8 @@ gnutls_record_set_max_early_data_size(gnutls_session_t session, if (session->security_parameters.entity == GNUTLS_CLIENT) return GNUTLS_E_INVALID_REQUEST; - if (size > UINT32_MAX) + /* Reject zero as well, as it is useless. */ + if (size == 0 || size > UINT32_MAX) return GNUTLS_E_INVALID_REQUEST; session->security_parameters.max_early_data_size = (uint32_t) size; diff --git a/lib/ext/pre_shared_key.c b/lib/ext/pre_shared_key.c index be18c264ff..bc7fc8aa95 100644 --- a/lib/ext/pre_shared_key.c +++ b/lib/ext/pre_shared_key.c @@ -23,7 +23,9 @@ #include "gnutls_int.h" #include "auth/psk.h" +#include "handshake.h" #include "secrets.h" +#include "tls13/anti_replay.h" #include "tls13/psk_ext_parser.h" #include "tls13/finished.h" #include "tls13/session_ticket.h" @@ -36,7 +38,6 @@ static int compute_psk_from_ticket(const tls13_ticket_st *ticket, gnutls_datum_t *key) { int ret; - char label[] = "resumption"; if (unlikely(ticket->prf == NULL || ticket->prf->output_size == 0)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); @@ -49,7 +50,7 @@ compute_psk_from_ticket(const tls13_ticket_st *ticket, gnutls_datum_t *key) key->size = ticket->prf->output_size; ret = _tls13_expand_secret2(ticket->prf, - label, sizeof(label)-1, + RESUMPTION_LABEL, sizeof(RESUMPTION_LABEL)-1, ticket->nonce, ticket->nonce_size, ticket->resumption_master_secret, key->size, @@ -67,9 +68,9 @@ compute_binder_key(const mac_entry_st *prf, void *out) { int ret; - const char ext_label[] = "ext binder"; + const char ext_label[] = EXT_BINDER_LABEL; const size_t ext_label_len = sizeof(ext_label) - 1; - const char res_label[] = "res binder"; + const char res_label[] = RES_BINDER_LABEL; const size_t res_label_len = sizeof(res_label) - 1; const char *label = resuming ? res_label : ext_label; size_t label_len = resuming ? res_label_len : ext_label_len; @@ -482,7 +483,9 @@ static int server_recv_params(gnutls_session_t session, struct psk_st psk; psk_auth_info_t info; tls13_ticket_st ticket_data; - uint32_t ticket_age; + /* These values should be set properly when session ticket is accepted. */ + uint32_t ticket_age = UINT32_MAX; + struct timespec ticket_creation_time = { 0, 0 }; bool resuming; ret = _gnutls13_psk_ext_parser_init(&psk_parser, data, len); @@ -526,6 +529,10 @@ static int server_recv_params(gnutls_session_t session, continue; } + memcpy(&ticket_creation_time, + &ticket_data.creation_time, + sizeof(struct timespec)); + tls13_ticket_deinit(&ticket_data); resuming = 1; @@ -612,6 +619,24 @@ static int server_recv_params(gnutls_session_t session, info->username[psk.identity.size] = 0; _gnutls_handshake_log("EXT[%p]: selected PSK identity: %s (%d)\n", session, info->username, psk_index); } else { + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) { + if (session->internals.anti_replay) { + ret = _gnutls_anti_replay_check(session, + ticket_age, + &ticket_creation_time, + &binder_recvd); + if (ret < 0) { + session->internals.hsk_flags &= ~HSK_EARLY_DATA_ACCEPTED; + _gnutls_handshake_log("EXT[%p]: replay detected; rejecting early data\n", + session); + } + } else { + _gnutls_handshake_log("EXT[%p]: anti-replay is not enabled; rejecting early data\n", + session); + session->internals.hsk_flags &= ~HSK_EARLY_DATA_ACCEPTED; + } + } + session->internals.resumed = RESUME_TRUE; _gnutls_handshake_log("EXT[%p]: selected resumption PSK identity (%d)\n", session, psk_index); } diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 3eece0278f..e34bea85b8 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -169,7 +169,8 @@ typedef enum hs_stage_t { STAGE_HS, STAGE_APP, STAGE_UPD_OURS, - STAGE_UPD_PEERS + STAGE_UPD_PEERS, + STAGE_EARLY } hs_stage_t; typedef enum record_send_state_t { @@ -272,7 +273,7 @@ typedef enum handshake_state_t { STATE0 = 0, STATE1, STATE2, STATE90=90, STATE91, STATE92, STATE93, STATE94, STATE99=99, STATE100=100, STATE101, STATE102, STATE103, STATE104, STATE105, STATE106, STATE107, STATE108, STATE109, STATE110, - STATE111, STATE112, STATE113, STATE114, + STATE111, STATE112, STATE113, STATE114, STATE115, STATE150 /* key update */ } handshake_state_t; @@ -538,6 +539,7 @@ struct gnutls_key_st { * early_secret, client_early_traffic_secret, ... */ uint8_t temp_secret[MAX_HASH_SIZE]; unsigned temp_secret_size; /* depends on negotiated PRF size */ + uint8_t e_ckey[MAX_HASH_SIZE]; /* client_early_traffic_secret */ uint8_t hs_ckey[MAX_HASH_SIZE]; /* client_hs_traffic_secret */ uint8_t hs_skey[MAX_HASH_SIZE]; /* server_hs_traffic_secret */ uint8_t ap_ckey[MAX_HASH_SIZE]; /* client_ap_traffic_secret */ @@ -1013,6 +1015,7 @@ typedef struct gnutls_dh_params_int { */ typedef struct { struct timespec arrival_time; + struct timespec creation_time; uint32_t lifetime; uint32_t age_add; uint8_t nonce[255]; @@ -1072,6 +1075,7 @@ typedef struct { int handshake_hash_buffer_prev_len; /* keeps the length of handshake_hash_buffer, excluding * the last received message */ + unsigned handshake_hash_buffer_client_hello_len; /* if non-zero it is the length of data until the client hello message */ unsigned handshake_hash_buffer_client_kx_len;/* if non-zero it is the length of data until the * the client key exchange message */ unsigned handshake_hash_buffer_server_finished_len;/* if non-zero it is the length of data until the @@ -1159,6 +1163,9 @@ typedef struct { * send. */ + mbuffer_head_st early_data_recv_buffer; + gnutls_buffer_st early_data_presend_buffer; + record_send_state_t rsend_state; /* buffer used temporarily during key update */ gnutls_buffer_st record_key_update_buffer; @@ -1209,6 +1216,7 @@ typedef struct { gnutls_db_store_func db_store_func; gnutls_db_retr_func db_retrieve_func; gnutls_db_remove_func db_remove_func; + gnutls_db_add_func db_add_func; void *db_ptr; /* post client hello callback (server side only) @@ -1341,8 +1349,13 @@ typedef struct { */ #define HSK_TICKET_RECEIVED (1<<20) /* client: a session ticket was received */ #define HSK_EARLY_START_USED (1<<21) -#define HSK_EARLY_DATA_IN_FLIGHT (1<<22) /* server: early_data extension was seen in ClientHello */ -#define HSK_RECORD_SIZE_LIMIT_NEGOTIATED (1<<23) +#define HSK_EARLY_DATA_IN_FLIGHT (1<<22) /* client: sent early_data extension in ClientHello + * server: early_data extension was seen in ClientHello + */ +#define HSK_EARLY_DATA_ACCEPTED (1<<23) /* client: early_data extension was seen in EncryptedExtensions + * server: intend to process early data + */ +#define HSK_RECORD_SIZE_LIMIT_NEGOTIATED (1<<24) /* The hsk_flags are for use within the ongoing handshake; * they are reset to zero prior to handshake start by gnutls_handshake. */ @@ -1447,6 +1460,9 @@ typedef struct { /* the amount of early data received so far */ uint32_t early_data_received; + /* anti-replay measure for 0-RTT mode */ + gnutls_anti_replay_t anti_replay; + /* If you add anything here, check _gnutls_handshake_internal_state_clear(). */ } internals_st; diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c index 5fed553310..fedef6cbbc 100644 --- a/lib/handshake-tls13.c +++ b/lib/handshake-tls13.c @@ -52,12 +52,13 @@ #include "tls13/certificate_request.h" #include "tls13/certificate_verify.h" #include "tls13/certificate.h" +#include "tls13/early_data.h" #include "tls13/finished.h" #include "tls13/key_update.h" #include "ext/pre_shared_key.h" static int generate_rms_keys(gnutls_session_t session); -static int generate_and_set_hs_traffic_keys(gnutls_session_t session); +static int generate_hs_traffic_keys(gnutls_session_t session); static int generate_ap_traffic_keys(gnutls_session_t session); #define SAVE_TRANSCRIPT \ @@ -90,59 +91,99 @@ int _gnutls13_handshake_client(gnutls_session_t session) #endif FALLTHROUGH; case STATE101: - ret = - generate_and_set_hs_traffic_keys(session); - STATE = STATE101; - IMED_RET_FATAL("generate session keys", ret, 0); + /* Note that we check IN_FLIGHT, not ACCEPTED + * here. This is because the client sends early data + * speculatively. */ + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + ret = _tls13_write_connection_state_init(session, STAGE_EARLY); + if (ret == 0) { + _gnutls_epoch_bump(session); + ret = _gnutls_epoch_dup(session, EPOCH_WRITE_CURRENT); + } + STATE = STATE101; + IMED_RET_FATAL("set early traffic keys", ret, 0); + } FALLTHROUGH; case STATE102: - ret = _gnutls13_recv_encrypted_extensions(session); + ret = _gnutls13_send_early_data(session); STATE = STATE102; - IMED_RET("recv encrypted extensions", ret, 0); + IMED_RET("send early data", ret, 0); FALLTHROUGH; case STATE103: - ret = _gnutls13_recv_certificate_request(session); STATE = STATE103; - IMED_RET("recv certificate request", ret, 0); + ret = generate_hs_traffic_keys(session); + /* Note that we check IN_FLIGHT, not ACCEPTED + * here. This is because the client sends early data + * speculatively. */ + IMED_RET_FATAL("generate hs traffic keys", ret, 0); + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) + ret = _tls13_read_connection_state_init(session, STAGE_HS); + else + ret = _tls13_connection_state_init(session, STAGE_HS); + IMED_RET_FATAL("set hs traffic keys", ret, 0); FALLTHROUGH; case STATE104: - ret = _gnutls13_recv_certificate(session); + ret = _gnutls13_recv_encrypted_extensions(session); STATE = STATE104; - IMED_RET("recv certificate", ret, 0); + IMED_RET("recv encrypted extensions", ret, 0); FALLTHROUGH; case STATE105: - ret = _gnutls13_recv_certificate_verify(session); + ret = _gnutls13_recv_certificate_request(session); STATE = STATE105; - IMED_RET("recv server certificate verify", ret, 0); + IMED_RET("recv certificate request", ret, 0); FALLTHROUGH; case STATE106: - ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); + ret = _gnutls13_recv_certificate(session); STATE = STATE106; - if (ret < 0) - return gnutls_assert_val(ret); + IMED_RET("recv certificate", ret, 0); FALLTHROUGH; case STATE107: - ret = _gnutls13_recv_finished(session); + ret = _gnutls13_recv_certificate_verify(session); STATE = STATE107; - IMED_RET("recv finished", ret, 0); + IMED_RET("recv server certificate verify", ret, 0); FALLTHROUGH; case STATE108: - ret = _gnutls13_send_certificate(session, AGAIN(STATE108)); + ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); STATE = STATE108; - IMED_RET("send certificate", ret, 0); + if (ret < 0) + return gnutls_assert_val(ret); FALLTHROUGH; case STATE109: - ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE109)); + ret = _gnutls13_recv_finished(session); STATE = STATE109; - IMED_RET("send certificate verify", ret, 0); + IMED_RET("recv finished", ret, 0); FALLTHROUGH; case STATE110: - ret = _gnutls13_send_finished(session, AGAIN(STATE110)); + ret = _gnutls13_send_end_of_early_data(session, AGAIN(STATE110)); STATE = STATE110; - IMED_RET("send finished", ret, 0); + IMED_RET("send end of early data", ret, 0); + + /* Note that we check IN_FLIGHT, not ACCEPTED + * here. This is because the client sends early data + * speculatively. */ + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + ret = _tls13_write_connection_state_init(session, STAGE_HS); + IMED_RET_FATAL("set hs traffic key after sending early data", ret, 0); + } FALLTHROUGH; case STATE111: + ret = _gnutls13_send_certificate(session, AGAIN(STATE111)); STATE = STATE111; + IMED_RET("send certificate", ret, 0); + FALLTHROUGH; + case STATE112: + ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE112)); + STATE = STATE112; + IMED_RET("send certificate verify", ret, 0); + FALLTHROUGH; + case STATE113: + ret = _gnutls13_send_finished(session, AGAIN(STATE113)); + STATE = STATE113; + IMED_RET("send finished", ret, 0); + FALLTHROUGH; + case STATE114: + STATE = STATE114; ret = generate_ap_traffic_keys(session); @@ -255,14 +296,14 @@ static int generate_ap_traffic_keys(gnutls_session_t session) session->security_parameters.prf->output_size); _gnutls_epoch_bump(session); - ret = _gnutls_epoch_dup(session); + ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT); if (ret < 0) return gnutls_assert_val(ret); return 0; } -static int generate_and_set_hs_traffic_keys(gnutls_session_t session) +static int generate_hs_traffic_keys(gnutls_session_t session) { int ret; unsigned null_key = 0; @@ -270,6 +311,14 @@ static int generate_and_set_hs_traffic_keys(gnutls_session_t session) if (unlikely(session->key.proto.tls13.temp_secret_size == 0)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + ret = _tls13_derive_secret(session, DERIVED_LABEL, sizeof(DERIVED_LABEL)-1, + NULL, 0, session->key.proto.tls13.temp_secret, + session->key.proto.tls13.temp_secret); + if (ret < 0) { + gnutls_assert(); + return ret; + } + if ((session->security_parameters.entity == GNUTLS_CLIENT && (!(session->internals.hsk_flags & HSK_KEY_SHARE_RECEIVED) || (!(session->internals.hsk_flags & HSK_PSK_KE_MODE_DHE_PSK) && @@ -309,12 +358,6 @@ static int generate_and_set_hs_traffic_keys(gnutls_session_t session) } } - ret = _tls13_connection_state_init(session, STAGE_HS); - if (ret < 0) { - gnutls_assert(); - return ret; - } - return 0; } @@ -380,10 +423,26 @@ int _gnutls13_handshake_server(gnutls_session_t session) #endif FALLTHROUGH; case STATE101: - ret = - generate_and_set_hs_traffic_keys(session); STATE = STATE101; - IMED_RET_FATAL("generate session keys", ret, 0); + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) { + ret = _tls13_read_connection_state_init(session, STAGE_EARLY); + if (ret == 0) { + _gnutls_epoch_bump(session); + ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT); + } + IMED_RET_FATAL("set early traffic keys", ret, 0); + + ret = generate_hs_traffic_keys(session); + IMED_RET_FATAL("generate hs traffic keys", ret, 0); + + ret = _tls13_write_connection_state_init(session, STAGE_HS); + } else { + ret = generate_hs_traffic_keys(session); + IMED_RET_FATAL("generate hs traffic keys", ret, 0); + + ret = _tls13_connection_state_init(session, STAGE_HS); + } + IMED_RET_FATAL("set hs traffic keys", ret, 0); FALLTHROUGH; case STATE102: ret = _gnutls13_send_encrypted_extensions(session, AGAIN(STATE102)); @@ -411,6 +470,16 @@ int _gnutls13_handshake_server(gnutls_session_t session) IMED_RET("send finished", ret, 0); FALLTHROUGH; case STATE107: + ret = _gnutls13_recv_end_of_early_data(session); + STATE = STATE107; + IMED_RET("recv end of early data", ret, 0); + + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) { + ret = _tls13_read_connection_state_init(session, STAGE_HS); + IMED_RET_FATAL("set hs traffic key after receiving early data", ret, 0); + } + FALLTHROUGH; + case STATE108: /* At this point our sending keys should be the app keys * see 4.4.4 at draft-ietf-tls-tls13-28 */ ret = @@ -420,7 +489,7 @@ int _gnutls13_handshake_server(gnutls_session_t session) /* If the session is unauthenticated, try to optimize the handshake by * sending the session ticket early. */ if (!(session->internals.hsk_flags & (HSK_CRT_REQ_SENT|HSK_PSK_SELECTED))) { - STATE = STATE107; + STATE = STATE108; ret = generate_non_auth_rms_keys(session); IMED_RET_FATAL("generate rms keys", ret, 0); @@ -435,15 +504,15 @@ int _gnutls13_handshake_server(gnutls_session_t session) _gnutls_handshake_log("HSK[%p]: switching early to application traffic keys\n", session); FALLTHROUGH; - case STATE108: + case STATE109: if (session->internals.resumed != RESUME_FALSE) _gnutls_set_resumed_parameters(session); if (session->internals.hsk_flags & HSK_EARLY_START_USED) { ret = _gnutls13_send_session_ticket(session, TICKETS_TO_SEND, - AGAIN(STATE108)); + AGAIN(STATE109)); - STATE = STATE108; + STATE = STATE109; IMED_RET("send session ticket", ret, 0); /* complete this phase of the handshake. We @@ -451,7 +520,7 @@ int _gnutls13_handshake_server(gnutls_session_t session) */ if (session->internals.flags & GNUTLS_ENABLE_EARLY_START) { - STATE = STATE112; /* finished */ + STATE = STATE113; /* finished */ gnutls_assert(); session->internals.recv_state = RECV_STATE_EARLY_START; @@ -459,31 +528,31 @@ int _gnutls13_handshake_server(gnutls_session_t session) } } FALLTHROUGH; - case STATE109: + case STATE110: ret = _gnutls13_recv_certificate(session); - STATE = STATE109; + STATE = STATE110; IMED_RET("recv certificate", ret, 0); FALLTHROUGH; - case STATE110: + case STATE111: ret = _gnutls13_recv_certificate_verify(session); - STATE = STATE110; + STATE = STATE111; IMED_RET("recv certificate verify", ret, 0); FALLTHROUGH; - case STATE111: + case STATE112: ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT); - STATE = STATE111; + STATE = STATE112; if (ret < 0) return gnutls_assert_val(ret); FALLTHROUGH; - case STATE112: /* can enter from STATE108 */ + case STATE113: /* can enter from STATE109 */ ret = _gnutls13_recv_finished(session); - STATE = STATE112; + STATE = STATE113; IMED_RET("recv finished", ret, 0); FALLTHROUGH; - case STATE113: + case STATE114: /* If we did request a client certificate, then we can * only send the tickets here */ - STATE = STATE113; + STATE = STATE114; if (!(session->internals.hsk_flags & HSK_EARLY_START_USED)) { ret = generate_rms_keys(session); @@ -494,11 +563,11 @@ int _gnutls13_handshake_server(gnutls_session_t session) IMED_RET_FATAL("set read app keys", ret, 0); FALLTHROUGH; - case STATE114: + case STATE115: if (!(session->internals.hsk_flags & (HSK_TLS13_TICKET_SENT|HSK_EARLY_START_USED))) { ret = _gnutls13_send_session_ticket(session, TICKETS_TO_SEND, - AGAIN(STATE114)); - STATE = STATE114; + AGAIN(STATE115)); + STATE = STATE115; IMED_RET("send session ticket", ret, 0); } diff --git a/lib/handshake.c b/lib/handshake.c index a20c7a302a..5080756c28 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -80,6 +80,7 @@ handshake_hash_buffer_reset(gnutls_session_t session) { _gnutls_buffers_log("BUF[HSK]: Emptied buffer\n"); + session->internals.handshake_hash_buffer_client_hello_len = 0; session->internals.handshake_hash_buffer_client_kx_len = 0; session->internals.handshake_hash_buffer_server_finished_len = 0; session->internals.handshake_hash_buffer_client_finished_len = 0; @@ -1408,6 +1409,9 @@ handshake_hash_add_recvd(gnutls_session_t session, /* save the size until client KX. That is because the TLS * session hash is calculated up to this message. */ + if (recv_type == GNUTLS_HANDSHAKE_CLIENT_HELLO) + session->internals.handshake_hash_buffer_client_hello_len = + session->internals.handshake_hash_buffer.length; if (recv_type == GNUTLS_HANDSHAKE_CLIENT_KEY_EXCHANGE) session->internals.handshake_hash_buffer_client_kx_len = session->internals.handshake_hash_buffer.length; @@ -1459,6 +1463,9 @@ handshake_hash_add_sent(gnutls_session_t session, if (ret < 0) return gnutls_assert_val(ret); + if (type == GNUTLS_HANDSHAKE_CLIENT_HELLO) + session->internals.handshake_hash_buffer_client_hello_len = + session->internals.handshake_hash_buffer.length; if (type == GNUTLS_HANDSHAKE_CLIENT_KEY_EXCHANGE) session->internals.handshake_hash_buffer_client_kx_len = session->internals.handshake_hash_buffer.length; @@ -1613,6 +1620,7 @@ _gnutls_recv_handshake(gnutls_session_t session, case GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY: case GNUTLS_HANDSHAKE_SUPPLEMENTAL: case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: + case GNUTLS_HANDSHAKE_END_OF_EARLY_DATA: ret = hsk.data.length; break; default: @@ -1776,6 +1784,26 @@ no_resume: } +static int generate_early_traffic_secret(gnutls_session_t session, + const mac_entry_st *prf) +{ + int ret; + + ret = _tls13_derive_secret2(prf, EARLY_TRAFFIC_LABEL, sizeof(EARLY_TRAFFIC_LABEL)-1, + session->internals.handshake_hash_buffer.data, + session->internals.handshake_hash_buffer_client_hello_len, + session->key.proto.tls13.temp_secret, + session->key.proto.tls13.e_ckey); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_nss_keylog_write(session, "CLIENT_EARLY_TRAFFIC_SECRET", + session->key.proto.tls13.e_ckey, + prf->output_size); + + return 0; +} + /* This function reads and parses the server hello handshake message. * This function also restores resumed parameters if we are resuming a * session. @@ -1964,7 +1992,8 @@ read_server_hello(gnutls_session_t session, return gnutls_assert_val(ret); /* Calculate TLS 1.3 Early Secret */ - if (vers->tls13_sem) { + if (vers->tls13_sem && + !(session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) { if (session->internals.hsk_flags & HSK_PSK_SELECTED) { psk = session->key.binders[0].psk.data; psk_size = session->key.binders[0].psk.size; @@ -1976,15 +2005,7 @@ read_server_hello(gnutls_session_t session, ret = _tls13_init_secret(session, psk, psk_size); if (ret < 0) { gnutls_assert(); - goto cleanup; - } - - ret = _tls13_derive_secret(session, DERIVED_LABEL, sizeof(DERIVED_LABEL)-1, - NULL, 0, session->key.proto.tls13.temp_secret, - session->key.proto.tls13.temp_secret); - if (ret < 0) { - gnutls_assert(); - goto cleanup; + return ret; } } @@ -2020,6 +2041,56 @@ append_null_comp(gnutls_session_t session, return ret; } +/* Calculate TLS 1.3 Early Secret and client_early_traffic_secret, + * assuming that the PSK we offer will be accepted by the server */ +static int +generate_early_traffic_secret_from_ticket(gnutls_session_t session) +{ + int ret = 0; + const uint8_t *psk; + size_t psk_size; + const mac_entry_st *prf; + + if (!(session->internals.hsk_flags & HSK_TLS13_TICKET_SENT)) { + ret = GNUTLS_E_INVALID_REQUEST; + gnutls_assert(); + goto cleanup; + } + + psk = session->key.binders[0].psk.data; + psk_size = session->key.binders[0].psk.size; + prf = session->key.binders[0].prf; + + if (psk_size == 0) { + ret = GNUTLS_E_INVALID_REQUEST; + gnutls_assert(); + goto cleanup; + } + + ret = _tls13_init_secret2(prf, psk, psk_size, + session->key.proto.tls13.temp_secret); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + session->key.proto.tls13.temp_secret_size = prf->output_size; + + ret = generate_early_traffic_secret(session, prf); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + return ret; + + cleanup: + /* If any of the above calculation fails, we are not going to + * send early data. */ + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + + return ret; +} + /* This function sends the client hello handshake message. */ static int send_client_hello(gnutls_session_t session, int again) @@ -2231,9 +2302,10 @@ static int send_client_hello(gnutls_session_t session, int again) bufel = _gnutls_buffer_to_mbuffer(&extdata); } - return - _gnutls_send_handshake(session, bufel, - GNUTLS_HANDSHAKE_CLIENT_HELLO); + ret = _gnutls_send_handshake(session, bufel, + GNUTLS_HANDSHAKE_CLIENT_HELLO); + + return ret; cleanup: _gnutls_buffer_clear(&extdata); @@ -2253,6 +2325,7 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) unsigned extflag = 0; const uint8_t *psk = NULL; size_t psk_size = 0; + const mac_entry_st *prf = session->security_parameters.prf; gnutls_ext_parse_type_t etype; _gnutls_buffer_init(&buf); @@ -2267,6 +2340,7 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) if (session->internals.hsk_flags & HSK_PSK_SELECTED) { psk = session->key.binders[0].psk.data; psk_size = session->key.binders[0].psk.size; + prf = session->key.binders[0].prf; } ret = _tls13_init_secret(session, psk, psk_size); @@ -2275,6 +2349,12 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) goto fail; } + ret = generate_early_traffic_secret(session, prf); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + vbytes[0] = 0x03; /* TLS1.2 */ vbytes[1] = 0x03; extflag |= GNUTLS_EXT_FLAG_TLS13_SERVER_HELLO; @@ -2345,14 +2425,6 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) } if (vers->tls13_sem) { - ret = _tls13_derive_secret(session, DERIVED_LABEL, sizeof(DERIVED_LABEL)-1, - NULL, 0, session->key.proto.tls13.temp_secret, - session->key.proto.tls13.temp_secret); - if (ret < 0) { - gnutls_assert(); - goto fail; - } - /* Under TLS1.3, the session ID is used for different purposes than * the TLS1.0 session ID. Ensure that there is an internally set * value which the server will see on the original and resumed sessions */ @@ -2723,6 +2795,8 @@ int gnutls_handshake(gnutls_session_t session) _gnutls_handshake_internal_state_clear(session); + _gnutls_buffer_clear(&session->internals.record_presend_buffer); + _gnutls_epoch_bump(session); } @@ -2850,6 +2924,10 @@ static int handshake_client(gnutls_session_t session) ret = send_client_hello(session, AGAIN(STATE1)); STATE = STATE1; IMED_RET("send hello", ret, 1); + if (session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + ret = generate_early_traffic_secret_from_ticket(session); + IMED_RET_FATAL("generate early traffic keys from ticket", ret, 0); + } FALLTHROUGH; case STATE2: if (IS_DTLS(session)) { diff --git a/lib/handshake.h b/lib/handshake.h index ee5ee7a437..a82263aad1 100644 --- a/lib/handshake.h +++ b/lib/handshake.h @@ -159,7 +159,7 @@ int _gnutls_check_if_cert_hash_is_same(gnutls_session_t session, gnutls_certific #define EXPORTER_MASTER_LABEL "exp master" #define RMS_MASTER_LABEL "res master" #define EXPORTER_LABEL "exp master" -#define RES_LABEL "res master" +#define RESUMPTION_LABEL "resumption" int _gnutls_call_hook_func(gnutls_session_t session, gnutls_handshake_description_t type, diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 5dcbc1c986..2af09bb24a 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -399,6 +399,7 @@ typedef enum { * finish; similarly to false start the handshake will be completed once data are received by the * client, while the server is able to transmit sooner. This is not enabled by default as it could * break certain existing server assumptions and use-cases. Since 3.6.4. + * @GNUTLS_ENABLE_EARLY_DATA: Under TLS1.3 allow the server to receive early data sent as part of the initial ClientHello (0-RTT). This is not enabled by default as early data has weaker security properties than other data. Since 3.6.5. * @GNUTLS_FORCE_CLIENT_CERT: When in client side and only a single cert is specified, send that certificate irrespective of the issuers expected by the server. Since 3.5.0. * @GNUTLS_NO_TICKETS: Flag to indicate that the session should not use resumption with session tickets. * @GNUTLS_KEY_SHARE_TOP3: Generate key shares for the top-3 different groups which are enabled. @@ -456,7 +457,8 @@ typedef enum { GNUTLS_SAFE_PADDING_CHECK = (1<<16), GNUTLS_ENABLE_EARLY_START = (1<<17), GNUTLS_ENABLE_CERT_TYPE_NEG = (1<<18), - GNUTLS_AUTO_REAUTH = (1<<19) + GNUTLS_AUTO_REAUTH = (1<<19), + GNUTLS_ENABLE_EARLY_DATA = (1<<20) } gnutls_init_flags_t; /* compatibility defines (previous versions of gnutls @@ -1434,7 +1436,14 @@ ssize_t gnutls_record_set_max_size(gnutls_session_t session, size_t size); size_t gnutls_record_check_pending(gnutls_session_t session); size_t gnutls_record_check_corked(gnutls_session_t session); +size_t gnutls_record_get_max_early_data_size(gnutls_session_t session); int gnutls_record_set_max_early_data_size(gnutls_session_t session, size_t size); +ssize_t gnutls_record_send_early_data(gnutls_session_t session, + const void *data, + size_t length); +ssize_t gnutls_record_recv_early_data(gnutls_session_t session, + void *data, + size_t data_size); void gnutls_session_force_valid(gnutls_session_t session); @@ -1502,6 +1511,7 @@ unsigned gnutls_session_etm_status(gnutls_session_t session); * @GNUTLS_SFLAGS_SESSION_TICKET: A session ticket has been received by the server. * @GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH: Indicates client capability for post-handshake auth; set only on server side. * @GNUTLS_SFLAGS_EARLY_START: The TLS1.3 server session returned early. + * @GNUTLS_SFLAGS_EARLY_DATA: The TLS1.3 early data has been received by the server. * * Enumeration of different session parameters. */ @@ -1515,7 +1525,8 @@ typedef enum { GNUTLS_SFLAGS_RFC7919 = 1<<6, GNUTLS_SFLAGS_SESSION_TICKET = 1<<7, GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH = 1<<8, - GNUTLS_SFLAGS_EARLY_START = 1<<9 + GNUTLS_SFLAGS_EARLY_START = 1<<9, + GNUTLS_SFLAGS_EARLY_DATA = 1<<10 } gnutls_session_flags_t; unsigned gnutls_session_get_flags(gnutls_session_t session); @@ -1772,6 +1783,8 @@ typedef int (*gnutls_db_store_func) (void *, gnutls_datum_t key, gnutls_datum_t data); typedef int (*gnutls_db_remove_func) (void *, gnutls_datum_t key); typedef gnutls_datum_t(*gnutls_db_retr_func) (void *, gnutls_datum_t key); +typedef int (*gnutls_db_add_func) (void *, gnutls_datum_t key, + gnutls_datum_t data); void gnutls_db_set_cache_expiration(gnutls_session_t session, int seconds); unsigned gnutls_db_get_default_cache_expiration(void); @@ -1783,11 +1796,14 @@ void gnutls_db_set_remove_function(gnutls_session_t session, gnutls_db_remove_func rem_func); void gnutls_db_set_store_function(gnutls_session_t session, gnutls_db_store_func store_func); +void gnutls_db_set_add_function(gnutls_session_t session, + gnutls_db_add_func add_func); void gnutls_db_set_ptr(gnutls_session_t session, void *ptr); void *gnutls_db_get_ptr(gnutls_session_t session); int gnutls_db_check_entry(gnutls_session_t session, gnutls_datum_t session_entry); time_t gnutls_db_check_entry_time(gnutls_datum_t * entry); +time_t gnutls_db_check_entry_expire_time(gnutls_datum_t * entry); /** * gnutls_handshake_hook_func: @@ -2975,6 +2991,17 @@ void gnutls_supplemental_recv(gnutls_session_t session, unsigned do_recv_supplem void gnutls_supplemental_send(gnutls_session_t session, unsigned do_send_supplemental); +/* Anti-replay related functions */ + +typedef struct gnutls_anti_replay_st *gnutls_anti_replay_t; + +int gnutls_anti_replay_init(gnutls_anti_replay_t *anti_replay); +void gnutls_anti_replay_deinit(gnutls_anti_replay_t anti_replay); +void gnutls_anti_replay_set_window(gnutls_anti_replay_t anti_replay, + unsigned int window); +void gnutls_anti_replay_enable(gnutls_session_t session, + gnutls_anti_replay_t anti_replay); + /* FIPS140-2 related functions */ unsigned gnutls_fips140_mode_enabled(void); @@ -3253,6 +3280,8 @@ void gnutls_fips140_set_mode(gnutls_fips_mode_t mode, unsigned flags); #define GNUTLS_E_TOO_MANY_MATCHES -425 #define GNUTLS_E_CRL_VERIFICATION_ERROR -426 #define GNUTLS_E_MISSING_EXTENSION -427 +#define GNUTLS_E_DB_ENTRY_EXISTS -428 +#define GNUTLS_E_EARLY_DATA_REJECTED -429 #define GNUTLS_E_UNIMPLEMENTED_FEATURE -1250 diff --git a/lib/libgnutls.map b/lib/libgnutls.map index ad6613b907..3cfc0c450b 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1248,6 +1248,20 @@ GNUTLS_3_6_4 gnutls_priority_certificate_type_list2; } GNUTLS_3_6_3; +GNUTLS_3_6_5 +{ + global: + gnutls_record_get_max_early_data_size; + gnutls_record_send_early_data; + gnutls_record_recv_early_data; + gnutls_db_check_entry_expire_time; + gnutls_db_set_add_function; + gnutls_anti_replay_init; + gnutls_anti_replay_deinit; + gnutls_anti_replay_set_window; + gnutls_anti_replay_enable; +} GNUTLS_3_6_4; + GNUTLS_FIPS140_3_4 { global: gnutls_cipher_self_test; @@ -1328,4 +1342,6 @@ GNUTLS_PRIVATE_3_4 { _gnutls_set_session_ticket_key_rotation_callback; # Internal symbols needed by tests/virt-time.h _gnutls_global_set_gettime_function; + # Internal symbols needed by tests/tls13/anti_replay.c + _gnutls_anti_replay_check; } GNUTLS_3_4; diff --git a/lib/record.c b/lib/record.c index 87b9dee304..5514ddcef1 100644 --- a/lib/record.c +++ b/lib/record.c @@ -1370,40 +1370,89 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type, _mbuffer_head_remove_bytes(&session->internals.record_recv_buffer, record.header_size + record.length); - /* FIXME: as 0-RTT is not implemented yet, when early data is - * indicated, skip decryption failure up to - * max_early_data_size. Otherwise, if the record is properly - * decrypted, treat it as the start of client's second flight. - * - * This implements the first way suggested in 4.2.10 of - * draft-ietf-tls-tls13-28. - */ - if (unlikely(session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) { - if (record.type == GNUTLS_APPLICATION_DATA && - (ret < 0 || - /* early data must always be encrypted, treat it - * as decryption failure if otherwise */ - record_params->cipher->id == GNUTLS_CIPHER_NULL)) { - if (record.length > - session->security_parameters.max_early_data_size - - session->internals.early_data_received) { + if (session->security_parameters.entity == GNUTLS_SERVER && + session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT) { + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) { + if (ret < 0 || + /* early data must always be encrypted, treat it + * as decryption failure if otherwise */ + record_params->cipher->id == GNUTLS_CIPHER_NULL) { _gnutls_record_log - ("REC[%p]: max_early_data_size exceeded\n", - session); - ret = GNUTLS_E_UNEXPECTED_PACKET; + ("REC[%p]: failed to decrypt early data, in epoch %d\n", + session, + record_params->epoch); + ret = GNUTLS_E_DECRYPTION_FAILED; goto sanity_check_error; - } + } else if (record.type == GNUTLS_APPLICATION_DATA) { + size_t decrypted_length = + _mbuffer_get_udata_size(decrypted); + _gnutls_record_log + ("REC[%p]: decrypted early data with length: %d, in epoch %d\n", + session, + (int) decrypted_length, + record_params->epoch); + if (decrypted_length > + session->security_parameters.max_early_data_size - + session->internals.early_data_received) { + _gnutls_record_log + ("REC[%p]: max_early_data_size exceeded\n", + session); + ret = GNUTLS_E_UNEXPECTED_PACKET; + goto sanity_check_error; + } + + _mbuffer_enqueue(&session->internals.early_data_recv_buffer, decrypted); + session->internals.early_data_received += + decrypted_length; + + /* Increase sequence number. We do both for TLS and DTLS, since in + * DTLS we also rely on that number (roughly) since it may get reported + * to application via gnutls_record_get_state(). + */ + if (sequence_increment(session, &record_state->sequence_number) != 0) { + session_invalidate(session); + gnutls_assert(); + ret = GNUTLS_E_RECORD_LIMIT_REACHED; + goto sanity_check_error; + } - _gnutls_record_log("REC[%p]: Discarded early data[%u] due to invalid decryption, length: %u\n", - session, - (unsigned int) - _gnutls_uint64touint32(packet_sequence), - (unsigned int) - record.length); - session->internals.early_data_received += record.length; - goto discard; + /* decrypted is now accounted */ + return GNUTLS_E_AGAIN; + } } else { - session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + /* We do not accept early data: skip decryption + * failure up to max_early_data_size. Otherwise, + * if the record is properly decrypted, treat it as + * the start of client's second flight. + */ + if (record.type == GNUTLS_APPLICATION_DATA && + (ret < 0 || + /* early data must always be encrypted, treat it + * as decryption failure if otherwise */ + record_params->cipher->id == GNUTLS_CIPHER_NULL)) { + if (record.length > + session->security_parameters.max_early_data_size - + session->internals.early_data_received) { + _gnutls_record_log + ("REC[%p]: max_early_data_size exceeded\n", + session); + ret = GNUTLS_E_UNEXPECTED_PACKET; + goto sanity_check_error; + } + + _gnutls_record_log("REC[%p]: Discarded early data[%u] due to invalid decryption, length: %u\n", + session, + (unsigned int) + _gnutls_uint64touint32(packet_sequence), + (unsigned int) + record.length); + session->internals.early_data_received += record.length; + /* silently discard received data */ + _mbuffer_xfree(&decrypted); + return gnutls_assert_val(GNUTLS_E_AGAIN); + } else { + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + } } } @@ -1924,7 +1973,8 @@ gnutls_record_send2(gnutls_session_t session, const void *data, * data. We allow sending however, if we are in false start handshake * state. */ if (session->internals.recv_state != RECV_STATE_FALSE_START && - session->internals.recv_state != RECV_STATE_EARLY_START) + session->internals.recv_state != RECV_STATE_EARLY_START && + !(session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) return gnutls_assert_val(GNUTLS_E_UNAVAILABLE_DURING_HANDSHAKE); } @@ -1980,6 +2030,94 @@ gnutls_record_send2(gnutls_session_t session, const void *data, } /** + * gnutls_record_send_early_data: + * @session: is a #gnutls_session_t type. + * @data: contains the data to send + * @data_size: is the length of the data + * + * This function can be used by a client to send data early in the + * handshake processes when resuming a session. This is used to + * implement a zero-roundtrip (0-RTT) mode. It has the same semantics + * as gnutls_record_send(). + * + * There may be a limit to the amount of data sent as early data. Use + * gnutls_record_get_max_early_data_size() to check the limit. + * + * Returns: The number of bytes sent, or a negative error code. The + * number of bytes sent might be less than @data_size. The maximum + * number of bytes this function can send in a single call depends + * on the negotiated maximum record size. + * + * Since: 3.6.5 + **/ +ssize_t gnutls_record_send_early_data(gnutls_session_t session, + const void *data, + size_t data_size) +{ + int ret; + + if (session->security_parameters.entity != GNUTLS_CLIENT) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + ret = + _gnutls_buffer_append_data(&session->internals. + early_data_presend_buffer, data, + data_size); + if (ret < 0) + return gnutls_assert_val(ret); + + return ret; +} + +/** + * gnutls_record_recv_early_data: + * @session: is a #gnutls_session_t type. + * @data: the buffer that the data will be read into + * @data_size: the number of requested bytes + * + * This function can be used by a searver to retrieve data sent early + * in the handshake processes when resuming a session. This is used + * to implement a zero-roundtrip (0-RTT) mode. It has the same + * semantics as gnutls_record_recv(). + * + * This function can be called either in a handshake hook, or after + * the handshake is complete. + * + * Returns: The number of bytes received and zero when early data + * reading is complete. A negative error code is returned in case of + * an error. If no early data is received during the handshake, this + * function returns %GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE. The + * number of bytes received might be less than the requested + * @data_size. + * + * Since: 3.6.5 + **/ +ssize_t +gnutls_record_recv_early_data(gnutls_session_t session, void *data, size_t data_size) +{ + mbuffer_st *bufel; + gnutls_datum_t msg; + size_t length; + + if (session->security_parameters.entity != GNUTLS_SERVER) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + bufel = _mbuffer_head_get_first(&session->internals.early_data_recv_buffer, + &msg); + if (bufel == NULL) + return + gnutls_assert_val + (GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + length = MIN(msg.size, data_size); + memcpy(data, msg.data, length); + _mbuffer_head_remove_bytes(&session->internals.early_data_recv_buffer, + length); + + return length; +} + +/** * gnutls_record_cork: * @session: is a #gnutls_session_t type. * diff --git a/lib/session_pack.c b/lib/session_pack.c index b83c9c7440..1869f7740b 100644 --- a/lib/session_pack.c +++ b/lib/session_pack.c @@ -104,6 +104,7 @@ _gnutls_session_pack(gnutls_session_t session, BUFFER_APPEND_NUM(&sb, PACKED_SESSION_MAGIC); BUFFER_APPEND_NUM(&sb, session->security_parameters.timestamp); + BUFFER_APPEND_NUM(&sb, session->internals.expire_time); BUFFER_APPEND(&sb, &id, 1); switch (id) { @@ -190,6 +191,7 @@ _gnutls_session_unpack(gnutls_session_t session, int ret; gnutls_buffer_st sb; uint32_t magic; + uint32_t expire_time; uint8_t id; _gnutls_buffer_init(&sb); @@ -220,6 +222,8 @@ _gnutls_session_unpack(gnutls_session_t session, BUFFER_POP_NUM(&sb, session->internals.resumed_security_parameters. timestamp); + BUFFER_POP_NUM(&sb, expire_time); + (void) expire_time; BUFFER_POP(&sb, &id, 1); switch (id) { @@ -311,8 +315,7 @@ _gnutls_session_unpack(gnutls_session_t session, * 1 bytes the resumption master secret length * x bytes the resumption master secret * 12 bytes the ticket arrival time - * - * WE DON'T STORE NewSessionTicket EXTENSIONS, as we don't support them yet. + * 4 bytes the max early data size * * We only store that info if we received a TLS 1.3 NewSessionTicket at some point. * If we didn't receive any NST then we cannot resume a TLS 1.3 session and hence @@ -348,6 +351,10 @@ tls13_pack_security_parameters(gnutls_session_t session, gnutls_buffer_st *ps) length += (1 + ticket->prf->output_size); BUFFER_APPEND_TS(ps, ticket->arrival_time); length += 12; + BUFFER_APPEND_NUM(ps, + session->security_parameters. + max_early_data_size); + length += 4; /* Overwrite the length field */ _gnutls_write_uint32(length, ps->data + length_pos); @@ -396,6 +403,9 @@ tls13_unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st *ps) ticket->prf = session->internals.resumed_security_parameters.prf; BUFFER_POP_TS(ps, ticket->arrival_time); + BUFFER_POP_NUM(ps, + session->security_parameters. + max_early_data_size); } error: diff --git a/lib/state.c b/lib/state.c index 303a3ad2f8..01288ad474 100644 --- a/lib/state.c +++ b/lib/state.c @@ -485,6 +485,8 @@ int gnutls_init(gnutls_session_t * session, unsigned int flags) _mbuffer_head_init(&(*session)->internals.record_buffer); _mbuffer_head_init(&(*session)->internals.record_send_buffer); _mbuffer_head_init(&(*session)->internals.record_recv_buffer); + _mbuffer_head_init(&(*session)->internals.early_data_recv_buffer); + _gnutls_buffer_init(&(*session)->internals.early_data_presend_buffer); _mbuffer_head_init(&(*session)->internals.handshake_send_buffer); _gnutls_handshake_recv_buffer_init(*session); @@ -620,6 +622,9 @@ void gnutls_deinit(gnutls_session_t session) _mbuffer_head_clear(&session->internals.record_recv_buffer); _mbuffer_head_clear(&session->internals.record_send_buffer); + _mbuffer_head_clear(&session->internals.early_data_recv_buffer); + _gnutls_buffer_clear(&session->internals.early_data_presend_buffer); + _gnutls_free_datum(&session->internals.resumption_data); _gnutls_free_datum(&session->internals.dtls.dcookie); @@ -1542,6 +1547,8 @@ unsigned gnutls_session_get_flags(gnutls_session_t session) flags |= GNUTLS_SFLAGS_SESSION_TICKET; if (session->security_parameters.post_handshake_auth) flags |= GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH; + if (session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED) + flags |= GNUTLS_SFLAGS_EARLY_DATA; return flags; } @@ -234,7 +234,7 @@ int _gnutls_hostname_compare(const char *certname, size_t certnamesize, } #define BUFFER_APPEND_TS(b, s) { \ - ret = _gnutls_buffer_append_prefix(b, 32, s.tv_sec >> 32); \ + ret = _gnutls_buffer_append_prefix(b, 32, (uint64_t) s.tv_sec >> 32); \ if (ret < 0) { \ gnutls_assert(); \ return ret; \ diff --git a/lib/tls13/anti_replay.c b/lib/tls13/anti_replay.c new file mode 100644 index 0000000000..5ae9926afd --- /dev/null +++ b/lib/tls13/anti_replay.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" +#include "db.h" +#include "system.h" +#include "tls13/anti_replay.h" + +/* The default time window in milliseconds; RFC8446 suggests the order + * of ten seconds is sufficient for the clients on the Internet. */ +#define DEFAULT_WINDOW_MS 10000 + +struct gnutls_anti_replay_st { + uint32_t window; + struct timespec start_time; +}; + +/** + * gnutls_anti_replay_init: + * @anti_replay: is a pointer to #gnutls_anti_replay_t type + * + * This function will allocate and initialize the @anti_replay context + * to be usable for detect replay attacks. The context can then be + * attached to a @gnutls_session_t with + * gnutls_anti_replay_enable(). + * + * Returns: Zero or a negative error code on error. + * + * Since: 3.6.5 + **/ +int +gnutls_anti_replay_init(gnutls_anti_replay_t *anti_replay) +{ + *anti_replay = gnutls_calloc(1, sizeof(struct gnutls_anti_replay_st)); + if (!*anti_replay) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + (*anti_replay)->window = DEFAULT_WINDOW_MS; + + gnutls_gettime(&(*anti_replay)->start_time); + + return 0; +} + +/** + * gnutls_anti_replay_set_window: + * @anti_replay: is a #gnutls_anti_replay_t type. + * @window: is the time window recording ClientHello, in milliseconds + * + * Sets the time window used for ClientHello recording. In order to + * protect against replay attacks, the server records ClientHello + * messages within this time period from the last update, and + * considers it a replay when a ClientHello outside of the period; if + * a ClientHello arrives within this period, the server checks the + * database and detects duplicates. + * + * For the details of the algorithm, see RFC 8446, section 8.2. + * + * Since: 3.6.5 + */ +void +gnutls_anti_replay_set_window(gnutls_anti_replay_t anti_replay, + unsigned int window) +{ + anti_replay->window = window; +} + +/** + * gnutls_anti_replay_deinit: + * @anti_replay: is a #gnutls_anti_replay type + * + * This function will deinitialize all resources occupied by the given + * anti-replay context. + * + * Since: 3.6.5 + **/ +void +gnutls_anti_replay_deinit(gnutls_anti_replay_t anti_replay) +{ + gnutls_free(anti_replay); +} + +/** + * gnutls_anti_replay_enable: + * @session: is a #gnutls_session_t type. + * @anti_replay: is a #gnutls_anti_replay_t type. + * + * Request that the server should use anti-replay mechanism. + * + * Since: 3.6.5 + **/ +void +gnutls_anti_replay_enable(gnutls_session_t session, + gnutls_anti_replay_t anti_replay) +{ + if (unlikely(session->security_parameters.entity != GNUTLS_SERVER)) { + gnutls_assert(); + return; + } + + session->internals.anti_replay = anti_replay; +} + +int +_gnutls_anti_replay_check(gnutls_session_t session, + uint32_t client_ticket_age, + struct timespec *ticket_creation_time, + gnutls_datum_t *id) +{ + gnutls_anti_replay_t anti_replay = session->internals.anti_replay; + struct timespec now; + uint32_t server_ticket_age, diff; + gnutls_datum_t key = { NULL, 0 }; + gnutls_datum_t entry = { NULL, 0 }; + unsigned char key_buffer[MAX_HASH_SIZE + 12]; + unsigned char entry_buffer[12]; /* magic + timestamp + expire_time */ + unsigned char *p; + int ret; + + if (unlikely(id->size > MAX_HASH_SIZE)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + gnutls_gettime(&now); + server_ticket_age = timespec_sub_ms(&now, ticket_creation_time); + + /* It shouldn't be possible that the server's view of ticket + * age is smaller than the client's view. + */ + if (unlikely(server_ticket_age < client_ticket_age)) + return gnutls_assert_val(GNUTLS_E_ILLEGAL_PARAMETER); + + /* If ticket is created before recording has started, discard + * reject early data. + */ + if (_gnutls_timespec_cmp(ticket_creation_time, + &anti_replay->start_time) < 0) { + _gnutls_handshake_log("anti_replay: ticket is created before recording has started\n"); + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + } + + /* If certain amount of time (window) has elapsed, rollover + * the recording. + */ + diff = timespec_sub_ms(&now, &anti_replay->start_time); + if (diff > anti_replay->window) + gnutls_gettime(&anti_replay->start_time); + + /* If expected_arrival_time is out of window, reject early + * data. + */ + if (server_ticket_age - client_ticket_age > anti_replay->window) { + _gnutls_handshake_log("anti_replay: server ticket age: %u, client ticket age: %u\n", + server_ticket_age, + client_ticket_age); + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + } + + /* Check if the ClientHello is stored in the database. + */ + if (!session->internals.db_add_func) + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + + /* Create a key for database lookup, prefixing window start + * time to ID. Note that this shouldn't clash with session ID + * used in TLS 1.2, because such IDs are 32 octets, while here + * the key becomes 44+ octets. + */ + p = key_buffer; + _gnutls_write_uint32((uint64_t) anti_replay->start_time.tv_sec >> 32, p); + p += 4; + _gnutls_write_uint32(anti_replay->start_time.tv_sec & 0xFFFFFFFF, p); + p += 4; + _gnutls_write_uint32(anti_replay->start_time.tv_nsec, p); + p += 4; + memcpy(p, id->data, id->size); + p += id->size; + key.data = key_buffer; + key.size = p - key_buffer; + + /* Create an entry to be stored on database if the lookup + * failed. This is formatted so that + * gnutls_db_entry_is_expired() work. + */ + p = entry_buffer; + _gnutls_write_uint32(PACKED_SESSION_MAGIC, p); + p += 4; + _gnutls_write_uint32(now.tv_sec, p); + p += 4; + _gnutls_write_uint32(anti_replay->window / 1000, p); + p += 4; + entry.data = entry_buffer; + entry.size = p - entry_buffer; + + ret = session->internals.db_add_func(session->internals.db_ptr, + key, entry); + if (ret < 0) { + _gnutls_handshake_log("anti_replay: duplicate ClientHello found\n"); + return gnutls_assert_val(GNUTLS_E_EARLY_DATA_REJECTED); + } + + return 0; +} diff --git a/lib/tls13/anti_replay.h b/lib/tls13/anti_replay.h new file mode 100644 index 0000000000..e44186c910 --- /dev/null +++ b/lib/tls13/anti_replay.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Daiki Ueno + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +int _gnutls_anti_replay_check(gnutls_session_t session, + uint32_t client_ticket_age, + struct timespec *ticket_creation_time, + gnutls_datum_t *id); diff --git a/lib/tls13/early_data.c b/lib/tls13/early_data.c new file mode 100644 index 0000000000..dd977fc410 --- /dev/null +++ b/lib/tls13/early_data.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +#include "gnutls_int.h" +#include "handshake.h" +#include "tls13/early_data.h" + +int _gnutls13_send_early_data(gnutls_session_t session) +{ + int ret; + + if (!(session->security_parameters.entity == GNUTLS_CLIENT && + session->internals.hsk_flags & HSK_EARLY_DATA_IN_FLIGHT)) + return 0; + + while (session->internals.early_data_presend_buffer.length > 0) { + ret = + gnutls_record_send(session, + session->internals. + early_data_presend_buffer.data, + session->internals. + early_data_presend_buffer. + length); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.early_data_presend_buffer.data += ret; + session->internals.early_data_presend_buffer.length -= ret; + } + + + return 0; +} + +int _gnutls13_send_end_of_early_data(gnutls_session_t session, unsigned again) +{ + int ret; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + + if (!(session->security_parameters.entity == GNUTLS_CLIENT && + session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED)) + return 0; + + if (again == 0) { + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + bufel = _gnutls_buffer_to_mbuffer(&buf); + } + + return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_END_OF_EARLY_DATA); +} + +int _gnutls13_recv_end_of_early_data(gnutls_session_t session) +{ + int ret; + gnutls_buffer_st buf; + + if (!(session->security_parameters.entity == GNUTLS_SERVER && + session->internals.hsk_flags & HSK_EARLY_DATA_ACCEPTED)) + return 0; + + ret = _gnutls_recv_handshake(session, GNUTLS_HANDSHAKE_END_OF_EARLY_DATA, 0, &buf); + if (ret < 0) + return gnutls_assert_val(ret); + + if (buf.length != 0) { + gnutls_assert(); + ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; + goto cleanup; + } + + session->internals.hsk_flags &= ~HSK_EARLY_DATA_IN_FLIGHT; + + ret = 0; +cleanup: + + _gnutls_buffer_clear(&buf); + return ret; +} diff --git a/lib/tls13/early_data.h b/lib/tls13/early_data.h new file mode 100644 index 0000000000..ddbd983293 --- /dev/null +++ b/lib/tls13/early_data.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +int _gnutls13_send_end_of_early_data(gnutls_session_t session, unsigned again); +int _gnutls13_recv_end_of_early_data(gnutls_session_t session); +int _gnutls13_send_early_data(gnutls_session_t session); diff --git a/lib/tls13/key_update.c b/lib/tls13/key_update.c index d9c495efdc..0c5c93734a 100644 --- a/lib/tls13/key_update.c +++ b/lib/tls13/key_update.c @@ -40,7 +40,7 @@ static int update_keys(gnutls_session_t session, hs_stage_t stage) return gnutls_assert_val(ret); _gnutls_epoch_bump(session); - ret = _gnutls_epoch_dup(session); + ret = _gnutls_epoch_dup(session, EPOCH_READ_CURRENT); if (ret < 0) return gnutls_assert_val(ret); diff --git a/lib/tls13/session_ticket.c b/lib/tls13/session_ticket.c index ad04a60919..7ea2b00f82 100644 --- a/lib/tls13/session_ticket.c +++ b/lib/tls13/session_ticket.c @@ -46,7 +46,7 @@ pack_ticket(gnutls_session_t session, tls13_ticket_st *ticket, gnutls_datum_t *p packed->size = 2 + 4 + 4 + 1 + ticket->prf->output_size + - 1 + ticket->nonce_size + 2 + state.size; + 1 + ticket->nonce_size + 2 + state.size + 12; packed->data = gnutls_malloc(packed->size); if (!packed->data) { @@ -77,6 +77,14 @@ pack_ticket(gnutls_session_t session, tls13_ticket_st *ticket, gnutls_datum_t *p p += 2; memcpy(p, state.data, state.size); + p += state.size; + + _gnutls_write_uint32(ticket->creation_time.tv_sec >> 32, p); + p += 4; + _gnutls_write_uint32(ticket->creation_time.tv_sec & 0xFFFFFFFF, p); + p += 4; + _gnutls_write_uint32(ticket->creation_time.tv_nsec, p); + ret = 0; cleanup: @@ -88,6 +96,7 @@ static int unpack_ticket(gnutls_session_t session, gnutls_datum_t *packed, tls13_ticket_st *data) { uint32_t age_add, lifetime; + struct timespec creation_time; uint8_t resumption_master_secret[MAX_HASH_SIZE]; size_t resumption_master_secret_size; uint8_t nonce[UINT8_MAX]; @@ -156,6 +165,15 @@ unpack_ticket(gnutls_session_t session, gnutls_datum_t *packed, tls13_ticket_st DECR_LEN(len, state.size); state.data = p; + p += state.size; + + DECR_LEN(len, 12); + creation_time.tv_sec = _gnutls_read_uint32(p); + p += 4; + creation_time.tv_sec <<= 32; + creation_time.tv_sec |= _gnutls_read_uint32(p); + p += 4; + creation_time.tv_nsec = _gnutls_read_uint32(p); ret = _gnutls_session_unpack(session, &state); if (ret < 0) @@ -169,6 +187,7 @@ unpack_ticket(gnutls_session_t session, gnutls_datum_t *packed, tls13_ticket_st data->nonce_size = nonce_size; data->age_add = age_add; data->lifetime = lifetime; + memcpy(&data->creation_time, &creation_time, sizeof(struct timespec)); return 0; } @@ -178,17 +197,18 @@ generate_session_ticket(gnutls_session_t session, tls13_ticket_st *ticket) { int ret; gnutls_datum_t packed = { NULL, 0 }; + struct timespec now; tls13_ticket_st ticket_data; - time_t now = gnutls_time(0); + gnutls_gettime(&now); if (session->internals.resumed != RESUME_FALSE) { /* If we are resuming ensure that we don't extend the lifetime * of the ticket past the original session expiration time */ - if (now >= session->security_parameters.timestamp + session->internals.expire_time) + if (now.tv_sec >= session->security_parameters.timestamp + session->internals.expire_time) return GNUTLS_E_INT_RET_0; /* don't send ticket */ else ticket->lifetime = session->security_parameters.timestamp + - session->internals.expire_time - now; + session->internals.expire_time - now.tv_sec; } else { /* Set ticket lifetime to the default expiration time */ ticket->lifetime = session->internals.expire_time; @@ -210,6 +230,7 @@ generate_session_ticket(gnutls_session_t session, tls13_ticket_st *ticket) /* Encrypt the ticket and place the result in ticket->ticket */ ticket_data.lifetime = ticket->lifetime; ticket_data.age_add = ticket->age_add; + memcpy(&ticket_data.creation_time, &now, sizeof(struct timespec)); memcpy(ticket_data.nonce, ticket->nonce, ticket->nonce_size); ticket_data.nonce_size = ticket->nonce_size; ticket_data.prf = ticket->prf; @@ -229,6 +250,23 @@ generate_session_ticket(gnutls_session_t session, tls13_ticket_st *ticket) return 0; } +static int append_nst_extension(void *ctx, gnutls_buffer_st *buf) +{ + gnutls_session_t session = ctx; + int ret; + + if (!(session->internals.flags & GNUTLS_ENABLE_EARLY_DATA)) + return 0; + + ret = _gnutls_buffer_append_prefix(buf, 32, + session->security_parameters. + max_early_data_size); + if (ret < 0) + gnutls_assert(); + + return ret; +} + int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigned again) { int ret = 0; @@ -253,6 +291,8 @@ int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigne if (again == 0) { for (i=0;i<nr;i++) { + unsigned init_pos; + memset(&ticket, 0, sizeof(tls13_ticket_st)); bufel = NULL; @@ -296,13 +336,28 @@ int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigne goto cleanup; } - ret = _gnutls_buffer_append_prefix(&buf, 16, 0); + _gnutls_free_datum(&ticket.ticket); + + /* append extensions */ + ret = _gnutls_extv_append_init(&buf); if (ret < 0) { gnutls_assert(); goto cleanup; } + init_pos = ret; - _gnutls_free_datum(&ticket.ticket); + ret = _gnutls_extv_append(&buf, ext_mod_early_data.tls_id, session, + (extv_append_func)append_nst_extension); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_extv_append_final(&buf, init_pos); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } bufel = _gnutls_buffer_to_mbuffer(&buf); @@ -337,7 +392,8 @@ static int parse_nst_extension(void *ctx, unsigned tls_id, const unsigned char * if (data_size < 4) return gnutls_assert_val(GNUTLS_E_TLS_PACKET_DECODING_ERROR); size = _gnutls_read_uint32(data); - session->security_parameters.max_early_data_size = size; + if (size < session->security_parameters.max_early_data_size) + session->security_parameters.max_early_data_size = size; } return 0; } |