diff options
author | Ander Juaristi <a@juaristi.eus> | 2018-04-16 17:13:47 +0200 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2018-05-26 04:12:04 +0000 |
commit | 4b5678716f506d46da8dabdd343b268a5b9dd9b4 (patch) | |
tree | f7fcb5c04a3848437254676aeda456d23f79ad87 | |
parent | aed3ac3a2dd976bbdef4705d7caa3db2b9239c79 (diff) | |
download | gnutls-4b5678716f506d46da8dabdd343b268a5b9dd9b4.tar.gz |
TLS 1.3: Introduced TLS 1.3 session resumption
This introduces session resumption under TLS 1.3. For that,
it enables the psk_ke_modes extension when we enable session
tickets. It enables sending session tickets in addition to
PSK usernames. The detection of resumption vs pure PSK is done by
comparing the indexes sent with the index received by the server.
TLS 1.3 session tickets are always sent to the peer unless the
GNUTLS_NO_TICKETS is specified.
Resolves #290
Signed-off-by: Ander Juaristi <a@juaristi.eus>
Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
Signed-off-by: Daiki Ueno <dueno@redhat.com>
-rw-r--r-- | .gitlab-ci.yml | 2 | ||||
-rw-r--r-- | lib/algorithms/ciphersuites.c | 5 | ||||
-rw-r--r-- | lib/auth.c | 24 | ||||
-rw-r--r-- | lib/constate.c | 32 | ||||
-rw-r--r-- | lib/constate.h | 2 | ||||
-rw-r--r-- | lib/db.c | 10 | ||||
-rw-r--r-- | lib/ext/pre_shared_key.c | 467 | ||||
-rw-r--r-- | lib/ext/pre_shared_key.h | 5 | ||||
-rw-r--r-- | lib/ext/psk_ke_modes.c | 36 | ||||
-rw-r--r-- | lib/ext/session_ticket.c | 17 | ||||
-rw-r--r-- | lib/gnutls_int.h | 63 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 38 | ||||
-rw-r--r-- | lib/handshake.c | 170 | ||||
-rw-r--r-- | lib/handshake.h | 1 | ||||
-rw-r--r-- | lib/hello_ext.c | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 4 | ||||
-rw-r--r-- | lib/session.c | 71 | ||||
-rw-r--r-- | lib/session_pack.c | 202 | ||||
-rw-r--r-- | lib/state.c | 31 | ||||
-rw-r--r-- | lib/state.h | 2 | ||||
-rw-r--r-- | lib/tls13/certificate.c | 3 | ||||
-rw-r--r-- | lib/tls13/certificate_verify.c | 3 | ||||
-rw-r--r-- | lib/tls13/finished.c | 11 | ||||
-rw-r--r-- | lib/tls13/finished.h | 1 | ||||
-rw-r--r-- | lib/tls13/hello_retry.c | 4 | ||||
-rw-r--r-- | lib/tls13/session_ticket.c | 342 | ||||
-rw-r--r-- | lib/tls13/session_ticket.h | 30 | ||||
-rw-r--r-- | m4/hooks.m4 | 16 | ||||
-rw-r--r-- | src/serv.c | 4 | ||||
-rw-r--r-- | tests/session-tickets-missing.c | 41 |
30 files changed, 1266 insertions, 373 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b4085048e..abdec0a1ff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -97,7 +97,7 @@ minimal.Fedora.x86_64: --disable-doc --disable-dtls-srtp-support --disable-alpn-support --disable-tests --disable-heartbeat-support --disable-srp-authentication --disable-psk-authentication --disable-anon-authentication --disable-dhe --disable-ecdhe - --disable-ocsp --disable-session-tickets --disable-non-suiteb-curves --with-included-unistring + --disable-ocsp --disable-non-suiteb-curves --with-included-unistring --disable-nls --disable-libdane --without-p11-kit --without-tpm --disable-ssl3-support --disable-ssl2-support --disable-doc --enable-openssl-compatibility --disable-gcc-warnings diff --git a/lib/algorithms/ciphersuites.c b/lib/algorithms/ciphersuites.c index dbfcbb0c90..7b24757468 100644 --- a/lib/algorithms/ciphersuites.c +++ b/lib/algorithms/ciphersuites.c @@ -1435,6 +1435,7 @@ static unsigned kx_is_ok(gnutls_session_t session, gnutls_kx_algorithm_t kx, uns return 1; } +/* Called on server-side only */ int _gnutls_figure_common_ciphersuite(gnutls_session_t session, const ciphersuite_list_st *peer_clist, @@ -1485,7 +1486,7 @@ _gnutls_figure_common_ciphersuite(gnutls_session_t session, /* if we have selected PSK, we need a ciphersuites which matches * the selected binder */ if (session->internals.hsk_flags & HSK_PSK_SELECTED) { - if (session->key.proto.tls13.binder_prf->id != session->internals.priorities->cs.entry[j]->prf) + if (session->key.binders[0].prf->id != session->internals.priorities->cs.entry[j]->prf) continue; } else if (cred_type == GNUTLS_CRD_CERTIFICATE) { ret = _gnutls_server_select_cert(session, peer_clist->entry[i]); @@ -1528,7 +1529,7 @@ _gnutls_figure_common_ciphersuite(gnutls_session_t session, /* if we have selected PSK, we need a ciphersuites which matches * the selected binder */ if (session->internals.hsk_flags & HSK_PSK_SELECTED) { - if (session->key.proto.tls13.binder_prf->id != session->internals.priorities->cs.entry[j]->prf) + if (session->key.binders[0].prf->id != session->internals.priorities->cs.entry[j]->prf) break; } else if (cred_type == GNUTLS_CRD_CERTIFICATE) { ret = _gnutls_server_select_cert(session, peer_clist->entry[i]); diff --git a/lib/auth.c b/lib/auth.c index ca4425e67e..e4cc3f9f31 100644 --- a/lib/auth.c +++ b/lib/auth.c @@ -230,17 +230,7 @@ gnutls_credentials_type_t gnutls_auth_get_type(gnutls_session_t session) gnutls_credentials_type_t gnutls_auth_server_get_type(gnutls_session_t session) { - gnutls_kx_algorithm_t kx; - - if (!session->security_parameters.cs) { - gnutls_assert(); - return 0; - } - - kx = gnutls_kx_get(session); - - return - _gnutls_map_kx_get_cred(kx, 1); + return session->security_parameters.server_auth_type; } /** @@ -257,17 +247,7 @@ gnutls_auth_server_get_type(gnutls_session_t session) gnutls_credentials_type_t gnutls_auth_client_get_type(gnutls_session_t session) { - gnutls_kx_algorithm_t kx; - - if (!session->security_parameters.cs) { - gnutls_assert(); - return 0; - } - - kx = gnutls_kx_get(session); - - return - _gnutls_map_kx_get_cred(kx, 0); + return session->security_parameters.client_auth_type; } diff --git a/lib/constate.c b/lib/constate.c index cc8b817715..ecfd53c494 100644 --- a/lib/constate.c +++ b/lib/constate.c @@ -648,30 +648,34 @@ int _gnutls_epoch_set_keys(gnutls_session_t session, uint16_t epoch, hs_stage_t } -#define CPY_COMMON dst->entity = src->entity; \ - dst->cs = src->cs; \ - dst->grp = src->grp; \ - dst->prf = src->prf; \ - memcpy( dst->master_secret, src->master_secret, GNUTLS_MASTER_SIZE); \ - memcpy( dst->client_random, src->client_random, GNUTLS_RANDOM_SIZE); \ - memcpy( dst->server_random, src->server_random, GNUTLS_RANDOM_SIZE); \ +#define CPY_COMMON(tls13_sem) \ + if (!tls13_sem) { \ + dst->cs = src->cs; \ + memcpy( dst->master_secret, src->master_secret, GNUTLS_MASTER_SIZE); \ + memcpy( dst->client_random, src->client_random, GNUTLS_RANDOM_SIZE); \ + memcpy( dst->server_random, src->server_random, GNUTLS_RANDOM_SIZE); \ + dst->ext_master_secret = src->ext_master_secret; \ + dst->etm = src->etm; \ + dst->max_record_recv_size = src->max_record_recv_size; \ + dst->max_record_send_size = src->max_record_send_size; \ + dst->prf = src->prf; \ + dst->grp = src->grp; \ + dst->pversion = src->pversion; \ + } \ memcpy( dst->session_id, src->session_id, GNUTLS_MAX_SESSION_ID_SIZE); \ dst->session_id_size = src->session_id_size; \ dst->cert_type = src->cert_type; \ dst->timestamp = src->timestamp; \ - dst->ext_master_secret = src->ext_master_secret; \ - dst->etm = src->etm; \ - dst->max_record_recv_size = src->max_record_recv_size; \ - dst->max_record_send_size = src->max_record_send_size + dst->client_auth_type = src->client_auth_type; \ + dst->server_auth_type = src->server_auth_type -static void _gnutls_set_resumed_parameters(gnutls_session_t session) +void _gnutls_set_resumed_parameters(gnutls_session_t session) { security_parameters_st *src = &session->internals.resumed_security_parameters; security_parameters_st *dst = &session->security_parameters; - CPY_COMMON; - dst->pversion = src->pversion; + CPY_COMMON(get_version(session)->tls13_sem); } /* Sets the current connection session to conform with the diff --git a/lib/constate.h b/lib/constate.h index 1d62edccfa..8a15400f5f 100644 --- a/lib/constate.h +++ b/lib/constate.h @@ -43,6 +43,8 @@ void _gnutls_epoch_gc(gnutls_session_t session); void _gnutls_epoch_free(gnutls_session_t session, record_parameters_st * state); +void _gnutls_set_resumed_parameters(gnutls_session_t session); + int _tls13_connection_state_init(gnutls_session_t session, hs_stage_t stage); static inline int _gnutls_epoch_is_valid(gnutls_session_t session, @@ -122,12 +122,18 @@ void *gnutls_db_get_ptr(gnutls_session_t session) * @session: is a #gnutls_session_t type. * @seconds: is the number of seconds. * - * Set the expiration time for resumed sessions. The default is 3600 - * (one hour) at the time of this writing. + * Set the expiration time for resumed sessions. The default is 21600 + * (size hours) at the time of writing. + * + * The maximum value that can be set using this function is 604800 + * (7 days). + * **/ void gnutls_db_set_cache_expiration(gnutls_session_t session, int seconds) { session->internals.expire_time = seconds; + if (session->internals.expire_time > 604800) + session->internals.expire_time = 604800; } /** diff --git a/lib/ext/pre_shared_key.c b/lib/ext/pre_shared_key.c index 0fa7df2d27..5c8a80c4a2 100644 --- a/lib/ext/pre_shared_key.c +++ b/lib/ext/pre_shared_key.c @@ -26,18 +26,53 @@ #include "secrets.h" #include "tls13/psk_ext_parser.h" #include "tls13/finished.h" +#include "tls13/session_ticket.h" #include "auth/psk_passwd.h" +#include <ext/session_ticket.h> #include <ext/pre_shared_key.h> #include <assert.h> static int +compute_psk_from_ticket(const tls13_ticket_t *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); + + key->data = gnutls_malloc(ticket->prf->output_size); + if (!key->data) { + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + key->size = ticket->prf->output_size; + + ret = _tls13_expand_secret2(ticket->prf, + label, sizeof(label)-1, + ticket->nonce, ticket->nonce_size, + ticket->resumption_master_secret, + key->size, + key->data); + if (ret < 0) + gnutls_assert(); + + return ret; +} + +static int compute_binder_key(const mac_entry_st *prf, - const uint8_t *key, size_t keylen, - void *out) + const uint8_t *key, size_t keylen, + bool resuming, + void *out) { int ret; - char label[] = "ext binder"; - size_t label_len = sizeof(label) - 1; + const char ext_label[] = "ext binder"; + const size_t ext_label_len = sizeof(ext_label) - 1; + const char res_label[] = "res binder"; + 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; uint8_t tmp_key[MAX_HASH_SIZE]; /* Compute HKDF-Extract(0, psk) */ @@ -46,11 +81,8 @@ compute_binder_key(const mac_entry_st *prf, return ret; /* Compute Derive-Secret(secret, label, transcript_hash) */ - ret = _tls13_derive_secret2(prf, - label, label_len, - NULL, 0, - tmp_key, - out); + ret = _tls13_derive_secret2(prf, label, label_len, + NULL, 0, tmp_key, out); if (ret < 0) return ret; @@ -59,10 +91,10 @@ compute_binder_key(const mac_entry_st *prf, static int compute_psk_binder(gnutls_session_t session, - const mac_entry_st *prf, unsigned binders_length, unsigned hash_size, - int exts_length, int ext_offset, - const gnutls_datum_t *psk, const gnutls_datum_t *client_hello, - void *out) + const mac_entry_st *prf, unsigned binders_length, + int exts_length, int ext_offset, + const gnutls_datum_t *psk, const gnutls_datum_t *client_hello, + bool resuming, void *out) { int ret; unsigned client_hello_pos, extensions_len_pos; @@ -83,9 +115,8 @@ compute_psk_binder(gnutls_session_t session, } client_hello_pos = handshake_buf.length; - ret = gnutls_buffer_append_data(&handshake_buf, - (const void *) client_hello->data, - client_hello->size); + ret = gnutls_buffer_append_data(&handshake_buf, client_hello->data, + client_hello->size); if (ret < 0) { gnutls_assert(); goto error; @@ -94,18 +125,16 @@ compute_psk_binder(gnutls_session_t session, /* This is a ClientHello message */ handshake_buf.data[client_hello_pos] = GNUTLS_HANDSHAKE_CLIENT_HELLO; - /* - * At this point we have not yet added the binders to the ClientHello, + /* At this point we have not yet added the binders to the ClientHello, * but we have to overwrite the size field, pretending as if binders * of the correct length were present. */ _gnutls_write_uint24(handshake_buf.length - client_hello_pos + binders_length - 2, &handshake_buf.data[client_hello_pos + 1]); _gnutls_write_uint16(handshake_buf.length - client_hello_pos + binders_length - ext_offset, - &handshake_buf.data[client_hello_pos + ext_offset]); - + &handshake_buf.data[client_hello_pos + ext_offset]); extensions_len_pos = handshake_buf.length - client_hello_pos - exts_length - 2; _gnutls_write_uint16(exts_length + binders_length + 2, - &handshake_buf.data[client_hello_pos + extensions_len_pos]); + &handshake_buf.data[client_hello_pos + extensions_len_pos]); } else { if (session->internals.hsk_flags & HSK_HRR_SENT) { if (unlikely(session->internals.handshake_hash_buffer.length <= client_hello->size)) { @@ -114,7 +143,7 @@ compute_psk_binder(gnutls_session_t session, } ret = gnutls_buffer_append_data(&handshake_buf, - (const void *) session->internals.handshake_hash_buffer.data, + session->internals.handshake_hash_buffer.data, session->internals.handshake_hash_buffer.length - client_hello->size); if (ret < 0) { gnutls_assert(); @@ -137,7 +166,7 @@ compute_psk_binder(gnutls_session_t session, } ret = compute_binder_key(prf, - psk->data, psk->size, + psk->data, psk->size, resuming, binder_key); if (ret < 0) { gnutls_assert(); @@ -145,7 +174,6 @@ compute_psk_binder(gnutls_session_t session, } ret = _gnutls13_compute_finished(prf, binder_key, - hash_size, &handshake_buf, out); if (ret < 0) { @@ -166,52 +194,155 @@ client_send_params(gnutls_session_t session, { int ret, ext_offset = 0; uint8_t binder_value[MAX_HASH_SIZE]; - size_t length, pos; - gnutls_datum_t username = {NULL, 0}, key = {NULL, 0}, client_hello; - const mac_entry_st *prf = cred->binder_algo; - unsigned hash_size = _gnutls_mac_get_algo_len(prf); - int free_data; - - if (prf == NULL || hash_size == 0 || hash_size > 255) - return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + size_t spos; + gnutls_datum_t username = {NULL, 0}; + gnutls_datum_t user_key = {NULL, 0}, rkey = {NULL, 0}; + gnutls_datum_t client_hello; + unsigned next_idx; + const mac_entry_st *prf_res = NULL; + const mac_entry_st *prf_psk = NULL; + time_t cur_time; + int ticket_age; + uint32_t ob_ticket_age; + int free_username = 0; + psk_auth_info_t info = NULL; + unsigned psk_id_len = 0; + unsigned binders_len, binders_pos; + + if (((session->internals.flags & GNUTLS_NO_TICKETS) || + session->internals.tls13_ticket.ticket.data == NULL) && + (!cred || !_gnutls_have_psk_credentials(cred, session))) { - /* Credentials but no username set - this extension is not applicable */ - if (!_gnutls_have_psk_credentials(cred)) return 0; + } + + binders_len = 0; - ret = _gnutls_find_psk_key(session, cred, &username, &key, &free_data); + /* placeholder to be filled later */ + spos = extdata->length; + ret = _gnutls_buffer_append_prefix(extdata, 16, 0); if (ret < 0) return gnutls_assert_val(ret); - if (username.size == 0 || username.size > UINT16_MAX) { - ret = gnutls_assert_val(GNUTLS_E_INVALID_PASSWORD); - goto cleanup; - } + /* First, let's see if we have a session ticket to send */ + if (!(session->internals.flags & GNUTLS_NO_TICKETS) && + session->internals.tls13_ticket.ticket.data != NULL) { + /* We found a session ticket */ + if (unlikely(session->internals.tls13_ticket.prf == NULL)) { + _gnutls13_session_ticket_unset(session); + ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + goto cleanup; + } - /* placeholder to be filled later */ - pos = extdata->length; - ret = _gnutls_buffer_append_prefix(extdata, 16, 0); - if (ret < 0) { - gnutls_assert_val(ret); - goto cleanup; - } + prf_res = session->internals.tls13_ticket.prf; - if ((ret = _gnutls_buffer_append_data_prefix(extdata, 16, - username.data, username.size)) < 0) { - gnutls_assert(); - goto cleanup; + /* Check whether the ticket is stale */ + cur_time = gnutls_time(0); + ticket_age = cur_time - session->internals.tls13_ticket.timestamp; + if (ticket_age < 0 || ticket_age > cur_time) { + gnutls_assert(); + _gnutls13_session_ticket_unset(session); + goto ignore_ticket; + } + + if ((unsigned int) ticket_age > session->internals.tls13_ticket.lifetime) { + _gnutls13_session_ticket_unset(session); + goto ignore_ticket; + } + + ret = compute_psk_from_ticket(&session->internals.tls13_ticket, &rkey); + if (ret < 0) { + _gnutls13_session_ticket_unset(session); + goto ignore_ticket; + } + + /* Calculate obfuscated ticket age, in milliseconds, mod 2^32 */ + ob_ticket_age = ticket_age * 1000 + session->internals.tls13_ticket.age_add; + + if ((ret = _gnutls_buffer_append_data_prefix(extdata, 16, + session->internals.tls13_ticket.ticket.data, + session->internals.tls13_ticket.ticket.size)) < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Now append the obfuscated ticket age */ + if ((ret = _gnutls_buffer_append_prefix(extdata, 32, ob_ticket_age)) < 0) { + gnutls_assert(); + goto cleanup; + } + + psk_id_len += 6 + session->internals.tls13_ticket.ticket.size; + binders_len += 1 + _gnutls_mac_get_algo_len(prf_res); } - /* Now append the ticket age, which is always zero for out-of-band PSKs */ - if ((ret = _gnutls_buffer_append_prefix(extdata, 32, 0)) < 0) { - gnutls_assert(); - goto cleanup; + ignore_ticket: + if (cred && _gnutls_have_psk_credentials(cred, session)) { + gnutls_datum_t tkey; + + if (cred->binder_algo == NULL) { + gnutls_assert(); + ret = gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + goto cleanup; + } + + prf_psk = cred->binder_algo; + + ret = _gnutls_find_psk_key(session, cred, &username, &tkey, &free_username); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + if (username.size == 0 || username.size > UINT16_MAX) { + ret = gnutls_assert_val(GNUTLS_E_INVALID_PASSWORD); + goto cleanup; + } + + if (!free_username) { + /* we need to copy the key */ + ret = _gnutls_set_datum(&user_key, tkey.data, tkey.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } else { + user_key.data = tkey.data; + user_key.size = tkey.size; + } + + ret = _gnutls_auth_info_set(session, GNUTLS_CRD_PSK, sizeof(psk_auth_info_st), 1); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + info = _gnutls_get_auth_info(session, GNUTLS_CRD_PSK); + assert(info != NULL); + + memcpy(info->username, username.data, username.size); + info->username[username.size] = 0; + + if ((ret = _gnutls_buffer_append_data_prefix(extdata, 16, + username.data, + username.size)) < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Now append the obfuscated ticket age */ + if ((ret = _gnutls_buffer_append_prefix(extdata, 32, 0)) < 0) { + gnutls_assert(); + goto cleanup; + } + + psk_id_len += 6 + username.size; + binders_len += 1 + _gnutls_mac_get_algo_len(prf_psk); } - /* Total length appended is the length of the data, plus six octets */ - length = (username.size + 6); - _gnutls_write_uint16(length, &extdata->data[pos]); + _gnutls_write_uint16(psk_id_len, &extdata->data[spos]); + binders_pos = extdata->length-spos; ext_offset = _gnutls_ext_get_extensions_offset(session); /* Compute the binders. extdata->data points to the start @@ -222,43 +353,89 @@ client_send_params(gnutls_session_t session, client_hello.data = extdata->data+sizeof(mbuffer_st); client_hello.size = extdata->length-sizeof(mbuffer_st); - ret = compute_psk_binder(session, prf, - hash_size+1, hash_size, extdata->length-pos, - ext_offset, &key, &client_hello, - binder_value); + next_idx = 0; + + ret = _gnutls_buffer_append_prefix(extdata, 16, binders_len); if (ret < 0) { - gnutls_assert(); + gnutls_assert_val(ret); goto cleanup; } - /* Associate the selected pre-shared key with the session */ - session->key.psk.data = key.data; - session->key.psk.size = key.size; - session->key.psk_needs_free = free_data; - key.data = NULL; - session->key.proto.tls13.binder_prf = prf; + if (prf_res && rkey.size > 0) { + ret = compute_psk_binder(session, prf_res, + binders_len, binders_pos, + ext_offset, &rkey, &client_hello, 1, + binder_value); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } - /* Now append the binders */ - ret = _gnutls_buffer_append_prefix(extdata, 16, hash_size+1); - if (ret < 0) { - gnutls_assert(); - goto cleanup; + /* Associate the selected pre-shared key with the session */ + gnutls_free(session->key.binders[next_idx].psk.data); + session->key.binders[next_idx].psk.data = rkey.data; + session->key.binders[next_idx].psk.size = rkey.size; + rkey.data = NULL; + + session->key.binders[next_idx].prf = prf_res; + session->key.binders[next_idx].resumption = 1; + session->key.binders[next_idx].idx = next_idx; + + _gnutls_handshake_log("EXT[%p]: sent PSK resumption identity (%d)\n", session, next_idx); + + next_idx++; + + /* Add the binder */ + ret = _gnutls_buffer_append_data_prefix(extdata, 8, binder_value, prf_res->output_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + session->internals.hsk_flags |= HSK_TLS13_TICKET_SENT; } - /* Add the size of the binder (we only have one) */ - ret = _gnutls_buffer_append_data_prefix(extdata, 8, binder_value, hash_size); - if (ret < 0) { - gnutls_assert(); - goto cleanup; + if (prf_psk && user_key.size > 0 && info) { + ret = compute_psk_binder(session, prf_psk, + binders_len, binders_pos, + ext_offset, &user_key, &client_hello, 0, + binder_value); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Associate the selected pre-shared key with the session */ + gnutls_free(session->key.binders[next_idx].psk.data); + session->key.binders[next_idx].psk.data = user_key.data; + session->key.binders[next_idx].psk.size = user_key.size; + user_key.data = NULL; + + session->key.binders[next_idx].prf = prf_psk; + session->key.binders[next_idx].resumption = 0; + session->key.binders[next_idx].idx = next_idx; + + _gnutls_handshake_log("EXT[%p]: sent PSK identity '%s' (%d)\n", session, info->username, next_idx); + + next_idx++; + + /* Add the binder */ + ret = _gnutls_buffer_append_data_prefix(extdata, 8, binder_value, prf_psk->output_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } } ret = 0; cleanup: - if (free_data) { + if (free_username) _gnutls_free_datum(&username); - _gnutls_free_temp_key_datum(&key); - } + + _gnutls_free_temp_key_datum(&user_key); + _gnutls_free_temp_key_datum(&rkey); + return ret; } @@ -271,7 +448,7 @@ server_send_params(gnutls_session_t session, gnutls_buffer_t extdata) return 0; ret = _gnutls_buffer_append_prefix(extdata, 16, - session->key.proto.tls13.psk_index); + session->key.binders[0].idx); if (ret < 0) return gnutls_assert_val(ret); @@ -286,13 +463,16 @@ static int server_recv_params(gnutls_session_t session, const mac_entry_st *prf; gnutls_datum_t full_client_hello; uint8_t binder_value[MAX_HASH_SIZE]; - int psk_index = -1; + int psk_index; gnutls_datum_t binder_recvd = { NULL, 0 }; gnutls_datum_t key = {NULL, 0}; - unsigned hash_size, cand_index; + unsigned cand_index; psk_ext_parser_st psk_parser; struct psk_st psk; psk_auth_info_t info; + tls13_ticket_t ticket_data; + int ticket_age; + bool resuming; ret = _gnutls13_psk_ext_parser_init(&psk_parser, data, len); if (ret < 0) { @@ -301,25 +481,68 @@ static int server_recv_params(gnutls_session_t session, return gnutls_assert_val(ret); } + psk_index = -1; + while ((ret = _gnutls13_psk_ext_parser_next_psk(&psk_parser, &psk)) >= 0) { - if (psk.ob_ticket_age == 0) { - cand_index = ret; + cand_index = ret; + /* Is this a PSK? */ + if (psk.ob_ticket_age == 0) { /* _gnutls_psk_pwd_find_entry() expects 0-terminated identities */ if (psk.identity.size > 0 && psk.identity.size <= MAX_USERNAME_SIZE) { char identity_str[psk.identity.size + 1]; + prf = pskcred->binder_algo; + memcpy(identity_str, psk.identity.data, psk.identity.size); identity_str[psk.identity.size] = 0; + /* this fails only on configuration errors; as such we always + * return its error code in that case */ ret = _gnutls_psk_pwd_find_entry(session, identity_str, &key); if (ret < 0) return gnutls_assert_val(ret); psk_index = cand_index; + resuming = 0; break; } } + + /* Is this a session ticket? */ + if (!(session->internals.flags & GNUTLS_NO_TICKETS) && + (ret = _gnutls13_unpack_session_ticket(session, &psk.identity, &ticket_data)) == 0) { + prf = ticket_data.prf; + + if (!prf) { + tls13_ticket_deinit(&ticket_data); + continue; + } + + /* Check whether ticket is stale or not */ + ticket_age = psk.ob_ticket_age - ticket_data.age_add; + if (ticket_age < 0) { + tls13_ticket_deinit(&ticket_data); + continue; + } + + if ((unsigned int) (ticket_age / 1000) > ticket_data.lifetime) { + tls13_ticket_deinit(&ticket_data); + continue; + } + + ret = compute_psk_from_ticket(&ticket_data, &key); + if (ret < 0) { + tls13_ticket_deinit(&ticket_data); + continue; + } + + tls13_ticket_deinit(&ticket_data); + + psk_index = cand_index; + resuming = 1; + break; + } } if (psk_index < 0) @@ -340,10 +563,8 @@ static int server_recv_params(gnutls_session_t session, } /* Compute the binder value for this PSK */ - prf = pskcred->binder_algo; - hash_size = prf->output_size; - ret = compute_psk_binder(session, prf, psk_parser.binder_len+2, hash_size, 0, 0, - &key, &full_client_hello, + ret = compute_psk_binder(session, prf, psk_parser.binder_len+2, 0, 0, + &key, &full_client_hello, resuming, binder_value); if (ret < 0) { gnutls_assert(); @@ -358,15 +579,15 @@ static int server_recv_params(gnutls_session_t session, } if (session->internals.hsk_flags & HSK_PSK_KE_MODE_DHE_PSK) - _gnutls_handshake_log("EXT[%p]: Selected DHE-PSK mode\n", session); + _gnutls_handshake_log("EXT[%p]: selected DHE-PSK mode\n", session); else { reset_cand_groups(session); - _gnutls_handshake_log("EXT[%p]: Selected PSK mode\n", session); + _gnutls_handshake_log("EXT[%p]: selected PSK mode\n", session); } /* save the username in psk_auth_info to make it available * using gnutls_psk_server_get_username() */ - if (psk.ob_ticket_age == 0) { + if (!resuming) { if (psk.identity.size >= sizeof(info->username)) { gnutls_assert(); ret = GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; @@ -384,18 +605,21 @@ static int server_recv_params(gnutls_session_t session, memcpy(info->username, psk.identity.data, psk.identity.size); info->username[psk.identity.size] = 0; - _gnutls_handshake_log("EXT[%p]: Selected PSK identity: %s\n", session, info->username); + _gnutls_handshake_log("EXT[%p]: selected PSK identity: %s (%d)\n", session, info->username, psk_index); + } else { + session->internals.resumed = RESUME_TRUE; + _gnutls_handshake_log("EXT[%p]: selected resumption PSK identity (%d)\n", session, psk_index); } session->internals.hsk_flags |= HSK_PSK_SELECTED; /* Reference the selected pre-shared key */ - session->key.psk.data = key.data; - session->key.psk.size = key.size; - session->key.psk_needs_free = 1; + session->key.binders[0].psk.data = key.data; + session->key.binders[0].psk.size = key.size; - session->key.proto.tls13.psk_index = psk_index; - session->key.proto.tls13.binder_prf = prf; + session->key.binders[0].idx = psk_index; + session->key.binders[0].prf = prf; + session->key.binders[0].resumption = resuming; return 0; @@ -445,24 +669,19 @@ static int _gnutls_psk_send_params(gnutls_session_t session, if (session->internals.hsk_flags & HSK_PSK_KE_MODES_SENT) { cred = (gnutls_psk_client_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_PSK); - /* If there are no PSK credentials, this extension is not applicable, - * so we return zero. */ - if (cred == NULL || !session->internals.priorities->have_psk) - return 0; + } - return client_send_params(session, extdata, cred); - } else { + if ((session->internals.flags & GNUTLS_NO_TICKETS) && !session->internals.priorities->have_psk) return 0; - } + + return client_send_params(session, extdata, cred); } else { vers = get_version(session); if (!vers || !vers->tls13_sem) return 0; - cred = (gnutls_psk_client_credentials_t) - _gnutls_get_cred(session, GNUTLS_CRD_PSK); - if (cred == NULL || !session->internals.priorities->have_psk) + if ((session->internals.flags & GNUTLS_NO_TICKETS) && !session->internals.priorities->have_psk) return 0; if (session->internals.hsk_flags & HSK_PSK_KE_MODES_RECEIVED) @@ -472,6 +691,15 @@ static int _gnutls_psk_send_params(gnutls_session_t session, } } +static void swap_binders(gnutls_session_t session) +{ + struct binder_data_st tmp; + + memcpy(&tmp, &session->key.binders[0], sizeof(struct binder_data_st)); + memcpy(&session->key.binders[0], &session->key.binders[1], sizeof(struct binder_data_st)); + memcpy(&session->key.binders[1], &tmp, sizeof(struct binder_data_st)); +} + /* * Return values for this function: * - 0 : Not applicable. @@ -481,6 +709,7 @@ static int _gnutls_psk_send_params(gnutls_session_t session, static int _gnutls_psk_recv_params(gnutls_session_t session, const unsigned char *data, size_t len) { + unsigned i; gnutls_psk_server_credentials_t pskcred; const version_entry_st *vers = get_version(session); @@ -491,10 +720,22 @@ static int _gnutls_psk_recv_params(gnutls_session_t session, if (session->internals.hsk_flags & HSK_PSK_KE_MODES_SENT) { uint16_t selected_identity = _gnutls_read_uint16(data); - if (selected_identity == 0) { - _gnutls_handshake_log("EXT[%p]: Selected PSK mode\n", session); - session->internals.hsk_flags |= HSK_PSK_SELECTED; + for (i=0;i<sizeof(session->key.binders)/sizeof(session->key.binders[0]);i++) { + if (session->key.binders[i].prf != NULL && session->key.binders[i].idx == selected_identity) { + if (session->key.binders[i].resumption) { + session->internals.resumed = RESUME_TRUE; + _gnutls_handshake_log("EXT[%p]: selected PSK-resumption mode\n", session); + } else { + _gnutls_handshake_log("EXT[%p]: selected PSK mode\n", session); + } + + /* ensure that selected binder is set on (our) index zero */ + if (i != 0) + swap_binders(session); + session->internals.hsk_flags |= HSK_PSK_SELECTED; + } } + return 0; } else { return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION); @@ -511,7 +752,7 @@ static int _gnutls_psk_recv_params(gnutls_session_t session, /* If there are no PSK credentials, this extension is not applicable, * so we return zero. */ - if (pskcred == NULL) + if (pskcred == NULL && (session->internals.flags & GNUTLS_NO_TICKETS)) return 0; return server_recv_params(session, data, len, pskcred); diff --git a/lib/ext/pre_shared_key.h b/lib/ext/pre_shared_key.h index 25dd159f6e..2e830ff52e 100644 --- a/lib/ext/pre_shared_key.h +++ b/lib/ext/pre_shared_key.h @@ -3,13 +3,14 @@ #include "auth/psk.h" #include <hello_ext.h> +#include "tls13/session_ticket.h" extern const hello_ext_entry_st ext_pre_shared_key; inline static -unsigned _gnutls_have_psk_credentials(const gnutls_psk_client_credentials_t cred) +unsigned _gnutls_have_psk_credentials(const gnutls_psk_client_credentials_t cred, gnutls_session_t session) { - if (cred->get_function || cred->username.data) + if ((cred->get_function || cred->username.data) && session->internals.priorities->have_psk) return 1; else return 0; diff --git a/lib/ext/psk_ke_modes.c b/lib/ext/psk_ke_modes.c index 0da358fcd7..9c41d9e94a 100644 --- a/lib/ext/psk_ke_modes.c +++ b/lib/ext/psk_ke_modes.c @@ -28,26 +28,26 @@ #define PSK_KE 0 #define PSK_DHE_KE 1 +/* Relevant to client only */ static bool psk_ke_modes_is_required(gnutls_session_t session) { gnutls_psk_client_credentials_t cred; + if (!(session->internals.flags & GNUTLS_NO_TICKETS) && + session->internals.tls13_ticket.ticket.data != NULL) + return 1; + if (session->internals.priorities->have_psk) { cred = (gnutls_psk_client_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_PSK); - if (cred && _gnutls_have_psk_credentials(cred)) + if (cred && _gnutls_have_psk_credentials(cred, session)) return 1; } return 0; } -/* - * We only support ECDHE-authenticated PSKs. - * The client just sends a "psk_key_exchange_modes" extension - * with the value one. - */ static int psk_ke_modes_send_params(gnutls_session_t session, gnutls_buffer_t extdata) @@ -94,6 +94,17 @@ psk_ke_modes_send_params(gnutls_session_t session, break; } + /* For session resumption we need to send at least one */ + if (pos == 0) { + if (session->internals.flags & GNUTLS_NO_TICKETS) + return 0; + + data[pos++] = PSK_DHE_KE; + data[pos++] = PSK_KE; + session->internals.hsk_flags |= HSK_PSK_KE_MODE_DHE_PSK; + session->internals.hsk_flags |= HSK_PSK_KE_MODE_PSK; + } + ret = _gnutls_buffer_append_data_prefix(extdata, 8, data, pos); if (ret < 0) return gnutls_assert_val(ret); @@ -137,7 +148,7 @@ psk_ke_modes_recv_params(gnutls_session_t session, } cred = (gnutls_psk_server_credentials_t)_gnutls_get_cred(session, GNUTLS_CRD_PSK); - if (cred == NULL) { + if (cred == NULL && (session->internals.flags & GNUTLS_NO_TICKETS)) { session->internals.hsk_flags |= HSK_PSK_KE_MODE_INVALID; return gnutls_assert_val(0); } @@ -158,8 +169,12 @@ psk_ke_modes_recv_params(gnutls_session_t session, break; } - if (session->internals.priorities->groups.size == 0 && psk_pos == MAX_POS) - return gnutls_assert_val(0); + if (psk_pos == MAX_POS && dhpsk_pos == MAX_POS) { + if (!(session->internals.flags & GNUTLS_NO_TICKETS)) + dhpsk_pos = 0; + else if (session->internals.priorities->groups.size == 0) + return gnutls_assert_val(0); + } for (i=0;i<ke_modes_len;i++) { DECR_LEN(len, 1); @@ -188,10 +203,11 @@ psk_ke_modes_recv_params(gnutls_session_t session, if ((session->internals.hsk_flags & HSK_PSK_KE_MODE_PSK) || (session->internals.hsk_flags & HSK_PSK_KE_MODE_DHE_PSK)) { + return 0; } else { session->internals.hsk_flags |= HSK_PSK_KE_MODE_INVALID; - return 0; + return gnutls_assert_val(0); } } diff --git a/lib/ext/session_ticket.c b/lib/ext/session_ticket.c index e4a7a19d21..2bcc4cd984 100644 --- a/lib/ext/session_ticket.c +++ b/lib/ext/session_ticket.c @@ -44,8 +44,6 @@ #define MAC_ALGO GNUTLS_MAC_SHA1 #define MAC_SIZE 20 /* HMAC-SHA1 */ -#ifdef ENABLE_SESSION_TICKETS - static int session_ticket_recv_params(gnutls_session_t session, const uint8_t * data, size_t data_size); @@ -72,8 +70,6 @@ const hello_ext_entry_st ext_mod_session_ticket = { .cannot_be_overriden = 1 }; -#endif - #define NAME_POS (0) #define KEY_POS (TICKET_KEY_NAME_SIZE) #define MAC_SECRET_POS (TICKET_KEY_NAME_SIZE+TICKET_CIPHER_KEY_SIZE) @@ -358,8 +354,6 @@ cleanup: return ret; } -#ifdef ENABLE_SESSION_TICKETS - static int unpack_session(gnutls_session_t session, const gnutls_datum_t *state) { @@ -643,6 +637,9 @@ int _gnutls_send_new_session_ticket(gnutls_session_t session, int again) if (!session->internals.session_ticket_renew) return 0; + _gnutls_handshake_log + ("HSK[%p]: sending session ticket\n", session); + /* XXX: Temporarily set write algorithms to be used. _gnutls_write_connection_state_init() does this job, but it also triggers encryption, while NewSessionTicket should not be @@ -700,7 +697,7 @@ int _gnutls_send_new_session_ticket(gnutls_session_t session, int again) data_size = p - data; - session->internals.ticket_sent = 1; + session->internals.hsk_flags |= HSK_TLS12_TICKET_SENT; } return _gnutls_send_handshake(session, data_size ? bufel : NULL, GNUTLS_HANDSHAKE_NEW_SESSION_TICKET); @@ -792,6 +789,10 @@ int _gnutls_recv_new_session_ticket(gnutls_session_t session) } ret = 0; + _gnutls_handshake_log + ("HSK[%p]: received session ticket\n", session); + session->internals.hsk_flags |= HSK_TICKET_RECEIVED; + _gnutls_hello_ext_set_priv(session, GNUTLS_EXTENSION_SESSION_TICKET, epriv); @@ -801,5 +802,3 @@ int _gnutls_recv_new_session_ticket(gnutls_session_t session) return ret; } - -#endif diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 29e766185d..cc2003ae5f 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -160,7 +160,7 @@ typedef struct { #define _GNUTLS_EXT_TLS_POST_CS 177 /* expire time for resuming sessions */ -#define DEFAULT_EXPIRE_TIME 3600 +#define DEFAULT_EXPIRE_TIME 21600 #define DEFAULT_HANDSHAKE_TIMEOUT_MS 40*1000 /* The EC group to be used when the extension @@ -269,7 +269,7 @@ typedef enum handshake_state_t { STATE0 = 0, STATE1, STATE2, STATE90=90, STATE91, STATE92, STATE93, STATE99=99, STATE100=100, STATE101, STATE102, STATE103, STATE104, STATE105, STATE106, STATE107, STATE108, STATE109, STATE110, - STATE111, + STATE111, STATE112, STATE150 /* key update */ } handshake_state_t; @@ -469,6 +469,17 @@ typedef struct auth_cred_st { #define TICKET_CIPHER_KEY_SIZE 32 #define TICKET_MAC_SECRET_SIZE 16 +struct binder_data_st { + const struct mac_entry_st *prf; /* non-null if this struct is set */ + gnutls_datum_t psk; + + /* 0-based index of the selected PSK. + * This only applies if the HSK_PSK_SELECTED flag is set in internals.hsk_flags, + * which signals a PSK has indeed been selected. */ + uint8_t idx; + uint8_t resumption; /* whether it is a resumption binder */ +}; + struct gnutls_key_st { struct { /* These are kept outside the TLS1.3 union as they are * negotiated via extension, even before protocol is negotiated */ @@ -484,14 +495,6 @@ struct gnutls_key_st { */ union { struct { - /* - * 0-based index of the selected PSK. - * This only applies if the HSK_PSK_SELECTED flag is set in internals.hsk_flags, - * which signals a PSK has indeed been selected. - */ - unsigned psk_index; - const struct mac_entry_st *binder_prf; - /* the current (depending on state) secret, can be * early_secret, client_early_traffic_secret, ... */ uint8_t temp_secret[MAX_HASH_SIZE]; @@ -499,6 +502,7 @@ struct gnutls_key_st { uint8_t hs_ckey[MAX_HASH_SIZE]; /* client_handshake_traffic_secret */ uint8_t hs_skey[MAX_HASH_SIZE]; /* server_handshake_traffic_secret */ uint8_t ap_expkey[MAX_HASH_SIZE]; /* exporter_master_secret */ + uint8_t ap_rms[MAX_HASH_SIZE]; /* resumption_master_secret */ } tls13; /* tls1.3 */ /* Folow the SSL3.0 and TLS1.2 key exchanges */ @@ -533,9 +537,14 @@ struct gnutls_key_st { } tls12; /* from ssl3.0 to tls12 */ } proto; - /* Pre-shared key in use (if any); temporary storage */ - gnutls_datum_t psk; - unsigned psk_needs_free; + /* binders / pre-shared keys in use; temporary storage. + * On client side it will hold data for the resumption and external + * PSKs After server hello is received the selected binder is set on 0 position + * and HSK_PSK_SELECTED is set. + * + * On server side the first value is populated with + * the selected PSK data if HSK_PSK_SELECTED flag is set. */ + struct binder_data_st binders[2]; /* TLS pre-master key; applies to 1.2 and 1.3 */ gnutls_datum_t key; @@ -749,6 +758,9 @@ typedef struct { /* encrypt-then-mac -> rfc7366 */ uint8_t etm; + uint8_t client_auth_type; /* gnutls_credentials_type_t */ + uint8_t server_auth_type; + /* Note: if you add anything in Security_Parameters struct, then * also modify CPY_COMMON in constate.c, and session_pack.c, * in order to save it in the session storage. @@ -938,6 +950,19 @@ typedef struct gnutls_dh_params_int { */ } dh_params_st; +/* TLS 1.3 session ticket + */ +typedef struct tls13_ticket { + time_t timestamp; + uint32_t lifetime; + uint32_t age_add; + uint8_t nonce[255]; + size_t nonce_size; + const mac_entry_st *prf; + uint8_t resumption_master_secret[MAX_HASH_SIZE]; + gnutls_datum_t ticket; +} tls13_ticket_t; + /* DTLS session state */ typedef struct { @@ -997,7 +1022,7 @@ typedef struct { gnutls_buffer_st handshake_hash_buffer; /* used to keep the last received handshake * message */ bool resumable; /* TRUE or FALSE - if we can resume that session */ - bool ticket_sent; /* whether a session ticket was sent */ + bye_state_t bye_state; /* used by gnutls_bye() */ reauth_state_t reauth_state; /* used by gnutls_reauth() */ @@ -1240,6 +1265,13 @@ typedef struct { #define HSK_PSK_SELECTED (1<<15) #define HSK_KEY_SHARE_SENT (1<<16) /* server: key share was sent to client */ #define HSK_KEY_SHARE_RECEIVED (1<<17) /* client: key share was received */ +#define HSK_TLS13_TICKET_SENT (1<<18) /* client: sent a ticket under TLS1.3; + * server: a ticket was sent to client. + */ +#define HSK_TLS12_TICKET_SENT (1<<19) /* client: sent a ticket under TLS1.2; + * server: a ticket was sent to client. + */ +#define HSK_TICKET_RECEIVED (1<<20) /* client: a session ticket was received */ /* The hsk_flags are for use within the ongoing handshake; * they are reset to zero prior to handshake start by gnutls_handshake. */ @@ -1335,8 +1367,11 @@ typedef struct { /* the ciphersuite received in HRR */ uint8_t hrr_cs[2]; + /* this is only used under TLS1.2 or earlier */ int session_ticket_renew; + tls13_ticket_t tls13_ticket; + /* 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 de14cf106e..427392b6f3 100644 --- a/lib/handshake-tls13.c +++ b/lib/handshake-tls13.c @@ -54,7 +54,7 @@ #include "tls13/certificate.h" #include "tls13/finished.h" #include "tls13/key_update.h" -#include "tls13/session_ticket.h" +#include "ext/pre_shared_key.h" static int generate_hs_traffic_keys(gnutls_session_t session); static int generate_ap_traffic_keys(gnutls_session_t session); @@ -158,6 +158,9 @@ int _gnutls13_handshake_client(gnutls_session_t session) SAVE_TRANSCRIPT; + if (session->internals.resumed != RESUME_FALSE) + _gnutls_set_resumed_parameters(session); + return 0; } @@ -189,6 +192,14 @@ static int generate_ap_traffic_keys(gnutls_session_t session) session->key.proto.tls13.ap_expkey, session->security_parameters.prf->output_size); + ret = _tls13_derive_secret(session, RMS_MASTER_LABEL, sizeof(RMS_MASTER_LABEL)-1, + session->internals.handshake_hash_buffer.data, + session->internals.handshake_hash_buffer_client_finished_len, + session->key.proto.tls13.temp_secret, + session->key.proto.tls13.ap_rms); + if (ret < 0) + return gnutls_assert_val(ret); + _gnutls_epoch_bump(session); ret = _gnutls_epoch_dup(session); if (ret < 0) @@ -210,9 +221,11 @@ static int generate_hs_traffic_keys(gnutls_session_t session) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); if ((session->security_parameters.entity == GNUTLS_CLIENT && - !(session->internals.hsk_flags & HSK_KEY_SHARE_RECEIVED)) || + (!(session->internals.hsk_flags & HSK_KEY_SHARE_RECEIVED) || + (!(session->internals.hsk_flags & HSK_PSK_KE_MODE_DHE_PSK) && + session->internals.resumed != RESUME_FALSE))) || (session->security_parameters.entity == GNUTLS_SERVER && - !(session->internals.hsk_flags & HSK_KEY_SHARE_SENT))) { + !(session->internals.hsk_flags & HSK_KEY_SHARE_SENT))) { if ((session->internals.hsk_flags & HSK_PSK_SELECTED) && (session->internals.hsk_flags & HSK_PSK_KE_MODE_PSK)) { @@ -225,7 +238,7 @@ static int generate_hs_traffic_keys(gnutls_session_t session) unsigned digest_size; if (unlikely(session->security_parameters.prf == NULL)) - return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); digest_size = session->security_parameters.prf->output_size; memset(digest, 0, digest_size); @@ -237,7 +250,7 @@ static int generate_hs_traffic_keys(gnutls_session_t session) } } else { if (unlikely(session->key.key.size == 0)) - return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); ret = _tls13_update_secret(session, session->key.key.data, session->key.key.size); if (ret < 0) { @@ -362,6 +375,11 @@ int _gnutls13_handshake_server(gnutls_session_t session) generate_ap_traffic_keys(session); STATE = STATE111; IMED_RET("generate app keys", ret, 0); + /* fall through */ + case STATE112: + ret = _gnutls13_send_session_ticket(session, AGAIN(STATE112)); + STATE = STATE112; + IMED_RET("send session ticket", ret, 0); STATE = STATE0; break; @@ -375,6 +393,9 @@ int _gnutls13_handshake_server(gnutls_session_t session) SAVE_TRANSCRIPT; + if (session->internals.resumed != RESUME_FALSE) + _gnutls_set_resumed_parameters(session); + return 0; } @@ -440,6 +461,13 @@ _gnutls13_recv_async_handshake(gnutls_session_t session, gnutls_buffer_st *buf) ret = _gnutls13_recv_session_ticket(session, buf); if (ret < 0) return gnutls_assert_val(ret); + + memcpy(session->internals.tls13_ticket.resumption_master_secret, + session->key.proto.tls13.ap_rms, + session->key.proto.tls13.temp_secret_size); + + session->internals.tls13_ticket.prf = session->security_parameters.prf; + session->internals.hsk_flags |= HSK_TICKET_RECEIVED; break; default: gnutls_assert(); diff --git a/lib/handshake.c b/lib/handshake.c index 1c0d25fb93..a023ab2ad4 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -54,6 +54,7 @@ #include <random.h> #include <dtls.h> #include "secrets.h" +#include "tls13/session_ticket.h" #define TRUE 1 #define FALSE 0 @@ -511,6 +512,28 @@ _gnutls_user_hello_func(gnutls_session_t session, return sret; } +static int set_auth_types(gnutls_session_t session) +{ + const version_entry_st *ver = get_version(session); + gnutls_kx_algorithm_t kx; + + kx = session->security_parameters.cs->kx_algorithm; + if (kx == 0 && ver->tls13_sem) { + /* if we are resuming then the KX seen doesn't match the original */ + if (session->internals.resumed == RESUME_FALSE) + kx = gnutls_kx_get(session); + } + + if (kx) { + session->security_parameters.server_auth_type = _gnutls_map_kx_get_cred(kx, 1); + session->security_parameters.client_auth_type = _gnutls_map_kx_get_cred(kx, 0); + } else if (session->internals.resumed == RESUME_FALSE) { + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + return 0; +} + /* Read a client hello packet. * A client hello must be a known version client hello * or version 2.0 client hello (only for compatibility @@ -700,7 +723,7 @@ read_client_hello(gnutls_session_t session, uint8_t * data, } /* resumed by session_ticket extension */ - if (session->internals.resumed != RESUME_FALSE) { + if (!vers->tls13_sem && session->internals.resumed != RESUME_FALSE) { /* to indicate the client that the current session is resumed */ memcpy(session->internals.resumed_security_parameters. session_id, session_id, session_id_len); @@ -754,6 +777,12 @@ read_client_hello(gnutls_session_t session, uint8_t * data, return ret; } + ret = set_auth_types(session); + if (ret < 0) { + gnutls_assert(); + return ret; + } + return sret; } @@ -953,6 +982,7 @@ _gnutls_server_select_suite(gnutls_session_t session, uint8_t * data, unsigned int i; ciphersuite_list_st peer_clist; const gnutls_cipher_suite_entry_st *selected; + gnutls_kx_algorithm_t kx; int retval; const version_entry_st *vers = get_version(session); @@ -1016,7 +1046,8 @@ _gnutls_server_select_suite(gnutls_session_t session, uint8_t * data, if (!vers->tls13_sem) { /* check if the credentials (username, public key etc.) are ok */ - if (_gnutls_get_kx_cred(session, selected->kx_algorithm) == NULL) { + kx = selected->kx_algorithm; + if (_gnutls_get_kx_cred(session, kx) == NULL) { gnutls_assert(); return GNUTLS_E_INSUFFICIENT_CREDENTIALS; } @@ -1025,7 +1056,7 @@ _gnutls_server_select_suite(gnutls_session_t session, uint8_t * data, * according to the KX algorithm. This is needed since all the * handshake functions are read from there; */ - session->internals.auth_struct = _gnutls_kx_auth_struct(selected->kx_algorithm); + session->internals.auth_struct = _gnutls_kx_auth_struct(kx); if (session->internals.auth_struct == NULL) { _gnutls_handshake_log ("HSK[%p]: Cannot find the appropriate handler for the KX algorithm\n", @@ -1207,35 +1238,53 @@ _gnutls_send_handshake(gnutls_session_t session, mbuffer_st * bufel, return ret; } - if (vers && vers->tls13_sem && - session->internals.initial_negotiation_completed) { - /* we are under TLS1.3 in a re-authentication phase. - * we don't attempt to cache any messages */ - goto force_send; - } + /* Decide when to cache and when to send */ + if (vers && vers->tls13_sem) { - /* The messages which are followed by another are not sent by default - * but are cached instead */ - switch (type) { - case GNUTLS_HANDSHAKE_CERTIFICATE_PKT: /* this one is followed by ServerHelloDone - * or ClientKeyExchange always. - */ - case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: - case GNUTLS_HANDSHAKE_SERVER_KEY_EXCHANGE: /* as above */ - case GNUTLS_HANDSHAKE_SERVER_HELLO: /* as above */ - case GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST: /* as above */ - case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: /* followed by ChangeCipherSpec */ + if (session->internals.initial_negotiation_completed) { + /* we are under TLS1.3 in a re-authentication phase. + * we don't attempt to cache any messages */ + goto force_send; + } - /* now for client Certificate, ClientKeyExchange and - * CertificateVerify are always followed by ChangeCipherSpec - */ - case GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY: - case GNUTLS_HANDSHAKE_CLIENT_KEY_EXCHANGE: - ret = 0; - break; - default: - /* send cached messages */ - goto force_send; + /* The messages which are followed by another are not sent by default + * but are cached instead */ + switch (type) { + case GNUTLS_HANDSHAKE_SERVER_HELLO: /* always followed by something */ + case GNUTLS_HANDSHAKE_ENCRYPTED_EXTENSIONS: /* followed by finished or cert */ + case GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST: /* followed by certificate */ + case GNUTLS_HANDSHAKE_CERTIFICATE_PKT: /* this one is followed by cert verify */ + case GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY: /* followed by finished */ + ret = 0; /* cache */ + break; + default: + /* send this and any cached messages */ + goto force_send; + } + } else { + /* The messages which are followed by another are not sent by default + * but are cached instead */ + switch (type) { + case GNUTLS_HANDSHAKE_CERTIFICATE_PKT: /* this one is followed by ServerHelloDone + * or ClientKeyExchange always. + */ + case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS: + case GNUTLS_HANDSHAKE_SERVER_KEY_EXCHANGE: /* as above */ + case GNUTLS_HANDSHAKE_SERVER_HELLO: /* as above */ + case GNUTLS_HANDSHAKE_CERTIFICATE_REQUEST: /* as above */ + case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET: /* followed by ChangeCipherSpec */ + + /* now for client Certificate, ClientKeyExchange and + * CertificateVerify are always followed by ChangeCipherSpec + */ + case GNUTLS_HANDSHAKE_CERTIFICATE_VERIFY: + case GNUTLS_HANDSHAKE_CLIENT_KEY_EXCHANGE: + ret = 0; + break; + default: + /* send this and any cached messages */ + goto force_send; + } } return ret; @@ -1545,6 +1594,7 @@ set_client_ciphersuite(gnutls_session_t session, uint8_t suite[2]) int ret; const gnutls_cipher_suite_entry_st *selected = NULL; const version_entry_st *vers = get_version(session); + gnutls_kx_algorithm_t kx; for (j = 0; j < session->internals.priorities->cs.size; j++) { if (suite[0] == session->internals.priorities->cs.entry[j]->id[0] && @@ -1575,20 +1625,21 @@ set_client_ciphersuite(gnutls_session_t session, uint8_t suite[2]) * Actually checks if they exist. */ if (!vers->tls13_sem) { + kx = selected->kx_algorithm; + if (!session->internals.premaster_set && _gnutls_get_kx_cred - (session, selected->kx_algorithm) == NULL) { + (session, kx) == NULL) { gnutls_assert(); return GNUTLS_E_INSUFFICIENT_CREDENTIALS; } - /* set the mod_auth_st to the appropriate struct * according to the KX algorithm. This is needed since all the * handshake functions are read from there; */ session->internals.auth_struct = - _gnutls_kx_auth_struct(selected->kx_algorithm); + _gnutls_kx_auth_struct(kx); if (session->internals.auth_struct == NULL) { _gnutls_handshake_log @@ -1599,11 +1650,12 @@ set_client_ciphersuite(gnutls_session_t session, uint8_t suite[2]) } } else { if (session->internals.hsk_flags & HSK_PSK_SELECTED) { - if (session->key.proto.tls13.binder_prf->id != selected->prf) { + if (session->key.binders[0].prf->id != selected->prf) { _gnutls_handshake_log ("HSK[%p]: PRF of ciphersuite differs with the PSK identity (cs: %s, id: %s)\n", - session, selected->name, session->key.proto.tls13.binder_prf->name); + session, selected->name, session->key.binders[0].prf->name); gnutls_assert(); + return GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER; } } } @@ -1850,8 +1902,11 @@ read_server_hello(gnutls_session_t session, /* Calculate TLS 1.3 Early Secret */ if (vers->tls13_sem) { if (session->internals.hsk_flags & HSK_PSK_SELECTED) { - psk = session->key.psk.data; - psk_size = session->key.psk.size; + psk = session->key.binders[0].psk.data; + psk_size = session->key.binders[0].psk.size; + + if (psk_size == 0) + return gnutls_assert_val(GNUTLS_E_RECEIVED_ILLEGAL_PARAMETER); } ret = _tls13_init_secret(session, psk, psk_size); @@ -1863,8 +1918,16 @@ read_server_hello(gnutls_session_t session, 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) + if (ret < 0) { gnutls_assert(); + goto cleanup; + } + } + + ret = set_auth_types(session); + if (ret < 0) { + gnutls_assert(); + goto cleanup; } cleanup: @@ -1932,6 +1995,9 @@ static int send_client_hello(gnutls_session_t session, int again) hver = session->internals.resumed_security_parameters. pversion; + + if (hver && hver->tls13_sem) + hver = _gnutls_legacy_version_max(session); } if (hver == NULL) { @@ -2120,6 +2186,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; + gnutls_ext_parse_type_t etype; _gnutls_buffer_init(&buf); @@ -2131,8 +2198,8 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) if (vers->tls13_sem) { /* TLS 1.3 Early Secret */ if (session->internals.hsk_flags & HSK_PSK_SELECTED) { - psk = session->key.psk.data; - psk_size = session->key.psk.size; + psk = session->key.binders[0].psk.data; + psk_size = session->key.binders[0].psk.size; } ret = _tls13_init_secret(session, psk, psk_size); @@ -2199,13 +2266,12 @@ int _gnutls_send_server_hello(gnutls_session_t session, int again) goto fail; } + if (!vers->tls13_sem && session->internals.resumed != RESUME_FALSE) + etype = GNUTLS_EXT_MANDATORY; + else + etype = GNUTLS_EXT_ANY; ret = - _gnutls_gen_hello_extensions(session, &buf, - extflag, - (session->internals.resumed == - RESUME_TRUE) ? - GNUTLS_EXT_MANDATORY : - GNUTLS_EXT_ANY); + _gnutls_gen_hello_extensions(session, &buf, extflag, etype); if (ret < 0) { gnutls_assert(); goto fail; @@ -2501,11 +2567,10 @@ static int _gnutls_recv_supplemental(gnutls_session_t session) **/ int gnutls_handshake(gnutls_session_t session) { + const version_entry_st *vers = get_version(session); int ret; if (unlikely(session->internals.initial_negotiation_completed)) { - const version_entry_st *vers = get_version(session); - if (vers->tls13_sem) { if (session->security_parameters.entity == GNUTLS_CLIENT) { return gnutls_session_key_update(session, GNUTLS_KU_PEER); @@ -2553,6 +2618,7 @@ int gnutls_handshake(gnutls_session_t session) } else { ret = handshake_server(session); } + if (ret < 0) { /* In the case of a rehandshake abort * we should reset the handshake's internal state. @@ -2855,12 +2921,10 @@ static int handshake_client(gnutls_session_t session) if (session->internals.resumed == RESUME_FALSE) { ret = send_handshake_final(session, TRUE); IMED_RET("send handshake final 2", ret, 1); -#ifdef ENABLE_SESSION_TICKETS } else { ret = _gnutls_recv_new_session_ticket(session); IMED_RET("recv handshake new session ticket", ret, 1); -#endif } /* fall through */ case STATE17: @@ -2881,11 +2945,9 @@ static int handshake_client(gnutls_session_t session) STATE = STATE18; if (session->internals.resumed == RESUME_FALSE) { -#ifdef ENABLE_SESSION_TICKETS ret = _gnutls_recv_new_session_ticket(session); IMED_RET("recv handshake new session ticket", ret, 1); -#endif } else { ret = recv_handshake_final(session, TRUE); IMED_RET("recv handshake final", ret, 1); @@ -3286,13 +3348,11 @@ static int handshake_server(gnutls_session_t session) } /* fall through */ case STATE16: -#ifdef ENABLE_SESSION_TICKETS ret = _gnutls_send_new_session_ticket(session, AGAIN(STATE16)); STATE = STATE16; IMED_RET("send handshake new session ticket", ret, 0); -#endif /* fall through */ case STATE17: STATE = STATE17; @@ -3302,7 +3362,7 @@ static int handshake_server(gnutls_session_t session) if (session->security_parameters.entity == GNUTLS_SERVER - && session->internals.ticket_sent == 0) { + && !(session->internals.hsk_flags & HSK_TLS12_TICKET_SENT)) { /* if no ticket, save session data */ _gnutls_server_register_current_session (session); diff --git a/lib/handshake.h b/lib/handshake.h index 2175d6f2db..bdd9efa76d 100644 --- a/lib/handshake.h +++ b/lib/handshake.h @@ -138,6 +138,7 @@ int _gnutls_check_if_cert_hash_is_same(gnutls_session_t session, gnutls_certific #define APPLICATION_SERVER_TRAFFIC_LABEL "s ap traffic" #define APPLICATION_TRAFFIC_UPDATE "traffic upd" #define EXPORTER_MASTER_LABEL "exp master" +#define RMS_MASTER_LABEL "res master" #define EXPORTER_LABEL "exp master" #define RES_LABEL "res master" diff --git a/lib/hello_ext.c b/lib/hello_ext.c index d61f846f51..ad3cf54d3f 100644 --- a/lib/hello_ext.c +++ b/lib/hello_ext.c @@ -75,9 +75,7 @@ static hello_ext_entry_st const *extfunc[MAX_EXT_TYPES+1] = { #ifdef ENABLE_HEARTBEAT [GNUTLS_EXTENSION_HEARTBEAT] = &ext_mod_heartbeat, #endif -#ifdef ENABLE_SESSION_TICKETS [GNUTLS_EXTENSION_SESSION_TICKET] = &ext_mod_session_ticket, -#endif [GNUTLS_EXTENSION_SUPPORTED_ECC] = &ext_mod_supported_ecc, [GNUTLS_EXTENSION_SUPPORTED_ECC_PF] = &ext_mod_supported_ecc_pf, [GNUTLS_EXTENSION_SIGNATURE_ALGORITHMS] = &ext_mod_sig, diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index dac1c505dd..b4f909873d 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -1360,6 +1360,7 @@ unsigned gnutls_session_etm_status(gnutls_session_t session); * @GNUTLS_SFLAGS_HB_LOCAL_SEND: The heartbeat negotiation allows the local side to send heartbeat messages * @GNUTLS_SFLAGS_HB_PEER_SEND: The heartbeat negotiation allows the peer to send heartbeat messages * @GNUTLS_SFLAGS_FALSE_START: The appdata set with gnutls_handshake_set_appdata() were sent during handshake (false start) + * @GNUTLS_SFLAGS_SESSION_TICKET: A session ticket has been received by the server. * * Enumeration of different session parameters. */ @@ -1370,7 +1371,8 @@ typedef enum { GNUTLS_SFLAGS_HB_LOCAL_SEND = 1<<3, GNUTLS_SFLAGS_HB_PEER_SEND = 1<<4, GNUTLS_SFLAGS_FALSE_START = 1<<5, - GNUTLS_SFLAGS_RFC7919 = 1<<6 + GNUTLS_SFLAGS_RFC7919 = 1<<6, + GNUTLS_SFLAGS_SESSION_TICKET = 1<<7 } gnutls_session_flags_t; unsigned gnutls_session_get_flags(gnutls_session_t session); diff --git a/lib/session.c b/lib/session.c index 3e29c15292..84a529c7a1 100644 --- a/lib/session.c +++ b/lib/session.c @@ -25,6 +25,7 @@ #include "debug.h" #include <session_pack.h> #include <datum.h> +#include "buffers.h" #include "state.h" /** @@ -33,13 +34,10 @@ * @session_data: is a pointer to space to hold the session. * @session_data_size: is the session_data's size, or it will be set by the function. * - * Returns all session parameters needed to be stored to support resumption. - * The client should call this, and store the returned session data. A session - * may be resumed later by calling gnutls_session_set_data(). + * Returns all session parameters needed to be stored to support resumption, + * in a pre-allocated buffer. * - * This function will fail if called prior to handshake completion. In - * case of false start TLS, the handshake completes only after data have - * been successfully received from the peer. + * See gnutls_session_get_data2() for more information. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise * an error code is returned. @@ -75,14 +73,20 @@ gnutls_session_get_data(gnutls_session_t session, return ret; } +#define EMPTY_DATA "\x00\x00\x00\x00" +#define EMPTY_DATA_SIZE 4 + /** * gnutls_session_get_data2: * @session: is a #gnutls_session_t type. * @data: is a pointer to a datum that will hold the session. * - * Returns all session parameters needed to be stored to support resumption. - * The client should call this, and store the returned session data. A session - * may be resumed later by calling gnutls_session_set_data(). + * Returns necessary parameters to support resumption. The client + * should call this function and store the returned session data. A session + * can be resumed later by calling gnutls_session_set_data() with the returned + * data. Note that under TLS 1.3, it is recommended for clients to use + * session parameters only once, to prevent passive-observers from correlating + * the different connections. * * The returned @data are allocated and must be released using gnutls_free(). * @@ -90,25 +94,54 @@ gnutls_session_get_data(gnutls_session_t session, * case of false start TLS, the handshake completes only after data have * been successfully received from the peer. * + * Under TLS1.3 session resumption is possible only after a session ticket + * is received by the client. To ensure that such a ticket has been received use + * gnutls_session_get_flags() and check for flag %GNUTLS_SFLAGS_SESSION_TICKET; + * if this flag is not set, this function will wait for a new ticket within + * 50ms, and if not received will return dummy data which cannot lead to + * resumption. To get notified when new tickets are received by the server + * use gnutls_handshake_set_hook_function() to wait for %GNUTLS_HANDSHAKE_NEW_SESSION_TICKET + * messages. Each call of gnutls_session_get_data2() after a ticket is + * received, will return session resumption data corresponding to the last + * received ticket. + * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise * an error code is returned. **/ int gnutls_session_get_data2(gnutls_session_t session, gnutls_datum_t *data) { - + const version_entry_st *vers = get_version(session); int ret; - if (data == NULL) { + if (data == NULL || vers == NULL) { return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); } - if (gnutls_session_is_resumed(session) && session->internals.resumption_data.data) { - ret = _gnutls_set_datum(data, session->internals.resumption_data.data, session->internals.resumption_data.size); - if (ret < 0) + if (vers->tls13_sem && !(session->internals.hsk_flags & HSK_TICKET_RECEIVED)) { + /* wait for a message with timeout of 1ms */ + ret = _gnutls_recv_in_buffers(session, GNUTLS_APPLICATION_DATA, -1, 100); + if (ret < 0 && (gnutls_error_is_fatal(ret) && ret != GNUTLS_E_TIMEDOUT)) { return gnutls_assert_val(ret); + } - return 0; + if (!(session->internals.hsk_flags & HSK_TICKET_RECEIVED)) { + ret = _gnutls_set_datum(data, EMPTY_DATA, EMPTY_DATA_SIZE); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; + } + } else if (!vers->tls13_sem) { + /* under TLS1.3 we want to pack the latest ticket, while that's + * not the case in TLS1.2 or earlier. */ + if (gnutls_session_is_resumed(session) && session->internals.resumption_data.data) { + ret = _gnutls_set_datum(data, session->internals.resumption_data.data, session->internals.resumption_data.size); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; + } } if (session->internals.resumable == RESUME_FALSE) @@ -221,6 +254,14 @@ gnutls_session_set_data(gnutls_session_t session, gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } + + /* under TLS1.3 we always return some data on resumption when there + * is no ticket in order to keep compatibility with existing apps */ + if (session_data_size == EMPTY_DATA_SIZE && + memcmp(session_data, EMPTY_DATA, EMPTY_DATA_SIZE) == 0) { + return 0; + } + ret = _gnutls_session_unpack(session, &psession); if (ret < 0) { gnutls_assert(); diff --git a/lib/session_pack.c b/lib/session_pack.c index 977110595b..be4cdafc2d 100644 --- a/lib/session_pack.c +++ b/lib/session_pack.c @@ -44,6 +44,7 @@ #include <algorithms.h> #include <state.h> #include <db.h> +#include "tls13/session_ticket.h" static int pack_certificate_auth_info(gnutls_session_t, gnutls_buffer_st * packed_session); @@ -69,6 +70,10 @@ static int unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st * packed_session); static int pack_security_parameters(gnutls_session_t session, gnutls_buffer_st * packed_session); +static int tls13_unpack_security_parameters(gnutls_session_t session, + gnutls_buffer_st * packed_session); +static int tls13_pack_security_parameters(gnutls_session_t session, + gnutls_buffer_st * packed_session); /* Since auth_info structures contain malloced data, this function @@ -150,6 +155,15 @@ _gnutls_session_pack(gnutls_session_t session, goto fail; } + + if (session->security_parameters.pversion->tls13_sem) { + ret = tls13_pack_security_parameters(session, &sb); + if (ret < 0) { + gnutls_assert(); + goto fail; + } + } + ret = _gnutls_hello_ext_pack(session, &sb); if (ret < 0) { gnutls_assert(); @@ -256,6 +270,15 @@ _gnutls_session_unpack(gnutls_session_t session, goto error; } + if (session->internals.resumed_security_parameters.pversion->tls13_sem) { + /* 'prf' will not be NULL at this point, else unpack_security_parameters() would have failed */ + ret = tls13_unpack_security_parameters(session, &sb); + if (ret < 0) { + gnutls_assert(); + goto error; + } + } + ret = _gnutls_hello_ext_unpack(session, &sb); if (ret < 0) { gnutls_assert(); @@ -270,7 +293,107 @@ _gnutls_session_unpack(gnutls_session_t session, return ret; } +/* + * If we're using TLS 1.3 semantics, we might have TLS 1.3-specific data. + * Format: + * 4 bytes the total length + * 4 bytes the ticket lifetime + * 4 bytes the ticket age add value + * 1 byte the ticket nonce length + * x bytes the ticket nonce + * 4 bytes the ticket length + * x bytes the ticket + * 1 bytes the resumption master secret length + * x bytes the resumption master secret + * + * WE DON'T STORE NewSessionTicket EXTENSIONS, as we don't support them yet. + * + * 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 + * its nonsense to store all that info. + */ +static int +tls13_pack_security_parameters(gnutls_session_t session, gnutls_buffer_st *ps) +{ + int ret = 0; + uint32_t length = 0; + size_t length_pos; + tls13_ticket_t *ticket = &session->internals.tls13_ticket; + + length_pos = ps->length; + BUFFER_APPEND_NUM(ps, 0); + + if (ticket->ticket.data != NULL) { + BUFFER_APPEND_NUM(ps, ticket->timestamp); + length += 4; + BUFFER_APPEND_NUM(ps, ticket->lifetime); + length += 4; + BUFFER_APPEND_NUM(ps, ticket->age_add); + length += 4; + BUFFER_APPEND_PFX1(ps, + ticket->nonce, + ticket->nonce_size); + length += (1 + ticket->nonce_size); + BUFFER_APPEND_PFX4(ps, + ticket->ticket.data, + ticket->ticket.size); + length += (4 + ticket->ticket.size); + BUFFER_APPEND_PFX1(ps, + ticket->resumption_master_secret, + ticket->prf->output_size); + length += (1 + ticket->prf->output_size); + + /* Overwrite the length field */ + _gnutls_write_uint32(length, ps->data + length_pos); + } + + return ret; +} + +static int +tls13_unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st *ps) +{ + uint32_t ttl_len; + tls13_ticket_t *ticket = &session->internals.tls13_ticket; + gnutls_datum_t t; + int ret = 0; + + BUFFER_POP_NUM(ps, ttl_len); + + if (ttl_len > 0) { + BUFFER_POP_NUM(ps, ticket->timestamp); + BUFFER_POP_NUM(ps, ticket->lifetime); + BUFFER_POP_NUM(ps, ticket->age_add); + + ret = _gnutls_buffer_pop_datum_prefix8(ps, &t); + if (ret < 0 || t.size > sizeof(ticket->nonce)) { + ret = GNUTLS_E_PARSING_ERROR; + gnutls_assert(); + goto error; + } + ticket->nonce_size = t.size; + memcpy(ticket->nonce, t.data, t.size); + + BUFFER_POP_DATUM(ps, &ticket->ticket); + + ret = _gnutls_buffer_pop_datum_prefix8(ps, &t); + if (ret < 0 || t.size > sizeof(ticket->resumption_master_secret)) { + ret = GNUTLS_E_PARSING_ERROR; + gnutls_assert(); + goto error; + } + memcpy(ticket->resumption_master_secret, t.data, t.size); + + if (unlikely(session->internals.resumed_security_parameters.prf == NULL || + session->internals.resumed_security_parameters.prf->output_size != t.size)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + ticket->prf = session->internals.resumed_security_parameters.prf; + } + +error: + return ret; +} /* Format: * 1 byte the credentials type @@ -740,10 +863,13 @@ unpack_psk_auth_info(gnutls_session_t session, gnutls_buffer_st * ps) * 2 bytes the cipher suite * 4 bytes the PRF ID * - * 48 bytes the master secret + * 1 byte the size of the master secret + * master secret (not in tls1.3) * - * 32 bytes the client random - * 32 bytes the server random + * 1 byte the size of client random + * the client random (not in tls1.3) + * 1 byte the size of server random + * the server random (not in tls1.3) * * 1 byte the session ID size * x bytes the session ID (32 bytes max) @@ -788,12 +914,19 @@ pack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps) BUFFER_APPEND_NUM(ps, session->security_parameters.cert_type); BUFFER_APPEND_NUM(ps, session->security_parameters.pversion->id); - BUFFER_APPEND(ps, session->security_parameters.master_secret, - GNUTLS_MASTER_SIZE); - BUFFER_APPEND(ps, session->security_parameters.client_random, - GNUTLS_RANDOM_SIZE); - BUFFER_APPEND(ps, session->security_parameters.server_random, - GNUTLS_RANDOM_SIZE); + /* if we are under TLS 1.3 do not pack keys - they are not necessary */ + if (session->security_parameters.pversion->tls13_sem) { + BUFFER_APPEND_PFX1(ps, NULL, 0); + BUFFER_APPEND_PFX1(ps, NULL, 0); + BUFFER_APPEND_PFX1(ps, NULL, 0); + } else { + BUFFER_APPEND_PFX1(ps, session->security_parameters.master_secret, + GNUTLS_MASTER_SIZE); + BUFFER_APPEND_PFX1(ps, session->security_parameters.client_random, + GNUTLS_RANDOM_SIZE); + BUFFER_APPEND_PFX1(ps, session->security_parameters.server_random, + GNUTLS_RANDOM_SIZE); + } BUFFER_APPEND(ps, &session->security_parameters.session_id_size, 1); @@ -824,6 +957,11 @@ pack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps) BUFFER_APPEND_NUM(ps, session->security_parameters.etm); + BUFFER_APPEND_NUM(ps, + session->security_parameters.client_auth_type); + BUFFER_APPEND_NUM(ps, + session->security_parameters.server_auth_type); + _gnutls_write_uint32(ps->length - cur_size, ps->data + size_offset); @@ -836,6 +974,7 @@ unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps) size_t pack_size; int ret; unsigned version; + gnutls_datum_t t; time_t timestamp; uint8_t cs[2]; @@ -876,16 +1015,36 @@ unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps) NULL) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); - BUFFER_POP(ps, - session->internals.resumed_security_parameters. - master_secret, GNUTLS_MASTER_SIZE); + /* master secret */ + ret = _gnutls_buffer_pop_datum_prefix8(ps, &t); + if (ret < 0) { + ret = GNUTLS_E_PARSING_ERROR; + gnutls_assert(); + goto error; + } + if (t.size == GNUTLS_MASTER_SIZE) + memcpy(session->internals.resumed_security_parameters.master_secret, t.data, t.size); + + /* client random */ + ret = _gnutls_buffer_pop_datum_prefix8(ps, &t); + if (ret < 0) { + ret = GNUTLS_E_PARSING_ERROR; + gnutls_assert(); + goto error; + } + if (t.size == GNUTLS_RANDOM_SIZE) + memcpy(session->internals.resumed_security_parameters.client_random, t.data, t.size); + + /* server random */ + ret = _gnutls_buffer_pop_datum_prefix8(ps, &t); + if (ret < 0) { + ret = GNUTLS_E_PARSING_ERROR; + gnutls_assert(); + goto error; + } + if (t.size == GNUTLS_RANDOM_SIZE) + memcpy(session->internals.resumed_security_parameters.server_random, t.data, t.size); - BUFFER_POP(ps, - session->internals.resumed_security_parameters. - client_random, GNUTLS_RANDOM_SIZE); - BUFFER_POP(ps, - session->internals.resumed_security_parameters. - server_random, GNUTLS_RANDOM_SIZE); BUFFER_POP(ps, &session->internals.resumed_security_parameters. session_id_size, 1); @@ -922,6 +1081,13 @@ unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st * ps) session->internals.resumed_security_parameters. etm); + BUFFER_POP_NUM(ps, + session->internals.resumed_security_parameters. + client_auth_type); + BUFFER_POP_NUM(ps, + session->internals.resumed_security_parameters. + server_auth_type); + if (session->internals.resumed_security_parameters. max_record_recv_size == 0 || session->internals.resumed_security_parameters. diff --git a/lib/state.c b/lib/state.c index a669cf3d6c..bbba0ddb25 100644 --- a/lib/state.c +++ b/lib/state.c @@ -51,6 +51,7 @@ #include <intprops.h> #include <gnutls/dtls.h> #include "dtls.h" +#include "tls13/session_ticket.h" /* These should really be static, but src/tests.c calls them. Make them public functions? */ @@ -178,6 +179,13 @@ gnutls_compression_get(gnutls_session_t session) return GNUTLS_COMP_NULL; } +void reset_binders(gnutls_session_t session) +{ + _gnutls_free_temp_key_datum(&session->key.binders[0].psk); + _gnutls_free_temp_key_datum(&session->key.binders[1].psk); + memset(session->key.binders, 0, sizeof(session->key.binders)); +} + static void deinit_keys(gnutls_session_t session) { const version_entry_st *vers = get_version(session); @@ -218,8 +226,7 @@ static void deinit_keys(gnutls_session_t session) sizeof(session->key.proto.tls13.hs_skey)); } - if (session->key.psk_needs_free) - _gnutls_free_temp_key_datum(&session->key.psk); + reset_binders(session); _gnutls_free_temp_key_datum(&session->key.key); } @@ -328,7 +335,7 @@ int gnutls_init(gnutls_session_t * session, unsigned int flags) _mbuffer_head_init(&(*session)->internals.handshake_send_buffer); _gnutls_handshake_recv_buffer_init(*session); - (*session)->internals.expire_time = DEFAULT_EXPIRE_TIME; /* one hour default */ + (*session)->internals.expire_time = DEFAULT_EXPIRE_TIME; gnutls_handshake_set_max_packet_length((*session), MAX_HANDSHAKE_PACKET_SIZE); @@ -382,16 +389,16 @@ int gnutls_init(gnutls_session_t * session, unsigned int flags) /* Enable useful extensions */ if ((flags & GNUTLS_CLIENT) && !(flags & GNUTLS_NO_EXTENSIONS)) { -#ifdef ENABLE_SESSION_TICKETS - if (!(flags & GNUTLS_NO_TICKETS)) - gnutls_session_ticket_enable_client(*session); -#endif #ifdef ENABLE_OCSP gnutls_ocsp_status_request_enable_client(*session, NULL, 0, NULL); #endif } + /* session tickets in server side are enabled by setting a key */ + if (flags & GNUTLS_SERVER) + flags |= GNUTLS_NO_TICKETS; + (*session)->internals.flags = flags; return 0; @@ -457,6 +464,9 @@ void gnutls_deinit(gnutls_session_t session) gnutls_credentials_clear(session); _gnutls_selected_certs_deinit(session); + /* destroy any session ticket we may have received */ + _gnutls13_session_ticket_unset(session); + /* we rely on priorities' internal reference counting */ gnutls_priority_deinit(session->internals.priorities); @@ -724,6 +734,11 @@ gnutls_handshake_set_private_extensions(gnutls_session_t session, int gnutls_session_is_resumed(gnutls_session_t session) { if (session->security_parameters.entity == GNUTLS_CLIENT) { + const version_entry_st *ver = get_version(session); + if (ver && ver->tls13_sem && + session->internals.resumed != RESUME_FALSE) + return 1; + if (session->security_parameters.session_id_size > 0 && session->security_parameters.session_id_size == session->internals.resumed_security_parameters. @@ -1334,6 +1349,8 @@ unsigned gnutls_session_get_flags(gnutls_session_t session) flags |= GNUTLS_SFLAGS_FALSE_START; if (session->internals.hsk_flags & HSK_USED_FFDHE) flags |= GNUTLS_SFLAGS_RFC7919; + if (session->internals.hsk_flags & HSK_TICKET_RECEIVED) + flags |= GNUTLS_SFLAGS_SESSION_TICKET; return flags; } diff --git a/lib/state.h b/lib/state.h index 266af94e5c..75d0f35fc1 100644 --- a/lib/state.h +++ b/lib/state.h @@ -97,6 +97,8 @@ int _gnutls_session_is_psk(gnutls_session_t session); int _gnutls_openpgp_send_fingerprint(gnutls_session_t session); +void reset_binders(gnutls_session_t session); + inline static int _gnutls_PRF(gnutls_session_t session, const uint8_t * secret, unsigned int secret_size, diff --git a/lib/tls13/certificate.c b/lib/tls13/certificate.c index 52c485aaa4..90bd366854 100644 --- a/lib/tls13/certificate.c +++ b/lib/tls13/certificate.c @@ -203,6 +203,9 @@ int _gnutls13_send_certificate(gnutls_session_t session, unsigned again) if (again == 0) { if (session->internals.hsk_flags & HSK_PSK_SELECTED) return 0; + if (session->security_parameters.entity == GNUTLS_SERVER && + session->internals.resumed) + return 0; cred = (gnutls_certificate_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_CERTIFICATE); diff --git a/lib/tls13/certificate_verify.c b/lib/tls13/certificate_verify.c index 0a3fe7e9de..f1dbabab05 100644 --- a/lib/tls13/certificate_verify.c +++ b/lib/tls13/certificate_verify.c @@ -156,6 +156,9 @@ int _gnutls13_send_certificate_verify(gnutls_session_t session, unsigned again) if (again == 0) { if (session->internals.hsk_flags & HSK_PSK_SELECTED) return 0; + if (session->security_parameters.entity == GNUTLS_SERVER && + session->internals.resumed) + return 0; if (session->security_parameters.entity == GNUTLS_SERVER) server = 1; diff --git a/lib/tls13/finished.c b/lib/tls13/finished.c index bb535fff87..cb768b9739 100644 --- a/lib/tls13/finished.c +++ b/lib/tls13/finished.c @@ -30,7 +30,6 @@ int _gnutls13_compute_finished(const mac_entry_st *prf, const uint8_t *base_key, - unsigned hash_size, gnutls_buffer_st *handshake_hash_buffer, void *out) { @@ -42,7 +41,7 @@ int _gnutls13_compute_finished(const mac_entry_st *prf, "finished", 8, NULL, 0, base_key, - hash_size, fkey); + prf->output_size, fkey); if (ret < 0) return gnutls_assert_val(ret); @@ -54,8 +53,8 @@ int _gnutls13_compute_finished(const mac_entry_st *prf, return gnutls_assert_val(ret); ret = gnutls_hmac_fast(prf->id, - fkey, hash_size, - ts_hash, hash_size, + fkey, prf->output_size, + ts_hash, prf->output_size, out); if (ret < 0) return gnutls_assert_val(ret); @@ -82,7 +81,7 @@ int _gnutls13_recv_finished(gnutls_session_t session) base_key = session->key.proto.tls13.hs_ckey; ret = _gnutls13_compute_finished(session->security_parameters.prf, - base_key, hash_size, + base_key, &session->internals.handshake_hash_buffer, verifier); if (ret < 0) { @@ -140,7 +139,7 @@ int _gnutls13_send_finished(gnutls_session_t session, unsigned again) base_key = session->key.proto.tls13.hs_skey; ret = _gnutls13_compute_finished(session->security_parameters.prf, - base_key, hash_size, + base_key, &session->internals.handshake_hash_buffer, verifier); if (ret < 0) { diff --git a/lib/tls13/finished.h b/lib/tls13/finished.h index 2e732e7493..7b676f1253 100644 --- a/lib/tls13/finished.h +++ b/lib/tls13/finished.h @@ -22,7 +22,6 @@ int _gnutls13_compute_finished(const mac_entry_st *prf, const uint8_t *base_key, - unsigned hash_size, gnutls_buffer_st *handshake_hash_buffer, void *out); int _gnutls13_recv_finished(gnutls_session_t session); diff --git a/lib/tls13/hello_retry.c b/lib/tls13/hello_retry.c index 5676c52780..7f2bd1e529 100644 --- a/lib/tls13/hello_retry.c +++ b/lib/tls13/hello_retry.c @@ -27,6 +27,7 @@ #include "tls13/hello_retry.h" #include "auth/cert.h" #include "mbuffers.h" +#include "state.h" int _gnutls13_send_hello_retry_request(gnutls_session_t session, unsigned again) { @@ -89,8 +90,7 @@ int _gnutls13_send_hello_retry_request(gnutls_session_t session, unsigned again) /* reset extensions sent by this session to allow re-sending them */ session->internals.used_exts = 0; - if (session->key.psk_needs_free) - _gnutls_free_temp_key_datum(&session->key.psk); + reset_binders(session); bufel = _gnutls_buffer_to_mbuffer(&buf); } diff --git a/lib/tls13/session_ticket.c b/lib/tls13/session_ticket.c index d5d62f433f..4680a32d1a 100644 --- a/lib/tls13/session_ticket.c +++ b/lib/tls13/session_ticket.c @@ -1,7 +1,7 @@ /* - * Copyright (C) 2017 Red Hat, Inc. + * Copyright (C) 2017-2018 Red Hat, Inc. * - * Author: Nikos Mavrogiannopoulos + * Author: Nikos Mavrogiannopoulos, Ander Juaristi * * This file is part of GnuTLS. * @@ -24,60 +24,330 @@ #include "errors.h" #include "extv.h" #include "handshake.h" -#include "tls13/session_ticket.h" +#include "mbuffers.h" +#include "ext/pre_shared_key.h" +#include "ext/session_ticket.h" #include "auth/cert.h" +#include "tls13/session_ticket.h" + +static int +pack_ticket(tls13_ticket_t *ticket, gnutls_datum_t *state) +{ + uint8_t *p; + + state->size = 2 + 4 + 4 + + 1 + ticket->prf->output_size + + 1 + ticket->nonce_size; + + state->data = gnutls_malloc(state->size); + if (!state->data) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + p = state->data; + + _gnutls_write_uint16(ticket->prf->id, p); + p += 2; + _gnutls_write_uint32(ticket->age_add, p); + p += 4; + _gnutls_write_uint32(ticket->lifetime, p); + p += 4; + *p = ticket->prf->output_size; + p += 1; + memcpy(p, ticket->resumption_master_secret, ticket->prf->output_size); + p += ticket->prf->output_size; + *p = ticket->nonce_size; + + p += 1; + memcpy(p, ticket->nonce, ticket->nonce_size); + + return 0; +} + +static int +unpack_ticket(gnutls_datum_t *state, tls13_ticket_t *data) +{ + uint32_t age_add, lifetime; + uint8_t resumption_master_secret[MAX_HASH_SIZE]; + size_t resumption_master_secret_size; + uint8_t nonce[UINT8_MAX]; + size_t nonce_size; + gnutls_mac_algorithm_t kdf; + const mac_entry_st *prf; + uint8_t *p; + ssize_t len; + + if (unlikely(state == NULL || data == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + memset(data, 0, sizeof(*data)); + + p = state->data; + len = state->size; + + DECR_LEN(len, 2); + kdf = _gnutls_read_uint16(p); + p += 2; + + /* Check if the MAC ID we got is valid */ + prf = _gnutls_mac_to_entry(kdf); + if (prf == NULL) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + /* Read the ticket age add and the ticket lifetime */ + DECR_LEN(len, 4); + age_add = _gnutls_read_uint32(p); + p += 4; + + DECR_LEN(len, 4); + lifetime = _gnutls_read_uint32(p); + p += 4; + + /* + * Check if the whole ticket is large enough, + * and read the resumption master secret + */ + DECR_LEN(len, 1); + resumption_master_secret_size = *p; + p += 1; + + /* Check if the size of resumption_master_secret matches the PRF */ + if (resumption_master_secret_size != prf->output_size) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + DECR_LEN(len, resumption_master_secret_size); + memcpy(resumption_master_secret, p, resumption_master_secret_size); + p += resumption_master_secret_size; + + /* Read the ticket nonce */ + DECR_LEN(len, 1); + nonce_size = *p; + p += 1; + + DECR_LEN(len, nonce_size); + memcpy(nonce, p, nonce_size); + + /* No errors - Now return all the data to the caller */ + data->prf = prf; + memcpy(data->resumption_master_secret, resumption_master_secret, + resumption_master_secret_size); + memcpy(data->nonce, nonce, nonce_size); + data->nonce_size = nonce_size; + data->age_add = age_add; + data->lifetime = lifetime; + + return 0; +} + +static int +generate_session_ticket(gnutls_session_t session, tls13_ticket_t *ticket) +{ + int ret; + gnutls_datum_t state = { NULL, 0 }; + tls13_ticket_t ticket_data; + + /* Generate a random 128-bit ticket nonce */ + ticket->nonce_size = 16; + + if ((ret = gnutls_rnd(GNUTLS_RND_NONCE, + ticket->nonce, ticket->nonce_size)) < 0) + return gnutls_assert_val(ret); + + if ((ret = gnutls_rnd(GNUTLS_RND_NONCE, &ticket->age_add, sizeof(uint32_t))) < 0) + return gnutls_assert_val(ret); + + /* Set ticket lifetime to 1 day (86400 seconds) */ + ticket->lifetime = session->internals.expire_time; + + ticket->prf = session->security_parameters.prf; + + /* 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.nonce, ticket->nonce, ticket->nonce_size); + ticket_data.nonce_size = ticket->nonce_size; + ticket_data.prf = ticket->prf; + memcpy(&ticket_data.resumption_master_secret, + session->key.proto.tls13.ap_rms, + ticket->prf->output_size); + + ret = pack_ticket(&ticket_data, &state); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_encrypt_session_ticket(session, &state, &ticket->ticket); + _gnutls_free_datum(&state); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; +} + +int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned again) +{ + int ret = 0; + mbuffer_st *bufel = NULL; + gnutls_buffer_st buf; + tls13_ticket_t ticket; + + /* Client does not send a NewSessionTicket */ + if (unlikely(session->security_parameters.entity == GNUTLS_CLIENT)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + /* Session resumption has not been enabled */ + if (session->internals.flags & GNUTLS_NO_TICKETS) + return gnutls_assert_val(0); + + if (again == 0) { + memset(&ticket, 0, sizeof(tls13_ticket_t)); -static int parse_nst_extension(void *ctx, unsigned tls_id, const uint8_t *data, unsigned data_size); + ret = _gnutls_buffer_init_handshake_mbuffer(&buf); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = generate_session_ticket(session, &ticket); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_prefix(&buf, 32, ticket.lifetime); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_prefix(&buf, 32, ticket.age_add); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* append ticket_nonce */ + ret = _gnutls_buffer_append_data_prefix(&buf, 8, ticket.nonce, ticket.nonce_size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* append ticket */ + ret = _gnutls_buffer_append_data_prefix(&buf, 16, ticket.ticket.data, ticket.ticket.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ret = _gnutls_buffer_append_prefix(&buf, 16, 0); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + _gnutls_free_datum(&ticket.ticket); + + bufel = _gnutls_buffer_to_mbuffer(&buf); + } + + ret = _gnutls_send_handshake(session, bufel, + GNUTLS_HANDSHAKE_NEW_SESSION_TICKET); + if (ret > 0) + session->internals.hsk_flags |= HSK_TLS13_TICKET_SENT; + + return ret; + +cleanup: + _gnutls_free_datum(&ticket.ticket); + _mbuffer_xfree(&bufel); + + return ret; +} + +static int parse_nst_extension(void *ctx, unsigned tls_id, const unsigned char *data, unsigned data_size) +{ + /* ignore all extensions */ + return 0; +} int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *buf) { int ret; - size_t val; - gnutls_datum_t nonce; - gnutls_datum_t ticket; + uint8_t value; + tls13_ticket_t *ticket = &session->internals.tls13_ticket; + gnutls_datum_t t; + + if (unlikely(buf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + _gnutls_free_datum(&ticket->ticket); + memset(ticket, 0, sizeof(tls13_ticket_t)); _gnutls_handshake_log("HSK[%p]: parsing session ticket message\n", session); /* ticket_lifetime */ - ret = _gnutls_buffer_pop_prefix32(buf, &val, 0); - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } + ret = _gnutls_buffer_pop_prefix32(buf, (size_t *) &ticket->lifetime, 0); + if (ret < 0) + return gnutls_assert_val(ret); /* ticket_age_add */ - ret = _gnutls_buffer_pop_prefix32(buf, &val, 0); - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } + ret = _gnutls_buffer_pop_prefix32(buf, (size_t *) &ticket->age_add, 0); + if (ret < 0) + return gnutls_assert_val(ret); - ret = _gnutls_buffer_pop_datum_prefix8(buf, &nonce); - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } + /* ticket_nonce */ + ret = _gnutls_buffer_pop_prefix8(buf, &value, 0); + if (ret < 0) + return gnutls_assert_val(ret); - ret = _gnutls_buffer_pop_datum_prefix16(buf, &ticket); - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } + ticket->nonce_size = value; + ret = _gnutls_buffer_pop_data(buf, ticket->nonce, ticket->nonce_size); + if (ret < 0) + return gnutls_assert_val(ret); + + /* ticket */ + ret = _gnutls_buffer_pop_datum_prefix16(buf, &t); + if (ret < 0) + return gnutls_assert_val(ret); + + gnutls_free(ticket->ticket.data); + ret = _gnutls_set_datum(&ticket->ticket, t.data, t.size); + if (ret < 0) + return gnutls_assert_val(ret); + /* Extensions */ ret = _gnutls_extv_parse(NULL, parse_nst_extension, buf->data, buf->length); - if (ret < 0) { - gnutls_assert(); - goto cleanup; - } + if (ret < 0) + return gnutls_assert_val(ret); - ret = 0; -cleanup: + /* Set the ticket timestamp */ + ticket->timestamp = gnutls_time(0); - return ret; + return 0; } -static int parse_nst_extension(void *ctx, unsigned tls_id, const uint8_t *data, unsigned data_size) +/* + * Parse the ticket in 'data' and return the resumption master secret + * and the KDF ID associated to it. + */ +int _gnutls13_unpack_session_ticket(gnutls_session_t session, + gnutls_datum_t *data, + tls13_ticket_t *ticket_data) { - /* ignore all extensions */ + int ret; + gnutls_datum_t decrypted = { NULL, 0 }; + + if (unlikely(data == NULL || ticket_data == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + /* Check MAC and decrypt ticket */ + ret = _gnutls_decrypt_session_ticket(session, data, &decrypted); + if (ret < 0) + return gnutls_assert_val(ret); + + /* Return ticket parameters */ + ret = unpack_ticket(&decrypted, ticket_data); + _gnutls_free_datum(&decrypted); + if (ret < 0) { + return ret; + } + return 0; } diff --git a/lib/tls13/session_ticket.h b/lib/tls13/session_ticket.h index 1c31589a26..073c28f1f2 100644 --- a/lib/tls13/session_ticket.h +++ b/lib/tls13/session_ticket.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2017 Red Hat, Inc. * - * Author: Nikos Mavrogiannopoulos + * Author: Nikos Mavrogiannopoulos, Ander Juaristi * * This file is part of GnuTLS. * @@ -19,5 +19,33 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ +#ifndef SESSION_TICKET_H +#define SESSION_TICKET_H int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *buf); +int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned again); + +int _gnutls13_unpack_session_ticket(gnutls_session_t session, + gnutls_datum_t *data, + tls13_ticket_t *ticket_data); + +inline static +void tls13_ticket_deinit(tls13_ticket_t *ticket) +{ + if (ticket) { + zeroize_temp_key(&ticket->resumption_master_secret, + sizeof(ticket->resumption_master_secret)); + + _gnutls_free_datum(&ticket->ticket); + memset(ticket, 0, sizeof(tls13_ticket_t)); + } +} + +inline static +void _gnutls13_session_ticket_unset(gnutls_session_t session) +{ + if (session->internals.tls13_ticket.ticket.data != NULL) + tls13_ticket_deinit(&session->internals.tls13_ticket); +} + +#endif diff --git a/m4/hooks.m4 b/m4/hooks.m4 index aef186d932..f407753b74 100644 --- a/m4/hooks.m4 +++ b/m4/hooks.m4 @@ -329,22 +329,6 @@ LIBTASN1_MINIMUM=4.9 fi AM_CONDITIONAL(ENABLE_OCSP, test "$ac_enable_ocsp" != "no") - - AC_MSG_CHECKING([whether to disable session tickets support]) - AC_ARG_ENABLE(session-tickets, - AS_HELP_STRING([--disable-session-tickets], - [disable session tickets support]), - ac_enable_session_tickets=$enableval,ac_enable_session_tickets=yes) - if test x$ac_enable_session_tickets != xno; then - ac_enable_session_tickets=yes - AC_MSG_RESULT(no) - AC_DEFINE([ENABLE_SESSION_TICKETS], 1, [enable session tickets support]) - else - ac_full=0 - AC_MSG_RESULT(yes) - fi - AM_CONDITIONAL(ENABLE_SESSION_TICKETS, test "$ac_enable_session_tickets" != "no") - # For storing integers in pointers without warnings # http://developer.gnome.org/doc/API/2.0/glib/glib-Type-Conversion-Macros.html#desc AC_CHECK_SIZEOF(void *) diff --git a/src/serv.c b/src/serv.c index ab1a6e6c65..34996d1792 100644 --- a/src/serv.c +++ b/src/serv.c @@ -398,11 +398,9 @@ gnutls_session_t initialize_session(int dtls) gnutls_db_set_ptr(session, NULL); } -#ifdef ENABLE_SESSION_TICKETS if (noticket == 0) gnutls_session_ticket_enable_server(session, &session_ticket_key); -#endif if (sni_hostname != NULL) gnutls_handshake_set_post_client_hello_function(session, @@ -1220,10 +1218,8 @@ int main(int argc, char **argv) } #endif -#ifdef ENABLE_SESSION_TICKETS if (noticket == 0) gnutls_session_ticket_key_generate(&session_ticket_key); -#endif if (HAVE_OPT(MTU)) mtu = OPT_VALUE_MTU; diff --git a/tests/session-tickets-missing.c b/tests/session-tickets-missing.c index 0a546491dd..a767cbfd37 100644 --- a/tests/session-tickets-missing.c +++ b/tests/session-tickets-missing.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 Red Hat, Inc + * Copyright (C) 2016-2018 Red Hat, Inc * * Author: Nikos Mavrogiannopoulos * @@ -15,9 +15,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with GnuTLS; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * 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/> + * */ #ifdef HAVE_CONFIG_H @@ -55,7 +55,10 @@ int main() static void terminate(void); /* This program tests that handshakes do not include a session ticket - * if the flag GNUTLS_NO_TICKETS is specified. + * if the flag GNUTLS_NO_TICKETS is specified under TLS 1.2. + * + * Under TLS 1.3 it verifies that not enabling session tickets doesn't + * result in a ticket being sent. */ static time_t mytime(time_t * t) @@ -168,13 +171,17 @@ static void terminate(void) exit(1); } -static void server(int fd, const char *prio) +static void server(int fd, const char *prio, unsigned server_no_tickets) { int ret; char buffer[MAX_BUF + 1]; gnutls_session_t session; gnutls_certificate_credentials_t x509_cred; - gnutls_datum_t skey; + gnutls_datum_t skey = {NULL, 0}; + unsigned int flags = GNUTLS_SERVER; + + if (server_no_tickets) + flags |= GNUTLS_NO_TICKETS; /* this must be called once in the program */ @@ -191,10 +198,12 @@ static void server(int fd, const char *prio) &server_key, GNUTLS_X509_FMT_PEM)>=0); - assert(gnutls_init(&session, GNUTLS_SERVER)>=0); + assert(gnutls_init(&session, flags)>=0); - assert(gnutls_session_ticket_key_generate(&skey)>=0); - assert(gnutls_session_ticket_enable_server(session, &skey) >= 0); + if (!server_no_tickets) { + assert(gnutls_session_ticket_key_generate(&skey)>=0); + assert(gnutls_session_ticket_enable_server(session, &skey) >= 0); + } gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, @@ -254,7 +263,7 @@ static void ch_handler(int sig) } static -void start(const char *prio) +void start(const char *prio, unsigned server_no_tickets) { int fd[2]; int ret, status = 0; @@ -281,7 +290,7 @@ void start(const char *prio) if (child) { /* parent */ close(fd[1]); - server(fd[0], prio); + server(fd[0], prio, server_no_tickets); waitpid(child, &status, 0); check_wait_status(status); } else { @@ -295,9 +304,11 @@ void start(const char *prio) void doit(void) { - start("NORMAL:-VERS-ALL:+VERS-TLS1.2"); - start("NORMAL:-VERS-ALL:+VERS-TLS1.3"); - start("NORMAL"); + start("NORMAL:-VERS-ALL:+VERS-TLS1.2", 0); + /* Under TLS 1.3 session tickets are not negotiated; they are + * "always sent unless server sets GNUTLS_NO_TICKETS */ + start("NORMAL:-VERS-ALL:+VERS-TLS1.3", 1); + start("NORMAL", 0); } #endif /* _WIN32 */ |