diff options
author | Ander Juaristi <a@juaristi.eus> | 2018-09-18 09:40:20 +0200 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2018-09-19 14:50:45 +0200 |
commit | 553f9aaaa92d8bbc027af7f46bdd5355a2c0abc0 (patch) | |
tree | 2e9bd26e4e29da8dc29e1d01db26c8f83b7da2d6 | |
parent | d165c2a37f7d072cc88db88ec97f057a9ac6e4aa (diff) | |
download | gnutls-553f9aaaa92d8bbc027af7f46bdd5355a2c0abc0.tar.gz |
Added session ticket key rotation with TOTP
This introduces session ticket key rotation on server side. The
key set with gnutls_session_ticket_enable_server() is used as a
master key to generate time-based keys for tickets. The rotation
relates to the gnutls_db_set_cache_expiration() period.
Resolves #184
Signed-off-by: Ander Juaristi <a@juaristi.eus>
-rw-r--r-- | NEWS | 5 | ||||
-rw-r--r-- | lib/Makefile.am | 4 | ||||
-rw-r--r-- | lib/ext/session_ticket.c | 133 | ||||
-rw-r--r-- | lib/gnutls_int.h | 36 | ||||
-rw-r--r-- | lib/libgnutls.map | 2 | ||||
-rw-r--r-- | lib/state.c | 3 | ||||
-rw-r--r-- | lib/stek.c | 357 | ||||
-rw-r--r-- | lib/stek.h | 38 | ||||
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/resume-with-previous-stek.c | 252 | ||||
-rw-r--r-- | tests/resume-with-stek-expiration.c | 322 |
11 files changed, 1084 insertions, 71 deletions
@@ -17,6 +17,11 @@ See the end for copying conditions. certificate is presented by client and the gnutls_init() flag GNUTLS_ENABLE_EARLY_START is specified. +** libgnutls: Added session ticket key rotation on server side with TOTP. + The key set with gnutls_session_ticket_enable_server() is used as a + master key to generate time-based keys for tickets. The rotation + relates to the gnutls_db_set_cache_expiration() period. + ** libgnutls: The 'record size limit' extension is added and preferred to the 'max record size' extension when possible. diff --git a/lib/Makefile.am b/lib/Makefile.am index 11de0a05bf..e101ec68f2 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -80,7 +80,7 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c gthreads.h handshake-tls system-keys.h urls.c urls.h prf.c auto-verify.c dh-session.c \ cert-session.c handshake-checks.c dtls-sw.c dh-primes.c openpgp_compat.c \ crypto-selftests.c crypto-selftests-pk.c secrets.c extv.c extv.h \ - hello_ext_lib.c hello_ext_lib.h ocsp-api.c + hello_ext_lib.c hello_ext_lib.h ocsp-api.c stek.c if WINDOWS COBJECTS += system/keys-win.c @@ -122,7 +122,7 @@ HFILES = abstract_int.h debug.h cipher.h \ srp.h auth/srp_kx.h auth/srp_passwd.h \ file.h supplemental.h crypto.h random.h system.h\ locks.h mbuffers.h ecc.h pin.h fips.h \ - priority_options.h secrets.h + priority_options.h secrets.h stek.h if ENABLE_PKCS11 HFILES += pkcs11_int.h pkcs11x.h diff --git a/lib/ext/session_ticket.c b/lib/ext/session_ticket.c index d2a15e5eb3..177135e642 100644 --- a/lib/ext/session_ticket.c +++ b/lib/ext/session_ticket.c @@ -34,16 +34,9 @@ #include <hello_ext.h> #include <constate.h> #include <dtls.h> +#include "stek.h" #include "db.h" -/* They are restricted by TICKET_CIPHER_KEY_SIZE and TICKET_MAC_SECRET_SIZE */ -#define CIPHER GNUTLS_CIPHER_AES_256_CBC -#define IV_SIZE 16 -#define BLOCK_SIZE 16 - -#define MAC_ALGO GNUTLS_MAC_SHA1 -#define MAC_SIZE 20 /* HMAC-SHA1 */ - static int session_ticket_recv_params(gnutls_session_t session, const uint8_t * data, size_t data_size); @@ -70,23 +63,11 @@ const hello_ext_entry_st ext_mod_session_ticket = { .cannot_be_overriden = 1 }; -#define NAME_POS (0) -#define KEY_POS (TICKET_KEY_NAME_SIZE) -#define MAC_SECRET_POS (TICKET_KEY_NAME_SIZE+TICKET_CIPHER_KEY_SIZE) - typedef struct { uint8_t *session_ticket; int session_ticket_len; } session_ticket_ext_st; -struct ticket_st { - uint8_t key_name[TICKET_KEY_NAME_SIZE]; - uint8_t IV[IV_SIZE]; - uint8_t *encrypted_state; - uint16_t encrypted_state_len; - uint8_t mac[MAC_SIZE]; -}; - static void deinit_ticket(struct ticket_st *ticket) { @@ -111,9 +92,9 @@ unpack_ticket(const gnutls_datum_t *ticket_data, struct ticket_st *ticket) memcpy(ticket->key_name, data, TICKET_KEY_NAME_SIZE); data += TICKET_KEY_NAME_SIZE; - DECR_LEN(data_size, IV_SIZE); - memcpy(ticket->IV, data, IV_SIZE); - data += IV_SIZE; + DECR_LEN(data_size, TICKET_IV_SIZE); + memcpy(ticket->IV, data, TICKET_IV_SIZE); + data += TICKET_IV_SIZE; DECR_LEN(data_size, 2); ticket->encrypted_state_len = _gnutls_read_uint16(data); @@ -124,8 +105,8 @@ unpack_ticket(const gnutls_datum_t *ticket_data, struct ticket_st *ticket) DECR_LEN(data_size, ticket->encrypted_state_len); data += ticket->encrypted_state_len; - DECR_LEN(data_size, MAC_SIZE); - memcpy(ticket->mac, data, MAC_SIZE); + DECR_LEN(data_size, TICKET_MAC_SIZE); + memcpy(ticket->mac, data, TICKET_MAC_SIZE); ticket->encrypted_state = gnutls_malloc(ticket->encrypted_state_len); @@ -149,8 +130,8 @@ pack_ticket(const struct ticket_st *ticket, gnutls_datum_t *ticket_data) memcpy(p, ticket->key_name, TICKET_KEY_NAME_SIZE); p += TICKET_KEY_NAME_SIZE; - memcpy(p, ticket->IV, IV_SIZE); - p += IV_SIZE; + memcpy(p, ticket->IV, TICKET_IV_SIZE); + p += TICKET_IV_SIZE; _gnutls_write_uint16(ticket->encrypted_state_len, p); p += 2; @@ -158,7 +139,7 @@ pack_ticket(const struct ticket_st *ticket, gnutls_datum_t *ticket_data) memcpy(p, ticket->encrypted_state, ticket->encrypted_state_len); p += ticket->encrypted_state_len; - memcpy(p, ticket->mac, MAC_SIZE); + memcpy(p, ticket->mac, TICKET_MAC_SIZE); } static @@ -169,7 +150,7 @@ int digest_ticket(const gnutls_datum_t * key, struct ticket_st *ticket, uint16_t length16; int ret; - ret = _gnutls_mac_init(&digest_hd, mac_to_entry(MAC_ALGO), + ret = _gnutls_mac_init(&digest_hd, mac_to_entry(TICKET_MAC_ALGO), key->data, key->size); if (ret < 0) { gnutls_assert(); @@ -177,7 +158,7 @@ int digest_ticket(const gnutls_datum_t * key, struct ticket_st *ticket, } _gnutls_mac(&digest_hd, ticket->key_name, TICKET_KEY_NAME_SIZE); - _gnutls_mac(&digest_hd, ticket->IV, IV_SIZE); + _gnutls_mac(&digest_hd, ticket->IV, TICKET_IV_SIZE); length16 = _gnutls_conv_uint16(ticket->encrypted_state_len); _gnutls_mac(&digest_hd, &length16, 2); _gnutls_mac(&digest_hd, ticket->encrypted_state, @@ -193,50 +174,60 @@ _gnutls_decrypt_session_ticket(gnutls_session_t session, gnutls_datum_t *state) { cipher_hd_st cipher_hd; - gnutls_datum_t key, IV, mac_secret; - uint8_t cmac[MAC_SIZE]; + gnutls_datum_t IV; + gnutls_datum_t stek_key_name, stek_cipher_key, stek_mac_key; + uint8_t cmac[TICKET_MAC_SIZE]; struct ticket_st ticket; int ret; - /* If the key name of the ticket does not match the one that we - hold, issue a new ticket. */ - if (ticket_data->size < TICKET_KEY_NAME_SIZE || - memcmp(ticket_data->data, &session->key.session_ticket_key[NAME_POS], - TICKET_KEY_NAME_SIZE)) + /* callers must have that checked */ + assert(!(session->internals.flags & GNUTLS_NO_TICKETS)); + + /* Retrieve ticket decryption keys */ + if (_gnutls_get_session_ticket_decryption_key(session, + ticket_data, + &stek_key_name, + &stek_mac_key, + &stek_cipher_key) < 0) return gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); ret = unpack_ticket(ticket_data, &ticket); if (ret < 0) return ret; + /* If the key name of the ticket does not match the one that is currently active, + issue a new ticket. */ + if (memcmp + (ticket.key_name, stek_key_name.data, + stek_key_name.size)) { + ret = GNUTLS_E_DECRYPTION_FAILED; + goto cleanup; + } + /* Check the integrity of ticket */ - mac_secret.data = (void *) &session->key.session_ticket_key[MAC_SECRET_POS]; - mac_secret.size = TICKET_MAC_SECRET_SIZE; - ret = digest_ticket(&mac_secret, &ticket, cmac); + ret = digest_ticket(&stek_mac_key, &ticket, cmac); if (ret < 0) { gnutls_assert(); goto cleanup; } - if (memcmp(ticket.mac, cmac, MAC_SIZE)) { + if (memcmp(ticket.mac, cmac, TICKET_MAC_SIZE)) { ret = gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); goto cleanup; } - if (ticket.encrypted_state_len % BLOCK_SIZE != 0) { + if (ticket.encrypted_state_len % TICKET_BLOCK_SIZE != 0) { ret = gnutls_assert_val(GNUTLS_E_DECRYPTION_FAILED); goto cleanup; } /* Decrypt encrypted_state */ - key.data = (void *) &session->key.session_ticket_key[KEY_POS]; - key.size = TICKET_CIPHER_KEY_SIZE; IV.data = ticket.IV; - IV.size = IV_SIZE; + IV.size = TICKET_IV_SIZE; ret = _gnutls_cipher_init(&cipher_hd, - cipher_to_entry(CIPHER), - &key, &IV, 0); + cipher_to_entry(TICKET_CIPHER), + &stek_cipher_key, &IV, 0); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -272,32 +263,39 @@ _gnutls_encrypt_session_ticket(gnutls_session_t session, gnutls_datum_t *ticket_data) { cipher_hd_st cipher_hd; - gnutls_datum_t key, IV; + gnutls_datum_t IV; gnutls_datum_t encrypted_state = {NULL,0}; - uint8_t iv[IV_SIZE]; - gnutls_datum_t mac_secret; + uint8_t iv[TICKET_IV_SIZE]; + gnutls_datum_t stek_cipher_key, stek_mac_key, stek_key_name; struct ticket_st ticket; int ret; - encrypted_state.size = ((state->size + BLOCK_SIZE - 1) / BLOCK_SIZE) * BLOCK_SIZE; - ticket_data->size = TICKET_KEY_NAME_SIZE + IV_SIZE + 2 + - encrypted_state.size + MAC_SIZE; + encrypted_state.size = ((state->size + TICKET_BLOCK_SIZE - 1) / TICKET_BLOCK_SIZE) * TICKET_BLOCK_SIZE; + ticket_data->size = TICKET_KEY_NAME_SIZE + TICKET_IV_SIZE + 2 + + encrypted_state.size + TICKET_MAC_SIZE; ticket_data->data = gnutls_calloc(1, ticket_data->size); if (!ticket_data->data) { gnutls_assert(); ret = GNUTLS_E_MEMORY_ERROR; goto cleanup; } - encrypted_state.data = ticket_data->data + TICKET_KEY_NAME_SIZE + IV_SIZE + 2; + encrypted_state.data = ticket_data->data + TICKET_KEY_NAME_SIZE + TICKET_IV_SIZE + 2; memcpy(encrypted_state.data, state->data, state->size); + /* Retrieve ticket encryption keys */ + if (_gnutls_get_session_ticket_encryption_key(session, + &stek_key_name, + &stek_mac_key, + &stek_cipher_key) < 0) { + ret = GNUTLS_E_ENCRYPTION_FAILED; + goto cleanup; + } + /* Encrypt state */ - key.data = (void *) &session->key.session_ticket_key[KEY_POS]; - key.size = TICKET_CIPHER_KEY_SIZE; IV.data = iv; - IV.size = IV_SIZE; + IV.size = TICKET_IV_SIZE; - ret = gnutls_rnd(GNUTLS_RND_NONCE, iv, IV_SIZE); + ret = gnutls_rnd(GNUTLS_RND_NONCE, iv, TICKET_IV_SIZE); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -305,8 +303,8 @@ _gnutls_encrypt_session_ticket(gnutls_session_t session, ret = _gnutls_cipher_init(&cipher_hd, - cipher_to_entry(CIPHER), - &key, &IV, 1); + cipher_to_entry(TICKET_CIPHER), + &stek_cipher_key, &IV, 1); if (ret < 0) { gnutls_assert(); goto cleanup; @@ -321,14 +319,12 @@ _gnutls_encrypt_session_ticket(gnutls_session_t session, /* Fill the ticket structure to compute MAC. */ - memcpy(ticket.key_name, &session->key.session_ticket_key[NAME_POS], TICKET_KEY_NAME_SIZE); + memcpy(ticket.key_name, stek_key_name.data, stek_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 = &session->key.session_ticket_key[MAC_SECRET_POS]; - mac_secret.size = TICKET_MAC_SECRET_SIZE; - ret = digest_ticket(&mac_secret, &ticket, ticket.mac); + ret = digest_ticket(&stek_mac_key, &ticket, ticket.mac); if (ret < 0) { gnutls_assert(); goto cleanup2; @@ -606,12 +602,17 @@ int gnutls_session_ticket_enable_server(gnutls_session_t session, const gnutls_datum_t * key) { - if (!session || !key || key->size != TICKET_MASTER_KEY_SIZE) { + int ret; + + if (!session || !key || key->size != TICKET_MASTER_KEY_SIZE || !key->data) { gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } - memcpy(session->key.session_ticket_key, key->data, key->size); + ret = _gnutls_initialize_session_ticket_key_rotation(session, key); + if (ret < 0) + return gnutls_assert_val(ret); + session->internals.flags &= ~GNUTLS_NO_TICKETS; return 0; diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index f35d6df5f5..2574f4e420 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -157,6 +157,7 @@ typedef struct { /* expire time for resuming sessions */ #define DEFAULT_EXPIRE_TIME 21600 +#define STEK_ROTATION_PERIOD_PRODUCT 3 #define DEFAULT_HANDSHAKE_TIMEOUT_MS 40*1000 /* The EC group to be used when the extension @@ -490,6 +491,22 @@ typedef struct auth_cred_st { #define TICKET_CIPHER_KEY_SIZE 32 #define TICKET_MAC_SECRET_SIZE 16 +/* These are restricted by TICKET_CIPHER_KEY_SIZE and TICKET_MAC_SECRET_SIZE */ +#define TICKET_CIPHER GNUTLS_CIPHER_AES_256_CBC +#define TICKET_IV_SIZE 16 +#define TICKET_BLOCK_SIZE 16 + +#define TICKET_MAC_ALGO GNUTLS_MAC_SHA1 +#define TICKET_MAC_SIZE 20 /* HMAC-SHA1 */ + +struct ticket_st { + uint8_t key_name[TICKET_KEY_NAME_SIZE]; + uint8_t IV[TICKET_IV_SIZE]; + uint8_t *encrypted_state; + uint16_t encrypted_state_len; + uint8_t mac[TICKET_MAC_SIZE]; +}; + struct binder_data_st { const struct mac_entry_st *prf; /* non-null if this struct is set */ gnutls_datum_t psk; @@ -501,6 +518,10 @@ struct binder_data_st { uint8_t resumption; /* whether it is a resumption binder */ }; +typedef void (* gnutls_stek_rotation_callback_t) (const gnutls_datum_t *prev_key, + const gnutls_datum_t *new_key, + uint64_t t); + struct gnutls_key_st { struct { /* These are kept outside the TLS1.3 union as they are * negotiated via extension, even before protocol is negotiated */ @@ -572,8 +593,13 @@ struct gnutls_key_st { /* TLS pre-master key; applies to 1.2 and 1.3 */ gnutls_datum_t key; - /* The key to encrypt and decrypt session tickets */ - uint8_t session_ticket_key[TICKET_MASTER_KEY_SIZE]; + uint8_t + /* The key to encrypt and decrypt session tickets */ + session_ticket_key[TICKET_MASTER_KEY_SIZE], + /* Static buffer for the previous key, whenever we need it */ + previous_ticket_key[TICKET_MASTER_KEY_SIZE], + /* Initial key supplied by the caller */ + initial_stek[TICKET_MASTER_KEY_SIZE]; /* this is used to hold the peers authentication data */ @@ -586,6 +612,12 @@ struct gnutls_key_st { int auth_info_size; /* needed in order to store to db for restoring */ auth_cred_st *cred; /* used to specify keys/certificates etc */ + + struct { + uint64_t last_result; + uint8_t was_rotated; + gnutls_stek_rotation_callback_t cb; + } totp; }; typedef struct gnutls_key_st gnutls_key_st; diff --git a/lib/libgnutls.map b/lib/libgnutls.map index dd77025f07..041fda7b80 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1324,4 +1324,6 @@ GNUTLS_PRIVATE_3_4 { # Internal symbols needed by tests/name-constraints-merge: _gnutls_x509_name_constraints_merge; _gnutls_server_name_set_raw; + # Internal symbols needed by tests/suite/resume-with-stek-expiration + _gnutls_set_session_ticket_key_rotation_callback; } GNUTLS_3_4; diff --git a/lib/state.c b/lib/state.c index 58db8f9a32..86edd3c4c4 100644 --- a/lib/state.c +++ b/lib/state.c @@ -491,6 +491,9 @@ int gnutls_init(gnutls_session_t * session, unsigned int flags) (*session)->internals.expire_time = DEFAULT_EXPIRE_TIME; + /* Ticket key rotation - set the default X to 3 times the ticket expire time */ + (*session)->key.totp.last_result = 0; + gnutls_handshake_set_max_packet_length((*session), MAX_HANDSHAKE_PACKET_SIZE); diff --git a/lib/stek.c b/lib/stek.c new file mode 100644 index 0000000000..2248e51b6f --- /dev/null +++ b/lib/stek.c @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2018 Free Software Foundation, Inc. + * + * Author: Ander Juaristi + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +#include "gnutls_int.h" +#include "stek.h" + +#define NAME_POS (0) +#define KEY_POS (TICKET_KEY_NAME_SIZE) +#define MAC_SECRET_POS (TICKET_KEY_NAME_SIZE+TICKET_CIPHER_KEY_SIZE) + +static int totp_sha3(gnutls_session_t session, + uint64_t t, + const gnutls_datum_t *secret, + uint8_t out[TICKET_MASTER_KEY_SIZE]) +{ + int retval; + uint8_t t_be[8]; + digest_hd_st hd; + /* + * We choose SHA3-512 because it outputs 64 bytes, + * just the same length as the ticket key. + */ + const gnutls_digest_algorithm_t algo = GNUTLS_DIG_SHA3_512; +#if TICKET_MASTER_KEY_SIZE != 64 +#error "TICKET_MASTER_KEY_SIZE must be 64 bytes" +#endif + + if (unlikely(secret == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if ((retval = _gnutls_hash_init(&hd, hash_to_entry(algo))) < 0) + return gnutls_assert_val(retval); + + _gnutls_write_uint64(t, t_be); + + if ((retval = _gnutls_hash(&hd, t_be, sizeof(t_be))) < 0) + return gnutls_assert_val(retval); + if ((retval = _gnutls_hash(&hd, secret->data, secret->size)) < 0) + return gnutls_assert_val(retval); + + _gnutls_hash_deinit(&hd, out); + return GNUTLS_E_SUCCESS; +} + +static uint64_t T(gnutls_session_t session, time_t t) +{ + uint64_t numeral = t; + unsigned int x = session->internals.expire_time * STEK_ROTATION_PERIOD_PRODUCT; + + if (numeral <= 0) + return 0; + + return (numeral / x); +} + +static int64_t totp_next(gnutls_session_t session) +{ + time_t t; + uint64_t result; + + t = gnutls_time(NULL); + if (unlikely(t == (time_t) -1)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + result = T(session, t); + if (result == 0) + return 0; + + if (result == session->key.totp.last_result) + return 0; + + return result; +} + +static int64_t totp_previous(gnutls_session_t session) +{ + uint64_t result; + + if (session->key.totp.last_result == 0) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + if (!session->key.totp.was_rotated) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + result = session->key.totp.last_result - 1; + if (result == 0) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + return result; +} + +static void call_rotation_callback(gnutls_session_t session, + uint8_t key[TICKET_MASTER_KEY_SIZE], uint64_t t) +{ + gnutls_datum_t prev_key, new_key; + + if (session->key.totp.cb) { + new_key.data = key; + new_key.size = TICKET_MASTER_KEY_SIZE; + prev_key.data = session->key.session_ticket_key; + prev_key.size = TICKET_MASTER_KEY_SIZE; + + session->key.totp.cb(&prev_key, &new_key, t); + } +} + +static int rotate(gnutls_session_t session) +{ + int64_t t; + gnutls_datum_t secret; + uint8_t key[TICKET_MASTER_KEY_SIZE]; + + /* Do we need to calculate new totp? */ + t = totp_next(session); + if (t > 0) { + secret.data = session->key.initial_stek; + secret.size = TICKET_MASTER_KEY_SIZE; + + /* Generate next key */ + if (totp_sha3(session, t, &secret, key) < 0) { + gnutls_assert(); + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + } + + /* Replace old key with new one, and call callback if provided */ + call_rotation_callback(session, key, t); + session->key.totp.last_result = t; + memcpy(session->key.session_ticket_key, key, sizeof(key)); + + session->key.totp.was_rotated = 1; + } else if (t < 0) { + return gnutls_assert_val(t); + } + + return GNUTLS_E_SUCCESS; +} + +static int rotate_back_and_peek(gnutls_session_t session, + uint8_t key[TICKET_MASTER_KEY_SIZE]) +{ + int64_t t; + gnutls_datum_t secret; + + /* Get the previous TOTP */ + t = totp_previous(session); + if (t < 0) + return gnutls_assert_val(t); + + secret.data = session->key.initial_stek; + secret.size = TICKET_MASTER_KEY_SIZE; + + if (totp_sha3(session, t, &secret, key) < 0) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + return 0; +} + +/* + * _gnutls_get_session_ticket_encryption_key: + * @key_name: an empty datum that will receive the key name part of the STEK + * @mac_key: an empty datum that will receive the MAC key part of the STEK + * @enc_key: an empty datum that will receive the encryption key part of the STEK + * + * Get the currently active session ticket encryption key (STEK). + * + * The STEK is a 64-byte blob which is further divided in three parts, + * and this function requires the caller to supply three separate datums for each one. + * Though the caller might omit one or more of those if not interested in that part of the STEK. + * + * These are the three parts the STEK is divided in: + * + * - Key name: 16 bytes + * - Encryption key: 32 bytes + * - MAC key: 16 bytes + * + * This function will transparently rotate the key, if the time has come for that, + * before returning it to the caller. + */ +int _gnutls_get_session_ticket_encryption_key(gnutls_session_t session, + gnutls_datum_t *key_name, + gnutls_datum_t *mac_key, + gnutls_datum_t *enc_key) +{ + int retval; + + if (unlikely(session == NULL)) { + gnutls_assert(); + return GNUTLS_E_INTERNAL_ERROR; + } + + if ((retval = rotate(session)) < 0) + return gnutls_assert_val(retval); + + /* Copy key parts to user-supplied datums (if provided) */ + if (key_name) { + key_name->data = &session->key.session_ticket_key[NAME_POS]; + key_name->size = TICKET_KEY_NAME_SIZE; + } + if (mac_key) { + mac_key->data = &session->key.session_ticket_key[MAC_SECRET_POS]; + mac_key->size = TICKET_MAC_SECRET_SIZE; + } + if (enc_key) { + enc_key->data = &session->key.session_ticket_key[KEY_POS]; + enc_key->size = TICKET_CIPHER_KEY_SIZE; + } + + return retval; +} + +/* + * _gnutls_get_session_ticket_decryption_key: + * @ticket_data: the bytes of a session ticket that must be decrypted + * @key_name: an empty datum that will receive the key name part of the STEK + * @mac_key: an empty datum that will receive the MAC key part of the STEK + * @enc_key: an empty datum that will receive the encryption key part of the STEK + * + * Get the key (STEK) the given session ticket was encrypted with. + * + * As with its encryption counterpart (%_gnutls_get_session_ticket_encryption_key), + * this function will also transparently rotate + * the currently active STEK if time has come for that, and it also requires the different + * parts of the STEK to be obtained in different datums. + * + * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, or a negative error code, such as + * %GNUTLS_E_REQUSTED_DATA_NOT_AVAILABLE if no key could be found for the supplied ticket. + */ +int _gnutls_get_session_ticket_decryption_key(gnutls_session_t session, + const gnutls_datum_t *ticket_data, + gnutls_datum_t *key_name, + gnutls_datum_t *mac_key, + gnutls_datum_t *enc_key) +{ + int retval; + gnutls_datum_t key = { + .data = session->key.session_ticket_key, + .size = TICKET_MASTER_KEY_SIZE + }; + + if (unlikely(session == NULL || ticket_data == NULL || ticket_data->data == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (ticket_data->size < TICKET_KEY_NAME_SIZE) + return gnutls_assert_val(GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE); + + if ((retval = rotate(session)) < 0) + return gnutls_assert_val(retval); + + /* + * Is current key valid? + * We compare the first 16 bytes --> The key_name field. + */ + if (memcmp(ticket_data->data, + &key.data[NAME_POS], + TICKET_KEY_NAME_SIZE) == 0) + goto key_found; + + key.size = TICKET_MASTER_KEY_SIZE; + key.data = session->key.previous_ticket_key; + + /* + * Current key is not valid. + * Compute previous key and see if that matches. + */ + if ((retval = rotate_back_and_peek(session, key.data)) < 0) + return gnutls_assert_val(retval); + + if (memcmp(ticket_data->data, + &key.data[NAME_POS], + TICKET_KEY_NAME_SIZE) == 0) + goto key_found; + + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; + +key_found: + if (key_name) { + key_name->data = &key.data[NAME_POS]; + key_name->size = TICKET_KEY_NAME_SIZE; + } + if (mac_key) { + mac_key->data = &key.data[MAC_SECRET_POS]; + mac_key->size = TICKET_MAC_SECRET_SIZE; + } + if (enc_key) { + enc_key->data = &key.data[KEY_POS]; + enc_key->size = TICKET_CIPHER_KEY_SIZE; + } + + return GNUTLS_E_SUCCESS; +} + +/* + * _gnutls_initialize_session_ticket_key_rotation: + * @key: Initial session ticket key + * + * Initialize the session ticket key rotation. + * + * This function will not enable session ticket keys on the server side. That is done + * with the gnutls_session_ticket_enable_server() function. This function just initializes + * the internal state to support periodical rotation of the session ticket encryption key. + * + * Returns: %GNUTLS_E_SUCCESS (0) on success, or %GNUTLS_E_INVALID_REQUEST on error. + */ +int _gnutls_initialize_session_ticket_key_rotation(gnutls_session_t session, const gnutls_datum_t *key) +{ + if (unlikely(session == NULL || key == NULL)) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + if (session->key.totp.last_result == 0) { + int64_t t; + memcpy(session->key.initial_stek, key->data, key->size); + t = totp_next(session); + if (t < 0) + return gnutls_assert_val(t); + + session->key.totp.last_result = t; + session->key.totp.was_rotated = 0; + + return GNUTLS_E_SUCCESS; + } + + return GNUTLS_E_INVALID_REQUEST; +} + +/* + * _gnutls_set_session_ticket_key_rotation_callback: + * @cb: the callback function + * + * Set a callback function that will be invoked every time the session ticket key + * is rotated. + * + * The function will take as arguments the previous key, the new key and the time + * step value that caused the key to rotate. + * + */ +void _gnutls_set_session_ticket_key_rotation_callback(gnutls_session_t session, gnutls_stek_rotation_callback_t cb) +{ + if (session) + session->key.totp.cb = cb; +} diff --git a/lib/stek.h b/lib/stek.h new file mode 100644 index 0000000000..bec781edf0 --- /dev/null +++ b/lib/stek.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2018 Free Software Foundation, Inc. + * + * Author: Ander Juaristi + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +#include "gnutls_int.h" + +int _gnutls_get_session_ticket_encryption_key(gnutls_session_t session, + gnutls_datum_t *key_name, + gnutls_datum_t *mac_key, + gnutls_datum_t *enc_key); +int _gnutls_get_session_ticket_decryption_key(gnutls_session_t session, + const gnutls_datum_t *ticket_data, + gnutls_datum_t *key_name, + gnutls_datum_t *mac_key, + gnutls_datum_t *enc_key); + +void _gnutls_set_session_ticket_key_rotation_callback(gnutls_session_t session, + gnutls_stek_rotation_callback_t cb); + +int _gnutls_initialize_session_ticket_key_rotation(gnutls_session_t session, + const gnutls_datum_t *key); diff --git a/tests/Makefile.am b/tests/Makefile.am index 90e0fd71a1..d02d3d8d80 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -204,7 +204,8 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei ip-check mini-x509-ipaddr trust-store base64-raw random-art dhex509self \ dss-sig-val sign-pk-api tls-session-ext-override record-pad \ tls13-server-kx-neg gnutls_ext_raw_parse_dtls key-export-pkcs8 \ - null_retrieve_function tls-record-size-limit tls-crt_type-neg + null_retrieve_function tls-record-size-limit tls-crt_type-neg \ + resume-with-stek-expiration resume-with-previous-stek if HAVE_SECCOMP_TESTS ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp diff --git a/tests/resume-with-previous-stek.c b/tests/resume-with-previous-stek.c new file mode 100644 index 0000000000..ca59f7aa77 --- /dev/null +++ b/tests/resume-with-previous-stek.c @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2018 Free Software Foundation, Inc. + * + * Author: Ander Juaristi + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) +int main(int argc, char **argv) +{ + exit(77); +} +#else + +#include <stdint.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <gnutls/gnutls.h> +#include <assert.h> +#include "utils.h" +#include "cert-common.h" + +#define TICKET_EXPIRATION 1 /* seconds */ +#define TICKET_ROTATION_PERIOD 3 /* seconds */ + +unsigned num_stek_rotations; + +static void stek_rotation_callback(const gnutls_datum_t *prev_key, + const gnutls_datum_t *new_key, + uint64_t t) +{ + num_stek_rotations++; + success("STEK was rotated!\n"); +} + +static int client_handshake(gnutls_session_t session, gnutls_datum_t *session_data, + int resume) +{ + int ret; + + if (resume) { + if ((ret = gnutls_session_set_data(session, + session_data->data, + session_data->size)) < 0) { + fail("client: Could not get session data\n"); + } + } + + do { + ret = gnutls_handshake(session); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (ret < 0) { + fail("client: Handshake failed\n"); + } else { + success("client: Handshake was completed\n"); + } + + if (gnutls_session_is_resumed(session)) + fail("client: Session was resumed (but should not)\n"); + else + success("client: Success: Session was NOT resumed\n"); + + if (!resume) { + if ((ret = gnutls_session_get_data2(session, session_data)) < 0) { + fail("client: Could not get session data\n"); + } + } + + do { + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + return 0; +} + +static void client(int fd, int *resume, unsigned rounds, const char *prio) +{ + gnutls_session_t session; + gnutls_datum_t session_data; + gnutls_certificate_credentials_t clientx509cred = NULL; + + for (unsigned i = 0; i < rounds; i++) { + assert(gnutls_certificate_allocate_credentials(&clientx509cred)>=0); + + assert(gnutls_init(&session, GNUTLS_CLIENT)>=0); + assert(gnutls_priority_set_direct(session, prio, NULL)>=0); + + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, + clientx509cred); + + gnutls_transport_set_int(session, fd); + gnutls_handshake_set_timeout(session, 20 * 1000); + + sec_sleep(TICKET_ROTATION_PERIOD-1); + + /* Perform TLS handshake and obtain session ticket */ + if (client_handshake(session, &session_data, + resume[i]) < 0) + return; + + if (clientx509cred) { + gnutls_certificate_free_credentials(clientx509cred); + clientx509cred = NULL; + } + + gnutls_deinit(session); + } +} + +typedef void (* gnutls_stek_rotation_callback_t) (const gnutls_datum_t *prev_key, + const gnutls_datum_t *new_key, + uint64_t t); +void _gnutls_set_session_ticket_key_rotation_callback(gnutls_session_t session, + gnutls_stek_rotation_callback_t cb); + +static void server(int fd, unsigned rounds, const char *prio) +{ + int retval; + gnutls_session_t session; + gnutls_datum_t session_ticket_key = { NULL, 0 }; + gnutls_certificate_credentials_t serverx509cred = NULL; + + if (gnutls_session_ticket_key_generate(&session_ticket_key) < 0) { + fail("server: Could not generate session ticket key\n"); + } + + for (unsigned i = 0; i < rounds; i++) { + assert(gnutls_init(&session, GNUTLS_SERVER)>=0); + + assert(gnutls_certificate_allocate_credentials(&serverx509cred)>=0); + assert(gnutls_certificate_set_x509_key_mem(serverx509cred, + &server_cert, &server_key, + GNUTLS_X509_FMT_PEM)>=0); + + assert(gnutls_priority_set_direct(session, prio, NULL)>=0); + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, serverx509cred); + + gnutls_db_set_cache_expiration(session, TICKET_EXPIRATION); + _gnutls_set_session_ticket_key_rotation_callback(session, stek_rotation_callback); + + retval = gnutls_session_ticket_enable_server(session, + &session_ticket_key); + if (retval != GNUTLS_E_SUCCESS) { + fail("server: Could not enable session tickets: %s\n", gnutls_strerror(retval)); + } + + gnutls_transport_set_int(session, fd); + gnutls_handshake_set_timeout(session, 20 * 1000); + + do { + retval = gnutls_handshake(session); + } while (retval == GNUTLS_E_AGAIN || retval == GNUTLS_E_INTERRUPTED); + + if (retval < 0) { + fail("server: Handshake failed: %s\n", gnutls_strerror(retval)); + } else { + success("server: Handshake was completed\n"); + } + + if (gnutls_session_is_resumed(session)) + fail("server: Session was resumed (but should not)\n"); + else + success("server: Success: Session was NOT resumed\n"); + + gnutls_bye(session, GNUTLS_SHUT_RDWR); + gnutls_deinit(session); + gnutls_certificate_free_credentials(serverx509cred); + serverx509cred = NULL; + } + + if (num_stek_rotations != 2) + fail("STEK should be rotated exactly twice (%d)!\n", num_stek_rotations); + + if (serverx509cred) + gnutls_certificate_free_credentials(serverx509cred); + gnutls_free(session_ticket_key.data); +} + +static void run(const char *name, const char *prio, int resume[], int rounds) +{ + pid_t child; + int retval, sockets[2], status = 0; + + success("\ntesting %s\n\n", name); + + retval = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + if (retval == -1) { + perror("socketpair"); + fail("socketpair failed"); + return; + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork failed"); + return; + } + + if (child) { + /* We are the parent */ + server(sockets[0], rounds, prio); + waitpid(child, &status, 0); + check_wait_status(status); + gnutls_global_deinit(); + } else { + /* We are the child */ + client(sockets[1], resume, rounds, prio); + gnutls_global_deinit(); + exit(0); + } +} + +void doit(void) +{ + int resume[] = { 0, 1, 0 }; + + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + num_stek_rotations = 0; + run("tls1.2 resumption", "NORMAL:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0", resume, 3); + + num_stek_rotations = 0; + run("tls1.3 resumption", "NORMAL:-VERS-ALL:+VERS-TLS1.3", resume, 3); +} + +#endif + diff --git a/tests/resume-with-stek-expiration.c b/tests/resume-with-stek-expiration.c new file mode 100644 index 0000000000..fa30b8d397 --- /dev/null +++ b/tests/resume-with-stek-expiration.c @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2018 Free Software Foundation, Inc. + * + * Author: Ander Juaristi + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) +int main(int argc, char **argv) +{ + exit(77); +} +#else + +#include <stdint.h> +#include <unistd.h> +#include <sys/wait.h> +#include <sys/socket.h> +#include <gnutls/gnutls.h> +#include <assert.h> +#include "utils.h" +#include "cert-common.h" + +/* + * This will set the following values: + * + * - Ticket key expiration: 1 second. + * - Session ticket key rotation period: 3 seconds. + */ +#define TICKET_EXPIRATION 1 /* seconds */ + +unsigned num_stek_rotations; + +static void stek_rotation_callback(const gnutls_datum_t *prev_key, + const gnutls_datum_t *new_key, + uint64_t t) +{ + num_stek_rotations++; + success("STEK was rotated!\n"); +} + +typedef void (* gnutls_stek_rotation_callback_t) (const gnutls_datum_t *prev_key, + const gnutls_datum_t *new_key, + uint64_t t); +void _gnutls_set_session_ticket_key_rotation_callback(gnutls_session_t session, + gnutls_stek_rotation_callback_t cb); + +static int handshake(gnutls_session_t session, gnutls_datum_t *session_data, + int resumption_should_succeed) +{ + int ret; + + do { + ret = gnutls_handshake(session); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (ret < 0) { + gnutls_perror(ret); + fail("client: Handshake failed\n"); + } else { + success("client: Handshake was completed\n"); + } + + if (gnutls_session_is_resumed(session)) { + if (!resumption_should_succeed) + fail("client: Session was resumed (but should not)\n"); + else + success("client: Success: Session was resumed\n"); + } else { + if (resumption_should_succeed) + fail("client: Session was not resumed (but should)\n"); + else + success("client: Success: Session was NOT resumed\n"); + } + + ret = gnutls_session_get_data2(session, session_data); + if (ret < 0) { + gnutls_perror(ret); + fail("client: Could not get session data\n"); + } + + do { + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + return 0; +} + +static int resume_and_close(gnutls_session_t session, gnutls_datum_t *session_data, + int resumption_should_succeed) +{ + int ret; + + ret = gnutls_session_set_data(session, session_data->data, session_data->size); + if (ret < 0) { + gnutls_perror(ret); + fail("client: Could not get session data\n"); + } + + do { + ret = gnutls_handshake(session); + } while (ret < 0 && !gnutls_error_is_fatal(ret)); + + if (ret < 0) { + fail("client: Handshake failed: %s\n", gnutls_strerror(ret)); + } else { + success("client: Handshake was completed\n"); + } + + if (gnutls_session_is_resumed(session)) { + if (!resumption_should_succeed) + fail("client: Session was resumed (but should not)\n"); + else + success("client: Success: Session was resumed\n"); + } else { + if (resumption_should_succeed) + fail("client: Session was not resumed (but should)\n"); + else + success("client: Success: Session was NOT resumed\n"); + } + + do { + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + return 0; +} + +static void client(int fd, int *resumption_should_succeed, unsigned num_sessions, const char *prio) +{ + gnutls_session_t session; + gnutls_datum_t session_data; + gnutls_certificate_credentials_t clientx509cred = NULL; + + gnutls_certificate_allocate_credentials(&clientx509cred); + + /* Initialize TLS layer */ + gnutls_init(&session, GNUTLS_CLIENT); + gnutls_priority_set_direct(session, prio, NULL); + + /* put the anonymous credentials to the current session */ + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, + clientx509cred); + + gnutls_transport_set_int(session, fd); + gnutls_handshake_set_timeout(session, 20 * 1000); + + if (handshake(session, &session_data, resumption_should_succeed[0]) < 0) + return; + + if (clientx509cred) + gnutls_certificate_free_credentials(clientx509cred); + gnutls_deinit(session); + + for (unsigned i = 1; i < num_sessions; i++) { + assert(gnutls_certificate_allocate_credentials(&clientx509cred)>=0); + + /* Initialize TLS layer */ + assert(gnutls_init(&session, GNUTLS_CLIENT)>=0); + assert(gnutls_priority_set_direct(session, prio, NULL)>=0); + + /* put the anonymous credentials to the current session */ + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, + clientx509cred); + + gnutls_transport_set_int(session, fd); + + if (resume_and_close(session, &session_data, resumption_should_succeed[i]) < 0) + return; + + sec_sleep(TICKET_EXPIRATION); + + if (clientx509cred) + gnutls_certificate_free_credentials(clientx509cred); + gnutls_deinit(session); + } +} + +static void server(int fd, int *resumption_should_succeed, unsigned num_sessions, const char *prio) +{ + int retval; + gnutls_session_t session; + gnutls_certificate_credentials_t serverx509cred; + gnutls_datum_t session_ticket_key = { NULL, 0 }; + + if (gnutls_session_ticket_key_generate(&session_ticket_key) < 0) + fail("server: Could not generate session ticket key\n"); + + for (unsigned i = 0; i < num_sessions; i++) { + if ((retval = gnutls_init(&session, GNUTLS_SERVER)) < 0) { + gnutls_perror(retval); + fail("gnutls_init() failed\n"); + } + + assert(gnutls_certificate_allocate_credentials(&serverx509cred)>=0); + assert(gnutls_certificate_set_x509_key_mem(serverx509cred, + &server_cert, &server_key, + GNUTLS_X509_FMT_PEM)>=0); + + assert(gnutls_priority_set_direct(session, prio, NULL)>=0); + + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, serverx509cred); + + retval = gnutls_session_ticket_enable_server(session, &session_ticket_key); + if (retval != GNUTLS_E_SUCCESS) { + gnutls_perror(retval); + fail("server: Could not enable session tickets\n"); + } + + + gnutls_db_set_cache_expiration(session, TICKET_EXPIRATION); + + _gnutls_set_session_ticket_key_rotation_callback(session, stek_rotation_callback); + + gnutls_transport_set_int(session, fd); + gnutls_handshake_set_timeout(session, 20 * 1000); + + do { + retval = gnutls_handshake(session); + } while(retval == GNUTLS_E_AGAIN || retval == GNUTLS_E_INTERRUPTED); + + if (retval < 0) { + fail("server: Handshake failed: %s\n", gnutls_strerror(retval)); + } else { + success("server: Handshake was completed\n"); + } + + if (gnutls_session_is_resumed(session)) { + if (!resumption_should_succeed[i]) + fail("server: Session was resumed (but should not)\n"); + else + success("server: Success: Session was resumed\n"); + } else { + if (resumption_should_succeed[i]) + fail("server: Session was not resumed (but should)\n"); + else + success("server: Success: Session was NOT resumed\n"); + } + + gnutls_bye(session, GNUTLS_SHUT_RDWR); + gnutls_deinit(session); + gnutls_certificate_free_credentials(serverx509cred); + serverx509cred = NULL; + } + + if (num_stek_rotations != 4) + fail("STEK should be rotated exactly 4 times!\n"); + + if (serverx509cred) + gnutls_certificate_free_credentials(serverx509cred); + gnutls_free(session_ticket_key.data); +} + +static void run(const char *name, const char *prio, int resumption_should_succeed[], int rounds) +{ + pid_t child; + int retval, sockets[2], status = 0; + + success("\ntesting %s\n\n", name); + + retval = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + if (retval == -1) { + perror("socketpair"); + fail("socketpair failed"); + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork failed"); + } + + if (child) { + /* We are the parent */ + server(sockets[0], resumption_should_succeed, rounds, prio); + waitpid(child, &status, 0); + check_wait_status(status); + } else { + /* We are the child */ + client(sockets[1], resumption_should_succeed, rounds, prio); + exit(0); + } +} + +void doit(void) +{ + int resumption_should_succeed[] = { 0, 1, 1, 0 }; + + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + num_stek_rotations = 0; + run("tls1.2 resumption", "NORMAL:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0", + resumption_should_succeed, 4); + + num_stek_rotations = 0; + run("tls1.3 resumption", "NORMAL:-VERS-ALL:+VERS-TLS1.3", + resumption_should_succeed, 4); +} + +#endif |