diff options
author | Aniketh01 <anikethgireesh@gmail.com> | 2019-10-03 12:12:10 +0530 |
---|---|---|
committer | Daiki Ueno <dueno@redhat.com> | 2019-12-01 18:32:33 +0100 |
commit | 51eed2631d3e216b0fe4a56a713f4665dbfe1c5c (patch) | |
tree | 4c0293c452bddb5b7dcc46c81b24ad5879841ace | |
parent | 25ae05fdc0e5627b6e53c17c2c55a987117d9cfb (diff) | |
download | gnutls-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-- | .gitignore | 1 | ||||
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | devel/libgnutls-latest-x86_64.abi | 1 | ||||
-rw-r--r-- | devel/symbols.last | 2 | ||||
-rw-r--r-- | doc/Makefile.am | 3 | ||||
-rw-r--r-- | doc/manpages/Makefile.am | 1 | ||||
-rw-r--r-- | lib/Makefile.am | 2 | ||||
-rw-r--r-- | lib/constate.c | 24 | ||||
-rw-r--r-- | lib/ext/pre_shared_key.c | 12 | ||||
-rw-r--r-- | lib/gnutls_int.h | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 43 | ||||
-rw-r--r-- | lib/libgnutls.map | 6 | ||||
-rw-r--r-- | lib/quic-api.c | 63 | ||||
-rw-r--r-- | lib/quic.h | 38 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/quic/secret-hook.c | 460 |
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 @@ -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 */ |