summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorDaiki Ueno <ueno@gnu.org>2018-11-12 15:54:01 +0000
committerDaiki Ueno <ueno@gnu.org>2018-11-12 15:54:01 +0000
commit868a373f915f65259c9708023ed612beb513db21 (patch)
tree54555ab056b65c644ed26253b9e5cdbe4d707fbe /lib
parent0e9e406c6d92a5cda2020ebda9bede0d3503f4bd (diff)
parent4429256c40161b088847f8e058c8a4cfb8d5b5f1 (diff)
downloadgnutls-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.am4
-rw-r--r--lib/constate.c83
-rw-r--r--lib/constate.h2
-rw-r--r--lib/db.c61
-rw-r--r--lib/errors.c3
-rw-r--r--lib/ext/early_data.c49
-rw-r--r--lib/ext/pre_shared_key.c35
-rw-r--r--lib/gnutls_int.h24
-rw-r--r--lib/handshake-tls13.c175
-rw-r--r--lib/handshake.c120
-rw-r--r--lib/handshake.h2
-rw-r--r--lib/includes/gnutls/gnutls.h.in33
-rw-r--r--lib/libgnutls.map16
-rw-r--r--lib/record.c200
-rw-r--r--lib/session_pack.c14
-rw-r--r--lib/state.c7
-rw-r--r--lib/str.h2
-rw-r--r--lib/tls13/anti_replay.c221
-rw-r--r--lib/tls13/anti_replay.h26
-rw-r--r--lib/tls13/early_data.c101
-rw-r--r--lib/tls13/early_data.h25
-rw-r--r--lib/tls13/key_update.c2
-rw-r--r--lib/tls13/session_ticket.c70
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 = &params->write;
+ } else {
+ early_state = &params->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, &params->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, &params->read);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+ }
- ret = _tls13_init_record_state(params->cipher->id, &params->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, &params->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);
diff --git a/lib/db.c b/lib/db.c
index a029f351cd..414816fcc8 100644
--- a/lib/db.c
+++ b/lib/db.c
@@ -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;
}
diff --git a/lib/str.h b/lib/str.h
index c8be6c9913..a9cb48fd6e 100644
--- a/lib/str.h
+++ b/lib/str.h
@@ -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;
}