summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnder Juaristi <a@juaristi.eus>2018-09-18 09:40:20 +0200
committerNikos Mavrogiannopoulos <nmav@redhat.com>2018-09-19 14:50:45 +0200
commit553f9aaaa92d8bbc027af7f46bdd5355a2c0abc0 (patch)
tree2e9bd26e4e29da8dc29e1d01db26c8f83b7da2d6
parentd165c2a37f7d072cc88db88ec97f057a9ac6e4aa (diff)
downloadgnutls-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--NEWS5
-rw-r--r--lib/Makefile.am4
-rw-r--r--lib/ext/session_ticket.c133
-rw-r--r--lib/gnutls_int.h36
-rw-r--r--lib/libgnutls.map2
-rw-r--r--lib/state.c3
-rw-r--r--lib/stek.c357
-rw-r--r--lib/stek.h38
-rw-r--r--tests/Makefile.am3
-rw-r--r--tests/resume-with-previous-stek.c252
-rw-r--r--tests/resume-with-stek-expiration.c322
11 files changed, 1084 insertions, 71 deletions
diff --git a/NEWS b/NEWS
index 96fac0299d..e3874eaced 100644
--- a/NEWS
+++ b/NEWS
@@ -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