diff options
author | Ander Juaristi <a@juaristi.eus> | 2018-02-28 19:08:37 +0100 |
---|---|---|
committer | Ander Juaristi <a@juaristi.eus> | 2018-02-28 19:09:51 +0100 |
commit | 629a4e7b030bdd317605a1ce76c98d0d7eaf980e (patch) | |
tree | 3cb8dc9fef01070afac64231b37061aecde91ff7 | |
parent | cf4291befab566ecd34bac98c09d29dbce71f349 (diff) | |
download | gnutls-tmp-draft-ietf-tls-tls13-21-ajuaristi-session-resumption-2.tar.gz |
TLS 1.3: Session resumptiontmp-draft-ietf-tls-tls13-21-ajuaristi-session-resumption-2
Signed-off-by: Ander Juaristi <a@juaristi.eus>
-rw-r--r-- | lib/ext/pre_shared_key.c | 209 | ||||
-rw-r--r-- | lib/ext/pre_shared_key.h | 1 | ||||
-rw-r--r-- | lib/ext/psk_ke_modes.c | 12 | ||||
-rw-r--r-- | lib/ext/session_ticket.c | 149 | ||||
-rw-r--r-- | lib/ext/session_ticket.h | 17 | ||||
-rw-r--r-- | lib/gnutls_int.h | 27 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 26 | ||||
-rw-r--r-- | lib/handshake.c | 19 | ||||
-rw-r--r-- | lib/handshake.h | 1 | ||||
-rw-r--r-- | lib/session.c | 1 | ||||
-rw-r--r-- | lib/session_pack.c | 124 | ||||
-rw-r--r-- | lib/state.c | 4 | ||||
-rw-r--r-- | lib/tls13/session_ticket.c | 714 | ||||
-rw-r--r-- | lib/tls13/session_ticket.h | 45 |
14 files changed, 1198 insertions, 151 deletions
diff --git a/lib/ext/pre_shared_key.c b/lib/ext/pre_shared_key.c index b60e9d7cb2..27869eab7a 100644 --- a/lib/ext/pre_shared_key.c +++ b/lib/ext/pre_shared_key.c @@ -25,14 +25,47 @@ #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 "tls13/psk_ext_parser.h" typedef struct { - uint16_t selected_identity; + struct tls13_nst_st *session_ticket; + uint8_t *rms; + size_t rms_size; } psk_ext_st; static int +compute_psk_from_ticket(const mac_entry_st *prf, + const uint8_t *rms, + gnutls_datum_t *nonce, gnutls_datum_t *key) +{ + int ret; + unsigned hash_size = prf->output_size; + char label[] = "resumption"; + + key->data = gnutls_malloc(hash_size); + key->size = hash_size; + if (key->data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + ret = _tls13_expand_secret2(prf, + label, strlen(label), + nonce->data, nonce->size, + rms, + hash_size, + key->data); + if (ret < 0) { + _gnutls_free_datum(key); + return gnutls_assert_val(ret); + } + + return ret; +} + +static int compute_binder_key(const mac_entry_st *prf, const uint8_t *key, size_t keylen, void *out) @@ -181,27 +214,85 @@ client_send_params(gnutls_session_t session, gnutls_buffer_t extdata, const gnutls_psk_client_credentials_t cred) { - int ret, extdata_len = 0, ext_offset = 0; + int ret = 0, extdata_len = 0, ext_offset = 0; uint8_t binder_value[MAX_HASH_SIZE]; - size_t length, pos = extdata->length; - gnutls_datum_t username, key, client_hello; - const mac_entry_st *prf = _gnutls_mac_to_entry(cred->tls13_binder_algo); - unsigned hash_size = _gnutls_mac_get_algo_len(prf); + size_t length, pos; + const mac_entry_st *prf = NULL; + unsigned hash_size = 0; + struct tls13_nst_st ticket; + const uint8_t *rms = NULL; + time_t cur_time; + int ticket_age; + uint32_t ob_ticket_age = 0; + gnutls_datum_t username = { NULL, 0 }, key = { NULL, 0 }, + client_hello = { NULL, 0 }; + + memset(&ticket, 0, sizeof(struct tls13_nst_st)); + + if (cred) { + prf = _gnutls_mac_to_entry(cred->tls13_binder_algo); + hash_size = _gnutls_mac_get_algo_len(prf); + if (prf == NULL || hash_size == 0 || hash_size > 255) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = get_credentials(session, cred, &username, &key); + if (ret < 0) + return gnutls_assert_val(ret); + } - if (prf == NULL || hash_size == 0 || hash_size > 255) - return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + /* No out-of-band PSKs - let's see if we have a session ticket to send */ + if (prf == NULL && session->internals.session_ticket_enable) { + ret = _gnutls13_session_ticket_get(session, &ticket); + if (ret > 0) { + /* We found a session ticket */ + prf = _gnutls_mac_to_entry(session->key.proto.tls13.kdf_original); + hash_size = _gnutls_mac_get_algo_len(prf); + if (unlikely(prf == NULL || hash_size == 0)) { + _gnutls13_session_ticket_unset(session); + ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + goto cleanup; + } - memset(&username, 0, sizeof(gnutls_datum_t)); + /* Check whether the ticket is stale */ + cur_time = time(NULL); + ticket_age = cur_time - ticket.ticket_timestamp; + if (ticket_age < 0 || ticket_age > cur_time) { + _gnutls13_session_ticket_unset(session); + ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + goto cleanup; + } + if ((unsigned int) ticket_age > ticket.ticket_lifetime) { + _gnutls13_session_ticket_unset(session); + ret = 0; + goto cleanup; + } + + username.data = ticket.ticket.data; + username.size = ticket.ticket.size; + + rms = session->key.proto.tls13.ap_rms_original.data; + ret = compute_psk_from_ticket(prf, + rms, + &ticket.ticket_nonce, &key); + if (ret < 0) { + _gnutls13_session_ticket_unset(session); + gnutls_assert(); + goto cleanup; + } + + /* Calculate obfuscated ticket age, in milliseconds, mod 2^32 */ + ob_ticket_age = (ticket_age * 1000 + ticket.ticket_age_add) % 4294967296; + } + } - ret = get_credentials(session, cred, &username, &key); - if (ret < 0) - return gnutls_assert_val(ret); /* No credentials - this extension is not applicable */ - if (ret == 0) { + if (prf == NULL) { ret = 0; goto cleanup; } + /* Make some room for the identities length (16 bits) */ + pos = extdata->length; ret = _gnutls_buffer_append_prefix(extdata, 16, 0); if (ret < 0) { gnutls_assert_val(ret); @@ -220,8 +311,8 @@ client_send_params(gnutls_session_t session, gnutls_assert_val(ret); goto cleanup; } - /* Now append the ticket age, which is always zero for out-of-band PSKs */ - if ((ret = _gnutls_buffer_append_prefix(extdata, 32, 0)) < 0) { + /* Obfuscated ticket age */ + if ((ret = _gnutls_buffer_append_prefix(extdata, 32, ob_ticket_age)) < 0) { gnutls_assert_val(ret); goto cleanup; } @@ -269,6 +360,7 @@ client_send_params(gnutls_session_t session, ret = extdata_len; cleanup: + _gnutls13_session_ticket_destroy(&ticket); _gnutls_free_datum(&username); return ret; } @@ -294,15 +386,23 @@ static int server_recv_params(gnutls_session_t session, const gnutls_psk_server_credentials_t pskcred) { int ret; - const mac_entry_st *prf; + const mac_entry_st *prf = NULL; gnutls_datum_t full_client_hello; uint8_t binder_value[MAX_HASH_SIZE]; int psk_index = -1; + gnutls_datum_t key = { NULL, 0 }; gnutls_datum_t binder_recvd = { NULL, 0 }; - gnutls_datum_t key; + gnutls_datum_t ticket_bytes = { NULL, 0 }; + gnutls_datum_t ticket_nonce = { NULL, 0 }; + int ticket_age; + struct tls13_ticket_data ticket_data; unsigned hash_size; psk_ext_parser_t psk_parser; struct psk_st psk; + enum { + PSK = 1, + TICKET + } psk_kind = 0; ret = _gnutls13_psk_ext_parser_init(&psk_parser, data, len); if (ret == 0) { @@ -313,28 +413,74 @@ static int server_recv_params(gnutls_session_t session, } if (_gnutls13_psk_ext_parser_next_psk(psk_parser, &psk) >= 0) { + ticket_bytes.data = psk.identity.data; + ticket_bytes.size = psk.identity.size; + + if (_gnutls13_unpack_session_ticket(session, &ticket_bytes, &ticket_data) > 0) { + psk_index = psk.selected_index; + prf = _gnutls_mac_to_entry(ticket_data.kdf_id); + if (!prf) { + _gnutls13_ticket_data_destroy(&ticket_data); + return gnutls_assert_val(GNUTLS_E_INVALID_SESSION); + } + + session->internals.tls13_session_ticket_renew = 0; + + /* Check whether ticket is stale or not */ + ticket_age = psk.ob_ticket_age - ticket_data.ticket_age_add; + if (ticket_age < 0) { + session->internals.tls13_session_ticket_renew = 1; + _gnutls13_ticket_data_destroy(&ticket_data); + return 0; + } + if ((unsigned int) (ticket_age / 1000) > ticket_data.ticket_lifetime) { + session->internals.tls13_session_ticket_renew = 1; + _gnutls13_ticket_data_destroy(&ticket_data); + return 0; + } + + ticket_nonce.data = ticket_data.ticket_nonce; + ticket_nonce.size = ticket_data.ticket_nonce_len; + ret = compute_psk_from_ticket(prf, ticket_data.rms, &ticket_nonce, &key); + if (ret < 0) { + session->internals.tls13_session_ticket_renew = 1; + _gnutls13_ticket_data_destroy(&ticket_data); + return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + } + + _gnutls13_ticket_data_destroy(&ticket_data); + psk_kind = TICKET; + } + /* _gnutls_psk_pwd_find_entry() expects 0-terminated identities */ - if (psk.identity.size > 0) { + if (psk.identity.size > 0 && psk_kind == 0) { char identity_str[psk.identity.size + 1]; memcpy(identity_str, psk.identity.data, psk.identity.size); identity_str[psk.identity.size] = 0; ret = _gnutls_psk_pwd_find_entry(session, identity_str, &key); - if (ret == 0) + if (ret == 0) { + psk_kind = PSK; psk_index = psk.selected_index; + prf = _gnutls_mac_to_entry(pskcred->tls13_binder_algo); + } + + session->internals.tls13_session_ticket_renew = 0; } } if (psk_index < 0) return 0; + /* Are session tickets enabled? */ + if (psk_kind == TICKET && !session->internals.session_ticket_enable) + return 0; + ret = _gnutls13_psk_ext_parser_find_binder(psk_parser, psk_index, &binder_recvd); if (ret < 0) return gnutls_assert_val(ret); - if (binder_recvd.size == 0) - return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); ret = _gnutls13_psk_ext_parser_deinit(&psk_parser, &data, (size_t *) &len); @@ -350,7 +496,10 @@ static int server_recv_params(gnutls_session_t session, } /* Compute the binder value for this PSK */ - prf = _gnutls_mac_to_entry(pskcred->tls13_binder_algo); + if (!prf) { + ret = gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); + goto cleanup; + } hash_size = prf->output_size; compute_psk_binder(GNUTLS_SERVER, prf, hash_size, hash_size, 0, 0, 0, &key, &full_client_hello, @@ -422,13 +571,7 @@ static int _gnutls_psk_send_params(gnutls_session_t session, _gnutls_get_cred(session, GNUTLS_CRD_PSK); } - /* - * If there are no PSK credentials, this extension is not applicable, - * so we return zero. - */ - return (cred ? - client_send_params(session, extdata, cred) : - 0); + return client_send_params(session, extdata, cred); } else { if (session->internals.hsk_flags & HSK_PSK_KE_MODES_RECEIVED) return server_send_params(session, extdata); @@ -463,13 +606,7 @@ static int _gnutls_psk_recv_params(gnutls_session_t session, pskcred = (gnutls_psk_server_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_PSK); - /* - * If there are no PSK credentials, this extension is not applicable, - * so we return zero. - */ - return (pskcred ? - server_recv_params(session, data, len, pskcred) : - 0); + return server_recv_params(session, data, len, pskcred); } else { return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); } diff --git a/lib/ext/pre_shared_key.h b/lib/ext/pre_shared_key.h index 22f64a37c0..f517c13288 100644 --- a/lib/ext/pre_shared_key.h +++ b/lib/ext/pre_shared_key.h @@ -2,6 +2,7 @@ #define EXT_PRE_SHARED_KEY_H #include <hello_ext.h> +#include "tls13/session_ticket.h" extern const hello_ext_entry_st ext_pre_shared_key; diff --git a/lib/ext/psk_ke_modes.c b/lib/ext/psk_ke_modes.c index 44070423d8..0e5cbf4463 100644 --- a/lib/ext/psk_ke_modes.c +++ b/lib/ext/psk_ke_modes.c @@ -21,6 +21,7 @@ */ #include "gnutls_int.h" +#include "tls13/session_ticket.h" #include <ext/psk_ke_modes.h> #define PSK_DHE_KE 1 @@ -59,8 +60,17 @@ psk_ke_modes_send_params(gnutls_session_t session, cred = (gnutls_psk_client_credentials_t) _gnutls_get_cred(session, GNUTLS_CRD_PSK); + if (cred) + goto have_psk; - if (cred) { + /* + * No out-of-band PSKs - do we have a session ticket? + * We're not interested in the ticket itself. + */ + retval = _gnutls13_session_ticket_peek(session, NULL); + +have_psk: + if (cred || retval) { retval = send_params(extdata, PSK_DHE_KE); if (retval < 0) gnutls_assert_val(retval); diff --git a/lib/ext/session_ticket.c b/lib/ext/session_ticket.c index 7e0a0b2f8d..38e78c42f8 100644 --- a/lib/ext/session_ticket.c +++ b/lib/ext/session_ticket.c @@ -38,13 +38,10 @@ #ifdef ENABLE_SESSION_TICKETS -#define KEY_NAME_SIZE 16 -#define CIPHER_KEY_SIZE 32 #define CIPHER GNUTLS_CIPHER_AES_256_CBC #define IV_SIZE 16 #define BLOCK_SIZE 16 -#define MAC_SECRET_SIZE 16 #define MAC_ALGO GNUTLS_MAC_SHA1 #define MAC_SIZE 20 /* HMAC-SHA1 */ @@ -74,29 +71,11 @@ const hello_ext_entry_st ext_mod_session_ticket = { .cannot_be_overriden = 1 }; -#define SESSION_KEY_SIZE (KEY_NAME_SIZE+CIPHER_KEY_SIZE+MAC_SECRET_SIZE) -#define NAME_POS (0) -#define KEY_POS (KEY_NAME_SIZE) -#define MAC_SECRET_POS (KEY_NAME_SIZE+CIPHER_KEY_SIZE) - typedef struct { - int session_ticket_enable; - int session_ticket_renew; - uint8_t *session_ticket; int session_ticket_len; - - uint8_t key[SESSION_KEY_SIZE]; } session_ticket_ext_st; -struct ticket_st { - uint8_t key_name[KEY_NAME_SIZE]; - uint8_t IV[IV_SIZE]; - uint8_t *encrypted_state; - uint16_t encrypted_state_len; - uint8_t mac[MAC_SIZE]; -}; - static int digest_ticket(const gnutls_datum_t * key, struct ticket_st *ticket, uint8_t * digest) @@ -124,7 +103,7 @@ int digest_ticket(const gnutls_datum_t * key, struct ticket_st *ticket, } static int -decrypt_ticket(gnutls_session_t session, session_ticket_ext_st * priv, +decrypt_ticket(gnutls_session_t session, struct ticket_st *ticket) { cipher_hd_st cipher_hd; @@ -134,7 +113,7 @@ decrypt_ticket(gnutls_session_t session, session_ticket_ext_st * priv, int ret; /* Check the integrity of ticket */ - mac_secret.data = (void *) &priv->key[MAC_SECRET_POS]; + mac_secret.data = (void *) &session->key.session_ticket_key[MAC_SECRET_POS]; mac_secret.size = MAC_SECRET_SIZE; ret = digest_ticket(&mac_secret, ticket, cmac); if (ret < 0) @@ -147,7 +126,7 @@ decrypt_ticket(gnutls_session_t session, session_ticket_ext_st * priv, return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); /* Decrypt encrypted_state */ - key.data = (void *) &priv->key[KEY_POS]; + key.data = (void *) &session->key.session_ticket_key[KEY_POS]; key.size = CIPHER_KEY_SIZE; IV.data = ticket->IV; IV.size = IV_SIZE; @@ -202,7 +181,7 @@ cleanup: } static int -encrypt_ticket(gnutls_session_t session, session_ticket_ext_st * priv, +encrypt_ticket(gnutls_session_t session, struct ticket_st *ticket) { cipher_hd_st cipher_hd; @@ -230,7 +209,7 @@ encrypt_ticket(gnutls_session_t session, session_ticket_ext_st * priv, memcpy(encrypted_state.data, state.data, state.size); /* Encrypt state */ - key.data = (void *) &priv->key[KEY_POS]; + key.data = (void *) &session->key.session_ticket_key[KEY_POS]; key.size = CIPHER_KEY_SIZE; IV.data = iv; IV.size = IV_SIZE; @@ -261,12 +240,12 @@ encrypt_ticket(gnutls_session_t session, session_ticket_ext_st * priv, /* Fill the ticket structure to compute MAC. */ - memcpy(ticket->key_name, &priv->key[NAME_POS], KEY_NAME_SIZE); + memcpy(ticket->key_name, &session->key.session_ticket_key[NAME_POS], KEY_NAME_SIZE); memcpy(ticket->IV, IV.data, IV.size); ticket->encrypted_state_len = encrypted_state.size; ticket->encrypted_state = encrypted_state.data; - mac_secret.data = &priv->key[MAC_SECRET_POS]; + mac_secret.data = &session->key.session_ticket_key[MAC_SECRET_POS]; mac_secret.size = MAC_SECRET_SIZE; ret = digest_ticket(&mac_secret, ticket, ticket->mac); if (ret < 0) { @@ -293,20 +272,9 @@ session_ticket_recv_params(gnutls_session_t session, const uint8_t * data, size_t _data_size) { ssize_t data_size = _data_size; - session_ticket_ext_st *priv = NULL; - gnutls_ext_priv_data_t epriv; int ret; - ret = - _gnutls_hello_ext_get_priv(session, - GNUTLS_EXTENSION_SESSION_TICKET, - &epriv); - if (ret < 0) { - return 0; - } - priv = epriv; - - if (!priv->session_ticket_enable) + if (!session->internals.session_ticket_enable) return 0; if (session->security_parameters.entity == GNUTLS_SERVER) { @@ -315,7 +283,7 @@ session_ticket_recv_params(gnutls_session_t session, /* The client requested a new session ticket. */ if (data_size == 0) { - priv->session_ticket_renew = 1; + session->internals.session_ticket_renew = 1; return 0; } @@ -333,9 +301,9 @@ session_ticket_recv_params(gnutls_session_t session, /* If the key name of the ticket does not match the one that we hold, issue a new ticket. */ if (memcmp - (ticket.key_name, &priv->key[NAME_POS], + (ticket.key_name, &session->key.session_ticket_key[NAME_POS], KEY_NAME_SIZE)) { - priv->session_ticket_renew = 1; + session->internals.session_ticket_renew = 1; return 0; } @@ -364,19 +332,19 @@ session_ticket_recv_params(gnutls_session_t session, memcpy(ticket.encrypted_state, encrypted_state, ticket.encrypted_state_len); - ret = decrypt_ticket(session, priv, &ticket); + ret = decrypt_ticket(session, &ticket); gnutls_free(ticket.encrypted_state); ticket.encrypted_state = NULL; if (ret < 0) { - priv->session_ticket_renew = 1; + session->internals.session_ticket_renew = 1; return 0; } } else { /* Client */ if (data_size == 0) { - priv->session_ticket_renew = 1; + session->internals.session_ticket_renew = 1; return 0; } } @@ -402,11 +370,11 @@ session_ticket_send_params(gnutls_session_t session, if (ret >= 0) priv = epriv; - if (priv == NULL || !priv->session_ticket_enable) + if (priv == NULL || !session->internals.session_ticket_enable) return 0; if (session->security_parameters.entity == GNUTLS_SERVER) { - if (priv && priv->session_ticket_renew) { + if (priv && session->internals.session_ticket_renew) { return GNUTLS_E_INT_RET_0; } } else { @@ -422,7 +390,7 @@ session_ticket_send_params(gnutls_session_t session, return GNUTLS_E_INT_RET_0; /* previous data had session tickets disabled. Don't advertize. Ignore. */ - if (!priv->session_ticket_enable) + if (!session->internals.session_ticket_enable) return 0; if (priv->session_ticket_len > 0) { @@ -458,7 +426,6 @@ session_ticket_pack(gnutls_ext_priv_data_t epriv, gnutls_buffer_st * ps) BUFFER_APPEND_PFX4(ps, priv->session_ticket, priv->session_ticket_len); - BUFFER_APPEND_NUM(ps, priv->session_ticket_enable); return 0; } @@ -480,7 +447,6 @@ session_ticket_unpack(gnutls_buffer_st * ps, gnutls_ext_priv_data_t * _priv) BUFFER_POP_DATUM(ps, &ticket); priv->session_ticket = ticket.data; priv->session_ticket_len = ticket.size; - BUFFER_POP_NUM(ps, priv->session_ticket_enable); epriv = priv; *_priv = epriv; @@ -546,25 +512,12 @@ int gnutls_session_ticket_key_generate(gnutls_datum_t * key) **/ int gnutls_session_ticket_enable_client(gnutls_session_t session) { - session_ticket_ext_st *priv = NULL; - gnutls_ext_priv_data_t epriv; - if (!session) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } - priv = gnutls_calloc(1, sizeof(*priv)); - if (priv == NULL) { - gnutls_assert(); - return GNUTLS_E_MEMORY_ERROR; - } - priv->session_ticket_enable = 1; - epriv = priv; - - _gnutls_hello_ext_set_priv(session, - GNUTLS_EXTENSION_SESSION_TICKET, - epriv); + session->internals.session_ticket_enable = 1; return 0; } @@ -588,31 +541,21 @@ int gnutls_session_ticket_enable_server(gnutls_session_t session, const gnutls_datum_t * key) { - session_ticket_ext_st *priv = NULL; - gnutls_ext_priv_data_t epriv; - if (!session || !key || key->size != SESSION_KEY_SIZE) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } - priv = gnutls_calloc(1, sizeof(*priv)); - if (priv == NULL) { - gnutls_assert(); - return GNUTLS_E_MEMORY_ERROR; - } - epriv = priv; - - memcpy(&priv->key, key->data, key->size); - priv->session_ticket_enable = 1; - - _gnutls_hello_ext_set_priv(session, - GNUTLS_EXTENSION_SESSION_TICKET, - epriv); + memcpy(session->key.session_ticket_key, key->data, key->size); + session->internals.session_ticket_enable = 1; + session->internals.tls13_session_ticket_renew = 1; return 0; } +/* + * Return zero if session tickets haven't been enabled. + */ int _gnutls_send_new_session_ticket(gnutls_session_t session, int again) { mbuffer_st *bufel = NULL; @@ -621,20 +564,12 @@ int _gnutls_send_new_session_ticket(gnutls_session_t session, int again) int ret; struct ticket_st ticket; uint16_t ticket_len; - session_ticket_ext_st *priv = NULL; - gnutls_ext_priv_data_t epriv; uint16_t epoch_saved = session->security_parameters.epoch_write; if (again == 0) { - ret = - _gnutls_hello_ext_get_priv(session, - GNUTLS_EXTENSION_SESSION_TICKET, - &epriv); - if (ret < 0) + if (!session->internals.session_ticket_enable) return 0; - priv = epriv; - - if (!priv->session_ticket_renew) + if (!session->internals.session_ticket_renew) return 0; /* XXX: Temporarily set write algorithms to be used. @@ -653,7 +588,7 @@ int _gnutls_send_new_session_ticket(gnutls_session_t session, int again) session->security_parameters.epoch_write = session->security_parameters.epoch_next; - ret = encrypt_ticket(session, priv, &ticket); + ret = encrypt_ticket(session, &ticket); session->security_parameters.epoch_write = epoch_saved; if (ret < 0) { gnutls_assert(); @@ -706,6 +641,9 @@ int _gnutls_send_new_session_ticket(gnutls_session_t session, int again) GNUTLS_HANDSHAKE_NEW_SESSION_TICKET); } +/* + * Return zero if session ticets haven't been enabled. + */ int _gnutls_recv_new_session_ticket(gnutls_session_t session) { uint8_t *p; @@ -716,17 +654,9 @@ int _gnutls_recv_new_session_ticket(gnutls_session_t session) session_ticket_ext_st *priv = NULL; gnutls_ext_priv_data_t epriv; - ret = - _gnutls_hello_ext_get_priv(session, - GNUTLS_EXTENSION_SESSION_TICKET, - &epriv); - if (ret < 0) { - gnutls_assert(); + if (!session->internals.session_ticket_enable) return 0; - } - priv = epriv; - - if (!priv->session_ticket_renew) + if (!session->internals.session_ticket_renew) return 0; /* This is the last flight and peer cannot be sure @@ -764,6 +694,16 @@ int _gnutls_recv_new_session_ticket(gnutls_session_t session) DECR_LENGTH_COM(data_size, ticket_len, ret = GNUTLS_E_UNEXPECTED_PACKET_LENGTH; goto error); + + ret = + _gnutls_hello_ext_get_priv(session, + GNUTLS_EXTENSION_SESSION_TICKET, + &epriv); + if (ret < 0) { + gnutls_assert(); + return 0; + } + priv = epriv; priv->session_ticket = gnutls_realloc_fast(priv->session_ticket, ticket_len); if (!priv->session_ticket) { @@ -789,6 +729,11 @@ int _gnutls_recv_new_session_ticket(gnutls_session_t session) } ret = 0; + epriv = priv; + _gnutls_hello_ext_set_priv(session, + GNUTLS_EXTENSION_SESSION_TICKET, + epriv); + error: _gnutls_buffer_clear(&buf); diff --git a/lib/ext/session_ticket.h b/lib/ext/session_ticket.h index c00c3f6b7e..e744dd687b 100644 --- a/lib/ext/session_ticket.h +++ b/lib/ext/session_ticket.h @@ -25,9 +25,26 @@ #include <hello_ext.h> +#define KEY_NAME_SIZE 16 +#define IV_SIZE 16 +#define MAC_SIZE 20 /* HMAC-SHA1 */ + +struct ticket_st { + uint8_t key_name[KEY_NAME_SIZE]; + uint8_t IV[IV_SIZE]; + uint8_t *encrypted_state; + uint16_t encrypted_state_len; + uint8_t mac[MAC_SIZE]; +}; + extern const hello_ext_entry_st ext_mod_session_ticket; int _gnutls_send_new_session_ticket(gnutls_session_t session, int again); int _gnutls_recv_new_session_ticket(gnutls_session_t session); +int _gnutls_encrypt_session_ticket(gnutls_session_t session, + struct ticket_st *ticket); +int _gnutls_decrypt_session_ticket(gnutls_session_t session, + struct ticket_st *ticket); + #endif diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 4770684857..d660447299 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -257,6 +257,7 @@ typedef enum handshake_state_t { STATE0 = 0, STATE1, STATE2, STATE90=90, STATE91, STATE92, STATE93, STATE100=100, STATE101, STATE102, STATE103, STATE104, STATE105, STATE106, STATE107, STATE108, STATE109, STATE110, + STATE111, /* new session ticket */ STATE150 /* key update */ } handshake_state_t; @@ -473,6 +474,13 @@ 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 */ + /* + * This is the resumption master secret from the original connection. + * Used for session resumption. + */ + gnutls_datum_t ap_rms_original; + gnutls_mac_algorithm_t kdf_original; /* KDF of the original connection */ } tls13; /* tls1.3 */ /* Folow the SSL3.0 and TLS1.2 key exchanges */ @@ -510,6 +518,18 @@ struct gnutls_key_st { /* TLS pre-master key; applies to 1.2 and 1.3 */ gnutls_datum_t key; +#ifdef ENABLE_SESSION_TICKETS + /* The key to encrypt and decrypt session tickets */ +#define KEY_NAME_SIZE 16 +#define CIPHER_KEY_SIZE 32 +#define MAC_SECRET_SIZE 16 +#define SESSION_KEY_SIZE (KEY_NAME_SIZE+CIPHER_KEY_SIZE+MAC_SECRET_SIZE) +#define NAME_POS (0) +#define KEY_POS (KEY_NAME_SIZE) +#define MAC_SECRET_POS (KEY_NAME_SIZE+CIPHER_KEY_SIZE) + uint8_t session_ticket_key[SESSION_KEY_SIZE]; +#endif + /* this is used to hold the peers authentication data */ /* auth_info_t structures SHOULD NOT contain malloced @@ -1287,6 +1307,13 @@ typedef struct { /* the ciphersuite received in HRR */ uint8_t hrr_cs[2]; + int session_ticket_enable; + int session_ticket_renew; + + void *tls13_ticket; + unsigned tls13_ticket_len; + int tls13_session_ticket_renew; + /* 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 e39484a46b..63c80ef0e0 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); @@ -185,6 +185,18 @@ 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_server_finished_len, + session->key.proto.tls13.temp_secret, + session->key.proto.tls13.ap_rms); + if (ret < 0) + return gnutls_assert_val(ret); + + _gnutls_nss_keylog_write(session, "RESUMPTION_MASTER_SECRET", + session->key.proto.tls13.ap_rms, + session->security_parameters.prf->output_size); + _gnutls_epoch_bump(session); ret = _gnutls_epoch_dup(session); if (ret < 0) @@ -352,6 +364,7 @@ _gnutls13_recv_async_handshake(gnutls_session_t session, gnutls_buffer_st *buf) int ret; size_t handshake_header_size = HANDSHAKE_HEADER_SIZE(session); size_t length; + struct tls13_nst_st ticket; if (buf->length < handshake_header_size) { gnutls_assert(); @@ -399,9 +412,18 @@ _gnutls13_recv_async_handshake(gnutls_session_t session, gnutls_buffer_st *buf) if (session->security_parameters.entity != GNUTLS_CLIENT) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); - ret = _gnutls13_recv_session_ticket(session, buf); + memset(&ticket, 0, sizeof(struct tls13_nst_st)); + ret = _gnutls13_recv_session_ticket(session, buf, &ticket); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls13_session_ticket_set(session, &ticket, + session->key.proto.tls13.ap_rms, + session->key.proto.tls13.temp_secret_size, + session->security_parameters.prf); if (ret < 0) return gnutls_assert_val(ret); + memset(&ticket, 0, sizeof(struct tls13_nst_st)); break; default: gnutls_assert(); diff --git a/lib/handshake.c b/lib/handshake.c index 43492124a5..aa5b46fee9 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 @@ -2474,6 +2475,24 @@ int gnutls_handshake(gnutls_session_t session) return ret; } + /* + * Handshake is complete, and application traffic keys are available. + * Now we send a TLS 1.3 NewSessionTicket if requested. + */ + if (session->security_parameters.entity == GNUTLS_SERVER && + get_version(session)->tls13_sem && + session->internals.tls13_session_ticket_renew) { + ret = _gnutls13_send_session_ticket(session, AGAIN(STATE111)); + STATE = STATE111; + + if (ret < 0) + return gnutls_assert_val(ret); + + if (ret > 0) + session->internals.ticket_sent = 1; + STATE = STATE0; + } + /* clear handshake buffer */ if (session->security_parameters.entity != GNUTLS_CLIENT || !(session->internals.flags & GNUTLS_ENABLE_FALSE_START) || diff --git a/lib/handshake.h b/lib/handshake.h index 109f1247c8..f317c2dc7f 100644 --- a/lib/handshake.h +++ b/lib/handshake.h @@ -121,6 +121,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/session.c b/lib/session.c index 6c2671d70e..41018349ea 100644 --- a/lib/session.c +++ b/lib/session.c @@ -228,6 +228,7 @@ gnutls_session_set_data(gnutls_session_t session, } session->internals.resumption_requested = 1; + session->internals.session_ticket_enable = 1; if (session->internals.resumption_data.data != NULL) gnutls_free(session->internals.resumption_data.data); diff --git a/lib/session_pack.c b/lib/session_pack.c index 977110595b..8087dfc376 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,11 @@ 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, + const mac_entry_st * prf); +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 +156,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 +271,16 @@ _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, + session->internals.resumed_security_parameters.prf); + if (ret < 0) { + gnutls_assert(); + goto error; + } + } + ret = _gnutls_hello_ext_unpack(session, &sb); if (ret < 0) { gnutls_assert(); @@ -270,7 +295,106 @@ _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 + * 4 byte the ticket nonce length + * x bytes the ticket nonce + * 4 bytes the ticket length + * x bytes the ticket + * 4 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; + struct tls13_nst_st ticket; + + length_pos = ps->length; + BUFFER_APPEND_NUM(ps, 0); + + ret = _gnutls13_session_ticket_peek(session, &ticket); + if (likely(ret > 0)) { + BUFFER_APPEND_NUM(ps, ticket.ticket_timestamp); + length += 4; + BUFFER_APPEND_NUM(ps, ticket.ticket_lifetime); + length += 4; + BUFFER_APPEND_NUM(ps, ticket.ticket_age_add); + length += 4; + BUFFER_APPEND_PFX4(ps, + ticket.ticket_nonce.data, + ticket.ticket_nonce.size); + length += (4 + ticket.ticket_nonce.size); + BUFFER_APPEND_PFX4(ps, + ticket.ticket.data, + ticket.ticket.size); + length += (4 + ticket.ticket.size); + BUFFER_APPEND_PFX4(ps, + ticket.rms.data, + ticket.rms.size); + length += (4 + ticket.rms.size); + + /* Overwrite the length field */ + _gnutls_write_uint32(length, ps->data + length_pos); + } else if (ret < 0) { + return gnutls_assert_val(ret); + } + + return ret; +} + +static int +tls13_unpack_security_parameters(gnutls_session_t session, gnutls_buffer_st *ps, + const mac_entry_st *prf) +{ + int ret = 0; + uint32_t ttl_len; + struct tls13_nst_st ticket; + gnutls_datum_t rms = { + .data = NULL, + .size = 0 + }; + + memset(&ticket, 0, sizeof(struct tls13_nst_st)); + + BUFFER_POP_NUM(ps, ttl_len); + + if (ttl_len > 0) { + BUFFER_POP_NUM(ps, ticket.ticket_timestamp); + BUFFER_POP_NUM(ps, ticket.ticket_lifetime); + BUFFER_POP_NUM(ps, ticket.ticket_age_add); + BUFFER_POP_DATUM(ps, &ticket.ticket_nonce); + BUFFER_POP_DATUM(ps, &ticket.ticket); + BUFFER_POP_DATUM(ps, &rms); + + if (unlikely(prf == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + ret = _gnutls13_session_ticket_set(session, + &ticket, + rms.data, rms.size, + prf); + if (ret < 0) + gnutls_assert(); + } +error: + _gnutls13_session_ticket_destroy(&ticket); + _gnutls_free_datum(&rms); + return ret; +} /* Format: * 1 byte the credentials type diff --git a/lib/state.c b/lib/state.c index 057bf10d74..9fdeb4dce6 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? */ @@ -444,6 +445,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); diff --git a/lib/tls13/session_ticket.c b/lib/tls13/session_ticket.c index 3dbec9260f..dadf6fffc1 100644 --- a/lib/tls13/session_ticket.c +++ b/lib/tls13/session_ticket.c @@ -19,46 +19,475 @@ * along with this program. If not, see <http://www.gnu.org/licenses/> * */ - +#include <time.h> #include "gnutls_int.h" #include "errors.h" #include "extv.h" #include "handshake.h" +#include "mbuffers.h" +#include "ext/pre_shared_key.h" +#include "ext/session_ticket.h" #include "tls13/session_ticket.h" #include "auth/cert.h" +#define IV_SIZE 16 +#define BLOCK_SIZE 16 +#define CIPHER GNUTLS_CIPHER_AES_256_CBC + +#define MAC_ALGO GNUTLS_MAC_SHA1 + static int parse_nst_extension(void *ctx, uint16_t tls_id, const uint8_t *data, int data_size); -int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *buf) +static int pack_ticket(struct tls13_ticket_data *ticket_data, + gnutls_datum_t *state) +{ + unsigned char *p; + + state->size = sizeof(uint16_t) + + sizeof(uint32_t) * 3 + + ticket_data->rms_len + + sizeof(uint32_t) + + ticket_data->ticket_nonce_len; + state->data = gnutls_malloc(state->size); + if (!state->data) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + p = state->data; + + _gnutls_write_uint16(ticket_data->kdf_id, p); + p += sizeof(uint16_t); + _gnutls_write_uint32(ticket_data->ticket_age_add, p); + p += sizeof(uint32_t); + _gnutls_write_uint32(ticket_data->ticket_lifetime, p); + p += sizeof(uint32_t); + _gnutls_write_uint32(ticket_data->rms_len, p); + p += sizeof(uint32_t); + memcpy(p, ticket_data->rms, ticket_data->rms_len); + p += ticket_data->rms_len; + _gnutls_write_uint32(ticket_data->ticket_nonce_len, p); + p += sizeof(uint32_t); + memcpy(p, ticket_data->ticket_nonce, ticket_data->ticket_nonce_len); + + return 0; +} + +static int unpack_ticket(gnutls_datum_t *state, + struct tls13_ticket_data *data) +{ + int retval, kdf; + uint32_t ticket_age_add, ticket_lifetime; + gnutls_datum_t rms = { NULL, 0 }, ticket_nonce = { NULL, 0 }; + unsigned char *p; + size_t expected_len = sizeof(uint16_t) + sizeof(uint32_t) * 3; + + if (unlikely(state == NULL || data == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (state->size <= expected_len) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + p = state->data; + + kdf = _gnutls_read_uint16(p); + p += sizeof(uint16_t); + + /* Check if the MAC ID we got is valid */ + if (_gnutls_mac_to_entry(kdf) == NULL) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + /* Read the ticket age add and the ticket lifetime */ + ticket_age_add = _gnutls_read_uint32(p); + p += sizeof(uint32_t); + + ticket_lifetime = _gnutls_read_uint32(p); + p += sizeof(uint32_t); + + /* + * Check if the whole ticket is large enough, + * and read the resumption master secret + */ + rms.size = (unsigned) _gnutls_read_uint32(p); + p += sizeof(uint32_t); + + expected_len += rms.size; + + if (state->size < expected_len) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + rms.data = gnutls_malloc(rms.size); + if (!rms.data) { + retval = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto error; + } + memcpy(rms.data, p, rms.size); + p += rms.size; + + /* Read the ticket nonce */ + ticket_nonce.size = (unsigned) _gnutls_read_uint32(p); + p += sizeof(uint32_t); + + expected_len += ticket_nonce.size; + + if (state->size < ticket_nonce.size) { + retval = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + goto error; + } + + ticket_nonce.data = gnutls_malloc(ticket_nonce.size); + if (!ticket_nonce.data) { + retval = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto error; + } + memcpy(ticket_nonce.data, p, ticket_nonce.size); + + /* No errors - Now return all the data to the caller */ + data->kdf_id = (gnutls_mac_algorithm_t) kdf; + data->rms = rms.data; + data->rms_len = rms.size; + data->ticket_nonce = ticket_nonce.data; + data->ticket_nonce_len = ticket_nonce.size; + data->ticket_age_add = ticket_age_add; + data->ticket_lifetime = ticket_lifetime; + + return 0; + +error: + _gnutls_free_datum(&rms); + _gnutls_free_datum(&ticket_nonce); + return retval; +} + +static int digest_ticket(const gnutls_datum_t *key, struct ticket_st *ticket, + uint8_t *digest) +{ + int ret; + mac_hd_st digest_hd; + uint16_t length16; + + ret = _gnutls_mac_init(&digest_hd, mac_to_entry(MAC_ALGO), + key->data, key->size); + if (ret < 0) + return gnutls_assert_val(ret); + + length16 = _gnutls_conv_uint16(ticket->encrypted_state_len); + + _gnutls_mac(&digest_hd, ticket->key_name, KEY_NAME_SIZE); + _gnutls_mac(&digest_hd, ticket->IV, IV_SIZE); + _gnutls_mac(&digest_hd, &length16, 2); + _gnutls_mac(&digest_hd, ticket->encrypted_state, ticket->encrypted_state_len); + + _gnutls_mac_deinit(&digest_hd, digest); + + return 0; +} + +static int encrypt_ticket(gnutls_session_t session, + gnutls_datum_t *state, struct ticket_st *enc_ticket) +{ + int ret; + cipher_hd_st cipher_hd; + gnutls_datum_t key, IV; + gnutls_datum_t encrypted_state = {NULL, 0}; + gnutls_datum_t mac_secret; + uint8_t iv[IV_SIZE]; + uint32_t t; + + memset(&cipher_hd, 0, sizeof(cipher_hd_st)); + + key.data = (void *) &session->key.session_ticket_key[KEY_POS]; + key.size = CIPHER_KEY_SIZE; + IV.data = iv; + IV.size = IV_SIZE; + + /* Generate an IV */ + t = gnutls_time(0); + memcpy(iv, &t, 4); + ret = gnutls_rnd(GNUTLS_RND_NONCE, iv + 4, IV_SIZE + 4); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Encrypt */ + ret = _gnutls_cipher_init(&cipher_hd, cipher_to_entry(CIPHER), + &key, &IV, 1); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + encrypted_state.size = ((state->size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE; + encrypted_state.data = gnutls_calloc(1, encrypted_state.size); + if (!encrypted_state.data) { + gnutls_assert(); + ret = GNUTLS_E_MEMORY_ERROR; + goto cleanup; + } + memcpy(encrypted_state.data, state->data, state->size); + + ret = _gnutls_cipher_encrypt(&cipher_hd, + encrypted_state.data, encrypted_state.size); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Compute the MAC */ + memcpy(enc_ticket->key_name, &session->key.session_ticket_key[NAME_POS], KEY_NAME_SIZE); + memcpy(enc_ticket->IV, IV.data, IV.size); + enc_ticket->encrypted_state = encrypted_state.data; + enc_ticket->encrypted_state_len = encrypted_state.size; + + mac_secret.data = &session->key.session_ticket_key[MAC_SECRET_POS]; + mac_secret.size = MAC_SECRET_SIZE; + + ret = digest_ticket(&mac_secret, enc_ticket, enc_ticket->mac); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + encrypted_state.data = NULL; + ret = 0; + +cleanup: + _gnutls_cipher_deinit(&cipher_hd); + _gnutls_free_datum(&encrypted_state); + return ret; +} + +static int decrypt_ticket(gnutls_session_t session, + struct ticket_st *enc_ticket, gnutls_datum_t *state) +{ + int ret; + cipher_hd_st cipher_hd; + gnutls_datum_t key, IV, mac_secret; + uint8_t cmac[MAC_SIZE]; + + /* Check the integrity of ticket */ + mac_secret.data = (void *) &session->key.session_ticket_key[MAC_SECRET_POS]; + mac_secret.size = MAC_SECRET_SIZE; + ret = digest_ticket(&mac_secret, enc_ticket, cmac); + if (ret < 0) + return gnutls_assert_val(ret); + + if (memcmp(enc_ticket->mac, cmac, MAC_SIZE)) + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + if (enc_ticket->encrypted_state_len % BLOCK_SIZE != 0) + return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); + + key.data = (void *) &session->key.session_ticket_key[KEY_POS]; + key.size = CIPHER_KEY_SIZE; + IV.data = enc_ticket->IV; + IV.size = IV_SIZE; + + ret = _gnutls_cipher_init(&cipher_hd, cipher_to_entry(CIPHER), + &key, &IV, 0); + if (ret < 0) + return gnutls_assert_val(ret); + + ret = _gnutls_cipher_decrypt(&cipher_hd, + enc_ticket->encrypted_state, enc_ticket->encrypted_state_len); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + state->data = enc_ticket->encrypted_state; + state->size = enc_ticket->encrypted_state_len; + + ret = 0; +cleanup: + _gnutls_cipher_deinit(&cipher_hd); + return ret; +} + +static int generate_session_ticket(gnutls_session_t session, struct tls13_nst_st *ticket) +{ + int ret; + unsigned char *p; + gnutls_datum_t state = { NULL, 0 }; + struct ticket_st encrypted_ticket; + /* This is the resumption master secret */ + const uint8_t *rms = session->key.proto.tls13.ap_rms; + unsigned rms_len = session->key.proto.tls13.temp_secret_size; + gnutls_mac_algorithm_t kdf_id; + struct tls13_ticket_data tdata; + + memset(&encrypted_ticket, 0, sizeof(struct ticket_st)); + + /* Generate a random 128-bit ticket nonce */ + ticket->ticket_nonce.size = 16; + ticket->ticket_nonce.data = gnutls_malloc(ticket->ticket_nonce.size); + if (ticket->ticket_nonce.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + if ((ret = gnutls_rnd(GNUTLS_RND_NONCE, + ticket->ticket_nonce.data, ticket->ticket_nonce.size)) < 0) + return gnutls_assert_val(ret); + if ((ret = gnutls_rnd(GNUTLS_RND_RANDOM, &ticket->ticket_age_add, sizeof(uint32_t))) < 0) + return gnutls_assert_val(ret); + + /* Set ticket lifetime to 1 day (86400 seconds) */ + ticket->ticket_lifetime = 86400; + kdf_id = session->security_parameters.cs->prf; + + /* Encrypt the ticket and place the result in ticket->ticket */ + tdata.rms = (uint8_t *) rms; + tdata.rms_len = rms_len; + tdata.ticket_nonce = ticket->ticket_nonce.data; + tdata.ticket_nonce_len = ticket->ticket_nonce.size; + tdata.kdf_id = kdf_id; + tdata.ticket_age_add = ticket->ticket_age_add; + tdata.ticket_lifetime = ticket->ticket_lifetime; + ret = pack_ticket(&tdata, &state); + if (ret < 0) + return gnutls_assert_val(ret); + ret = encrypt_ticket(session, &state, &encrypted_ticket); + if (ret < 0) + return gnutls_assert_val(ret); + + ticket->ticket.size = KEY_NAME_SIZE + + IV_SIZE + + MAC_SIZE + + sizeof(uint16_t) + + encrypted_ticket.encrypted_state_len; + ticket->ticket.data = gnutls_calloc(1, ticket->ticket.size); + if (!ticket->ticket.data) { + _gnutls_free_datum(&ticket->ticket_nonce); + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + } + + p = ticket->ticket.data; + + memcpy(p, encrypted_ticket.key_name, KEY_NAME_SIZE); + p += KEY_NAME_SIZE; + memcpy(p, encrypted_ticket.IV, IV_SIZE); + p += IV_SIZE; + _gnutls_write_uint16(encrypted_ticket.encrypted_state_len, p); + p += 2; + memcpy(p, encrypted_ticket.encrypted_state, + encrypted_ticket.encrypted_state_len); + p += encrypted_ticket.encrypted_state_len; + gnutls_free(encrypted_ticket.encrypted_state); + memcpy(p, encrypted_ticket.mac, MAC_SIZE); + p += MAC_SIZE; + + return 0; +} + +int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned again) +{ + int ret; + mbuffer_st *bufel = NULL; + struct tls13_nst_st ticket; + uint16_t ticket_len; + uint8_t *data = NULL, *p; + int data_size = 0; + + /* 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.session_ticket_enable) + return 0; + + memset(&ticket, 0, sizeof(struct tls13_nst_st)); + + if (again == 0) { + // ret = _gnutls13_session_ticket_get(session, &ticket); + ret = generate_session_ticket(session, &ticket); + if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + + ticket_len = sizeof(uint32_t) + /* ticket_lifetime */ + sizeof(uint32_t) + /* ticket_age_add */ + ticket.ticket_nonce.size + 1 + + ticket.ticket.size + 2 + + 2; /* extensions length */ + bufel = _gnutls_handshake_alloc(session, ticket_len); + if (bufel == NULL) { + ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto cleanup; + } + + data = _mbuffer_get_udata_ptr(bufel); + p = data; + + /* append ticket_lifetime */ + _gnutls_write_uint32(ticket.ticket_lifetime, p); + p += 4; + /* append ticket_age_add */ + _gnutls_write_uint32(ticket.ticket_age_add, p); + p += 4; + /* append ticket_nonce */ + *p = (uint8_t) ticket.ticket_nonce.size; + p++; + memcpy(p, ticket.ticket_nonce.data, ticket.ticket_nonce.size); + p += ticket.ticket_nonce.size; + /* append ticket */ + _gnutls_write_uint16(ticket.ticket.size, p); + p += 2; + memcpy(p, ticket.ticket.data, ticket.ticket.size); + p += ticket.ticket.size; + + /* No extensions */ + _gnutls_write_uint16(0, p); + p += 2; + + data_size = p - data; + } + +cleanup: + if (ret == 0) { + /* No errors - send the packet */ + ret = _gnutls_send_handshake(session, data_size ? bufel : NULL, + GNUTLS_HANDSHAKE_NEW_SESSION_TICKET); + } else { + _mbuffer_xfree(&bufel); + } + + if (ticket.ticket.data) + gnutls_free(ticket.ticket.data); + if (ticket.ticket_nonce.data) + gnutls_free(ticket.ticket_nonce.data); + + return ret; +} + +int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *buf, + struct tls13_nst_st *ticket) { int ret; - size_t val; - gnutls_datum_t nonce; - gnutls_datum_t ticket; + + if (unlikely(buf == NULL || ticket == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); _gnutls_handshake_log("HSK[%p]: parsing session ticket message\n", session); /* ticket_lifetime */ - ret = _gnutls_buffer_pop_prefix32(buf, &val, 0); + ret = _gnutls_buffer_pop_prefix32(buf, (size_t *) &ticket->ticket_lifetime, 0); if (ret < 0) { gnutls_assert(); goto cleanup; } /* ticket_age_add */ - ret = _gnutls_buffer_pop_prefix32(buf, &val, 0); + ret = _gnutls_buffer_pop_prefix32(buf, (size_t *) &ticket->ticket_age_add, 0); if (ret < 0) { gnutls_assert(); goto cleanup; } - ret = _gnutls_buffer_pop_datum_prefix8(buf, &nonce); + ret = _gnutls_buffer_pop_datum_prefix8(buf, &ticket->ticket_nonce); if (ret < 0) { gnutls_assert(); goto cleanup; } - ret = _gnutls_buffer_pop_datum_prefix16(buf, &ticket); + ret = _gnutls_buffer_pop_datum_prefix16(buf, &ticket->ticket); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -70,14 +499,281 @@ int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *bu goto cleanup; } + /* Set the ticket timestamp */ + ticket->ticket_timestamp = time(NULL); + ret = 0; cleanup: return ret; } +/* + * Parse the ticket in 'ticket' 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, + struct tls13_ticket_data *ticket_data) +{ + int ret; + const unsigned char *p = data->data; + ssize_t data_size = data->size; + struct ticket_st ticket; + gnutls_datum_t decrypted; + + if (unlikely(data == NULL || ticket_data == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + if (data_size == 0) + return 0; + + memset(&ticket, 0, sizeof(struct ticket_st)); + memset(&decrypted, 0, sizeof(gnutls_datum_t)); + + /* Parse the ticket fields. + * Format: + * Key name + * IV + * data length + * encrypted data + * MAC + */ + DECR_LEN(data_size, KEY_NAME_SIZE); + memcpy(ticket.key_name, p, KEY_NAME_SIZE); + p += KEY_NAME_SIZE; + + if (memcmp(ticket.key_name, + &session->key.session_ticket_key[NAME_POS], + KEY_NAME_SIZE)) { + session->internals.session_ticket_renew = 1; + return 0; + } + + DECR_LEN(data_size, IV_SIZE); + memcpy(ticket.IV, p, IV_SIZE); + p += IV_SIZE; + + DECR_LEN(data_size, 2); + ticket.encrypted_state_len = _gnutls_read_uint16(p); + p += 2; + + ticket.encrypted_state = gnutls_malloc(ticket.encrypted_state_len); + if (!ticket.encrypted_state) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + DECR_LEN(data_size, ticket.encrypted_state_len); + memcpy(ticket.encrypted_state, p, ticket.encrypted_state_len); + p += ticket.encrypted_state_len; + + DECR_LEN(data_size, MAC_SIZE); + memcpy(ticket.mac, p, MAC_SIZE); + + /* Check MAC and decrypt ticket */ + ret = decrypt_ticket(session, &ticket, &decrypted); + /* Do not free, as the ticket is decrypted in-place */ +// gnutls_free(ticket.encrypted_state); + + if (ret < 0) { + session->internals.session_ticket_renew = 1; + return 0; + } + + /* Return ticket parameters */ + ret = unpack_ticket(&decrypted, ticket_data); + if (ret < 0) { + session->internals.session_ticket_renew = 1; + return 0; + } + + return decrypted.size; +} + static int parse_nst_extension(void *ctx, uint16_t tls_id, const uint8_t *data, int data_size) { /* ignore all extensions */ return 0; } + +static int copy_ticket(struct tls13_nst_st *src, struct tls13_nst_st *dst) +{ + dst->ticket_lifetime = src->ticket_lifetime; + dst->ticket_age_add = src->ticket_age_add; + dst->ticket_timestamp = src->ticket_timestamp; + + if (src->ticket_nonce.size > 0) { + dst->ticket_nonce.data = gnutls_malloc(src->ticket_nonce.size); + if (dst->ticket_nonce.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + dst->ticket_nonce.size = src->ticket_nonce.size; + memcpy(dst->ticket_nonce.data, src->ticket_nonce.data, src->ticket_nonce.size); + } + + if (src->ticket.size > 0) { + dst->ticket.data = gnutls_malloc(src->ticket.size); + if (dst->ticket.data == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + dst->ticket.size = src->ticket.size; + memcpy(dst->ticket.data, src->ticket.data, src->ticket.size); + } + + return 0; +} + +void _gnutls13_session_ticket_destroy(struct tls13_nst_st *ticket) +{ + if (ticket) { + _gnutls_free_datum(&ticket->ticket); + _gnutls_free_datum(&ticket->ticket_nonce); + _gnutls_free_datum(&ticket->rms); + memset(ticket, 0, sizeof(struct tls13_nst_st)); + } +} + +void _gnutls13_ticket_data_destroy(struct tls13_ticket_data *ticket_data) +{ + if (ticket_data) { + gnutls_free(ticket_data->rms); + gnutls_free(ticket_data->ticket_nonce); + } +} + +/* + * Stores a session ticket locally. + * All the fields of the ticket are copied, so they can safely be freed when this function returns. + * The resumption master secret ('rms') is also copied. + */ +int _gnutls13_session_ticket_set(gnutls_session_t session, + struct tls13_nst_st *ticket, + const uint8_t *rms, size_t rms_size, + const mac_entry_st *prf) +{ + gnutls_datum_t *rms_original; + struct tls13_nst_st *src, *dst; + + if (unlikely(ticket == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + if (unlikely(rms == NULL || rms_size == 0)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (session->internals.tls13_ticket) + gnutls_free(session->internals.tls13_ticket); + + session->internals.tls13_ticket = gnutls_calloc(1, sizeof(struct tls13_nst_st)); + if (session->internals.tls13_ticket == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + /* Copy the ticket */ + src = ticket; + dst = session->internals.tls13_ticket; + + if (copy_ticket(src, dst) < 0) { + gnutls_assert(); + goto cleanup; + } + + /* Copy the resumption master secret ('rms') for this session */ + rms_original = &session->key.proto.tls13.ap_rms_original; + rms_original->data = gnutls_calloc(1, rms_size); + if (!rms_original->data) { + gnutls_assert(); + goto cleanup; + } + rms_original->size = rms_size; + memcpy(rms_original->data, rms, rms_size); + + /* Set the KDF of the original connection */ + session->key.proto.tls13.kdf_original = prf->id; + + session->internals.tls13_ticket_len = sizeof(struct tls13_nst_st); + return 0; + +cleanup: + _gnutls13_session_ticket_destroy((struct tls13_nst_st *) session->internals.tls13_ticket); + session->internals.tls13_ticket = NULL; + session->internals.tls13_ticket_len = 0; + return GNUTLS_E_MEMORY_ERROR; +} + +/* + * Copy the locally stored session ticket to 'ticket'. + * The fields of 'ticket' are copied not referenced, so they can be safely freed + * after this function returns. + */ +int _gnutls13_session_ticket_get(gnutls_session_t session, struct tls13_nst_st *ticket) +{ + int ret = 0; + struct tls13_nst_st *src, *dst; + + if (unlikely(ticket == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (session->internals.tls13_ticket_len > 0) { + src = session->internals.tls13_ticket; + dst = ticket; + + if (!src) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if ((ret = copy_ticket(src, dst)) < 0) { + _gnutls13_session_ticket_destroy(ticket); + return gnutls_assert_val(ret); + } + + if ((ret = _gnutls_set_datum(&dst->rms, + session->key.proto.tls13.ap_rms_original.data, + session->key.proto.tls13.ap_rms_original.size)) < 0) { + gnutls_assert(); + goto error; + } + + ret = session->internals.tls13_ticket_len + dst->rms.size; + } + + return ret; + +error: + _gnutls13_session_ticket_destroy(dst); + return ret; +} + +/* + * Behaves just like _gnutls13_session_ticket_get(), but does not copy the data (except the scalars). + * It just references the pointers. + */ +int _gnutls13_session_ticket_peek(gnutls_session_t session, struct tls13_nst_st *ticket) +{ + int ret = 0; + struct tls13_nst_st *src, *dst; + + src = session->internals.tls13_ticket; + dst = ticket; + + if (session->internals.tls13_ticket_len > 0) { + if (!src) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (dst) { + memcpy(dst, src, sizeof(struct tls13_nst_st)); + memcpy(&dst->rms, &session->key.proto.tls13.ap_rms_original, sizeof(gnutls_datum_t)); + } + + ret = session->internals.tls13_ticket_len + session->key.proto.tls13.ap_rms_original.size; + } + + return ret; +} + +void _gnutls13_session_ticket_unset(gnutls_session_t session) +{ + if (session->internals.tls13_ticket_len > 0) { + _gnutls13_session_ticket_destroy(session->internals.tls13_ticket); + gnutls_free(session->internals.tls13_ticket); + session->internals.tls13_ticket = NULL; + session->internals.tls13_ticket_len = 0; + } + + if (session->key.proto.tls13.ap_rms_original.data) { + gnutls_free(session->key.proto.tls13.ap_rms_original.data); + session->key.proto.tls13.ap_rms_original.size = 0; + } +} diff --git a/lib/tls13/session_ticket.h b/lib/tls13/session_ticket.h index 1c31589a26..6710d96bff 100644 --- a/lib/tls13/session_ticket.h +++ b/lib/tls13/session_ticket.h @@ -19,5 +19,48 @@ * 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); +struct tls13_nst_st { + time_t ticket_timestamp; + uint32_t ticket_lifetime; + uint32_t ticket_age_add; + gnutls_datum_t ticket_nonce; + gnutls_datum_t ticket; + gnutls_datum_t rms; +}; + +struct tls13_ticket_data { + uint8_t *rms; + unsigned rms_len; + uint8_t *ticket_nonce; + unsigned ticket_nonce_len; + uint32_t ticket_age_add; + uint32_t ticket_lifetime; + gnutls_mac_algorithm_t kdf_id; +}; + +int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned again); +int _gnutls13_recv_session_ticket(gnutls_session_t session, + gnutls_buffer_st *buf, struct tls13_nst_st *ticket); + +int _gnutls13_unpack_session_ticket(gnutls_session_t session, + gnutls_datum_t *data, + struct tls13_ticket_data *ticket_data); + +int _gnutls13_session_ticket_set(gnutls_session_t session, + struct tls13_nst_st *ticket, + const uint8_t *rms, size_t rms_size, + const mac_entry_st *prf); +int _gnutls13_session_ticket_get(gnutls_session_t session, + struct tls13_nst_st *ticket); +int _gnutls13_session_ticket_peek(gnutls_session_t session, + struct tls13_nst_st *ticket); + +void _gnutls13_session_ticket_destroy(struct tls13_nst_st *ticket); +void _gnutls13_session_ticket_unset(gnutls_session_t session); + +void _gnutls13_ticket_data_destroy(struct tls13_ticket_data *ticket_data); + +#endif |