summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAniketh01 <anikethgireesh@gmail.com>2019-10-03 12:12:10 +0530
committerDaiki Ueno <dueno@redhat.com>2019-12-01 18:32:33 +0100
commit51eed2631d3e216b0fe4a56a713f4665dbfe1c5c (patch)
tree4c0293c452bddb5b7dcc46c81b24ad5879841ace
parent25ae05fdc0e5627b6e53c17c2c55a987117d9cfb (diff)
downloadgnutls-tmp-secret-hook.tar.gz
gnutls_session_set_secret_hook_function: new functiontmp-secret-hook
This adds a callback to get notified when a new traffic secret is set. This is particularly useful with QUIC, where the QUIC implementations calculate actual traffic keys from the TLS secrets. Signed-off-by: Aniketh01 <anikethgireesh@gmail.com> Signed-off-by: Daiki Ueno <dueno@redhat.com>
-rw-r--r--.gitignore1
-rw-r--r--NEWS2
-rw-r--r--devel/libgnutls-latest-x86_64.abi1
-rw-r--r--devel/symbols.last2
-rw-r--r--doc/Makefile.am3
-rw-r--r--doc/manpages/Makefile.am1
-rw-r--r--lib/Makefile.am2
-rw-r--r--lib/constate.c24
-rw-r--r--lib/ext/pre_shared_key.c12
-rw-r--r--lib/gnutls_int.h2
-rw-r--r--lib/includes/gnutls/gnutls.h.in43
-rw-r--r--lib/libgnutls.map6
-rw-r--r--lib/quic-api.c63
-rw-r--r--lib/quic.h38
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/quic/secret-hook.c460
16 files changed, 659 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 606257a025..49efe83f44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -612,6 +612,7 @@ tests/privkey-verify-broken
tests/psk-file
tests/pskself
tests/pubkey-import-export
+tests/quic/secret-hook
tests/random-art
tests/rawpk-api
tests/record-pad
diff --git a/NEWS b/NEWS
index 8f46165c62..2085ab7fdd 100644
--- a/NEWS
+++ b/NEWS
@@ -14,7 +14,7 @@ See the end for copying conditions.
Key material can be set via the --rawpkkeyfile and --rawpkfile flags.
** API and ABI modifications:
-No changes since last version.
+gnutls_session_set_secret_hook_function: Added
* Version 3.6.10 (released 2019-09-29)
diff --git a/devel/libgnutls-latest-x86_64.abi b/devel/libgnutls-latest-x86_64.abi
index b2c058d71d..27573a4acc 100644
--- a/devel/libgnutls-latest-x86_64.abi
+++ b/devel/libgnutls-latest-x86_64.abi
@@ -786,6 +786,7 @@
<elf-symbol name='gnutls_session_set_id' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='gnutls_session_set_premaster' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='gnutls_session_set_ptr' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
+ <elf-symbol name='gnutls_session_set_secret_hook_function' version='GNUTLS_3_6_11' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='gnutls_session_set_verify_cert2' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='gnutls_session_set_verify_cert' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='gnutls_session_set_verify_function' version='GNUTLS_3_4' is-default-version='yes' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
diff --git a/devel/symbols.last b/devel/symbols.last
index 730d75043e..e85b4a6c00 100644
--- a/devel/symbols.last
+++ b/devel/symbols.last
@@ -1,6 +1,7 @@
GNUTLS_3_4@GNUTLS_3_4
GNUTLS_3_6_0@GNUTLS_3_6_0
GNUTLS_3_6_10@GNUTLS_3_6_10
+GNUTLS_3_6_11@GNUTLS_3_6_11
GNUTLS_3_6_2@GNUTLS_3_6_2
GNUTLS_3_6_3@GNUTLS_3_6_3
GNUTLS_3_6_4@GNUTLS_3_6_4
@@ -758,6 +759,7 @@ gnutls_session_set_data@GNUTLS_3_4
gnutls_session_set_id@GNUTLS_3_4
gnutls_session_set_premaster@GNUTLS_3_4
gnutls_session_set_ptr@GNUTLS_3_4
+gnutls_session_set_secret_hook_function@GNUTLS_3_6_11
gnutls_session_set_verify_cert2@GNUTLS_3_4
gnutls_session_set_verify_cert@GNUTLS_3_4
gnutls_session_set_verify_function@GNUTLS_3_4
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 8eb30398df..20a9b99856 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -550,6 +550,7 @@ ENUMS += enums/gnutls_credentials_type_t
ENUMS += enums/gnutls_ctype_target_t
ENUMS += enums/gnutls_digest_algorithm_t
ENUMS += enums/gnutls_ecc_curve_t
+ENUMS += enums/gnutls_encryption_level_t
ENUMS += enums/gnutls_ext_flags_t
ENUMS += enums/gnutls_ext_parse_type_t
ENUMS += enums/gnutls_fips_mode_t
@@ -1939,6 +1940,8 @@ FUNCS += functions/gnutls_session_set_premaster
FUNCS += functions/gnutls_session_set_premaster.short
FUNCS += functions/gnutls_session_set_ptr
FUNCS += functions/gnutls_session_set_ptr.short
+FUNCS += functions/gnutls_session_set_secret_hook_function
+FUNCS += functions/gnutls_session_set_secret_hook_function.short
FUNCS += functions/gnutls_session_set_verify_cert
FUNCS += functions/gnutls_session_set_verify_cert.short
FUNCS += functions/gnutls_session_set_verify_cert2
diff --git a/doc/manpages/Makefile.am b/doc/manpages/Makefile.am
index ee855adf35..f181756c90 100644
--- a/doc/manpages/Makefile.am
+++ b/doc/manpages/Makefile.am
@@ -771,6 +771,7 @@ APIMANS += gnutls_session_set_data.3
APIMANS += gnutls_session_set_id.3
APIMANS += gnutls_session_set_premaster.3
APIMANS += gnutls_session_set_ptr.3
+APIMANS += gnutls_session_set_secret_hook_function.3
APIMANS += gnutls_session_set_verify_cert.3
APIMANS += gnutls_session_set_verify_cert2.3
APIMANS += gnutls_session_set_verify_function.3
diff --git a/lib/Makefile.am b/lib/Makefile.am
index f1e3bb90b6..cf6d9aed88 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -81,7 +81,7 @@ COBJECTS = range.c record.c compress.c debug.c cipher.c gthreads.h handshake-tls
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 stek.c cert-cred-rawpk.c \
- iov.c iov.h
+ iov.c iov.h quic-api.c quic.h
if WINDOWS
COBJECTS += system/keys-win.c
diff --git a/lib/constate.c b/lib/constate.c
index 51943ede69..c6329d7ba2 100644
--- a/lib/constate.c
+++ b/lib/constate.c
@@ -40,6 +40,7 @@
#include "handshake.h"
#include "crypto-api.h"
#include "locks.h"
+#include "quic.h"
static const char keyexp[] = "key expansion";
static const int keyexp_length = sizeof(keyexp) - 1;
@@ -274,6 +275,11 @@ _tls13_update_keys(gnutls_session_t session, hs_stage_t stage,
ret = _tls13_expand_secret(session, "iv", 2, NULL, 0, session->key.proto.tls13.ap_ckey, iv_size, iv_block);
if (ret < 0)
return gnutls_assert_val(ret);
+
+ _gnutls_call_secret_hook_func(session, GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_CLIENT,
+ session->key.proto.tls13.ap_ckey,
+ session->security_parameters.prf->output_size);
} else {
ret = _tls13_expand_secret(session, APPLICATION_TRAFFIC_UPDATE,
sizeof(APPLICATION_TRAFFIC_UPDATE)-1,
@@ -291,6 +297,11 @@ _tls13_update_keys(gnutls_session_t session, hs_stage_t stage,
ret = _tls13_expand_secret(session, "iv", 2, NULL, 0, session->key.proto.tls13.ap_skey, iv_size, iv_block);
if (ret < 0)
return gnutls_assert_val(ret);
+
+ _gnutls_call_secret_hook_func(session, GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_SERVER,
+ session->key.proto.tls13.ap_skey,
+ session->security_parameters.prf->output_size);
}
upd_state->mac_key_size = 0;
@@ -390,6 +401,7 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage,
unsigned label_size, hsk_len;
const char *keylog_label;
void *ckey, *skey;
+ gnutls_encryption_level_t level;
int ret;
if (stage == STAGE_UPD_OURS || stage == STAGE_UPD_PEERS)
@@ -406,12 +418,14 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage,
hsk_len = session->internals.handshake_hash_buffer.length;
keylog_label = "CLIENT_HANDSHAKE_TRAFFIC_SECRET";
ckey = session->key.proto.tls13.hs_ckey;
+ level = GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE;
} else {
label = APPLICATION_CLIENT_TRAFFIC_LABEL;
label_size = sizeof(APPLICATION_CLIENT_TRAFFIC_LABEL)-1;
hsk_len = session->internals.handshake_hash_buffer_server_finished_len;
keylog_label = "CLIENT_TRAFFIC_SECRET_0";
ckey = session->key.proto.tls13.ap_ckey;
+ level = GNUTLS_ENCRYPTION_LEVEL_APPLICATION;
}
ret = _tls13_derive_secret(session, label, label_size,
@@ -426,6 +440,10 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage,
ckey,
session->security_parameters.prf->output_size);
+ _gnutls_call_secret_hook_func(session, level,
+ GNUTLS_CLIENT, ckey,
+ session->security_parameters.prf->output_size);
+
/* client keys */
ret = _tls13_expand_secret(session, "key", 3, NULL, 0, ckey, key_size, ckey_block);
if (ret < 0)
@@ -441,11 +459,13 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage,
label_size = sizeof(HANDSHAKE_SERVER_TRAFFIC_LABEL)-1;
keylog_label = "SERVER_HANDSHAKE_TRAFFIC_SECRET";
skey = session->key.proto.tls13.hs_skey;
+ level = GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE;
} else {
label = APPLICATION_SERVER_TRAFFIC_LABEL;
label_size = sizeof(APPLICATION_SERVER_TRAFFIC_LABEL)-1;
keylog_label = "SERVER_TRAFFIC_SECRET_0";
skey = session->key.proto.tls13.ap_skey;
+ level = GNUTLS_ENCRYPTION_LEVEL_APPLICATION;
}
ret = _tls13_derive_secret(session, label, label_size,
@@ -461,6 +481,10 @@ _tls13_set_keys(gnutls_session_t session, hs_stage_t stage,
skey,
session->security_parameters.prf->output_size);
+ _gnutls_call_secret_hook_func(session, level,
+ GNUTLS_SERVER, skey,
+ session->security_parameters.prf->output_size);
+
ret = _tls13_expand_secret(session, "key", 3, NULL, 0, skey, key_size, skey_block);
if (ret < 0)
return gnutls_assert_val(ret);
diff --git a/lib/ext/pre_shared_key.c b/lib/ext/pre_shared_key.c
index d344922910..57d36dee95 100644
--- a/lib/ext/pre_shared_key.c
+++ b/lib/ext/pre_shared_key.c
@@ -30,6 +30,7 @@
#include "tls13/psk_ext_parser.h"
#include "tls13/finished.h"
#include "tls13/session_ticket.h"
+#include "quic.h"
#include "auth/psk_passwd.h"
#include <ext/session_ticket.h>
#include <ext/pre_shared_key.h>
@@ -194,6 +195,7 @@ generate_early_secrets(gnutls_session_t session,
const mac_entry_st *prf)
{
int ret;
+ record_parameters_st *record_params;
ret = _tls13_derive_secret2(prf, EARLY_TRAFFIC_LABEL, sizeof(EARLY_TRAFFIC_LABEL)-1,
session->internals.handshake_hash_buffer.data,
@@ -207,6 +209,16 @@ generate_early_secrets(gnutls_session_t session,
session->key.proto.tls13.e_ckey,
prf->output_size);
+ ret =
+ _gnutls_epoch_get(session, EPOCH_READ_CURRENT, &record_params);
+ if (ret < 0)
+ return gnutls_assert_val(ret);
+
+ _gnutls_call_secret_hook_func(session, GNUTLS_ENCRYPTION_LEVEL_EARLY,
+ GNUTLS_CLIENT,
+ session->key.proto.tls13.e_ckey,
+ prf->output_size);
+
ret = _tls13_derive_secret2(prf, EARLY_EXPORTER_MASTER_LABEL, sizeof(EARLY_EXPORTER_MASTER_LABEL)-1,
session->internals.handshake_hash_buffer.data,
session->internals.handshake_hash_buffer_client_hello_len,
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index 3b683a1de1..617757d8e8 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -1246,6 +1246,8 @@ typedef struct {
unsigned int h_type; /* the hooked type */
int16_t h_post; /* whether post-generation/receive */
+ gnutls_secret_hook_func secret_hook;
+
/* holds the selected certificate and key.
* use _gnutls_selected_certs_deinit() and _gnutls_selected_certs_set()
* to change them.
diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
index f4bbbce306..6878166017 100644
--- a/lib/includes/gnutls/gnutls.h.in
+++ b/lib/includes/gnutls/gnutls.h.in
@@ -3087,6 +3087,49 @@ void gnutls_anti_replay_set_add_function(gnutls_anti_replay_t,
void gnutls_anti_replay_set_ptr(gnutls_anti_replay_t, void *ptr);
+/* QUIC related functions */
+
+/**
+ * gnutls_encryption_level_t:
+ * @GNUTLS_ENCRYPTION_LEVEL_INITIAL: the Initial Keys encryption level.
+ * @GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE: the Handshake Keys encryption level.
+ * @GNUTLS_ENCRYPTION_LEVEL_APPLICATION: the Application Data (1-RTT) Keys encryption level.
+ * @GNUTLS_ENCRYPTION_LEVEL_EARLY: the Early Data (0-RTT) Keys encryption level.
+ *
+ * Enumeration of encryption levels where new secrets are set. This
+ * is used by a secret hook function set with
+ * gnutls_session_set_secret_hook_function().
+ *
+ * Since: 3.6.11
+ */
+typedef enum gnutls_encryption_level {
+ GNUTLS_ENCRYPTION_LEVEL_INITIAL,
+ GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_ENCRYPTION_LEVEL_EARLY
+} gnutls_encryption_level_t;
+
+/**
+ * gnutls_secret_hook_func:
+ * @session: the current session
+ * @level: the encryption level where the secret is installed
+ * @incoming: non zero if the secret is for reading an incoming message, zero otherwise
+ * @secret: the (const) data of the new traffic secret
+ *
+ * Function prototype for secret installation hooks. It is set using
+ * gnutls_session_set_secret_hook_function().
+ *
+ * Since: 3.6.11
+ */
+typedef void (*gnutls_secret_hook_func) (gnutls_session_t session,
+ gnutls_encryption_level_t level,
+ unsigned int incoming,
+ const gnutls_datum_t *secret);
+
+void gnutls_session_set_secret_hook_function(gnutls_session_t session,
+ gnutls_secret_hook_func func);
+
+
/* FIPS140-2 related functions */
unsigned gnutls_fips140_mode_enabled(void);
diff --git a/lib/libgnutls.map b/lib/libgnutls.map
index 6e1da857f6..1a912acb79 100644
--- a/lib/libgnutls.map
+++ b/lib/libgnutls.map
@@ -1301,6 +1301,12 @@ GNUTLS_3_6_10
gnutls_aead_cipher_decryptv2;
} GNUTLS_3_6_9;
+GNUTLS_3_6_11
+{
+ global:
+ gnutls_session_set_secret_hook_function;
+} GNUTLS_3_6_10;
+
GNUTLS_FIPS140_3_4 {
global:
gnutls_cipher_self_test;
diff --git a/lib/quic-api.c b/lib/quic-api.c
new file mode 100644
index 0000000000..31908ac507
--- /dev/null
+++ b/lib/quic-api.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 Free Software Foundation, Inc.
+ *
+ * Author: Aniketh Girish
+ *
+ * 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 <https://www.gnu.org/licenses/>
+ *
+ */
+
+/* This file contains TLS API for QUIC protocol related types, prototypes and includes.
+ */
+
+#include "gnutls_int.h"
+#include "quic.h"
+#include <gnutls/gnutls.h>
+
+
+/**
+ * gnutls_session_set_secret_hook_function:
+ * @session: is #gnutls_session_t type
+ * @func: is the function to be called
+ *
+ * This function will set a callback to be called when a new traffic
+ * secret is installed. The callback will only be called when TLS 1.3
+ * or later is negotiated.
+ *
+ * Since: 3.6.11
+ */
+void
+gnutls_session_set_secret_hook_function(gnutls_session_t session,
+ gnutls_secret_hook_func func)
+{
+ session->internals.secret_hook = func;
+}
+
+void
+_gnutls_call_secret_hook_func(gnutls_session_t session,
+ gnutls_encryption_level_t level,
+ unsigned int sender,
+ const uint8_t *secret,
+ size_t secret_size)
+{
+ if (session->internals.secret_hook != NULL) {
+ unsigned int incoming =
+ sender != session->security_parameters.entity;
+ gnutls_datum_t data = {(void*)secret, secret_size};
+
+ session->internals.secret_hook(session, level, incoming, &data);
+ }
+}
diff --git a/lib/quic.h b/lib/quic.h
new file mode 100644
index 0000000000..9eb6eebe0d
--- /dev/null
+++ b/lib/quic.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 Free Software Foundation, Inc.
+ *
+ * Author: Aniketh Girish
+ *
+ * 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 <https://www.gnu.org/licenses/>
+ *
+ */
+
+/* This file contains TLS API for QUIC protocol related types, prototypes and includes.
+ */
+
+
+#ifndef GNUTLS_LIB_QUIC_H
+#define GNUTLS_LIB_QUIC_H
+
+#include "gnutls_int.h"
+
+void _gnutls_call_secret_hook_func(gnutls_session_t session,
+ gnutls_encryption_level_t level,
+ unsigned int sender,
+ const uint8_t *secret,
+ size_t secret_size);
+
+#endif /* GNUTLS_LIB_QUIC_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0f488867de..10caa21946 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -215,7 +215,7 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei
resume-with-stek-expiration resume-with-previous-stek rawpk-api \
tls-record-size-limit-asym dh-compute ecdh-compute sign-verify-data-newapi \
sign-verify-newapi sign-verify-deterministic iov aead-cipher-vec \
- tls13-without-timeout-func buffer
+ tls13-without-timeout-func buffer quic/secret-hook
if HAVE_SECCOMP_TESTS
ctests += dtls-with-seccomp tls-with-seccomp dtls-client-with-seccomp tls-client-with-seccomp
diff --git a/tests/quic/secret-hook.c b/tests/quic/secret-hook.c
new file mode 100644
index 0000000000..03a5a7b0cc
--- /dev/null
+++ b/tests/quic/secret-hook.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2019 Free Software Foundation, Inc.
+ *
+ * Author: Aniketh Girish, Daiki Ueno
+ *
+ * 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 <https://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if !defined(__linux__) || !defined(__GNUC__)
+
+int main(int argc, char **argv)
+{
+ exit(77);
+}
+
+#else
+
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#include "cert-common.h"
+#include "utils.h"
+
+/* This program tests whether a secret hook function is called upon a
+ * new traffic secret is installed.
+ */
+
+static void server_log_func(int level, const char *str)
+{
+ fprintf(stderr, "server|<%d>| %s", level, str);
+}
+
+static void client_log_func(int level, const char *str)
+{
+ fprintf(stderr, "client|<%d>| %s", level, str);
+}
+
+const char *side = "";
+
+/* These are global */
+static pid_t child;
+#define SESSIONS 2
+#define MAX_BUF 1024
+#define MSG "Hello TLS"
+#define EARLY_MSG "Hello TLS, it's early"
+
+static const gnutls_encryption_level_t initial_levels[] =
+{
+ GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE,
+ GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+};
+
+static const gnutls_encryption_level_t resuming_levels[] =
+{
+ GNUTLS_ENCRYPTION_LEVEL_EARLY,
+ GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE,
+ GNUTLS_ENCRYPTION_LEVEL_HANDSHAKE,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+ GNUTLS_ENCRYPTION_LEVEL_APPLICATION,
+};
+
+struct test {
+ unsigned int call_count;
+ const gnutls_encryption_level_t *levels;
+ unsigned int exp_call_count;
+};
+
+static void
+secret_hook_func(gnutls_session_t session,
+ gnutls_encryption_level_t level,
+ unsigned int incoming,
+ const gnutls_datum_t *secret)
+{
+ struct test *test = gnutls_session_get_ptr(session);
+
+ if (level == GNUTLS_ENCRYPTION_LEVEL_INITIAL)
+ fail("initial encryption level received at call count %d\n",
+ test->call_count);
+
+ if (test->call_count >= test->exp_call_count)
+ fail("invalid call count %u\n", test->call_count);
+
+ if (level != test->levels[test->call_count])
+ fail("unexpected encryption level %u at call count %u\n",
+ level, test->call_count);
+
+ test->call_count++;
+}
+
+#define PRIORITY "NORMAL:-VERS-ALL:+VERS-TLS1.3"
+
+static void client(int sds[])
+{
+ gnutls_session_t session;
+ char buffer[MAX_BUF + 1];
+ int ret;
+ gnutls_certificate_credentials_t x509_cred;
+ int t;
+ gnutls_datum_t session_data = {NULL, 0};
+ struct test tests[] = {
+ { .levels = initial_levels,
+ .exp_call_count = sizeof(initial_levels)/sizeof(*initial_levels) },
+ { .levels = resuming_levels,
+ .exp_call_count = sizeof(resuming_levels)/sizeof(*resuming_levels) },
+ };
+
+ if (debug) {
+ gnutls_global_set_log_function(client_log_func);
+ gnutls_global_set_log_level(4711);
+ }
+
+ gnutls_certificate_allocate_credentials(&x509_cred);
+
+ for (t = 0; t < SESSIONS; t++) {
+ int sd = sds[t];
+
+ assert(gnutls_init(&session, GNUTLS_CLIENT) >= 0);
+ assert(gnutls_priority_set_direct(session, PRIORITY, NULL) >= 0);
+
+ gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
+
+ gnutls_transport_set_int(session, sd);
+
+ gnutls_session_set_ptr(session, &tests[t]);
+ gnutls_session_set_secret_hook_function(session, secret_hook_func);
+
+ if (t > 0) {
+ assert(gnutls_session_set_data(session, session_data.data, session_data.size) >= 0);
+ assert(gnutls_record_send_early_data(session, EARLY_MSG, sizeof(EARLY_MSG)) >= 0);
+ }
+
+ /* Perform the TLS handshake
+ */
+ do {
+ ret = gnutls_handshake(session);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+
+ if (ret < 0)
+ fail("client: Handshake failed: %s\n", gnutls_strerror(ret));
+ else {
+ if (debug)
+ success("client: Handshake was completed\n");
+ }
+
+ if (t == 0) {
+ /* get the session data size */
+ ret =
+ gnutls_session_get_data2(session,
+ &session_data);
+ if (ret < 0)
+ fail("client: Getting resume data failed\n");
+ }
+
+ if (t > 0) {
+ if (!gnutls_session_is_resumed(session)) {
+ fail("client: session_is_resumed error (%d)\n", t);
+ }
+ }
+
+ /* Send key update */
+ do {
+ ret = gnutls_session_key_update(session, GNUTLS_KU_PEER);
+ } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+ if (ret < 0)
+ fail("error in key update: %s\n", gnutls_strerror(ret));
+ else {
+ if (debug)
+ success("client: Sent key update\n");
+ }
+
+ gnutls_record_send(session, MSG, strlen(MSG));
+
+ do {
+ ret = gnutls_record_recv(session, buffer, MAX_BUF);
+ } while (ret == GNUTLS_E_AGAIN);
+ if (ret == 0) {
+ if (debug)
+ success
+ ("client: Peer has closed the TLS connection\n");
+ } else if (ret < 0) {
+ fail("client: Error: %s\n", gnutls_strerror(ret));
+ }
+
+ if (tests[t].call_count != tests[t].exp_call_count)
+ fail("secret hook is not called %u times (%u)\n",
+ tests[t].exp_call_count, tests[t].call_count);
+
+ gnutls_bye(session, GNUTLS_SHUT_WR);
+
+ close(sd);
+
+ gnutls_deinit(session);
+ }
+
+ gnutls_free(session_data.data);
+ gnutls_certificate_free_credentials(x509_cred);
+}
+
+#define MAX_CLIENT_HELLO_RECORDED 10
+
+struct storage_st {
+ gnutls_datum_t entries[MAX_CLIENT_HELLO_RECORDED];
+ size_t num_entries;
+};
+
+static int
+storage_add(void *ptr, time_t expires, const gnutls_datum_t *key, const gnutls_datum_t *value)
+{
+ struct storage_st *storage = ptr;
+ gnutls_datum_t *datum;
+ size_t i;
+
+ for (i = 0; i < storage->num_entries; i++) {
+ if (key->size == storage->entries[i].size &&
+ memcmp(storage->entries[i].data, key->data, key->size) == 0) {
+ return GNUTLS_E_DB_ENTRY_EXISTS;
+ }
+ }
+
+ /* If the maximum number of ClientHello exceeded, reject early
+ * data until next time.
+ */
+ if (storage->num_entries == MAX_CLIENT_HELLO_RECORDED)
+ return GNUTLS_E_DB_ERROR;
+
+ datum = &storage->entries[storage->num_entries];
+ datum->data = gnutls_malloc(key->size);
+ if (!datum->data)
+ return GNUTLS_E_MEMORY_ERROR;
+ memcpy(datum->data, key->data, key->size);
+ datum->size = key->size;
+
+ storage->num_entries++;
+
+ return 0;
+}
+
+static void
+storage_clear(struct storage_st *storage)
+{
+ size_t i;
+
+ for (i = 0; i < storage->num_entries; i++)
+ gnutls_free(storage->entries[i].data);
+ storage->num_entries = 0;
+}
+
+static void server(int sds[])
+{
+ int ret;
+ char buffer[MAX_BUF + 1];
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t x509_cred;
+ gnutls_datum_t session_ticket_key = { NULL, 0 };
+ struct storage_st storage;
+ gnutls_anti_replay_t anti_replay;
+ int t;
+ struct test tests[] = {
+ { .levels = initial_levels,
+ .exp_call_count = sizeof(initial_levels)/sizeof(*initial_levels) },
+ { .levels = resuming_levels,
+ .exp_call_count = sizeof(resuming_levels)/sizeof(*resuming_levels) },
+ };
+
+ /* this must be called once in the program
+ */
+ global_init();
+ memset(buffer, 0, sizeof(buffer));
+ memset(&storage, 0, sizeof(storage));
+
+ if (debug) {
+ gnutls_global_set_log_function(server_log_func);
+ gnutls_global_set_log_level(4711);
+ }
+
+ gnutls_certificate_allocate_credentials(&x509_cred);
+ gnutls_certificate_set_x509_key_mem(x509_cred,
+ &server_cert, &server_key,
+ GNUTLS_X509_FMT_PEM);
+
+ gnutls_session_ticket_key_generate(&session_ticket_key);
+ assert(gnutls_anti_replay_init(&anti_replay) >= 0);
+
+ gnutls_anti_replay_set_add_function(anti_replay, storage_add);
+ gnutls_anti_replay_set_ptr(anti_replay, &storage);
+
+ for (t = 0; t < SESSIONS; t++) {
+ int sd = sds[t];
+
+ assert(gnutls_init(&session, GNUTLS_SERVER|GNUTLS_ENABLE_EARLY_DATA) >= 0);
+
+ assert(gnutls_priority_set_direct(session, PRIORITY, NULL) >= 0);
+
+ gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
+
+ gnutls_session_ticket_enable_server(session,
+ &session_ticket_key);
+
+ gnutls_anti_replay_enable(session, anti_replay);
+
+ gnutls_transport_set_int(session, sd);
+
+ gnutls_session_set_ptr(session, &tests[t]);
+ gnutls_session_set_secret_hook_function(session, secret_hook_func);
+
+ do {
+ ret = gnutls_handshake(session);
+ }
+ while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
+ if (ret < 0) {
+ close(sd);
+ gnutls_deinit(session);
+ fail("server: Handshake has failed (%s)\n\n",
+ gnutls_strerror(ret));
+ return;
+ }
+ if (debug)
+ success("server: Handshake was completed\n");
+
+ if (t > 0) {
+ if (!gnutls_session_is_resumed(session)) {
+ fail("server: session_is_resumed error (%d)\n", t);
+ }
+
+ ret = gnutls_record_recv_early_data(session, buffer, sizeof(buffer));
+ if (ret < 0) {
+ fail("server: failed to retrieve early data: %s\n",
+ gnutls_strerror(ret));
+ }
+
+ if ((size_t) ret != sizeof(EARLY_MSG) ||
+ memcmp(buffer, EARLY_MSG, ret))
+ fail("server: early data mismatch\n");
+ }
+
+ memset(buffer, 0, MAX_BUF + 1);
+
+ do {
+ ret = gnutls_record_recv(session, buffer, MAX_BUF);
+ } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+ if (ret == 0) {
+ if (debug)
+ success("server: Peer has closed the GnuTLS connection\n");
+ } else if (ret < 0) {
+ fail("server: Received corrupted data(%d). Closing...\n", ret);
+ } else if (ret > 0) {
+ /* echo data back to the client
+ */
+ gnutls_record_send(session, buffer,
+ strlen(buffer));
+ }
+
+ if (tests[t].call_count != tests[t].exp_call_count)
+ fail("secret hook is not called %u times (%u)\n",
+ tests[t].exp_call_count, tests[t].call_count);
+
+ /* do not wait for the peer to close the connection.
+ */
+ gnutls_bye(session, GNUTLS_SHUT_WR);
+
+ close(sd);
+ gnutls_deinit(session);
+ }
+
+ gnutls_anti_replay_deinit(anti_replay);
+
+ storage_clear(&storage);
+
+ gnutls_free(session_ticket_key.data);
+
+ gnutls_certificate_free_credentials(x509_cred);
+
+ gnutls_global_deinit();
+
+ if (debug)
+ success("server: finished\n");
+}
+
+void doit(void)
+{
+ int client_sds[SESSIONS], server_sds[SESSIONS];
+ int i;
+ int ret;
+
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+
+ for (i = 0; i < SESSIONS; i++) {
+ int sockets[2];
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
+ if (ret < 0) {
+ perror("socketpair");
+ exit(1);
+ }
+
+ server_sds[i] = sockets[0];
+ client_sds[i] = sockets[1];
+ }
+
+ child = fork();
+ if (child < 0) {
+ perror("fork");
+ fail("fork");
+ exit(1);
+ }
+
+ if (child) {
+ /* parent */
+ for (i = 0; i < SESSIONS; i++)
+ close(client_sds[i]);
+ server(server_sds);
+ kill(child, SIGTERM);
+ } else {
+ for (i = 0; i < SESSIONS; i++)
+ close(server_sds[i]);
+ client(client_sds);
+ exit(0);
+ }
+}
+
+#endif /* _WIN32 */