diff options
-rw-r--r-- | NEWS | 9 | ||||
-rw-r--r-- | doc/cha-gtls-app.texi | 12 | ||||
-rw-r--r-- | lib/gnutls_int.h | 4 | ||||
-rw-r--r-- | lib/handshake-tls13.c | 22 | ||||
-rw-r--r-- | lib/handshake.c | 24 | ||||
-rw-r--r-- | lib/handshake.h | 2 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 9 | ||||
-rw-r--r-- | lib/record.c | 63 | ||||
-rw-r--r-- | lib/tls13/post_handshake.c | 4 | ||||
-rw-r--r-- | tests/Makefile.am | 4 | ||||
-rw-r--r-- | tests/eagain-auto-auth.c | 235 | ||||
-rw-r--r-- | tests/tls12-rehandshake-cert-auto.c | 278 | ||||
-rw-r--r-- | tests/tls13/post-handshake-with-cert-auto.c | 366 |
13 files changed, 992 insertions, 40 deletions
@@ -5,6 +5,15 @@ Copyright (C) 2000-2016 Free Software Foundation, Inc. Copyright (C) 2013-2017 Nikos Mavrogiannopoulos See the end for copying conditions. +* Version 3.6.5 (unreleased) + +** libgnutls: Provide the option of transparent re-handshake/reauthentication + when the GNUTLS_AUTO_REAUTH flag is specified in gnutls_init(). + +** API and ABI modifications: +GNUTLS_AUTO_REAUTH: Added + + * Version 3.6.4 (released 2018-09-24) ** libgnutls: Added the final (RFC8446) version numbering of the TLS1.3 protocol. diff --git a/doc/cha-gtls-app.texi b/doc/cha-gtls-app.texi index 0288543482..1575c8fa52 100644 --- a/doc/cha-gtls-app.texi +++ b/doc/cha-gtls-app.texi @@ -1835,6 +1835,12 @@ Due to limitations of early protocol versions, it is required to check whether safe renegotiation is in place, i.e., using @funcref{gnutls_safe_renegotiation_status}, which ensures that the server remains the same as the initial. +To make re-authentication transparent to the application when requested +by the server, use the @code{GNUTLS_AUTO_REAUTH} flag on the +@funcref{gnutls_init} call. In that case the re-authentication will happen +in the call of @funcref{gnutls_record_recv} that received the +reauthentication request. + @showfuncdesc{gnutls_safe_renegotiation_status} @subsubsection Server side @@ -1877,6 +1883,12 @@ A client receiving a re-authentication request will "see" the error code @code{GNUTLS_E_REAUTH_REQUEST} at @funcref{gnutls_record_recv}. At this point, it should also call @funcref{gnutls_reauth}. +To make re-authentication transparent to the application when requested +by the server, use the @code{GNUTLS_AUTO_REAUTH} and @code{GNUTLS_POST_HANDSHAKE_AUTH} +flags on the @funcref{gnutls_init} call. In that case the re-authentication will happen +in the call of @funcref{gnutls_record_recv} that received the +reauthentication request. + @node Parameter generation @subsection Parameter generation @cindex parameter generation diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 2574f4e420..4a514ccc71 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -314,7 +314,9 @@ typedef enum recv_state_t { RECV_STATE_ASYNC_HANDSHAKE, /* an incomplete async handshake message was seen */ /* server-side early start under TLS1.3; enabled when no client cert is received */ RECV_STATE_EARLY_START_HANDLING, /* we are calling gnutls_handshake() within record_recv() */ - RECV_STATE_EARLY_START /* gnutls_record_recv() should complete the handshake */ + RECV_STATE_EARLY_START, /* gnutls_record_recv() should complete the handshake */ + RECV_STATE_REHANDSHAKE, /* gnutls_record_recv() should complete any incoming re-handshake requests */ + RECV_STATE_REAUTH /* gnutls_record_recv() should complete any incoming reauthentication requests */ } recv_state_t; #include "str.h" diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c index 06c7c01d29..5fed553310 100644 --- a/lib/handshake-tls13.c +++ b/lib/handshake-tls13.c @@ -527,6 +527,7 @@ _gnutls13_recv_async_handshake(gnutls_session_t session) { int ret; handshake_buffer_st hsk; + recv_state_t next_state = RECV_STATE_0; /* The following messages are expected asynchronously after * the handshake process is complete */ @@ -576,9 +577,20 @@ _gnutls13_recv_async_handshake(gnutls_session_t session) goto cleanup; } - /* Application is expected to handle re-authentication - * explicitly. */ - ret = GNUTLS_E_REAUTH_REQUEST; + if (session->internals.flags & GNUTLS_AUTO_REAUTH) { + ret = gnutls_reauth(session, 0); + if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) { + next_state = RECV_STATE_REAUTH; + } else if (ret < 0) { + gnutls_assert(); + goto cleanup; + } + } else { + /* Application is expected to handle re-authentication + * explicitly. */ + ret = GNUTLS_E_REAUTH_REQUEST; + } + goto cleanup; case GNUTLS_HANDSHAKE_KEY_UPDATE: @@ -630,7 +642,7 @@ _gnutls13_recv_async_handshake(gnutls_session_t session) } while (_gnutls_record_buffer_get_size(session) > 0); - session->internals.recv_state = RECV_STATE_0; + session->internals.recv_state = next_state; return 0; @@ -640,7 +652,7 @@ _gnutls13_recv_async_handshake(gnutls_session_t session) if (_gnutls_record_buffer_get_size(session) > 0) session->internals.recv_state = RECV_STATE_ASYNC_HANDSHAKE; else - session->internals.recv_state = RECV_STATE_0; + session->internals.recv_state = next_state; _gnutls_handshake_buffer_clear(&hsk); return ret; diff --git a/lib/handshake.c b/lib/handshake.c index b513872ec3..7db134a638 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -3468,30 +3468,6 @@ int _gnutls_generate_session_id(uint8_t * session_id, uint8_t *len) return 0; } -int -_gnutls_recv_hello_request(gnutls_session_t session, void *data, - uint32_t data_size) -{ - uint8_t type; - - if (session->security_parameters.entity == GNUTLS_SERVER) { - gnutls_assert(); - return GNUTLS_E_UNEXPECTED_PACKET; - } - if (data_size < 1) { - gnutls_assert(); - return GNUTLS_E_UNEXPECTED_PACKET_LENGTH; - } - type = ((uint8_t *) data)[0]; - if (type == GNUTLS_HANDSHAKE_HELLO_REQUEST) { - if (IS_DTLS(session)) - session->internals.dtls.hsk_read_seq++; - return GNUTLS_E_REHANDSHAKE; - } else { - gnutls_assert(); - return GNUTLS_E_UNEXPECTED_PACKET; - } -} /** * gnutls_handshake_set_max_packet_length: diff --git a/lib/handshake.h b/lib/handshake.h index 184a349a91..38ef848784 100644 --- a/lib/handshake.h +++ b/lib/handshake.h @@ -71,8 +71,6 @@ int _gnutls_send_handshake(gnutls_session_t session, mbuffer_st * bufel, gnutls_handshake_description_t type); -int _gnutls_recv_hello_request(gnutls_session_t session, void *data, - uint32_t data_size); int _gnutls_recv_handshake(gnutls_session_t session, gnutls_handshake_description_t type, unsigned int optional, gnutls_buffer_st * buf); diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index ec8d6f4efc..01cf9a880e 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -411,6 +411,12 @@ typedef enum { * are already taking steps to hide the data processing time. This comes at a performance * penalty. * @GNUTLS_ENABLE_CERT_TYPE_NEG: Enable certificate type negotiation extensions (RFC7250). + * @GNUTLS_AUTO_REAUTH: Enable transparent re-authentication in client side when the server + * requests to. That is, reauthentication is handled within gnutls_record_recv(), and + * the %GNUTLS_E_REHANDSHAKE or %GNUTLS_E_REAUTH_REQUEST are not returned. This must be + * enabled with %GNUTLS_POST_HANDSHAKE_AUTH for TLS1.3, and it requires to restore interrupted + * calls to gnutls_record_recv() based on the output of gnutls_record_get_direction(), i.e., + * gnutls_record_recv() could also be interrupted when sending when this flag is enabled. * * Enumeration of different flags for gnutls_init() function. All the flags * can be combined except @GNUTLS_SERVER and @GNUTLS_CLIENT which are mutually @@ -439,7 +445,8 @@ typedef enum { GNUTLS_NO_AUTO_REKEY = (1<<15), GNUTLS_SAFE_PADDING_CHECK = (1<<16), GNUTLS_ENABLE_EARLY_START = (1<<17), - GNUTLS_ENABLE_CERT_TYPE_NEG = (1<<18) + GNUTLS_ENABLE_CERT_TYPE_NEG = (1<<18), + GNUTLS_AUTO_REAUTH = (1<<19) } gnutls_init_flags_t; /* compatibility defines (previous versions of gnutls diff --git a/lib/record.c b/lib/record.c index 745969a732..87b9dee304 100644 --- a/lib/record.c +++ b/lib/record.c @@ -756,6 +756,37 @@ record_check_version(gnutls_session_t session, return 0; } +static int +recv_hello_request(gnutls_session_t session, void *data, + uint32_t data_size) +{ + uint8_t type; + + if (session->security_parameters.entity == GNUTLS_SERVER) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + + if (data_size < 1) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + if (session->internals.handshake_in_progress) + return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + + type = ((uint8_t *) data)[0]; + if (type == GNUTLS_HANDSHAKE_HELLO_REQUEST) { + if (IS_DTLS(session)) + session->internals.dtls.hsk_read_seq++; + if (session->internals.flags & GNUTLS_AUTO_REAUTH) { + session->internals.recv_state = RECV_STATE_REHANDSHAKE; + return GNUTLS_E_AGAIN; + } else { + return GNUTLS_E_REHANDSHAKE; + } + } else { + gnutls_assert(); + return GNUTLS_E_UNEXPECTED_PACKET; + } +} + /* This function will check if the received record type is * the one we actually expect and adds it to the proper * buffer. The bufel will be deinitialized after calling @@ -995,12 +1026,10 @@ record_add_to_buffers(gnutls_session_t session, /* So we accept it, if it is a Hello. If not, this will * fail and trigger flight retransmissions after some time. */ ret = - _gnutls_recv_hello_request(session, - bufel->msg.data, - bufel->msg.size); + recv_hello_request(session, + bufel->msg.data, + bufel->msg.size); goto unexpected_packet; - - break; default: _gnutls_record_log @@ -1555,6 +1584,30 @@ check_session_status(gnutls_session_t session, unsigned ms) } switch (session->internals.recv_state) { + case RECV_STATE_REAUTH: + session->internals.recv_state = RECV_STATE_0; + + ret = gnutls_reauth(session, 0); + if (ret < 0) { + /* a temp or fatal error, make sure we reset the state + * so we can resume on temp errors */ + session->internals.recv_state = RECV_STATE_REAUTH; + return gnutls_assert_val(ret); + } + + return 1; + case RECV_STATE_REHANDSHAKE: + session->internals.recv_state = RECV_STATE_0; + + ret = gnutls_handshake(session); + if (ret < 0) { + /* a temp or fatal error, make sure we reset the state + * so we can resume on temp errors */ + session->internals.recv_state = RECV_STATE_REHANDSHAKE; + return gnutls_assert_val(ret); + } + + return 1; case RECV_STATE_ASYNC_HANDSHAKE: ret = _gnutls_recv_in_buffers(session, GNUTLS_HANDSHAKE, -1, ms); if (ret < 0 && ret != GNUTLS_E_SESSION_EOF) diff --git a/lib/tls13/post_handshake.c b/lib/tls13/post_handshake.c index ca0ed53b0a..c786d8cae3 100644 --- a/lib/tls13/post_handshake.c +++ b/lib/tls13/post_handshake.c @@ -145,8 +145,10 @@ int _gnutls13_reauth_server(gnutls_session_t session) (!(session->internals.flags & GNUTLS_POST_HANDSHAKE_AUTH))) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); - if (session->internals.send_cert_req == 0) + if (session->internals.send_cert_req == 0) { + _gnutls_debug_log("You need to call gnutls_certificate_server_set_request to enable post handshake auth\n"); return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } switch (REAUTH_STATE) { case REAUTH_STATE0: diff --git a/tests/Makefile.am b/tests/Makefile.am index 71d00bcd32..25c2bf624d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -109,7 +109,7 @@ ctests = tls13/supported_versions tls13/tls12-no-tls13-exts \ tls13/cookie tls13/key_share tls13/prf tls13/post-handshake-with-cert-ticket \ tls12-rollback-detection tls11-rollback-detection \ tls12-check-rollback-val tls11-check-rollback-val tls13/hello_random_value \ - tls13/post-handshake-with-psk + tls13/post-handshake-with-psk tls13/post-handshake-with-cert-auto ctests += tls13/hello_retry_request @@ -157,6 +157,7 @@ ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniquei long-session-id mini-x509-callbacks-intr mini-dtls-lowmtu set_x509_key_file-late \ crlverify mini-dtls-discard init_fds mini-record-failure \ tls12-rehandshake-cert-2 custom-urls set_x509_key_mem set_x509_key_file \ + tls12-rehandshake-cert-auto eagain-auto-auth \ mini-chain-unsorted x509-verify-with-crl mini-dtls-mtu privkey-verify-broken \ mini-dtls-record-asym key-import-export priority-set priority-set2 \ pubkey-import-export sign-is-secure spki spki-abstract rsa-rsa-pss \ @@ -226,6 +227,7 @@ str_idna_LDADD = $(CMOCKA_LDADD) tls10_prf_LDADD = $(CMOCKA_LDADD) tls12_prf_LDADD = $(CMOCKA_LDADD) eagain_LDADD = $(CMOCKA_LDADD) +eagain_auto_auth_LDADD = $(CMOCKA_LDADD) tls12_rehandshake_cert_LDADD = $(CMOCKA_LDADD) gnutls_record_overhead_CPPFLAGS = $(AM_CPPFLAGS) \ diff --git a/tests/eagain-auto-auth.c b/tests/eagain-auto-auth.c new file mode 100644 index 0000000000..441e7c02e4 --- /dev/null +++ b/tests/eagain-auto-auth.c @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2008-2012 Free Software Foundation, Inc. + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Simon Josefsson, Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * 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> +#include <string.h> +#include <errno.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <gnutls/abstract.h> + +#define RANDOMIZE +#include "cert-common.h" +#include "cmocka-common.h" + +/* This tests operation under non-blocking mode in TLS1.2/TLS1.3 + * rekey/rehandshake. + */ +static void tls_log_func(int level, const char *str) +{ + fprintf(stderr, "<%d>| %s", level, str); +} + +#define MAX_BUF 1024 +#define MSG "Hello TLS, and hi and how are you and more data here... and more... and even more and even more more data..." + +static unsigned int cert_asked = 0; + +static int cert_callback(gnutls_session_t session, + const gnutls_datum_t * req_ca_rdn, int nreqs, + const gnutls_pk_algorithm_t * sign_algos, + int sign_algos_length, gnutls_pcert_st ** pcert, + unsigned int *pcert_length, gnutls_privkey_t * pkey) +{ + cert_asked = 1; + *pcert_length = 0; + *pcert = NULL; + *pkey = NULL; + + return 0; +} + +static void async_handshake(void **glob_state, const char *prio, unsigned rehsk) +{ + /* Server stuff. */ + gnutls_certificate_credentials_t serverx509cred; + gnutls_session_t server; + int sret, cret; + /* Client stuff. */ + gnutls_certificate_credentials_t clientx509cred; + gnutls_session_t client; + /* Need to enable anonymous KX specifically. */ + char buffer[MAX_BUF + 1]; + int ret, transferred = 0, msglen; + + /* General init. */ + reset_buffers(); + cert_asked = 0; + gnutls_global_init(); + gnutls_global_set_log_function(tls_log_func); + + /* Init server */ + assert_return_code(gnutls_certificate_allocate_credentials(&serverx509cred), 0); + assert_return_code(gnutls_certificate_set_x509_key_mem(serverx509cred, + &server_cert, &server_key, + GNUTLS_X509_FMT_PEM), 0); + ret = gnutls_init(&server, GNUTLS_SERVER|GNUTLS_POST_HANDSHAKE_AUTH); + assert_return_code(ret, 0); + + ret = + gnutls_priority_set_direct(server, + prio, + NULL); + assert_return_code(ret, 0); + + + ret = gnutls_credentials_set(server, GNUTLS_CRD_CERTIFICATE, serverx509cred); + assert_return_code(ret, 0); + + gnutls_transport_set_push_function(server, server_push); + gnutls_transport_set_pull_function(server, server_pull); + gnutls_transport_set_ptr(server, server); + + /* Init client */ + + ret = gnutls_certificate_allocate_credentials(&clientx509cred); + assert_return_code(ret, 0); + + gnutls_certificate_set_retrieve_function2(clientx509cred, cert_callback); + + ret = gnutls_init(&client, GNUTLS_CLIENT|GNUTLS_AUTO_REAUTH|GNUTLS_POST_HANDSHAKE_AUTH); + ret = + gnutls_priority_set_direct(client, + prio, + NULL); + assert_return_code(ret, 0); + + ret = gnutls_credentials_set(client, GNUTLS_CRD_CERTIFICATE, clientx509cred); + assert_return_code(ret, 0); + + gnutls_transport_set_push_function(client, client_push); + gnutls_transport_set_pull_function(client, client_pull); + gnutls_transport_set_ptr(client, client); + + HANDSHAKE(client, server); + + if (rehsk == 1) { + char b[1]; + unsigned hstarted = 0; + + do { + sret = gnutls_rehandshake(server); + } while (sret == GNUTLS_E_AGAIN || sret == GNUTLS_E_INTERRUPTED); + assert_true(sret == 0); + assert_true(gnutls_record_get_direction(server)==1); + + sret = cret = GNUTLS_E_AGAIN; + do { + if (!hstarted) { + sret = gnutls_record_recv(server, b, 1); + if (sret == GNUTLS_E_INTERRUPTED) sret = GNUTLS_E_AGAIN; + + if (sret == GNUTLS_E_REHANDSHAKE) { + hstarted = 1; + sret = GNUTLS_E_AGAIN; + } + assert_true(sret == GNUTLS_E_AGAIN); + } + + if (sret == GNUTLS_E_AGAIN && hstarted) { + sret = gnutls_handshake (server); + if (sret == GNUTLS_E_INTERRUPTED) sret = GNUTLS_E_AGAIN; + assert_true(sret == GNUTLS_E_AGAIN || sret == 0); + } + + /* we are done in client side */ + if (hstarted && gnutls_record_get_direction(client) == 0 && to_client_len == 0) + cret = 0; + + if (cret == GNUTLS_E_AGAIN) { + cret = gnutls_record_recv(client, b, 1); + if (cret == GNUTLS_E_INTERRUPTED) cret = GNUTLS_E_AGAIN; + } + assert_true(cret == GNUTLS_E_AGAIN || cret >= 0); + + } while (cret == GNUTLS_E_AGAIN || sret == GNUTLS_E_AGAIN); + assert_true(hstarted != 0); + } else { + char b[1]; + + gnutls_certificate_server_set_request(server, GNUTLS_CERT_REQUEST); + + do { + sret = gnutls_reauth(server, 0); + } while (sret == GNUTLS_E_INTERRUPTED); + + assert_true(sret == GNUTLS_E_AGAIN || sret >= 0); + + cret = GNUTLS_E_AGAIN; + do { + if (cret == GNUTLS_E_AGAIN) { + cret = gnutls_record_recv(client, b, 1); + if (cret == GNUTLS_E_INTERRUPTED) cret = GNUTLS_E_AGAIN; + } + + if (sret == GNUTLS_E_AGAIN) { + sret = gnutls_reauth(server, 0); + if (sret == GNUTLS_E_INTERRUPTED) sret = GNUTLS_E_AGAIN; + } + + /* we are done in client side */ + if (gnutls_record_get_direction(client) == 0 && to_client_len == 0) + cret = 0; + } while (cret == GNUTLS_E_AGAIN || sret == GNUTLS_E_AGAIN); + } + assert_return_code(cret, 0); + assert_return_code(sret, 0); + assert_return_code(cert_asked, 1); + + msglen = strlen(MSG); + TRANSFER(client, server, MSG, msglen, buffer, MAX_BUF); + + assert_true(gnutls_bye(client, GNUTLS_SHUT_WR)>=0); + assert_true(gnutls_bye(server, GNUTLS_SHUT_WR)>=0); + + gnutls_deinit(client); + gnutls_deinit(server); + + gnutls_certificate_free_credentials(serverx509cred); + gnutls_certificate_free_credentials(clientx509cred); + + gnutls_global_deinit(); +} + +static void tls12_async_handshake(void **glob_state) +{ + async_handshake(glob_state, "NORMAL:-VERS-ALL:+VERS-TLS1.2", 1); +} + +static void tls13_async_handshake(void **glob_state) +{ + async_handshake(glob_state, "NORMAL:-VERS-ALL:+VERS-TLS1.3", 0); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(tls12_async_handshake), + cmocka_unit_test(tls13_async_handshake), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/tls12-rehandshake-cert-auto.c b/tests/tls12-rehandshake-cert-auto.c new file mode 100644 index 0000000000..88975c9ed4 --- /dev/null +++ b/tests/tls12-rehandshake-cert-auto.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2014 Nikos Mavrogiannopoulos + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GnuTLS; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> + +#if defined(_WIN32) + +int main() +{ + 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/dtls.h> +#include <signal.h> +#include <assert.h> + +#include "utils.h" +#include "cert-common.h" + +/* This program tests server initiated rehandshake when + * handled transparently by the client. + */ + +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); +} + + +#define MAX_BUF 1024 + +static void client(int fd) +{ + int ret; + char buffer[MAX_BUF + 1]; + gnutls_anon_client_credentials_t anoncred; + gnutls_certificate_credentials_t x509_cred; + gnutls_session_t session; + /* Need to enable anonymous KX specifically. */ + + global_init(); + memset(buffer, 2, sizeof(buffer)); + + if (debug) { + gnutls_global_set_log_function(client_log_func); + gnutls_global_set_log_level(4); + } + + gnutls_anon_allocate_client_credentials(&anoncred); + gnutls_certificate_allocate_credentials(&x509_cred); + + /* Initialize TLS session + */ + gnutls_init(&session, GNUTLS_CLIENT|GNUTLS_AUTO_REAUTH); + gnutls_handshake_set_timeout(session, 20 * 1000); + + /* Use default priorities */ + gnutls_priority_set_direct(session, "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.1:+VERS-TLS1.2", NULL); + + /* put the anonymous credentials to the current session + */ + gnutls_credentials_set(session, GNUTLS_CRD_ANON, anoncred); + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + gnutls_transport_set_int(session, fd); + + /* 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\n"); + gnutls_perror(ret); + exit(1); + } else { + if (debug) + success("client: Handshake was completed\n"); + } + + if (debug) + success("client: TLS version is: %s\n", + gnutls_protocol_get_name + (gnutls_protocol_get_version(session))); + + do { + ret = + gnutls_record_recv(session, buffer, + MAX_BUF); + } while (ret == GNUTLS_E_AGAIN + || ret == GNUTLS_E_INTERRUPTED); + + assert(ret == 0); + + gnutls_bye(session, GNUTLS_SHUT_WR); + close(fd); + + gnutls_deinit(session); + + gnutls_anon_free_client_credentials(anoncred); + gnutls_certificate_free_credentials(x509_cred); + + gnutls_global_deinit(); +} + +/* These are global */ +pid_t child; + +static void server(int fd) +{ + int ret; + char buffer[MAX_BUF + 1]; + gnutls_session_t session; + gnutls_anon_server_credentials_t anoncred; + gnutls_certificate_credentials_t x509_cred; + + /* this must be called once in the program + */ + global_init(); + memset(buffer, 0, sizeof(buffer)); + + if (debug) { + gnutls_global_set_log_function(server_log_func); + gnutls_global_set_log_level(4); + } + + gnutls_certificate_allocate_credentials(&x509_cred); + gnutls_certificate_set_x509_key_mem(x509_cred, &server_cert, + &server_key, GNUTLS_X509_FMT_PEM); + + gnutls_anon_allocate_server_credentials(&anoncred); + + gnutls_init(&session, GNUTLS_SERVER); + gnutls_handshake_set_timeout(session, 20 * 1000); + + /* avoid calling all the priority functions, since the defaults + * are adequate. + */ + gnutls_priority_set_direct(session, "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.1:+VERS-TLS1.2", NULL); + + gnutls_credentials_set(session, GNUTLS_CRD_ANON, anoncred); + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + if (ret < 0) { + close(fd); + gnutls_deinit(session); + fail("server: Handshake has failed (%s)\n\n", + gnutls_strerror(ret)); + } + if (debug) + success("server: Handshake was completed\n"); + + if (debug) + success("server: TLS version is: %s\n", + gnutls_protocol_get_name + (gnutls_protocol_get_version(session))); + + if (debug) + success("server: sending rehandshake request\n"); + + do { + ret = gnutls_rehandshake(session); + } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + if (ret < 0) { + fail("Error sending %d byte packet: %s\n", + (int)sizeof(buffer), gnutls_strerror(ret)); + } + + if (debug) + success("server: starting handshake\n"); + + ret = gnutls_handshake(session); + if (ret < 0) { + fail("server: didn't complete handshake: %s\n", gnutls_strerror(ret)); + } + + if (debug) + success("server: re-handshake is complete\n"); + + do { + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + } while(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + close(fd); + gnutls_deinit(session); + + gnutls_anon_free_server_credentials(anoncred); + gnutls_certificate_free_credentials(x509_cred); + + gnutls_global_deinit(); + + if (debug) + success("server: finished\n"); +} + +void doit(void) +{ + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + int fd[2]; + int ret; + int status = 0; + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if (ret < 0) { + perror("socketpair"); + exit(1); + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork"); + exit(1); + } + + if (child) { + /* parent */ + close(fd[1]); + server(fd[0]); + wait(&status); + check_wait_status(status); + } else { + close(fd[0]); + client(fd[1]); + exit(0); + } +} + +#endif /* _WIN32 */ diff --git a/tests/tls13/post-handshake-with-cert-auto.c b/tests/tls13/post-handshake-with-cert-auto.c new file mode 100644 index 0000000000..9d920da80c --- /dev/null +++ b/tests/tls13/post-handshake-with-cert-auto.c @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2017-2018 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * GnuTLS is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuTLS 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 + * 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() +{ + 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/dtls.h> +#include <signal.h> +#include <assert.h> + +#include "cert-common.h" +#include "tls13/ext-parse.h" +#include "utils.h" + +#define MAX_AUTHS 4 + +/* This program tests whether the Post Handshake Auth extension is + * present in the client hello, and whether it is missing from server + * hello. In addition it contains basic functionality test for + * post handshake authentication. + */ + +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); +} + +#define MAX_BUF 1024 +#define MAX_APP_DATA 3 + +static void client(int fd, unsigned send_cert, unsigned max_auths) +{ + int ret; + gnutls_certificate_credentials_t x509_cred; + gnutls_session_t session; + char buf[64]; + unsigned i; + + global_init(); + + if (debug) { + gnutls_global_set_log_function(client_log_func); + gnutls_global_set_log_level(7); + } + + assert(gnutls_certificate_allocate_credentials(&x509_cred)>=0); + + /* Initialize TLS session + */ + assert(gnutls_init(&session, GNUTLS_CLIENT|GNUTLS_POST_HANDSHAKE_AUTH|GNUTLS_AUTO_REAUTH)>=0); + + gnutls_handshake_set_timeout(session, 20 * 1000); + + ret = gnutls_priority_set_direct(session, "NORMAL:-VERS-ALL:+VERS-TLS1.3:+VERS-TLS1.2:+VERS-TLS1.0", NULL); + if (ret < 0) + fail("cannot set TLS 1.3 priorities\n"); + + + if (send_cert) { + assert(gnutls_certificate_set_x509_key_mem(x509_cred, &cli_ca3_cert, + &cli_ca3_key, + GNUTLS_X509_FMT_PEM)>=0); + } + + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + gnutls_transport_set_int(session, fd); + + /* Perform the TLS handshake + */ + do { + ret = gnutls_handshake(session); + } + while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret != 0) + fail("handshake failed: %s\n", gnutls_strerror(ret)); + + if (debug) + success("client handshake completed\n"); + + gnutls_record_set_timeout(session, 20 * 1000); + + for (i=0;i<max_auths;i++) { + if (debug) + success("waiting for auth nr %d\n", i); + + do { + ret = gnutls_record_recv(session, buf, sizeof(buf)); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (ret < 0) + fail("client: gnutls_record_recv did not succeed as expected: %s\n", gnutls_strerror(ret)); + } + + assert(ret == 0); + + do { + ret = gnutls_bye(session, GNUTLS_SHUT_WR); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + close(fd); + + gnutls_deinit(session); + + gnutls_certificate_free_credentials(x509_cred); + + gnutls_global_deinit(); +} + +static unsigned client_hello_ok = 0; +static unsigned server_hello_ok = 0; + +#define TLS_EXT_POST_HANDSHAKE 49 + +static void parse_ext(void *priv, gnutls_datum_t *msg) +{ + if (msg->size != 0) { + fail("error in extension length: %d\n", (int)msg->size); + } +} + +static int hellos_callback(gnutls_session_t session, unsigned int htype, + unsigned post, unsigned int incoming, const gnutls_datum_t *msg) +{ + if (htype == GNUTLS_HANDSHAKE_SERVER_HELLO && post == GNUTLS_HOOK_POST) { + if (find_server_extension(msg, TLS_EXT_POST_HANDSHAKE, NULL, NULL)) { + fail("Post handshake extension seen in server hello!\n"); + } + server_hello_ok = 1; + + return GNUTLS_E_INTERRUPTED; + } + + if (htype != GNUTLS_HANDSHAKE_CLIENT_HELLO || post != GNUTLS_HOOK_PRE) + return 0; + + if (find_client_extension(msg, TLS_EXT_POST_HANDSHAKE, NULL, parse_ext)) + client_hello_ok = 1; + else + fail("Post handshake extension NOT seen in client hello!\n"); + + return 0; +} + +static void server(int fd, int err, int type, unsigned max_auths) +{ + int ret; + char buffer[MAX_BUF + 1]; + gnutls_session_t session; + gnutls_certificate_credentials_t x509_cred; + unsigned i, retries; + + /* this must be called once in the program + */ + global_init(); + memset(buffer, 0, sizeof(buffer)); + + if (debug) { + gnutls_global_set_log_function(server_log_func); + gnutls_global_set_log_level(6); + } + + gnutls_certificate_allocate_credentials(&x509_cred); + gnutls_certificate_set_x509_key_mem(x509_cred, &server_cert, + &server_key, + GNUTLS_X509_FMT_PEM); + + gnutls_init(&session, GNUTLS_SERVER|GNUTLS_POST_HANDSHAKE_AUTH); + + gnutls_handshake_set_timeout(session, 20 * 1000); + gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_ANY, + GNUTLS_HOOK_BOTH, + hellos_callback); + + /* avoid calling all the priority functions, since the defaults + * are adequate. + */ + gnutls_priority_set_direct(session, "NORMAL:+VERS-TLS1.3", NULL); + + gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + gnutls_transport_set_int(session, fd); + + do { + ret = gnutls_handshake(session); + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret != 0) + fail("handshake failed: %s\n", gnutls_strerror(ret)); + + if (!(gnutls_session_get_flags(session) & GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH)) { + fail("server: session flags did not contain GNUTLS_SFLAGS_POST_HANDSHAKE_AUTH\n"); + } + + + if (client_hello_ok == 0) { + fail("server: did not verify the client hello\n"); + } + + if (server_hello_ok == 0) { + fail("server: did not verify the server hello contents\n"); + } + + if (debug) + success("server handshake completed\n"); + + gnutls_certificate_server_set_request(session, type); + + /* i = 0 */ + /* ask peer for re-authentication */ + retries = 0; + do { + do { + ret = gnutls_reauth(session, 0); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (ret == GNUTLS_E_GOT_APPLICATION_DATA) { + int ret2; + do { + ret2 = gnutls_record_recv(session, buffer, sizeof(buffer)); + } while (ret2 == GNUTLS_E_AGAIN || ret2 == GNUTLS_E_INTERRUPTED); + + if (ret2 < 0) + fail("error receiving app data: %s\n", gnutls_strerror(ret2)); + + /* sender memsets the message with the retry attempt */ + assert((uint8_t)buffer[0] == retries); + assert(retries < MAX_APP_DATA); + } + + retries++; + } while (ret == GNUTLS_E_GOT_APPLICATION_DATA); + + if (err) { + if (ret != err) + fail("server: expected error %s, got: %s\n", gnutls_strerror(err), + gnutls_strerror(ret)); + } else if (ret != 0) + fail("server: gnutls_reauth did not succeed as expected: %s\n", gnutls_strerror(ret)); + + + for (i=1;i<max_auths;i++) { + /* ask peer for re-authentication */ + do { + ret = gnutls_reauth(session, 0); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + if (err) { + if (ret != err) + fail("server: expected error %s, got: %s\n", gnutls_strerror(err), + gnutls_strerror(ret)); + } else if (ret != 0) + fail("server: gnutls_reauth did not succeed as expected: %s\n", gnutls_strerror(ret)); + } + + do { + ret = gnutls_bye(session, GNUTLS_SHUT_RDWR); + } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED); + + close(fd); + gnutls_deinit(session); + + gnutls_certificate_free_credentials(x509_cred); + + gnutls_global_deinit(); + + if (debug) + success("server: client/server hello were verified\n"); +} + +static +void start(const char *name, int err, int type, unsigned max_auths, unsigned send_cert) +{ + int fd[2]; + int ret; + pid_t child; + int status = 0; + + success("testing %s\n", name); + + client_hello_ok = 0; + server_hello_ok = 0; + + signal(SIGCHLD, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd); + if (ret < 0) { + perror("socketpair"); + exit(1); + } + + child = fork(); + if (child < 0) { + perror("fork"); + fail("fork"); + exit(1); + } + + if (child) { + /* parent */ + close(fd[1]); + server(fd[0], err, type, max_auths); + kill(child, SIGTERM); + wait(&status); + check_wait_status(status); + } else { + close(fd[0]); + client(fd[1], send_cert, max_auths); + exit(0); + } + +} + +void doit(void) +{ + start("multi-reauth", 0, GNUTLS_CERT_REQUIRE, MAX_AUTHS, 1); + start("reauth-require with no-cert", GNUTLS_E_NO_CERTIFICATE_FOUND, GNUTLS_CERT_REQUIRE, 1, 0); + start("reauth-request with no-cert", 0, GNUTLS_CERT_REQUEST, 1, 0); +} +#endif /* _WIN32 */ |