diff options
author | Ander Juaristi <a@juaristi.eus> | 2018-01-01 23:03:38 +0100 |
---|---|---|
committer | Ander Juaristi <a@juaristi.eus> | 2018-01-05 15:59:13 +0100 |
commit | 338e3e0bfcdc3bad7daead57e1b971120c79de74 (patch) | |
tree | 1d631f85104627bd558319c939785c36db0a2293 | |
parent | 557a0763b18dd548197cbb50948d4e09a719e0d3 (diff) | |
download | gnutls-338e3e0bfcdc3bad7daead57e1b971120c79de74.tar.gz |
TLS 1.3: Session resumption
Signed-off-by: Ander Juaristi <a@juaristi.eus>
-rw-r--r-- | lib/ext/pre_shared_key.c | 17 | ||||
-rw-r--r-- | lib/ext/session_ticket.c | 4 | ||||
-rw-r--r-- | lib/gnutls_int.h | 3 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 2 | ||||
-rw-r--r-- | lib/tls13/session_ticket.c | 385 | ||||
-rw-r--r-- | lib/tls13/session_ticket.h | 2 |
6 files changed, 366 insertions, 47 deletions
diff --git a/lib/ext/pre_shared_key.c b/lib/ext/pre_shared_key.c index de350b5cf8..1e06786a99 100644 --- a/lib/ext/pre_shared_key.c +++ b/lib/ext/pre_shared_key.c @@ -453,8 +453,7 @@ static int server_recv_params(gnutls_session_t session, * First check if this is an out-of-band PSK. * If it's not, try to decrypt it, as it might be a session ticket. */ - if (psk.ob_ticket_age == 0 && - username.size == psk.identity.size && + if (username.size == psk.identity.size && safe_memcmp(username.data, psk.identity.data, psk.identity.size) == 0) { psk_index = psk.selected_index; break; @@ -494,16 +493,10 @@ static int server_recv_params(gnutls_session_t session, if (binder_recvd.size == 0) return gnutls_assert_val(GNUTLS_E_INSUFFICIENT_CREDENTIALS); - if (_gnutls_hello_ext_get_priv(session, - GNUTLS_EXTENSION_SESSION_TICKET, - &epriv) == 0) { - priv = epriv; - } else { - priv = gnutls_malloc(sizeof(psk_ext_st)); - if (!priv) { - ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); - goto cleanup; - } + priv = gnutls_malloc(sizeof(psk_ext_st)); + if (!priv) { + ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto cleanup; } /* Get full ClientHello */ diff --git a/lib/ext/session_ticket.c b/lib/ext/session_ticket.c index 8575c54634..4320e83aa9 100644 --- a/lib/ext/session_ticket.c +++ b/lib/ext/session_ticket.c @@ -71,10 +71,6 @@ const hello_ext_entry_st ext_mod_session_ticket = { .cannot_be_overriden = 1 }; -#define NAME_POS (0) -#define KEY_POS (KEY_NAME_SIZE) -#define MAC_SECRET_POS (KEY_NAME_SIZE+CIPHER_KEY_SIZE) - typedef struct { uint8_t *session_ticket; int session_ticket_len; diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 6cb56d4054..44b8607dfc 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -514,6 +514,9 @@ struct gnutls_key_st { #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 diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c index a03ea0ff6b..64dbe2b22a 100644 --- a/lib/handshake-tls13.c +++ b/lib/handshake-tls13.c @@ -339,7 +339,7 @@ int _gnutls13_handshake_server(gnutls_session_t session) IMED_RET("generate app keys", ret, 0); /* fall through */ case STATE111: - ret = _gnutls13_send_session_ticket(session); + ret = _gnutls13_send_session_ticket(session, AGAIN(STATE111)); STATE = STATE0; IMED_RET("send new session ticket", ret, 0); break; diff --git a/lib/tls13/session_ticket.c b/lib/tls13/session_ticket.c index 61d6745348..375c87239f 100644 --- a/lib/tls13/session_ticket.c +++ b/lib/tls13/session_ticket.c @@ -24,56 +24,352 @@ #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_send_session_ticket(gnutls_session_t session) +static int pack_ticket(struct tls13_nst_st *ticket, + const uint8_t *rms, unsigned rms_size, + gnutls_datum_t *state) +{ + unsigned char *p; + + state->size = sizeof(uint16_t) + + sizeof(uint32_t) + + rms_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->kdf_id, p); + p += sizeof(uint16_t); + _gnutls_write_uint32(rms_size, p); + p += sizeof(uint32_t); + memcpy(p, rms, rms_size); + + return 0; +} + +static int unpack_ticket(gnutls_datum_t *state, + gnutls_mac_algorithm_t *kdf_id, + gnutls_datum_t *rms) +{ + unsigned rms_len; + unsigned char *p; + + if (unlikely(state == NULL || kdf_id == NULL || rms == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + p = state->data; + + *kdf_id = (gnutls_mac_algorithm_t) _gnutls_read_uint16(p); + p += sizeof(uint16_t); + + rms_len = (unsigned) _gnutls_read_uint32(p); + p += sizeof(uint32_t); + + rms->size = rms_len; + rms->data = gnutls_malloc(rms->size); + if (!rms->data) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + memcpy(rms->data, p, rms->size); + + return 0; +} + +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 = MAX_HASH_SIZE; + + 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; + ticket->kdf_id = session->security_parameters.cs->prf; + + /* Encrypt the ticket and place the result in ticket->ticket */ + ret = pack_ticket(ticket, rms, rms_len, &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; gnutls_buffer_st buf; 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; - ret = _gnutls13_session_ticket_get(session, &ticket); - if (ret > 0) { - if ((ret = _gnutls_buffer_init_handshake_mbuffer(&buf)) < 0) { - gnutls_assert(); - goto error; - } + memset(&buf, 0, sizeof(gnutls_buffer_st)); + memset(&ticket, 0, sizeof(struct tls13_nst_st)); - if ((ret = _gnutls_buffer_append_prefix(buf, 32, ticket.ticket_lifetime)) < 0) { - gnutls_assert(); - goto error; - } - if ((ret = _gnutls_buffer_append_prefix(buf, 32, ticket.ticket_age_add)) < 0) { - gnutls_assert(); - goto error; - } - if ((ret = _gnutls_buffer_append_data_prefix(buf, 8, - ticket.ticket_nonce.data, ticket.ticket_nonce.size)) < 0) { + if (again == 0) { + /* FIXME ticket must be generated */ + // ret = _gnutls13_session_ticket_get(session, &ticket); + ret = generate_session_ticket(session, &ticket); + if (ret < 0) { gnutls_assert(); - goto error; + goto cleanup; } - if ((ret = _gnutls_buffer_append_data_prefix(buf, 16, - ticket.ticket.data, ticket.ticket.size)) < 0) { - gnutls_assert(); - goto error; + + ticket_len = sizeof(uint32_t) + /* ticket_lifetime */ + sizeof(uint32_t) + /* ticket_age_add */ + ticket.ticket_nonce.size + + ticket.ticket.size; + bufel = _gnutls_handshake_alloc(session, ticket_len + 2); + if (bufel == NULL) { + ret = gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + goto cleanup; } - bufel = _gnutls_buffer_to_mbuffer(buf); - return _gnutls_send_handshake(session, bufel, - GNUTLS_HANDSHAKE_NEW_SESSION_TICKET); + data = _mbuffer_get_udata_ptr(bufel); + p = data; + + _gnutls_write_uint16(ticket_len, p); + p += 2; + _gnutls_write_uint32(ticket.ticket_lifetime, p); + p += 4; + _gnutls_write_uint32(ticket.ticket_age_add, p); + p += 4; + _gnutls_write_uint16(ticket.ticket_nonce.size, p); + p += 2; + memcpy(p, ticket.ticket_nonce.data, ticket.ticket_nonce.size); + p += ticket.ticket_nonce.size; + _gnutls_write_uint16(ticket.ticket.size, p); + p += 2; + memcpy(p, ticket.ticket.data, ticket.ticket.size); + p += ticket.ticket.size; + + data_size = p - data; + session->internals.ticket_sent = 1; } - return 0; + return _gnutls_send_handshake(session, data_size ? bufel : NULL, + GNUTLS_HANDSHAKE_NEW_SESSION_TICKET); + + /* FIXME should free bufel and the rest of the buffers */ -error: +cleanup: + if (ticket.ticket.data) + gnutls_free(ticket.ticket.data); + if (ticket.ticket_nonce.data) + gnutls_free(ticket.ticket_nonce.data); _gnutls_buffer_clear(&buf); return ret; } @@ -83,17 +379,20 @@ int _gnutls13_recv_session_ticket(gnutls_session_t session, gnutls_buffer_st *bu { int ret; + 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, &ticket->ticket_lifetime, 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, &ticket->ticket_age_add, 0); + ret = _gnutls_buffer_pop_prefix32(buf, (size_t *) &ticket->ticket_age_add, 0); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -123,6 +422,34 @@ cleanup: return ret; } +int _gnutls13_recv_session_ticket2(gnutls_session_t session, gnutls_datum_t *dat, + struct tls13_nst_st *ticket) +{ + int ret; + gnutls_buffer_st buf; + + if (unlikely(dat == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + _gnutls_buffer_init(&buf); + _gnutls_buffer_append_data(&buf, dat->data, dat->size); + ret = _gnutls13_recv_session_ticket(session, &buf, ticket); + _gnutls_buffer_clear(&buf); + + if (ret < 0) + gnutls_assert(); + return ret; +} + +int _gnutls13_parse_session_ticket(struct tls13_nst_st *ticket) +{ + int ret; + + /* TODO implement this */ + + return 0; +} + static int parse_nst_extension(void *ctx, uint16_t tls_id, const uint8_t *data, int data_size) { /* ignore all extensions */ diff --git a/lib/tls13/session_ticket.h b/lib/tls13/session_ticket.h index 92ee0feaa2..91c0c9839b 100644 --- a/lib/tls13/session_ticket.h +++ b/lib/tls13/session_ticket.h @@ -30,7 +30,7 @@ struct tls13_nst_st { gnutls_mac_algorithm_t kdf_id; }; -int _gnutls13_send_session_ticket(gnutls_session_t session); +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); #endif |