summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNikos Mavrogiannopoulos <nmav@redhat.com>2018-02-22 16:12:55 +0100
committerNikos Mavrogiannopoulos <nmav@redhat.com>2018-03-08 12:53:57 +0100
commit4a5f1b2953c1c773ff1dd3e9cc77bba605698bac (patch)
tree0c45466c830547e458194b59af1717d9933d12fe
parentb2fccff200c236958cc3173c390e50460e1628be (diff)
downloadgnutls-4a5f1b2953c1c773ff1dd3e9cc77bba605698bac.tar.gz
record: ignore any ChangeCipherSpec messages under TLS1.3 handshake
Also send ChangeCipherSpec messages under TLS1.3 handshake. This is a draft-ietf-tls-tls13-22 change. Resolves #395 Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
-rw-r--r--lib/gnutls_int.h6
-rw-r--r--lib/handshake-tls13.c112
-rw-r--r--lib/handshake.c44
-rw-r--r--lib/handshake.h2
-rw-r--r--lib/record.c29
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/tls13/change_cipher_spec.c344
7 files changed, 474 insertions, 65 deletions
diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
index baa9c14589..c4d8524a27 100644
--- a/lib/gnutls_int.h
+++ b/lib/gnutls_int.h
@@ -134,6 +134,9 @@ typedef struct {
#define GNUTLS_MASTER_SIZE 48
#define GNUTLS_RANDOM_SIZE 32
+/* Enable: Appendix D4. Middlebox Compatibility Mode */
+#define TLS13_APPENDIX_D4 1
+
/* DTLS */
#define DTLS_RETRANS_TIMEOUT 1000
@@ -254,9 +257,10 @@ typedef enum handshake_state_t { STATE0 = 0, STATE1, STATE2,
STATE15, STATE16, STATE17, STATE18, STATE19,
STATE20 = 20, STATE21, STATE22,
STATE30 = 30, STATE31, STATE40 = 40, STATE41, STATE50 = 50,
- STATE90=90, STATE91, STATE92, STATE93,
+ STATE90=90, STATE91, STATE92, STATE93, STATE99=99,
STATE100=100, STATE101, STATE102, STATE103, STATE104,
STATE105, STATE106, STATE107, STATE108, STATE109, STATE110,
+ STATE111,
STATE150 /* key update */
} handshake_state_t;
diff --git a/lib/handshake-tls13.c b/lib/handshake-tls13.c
index 721f334eca..edb6e80574 100644
--- a/lib/handshake-tls13.c
+++ b/lib/handshake-tls13.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 Red Hat, Inc.
+ * Copyright (C) 2017-2018 Red Hat, Inc.
*
* Author: Nikos Mavrogiannopoulos
*
@@ -78,62 +78,72 @@ int _gnutls13_handshake_client(gnutls_session_t session)
int ret = 0;
switch (STATE) {
+ case STATE99:
case STATE100:
+#ifdef TLS13_APPENDIX_D4
+ /* We send it before keys are generated. That works because CCS
+ * is always being cached and queued and not being sent directly */
+ ret = _gnutls_send_change_cipher_spec(session, AGAIN(STATE100));
+ STATE = STATE100;
+ IMED_RET("send change cipher spec", ret, 0);
+#endif
+ /* fall through */
+ case STATE101:
ret =
generate_hs_traffic_keys(session);
- STATE = STATE100;
+ STATE = STATE101;
IMED_RET("generate session keys", ret, 0);
/* fall through */
- case STATE101:
+ case STATE102:
ret = _gnutls13_recv_encrypted_extensions(session);
- STATE = STATE101;
+ STATE = STATE102;
IMED_RET("recv encrypted extensions", ret, 0);
/* fall through */
- case STATE102:
+ case STATE103:
ret = _gnutls13_recv_certificate_request(session);
- STATE = STATE102;
+ STATE = STATE103;
IMED_RET("recv certificate request", ret, 0);
/* fall through */
- case STATE103:
+ case STATE104:
ret = _gnutls13_recv_certificate(session);
- STATE = STATE103;
+ STATE = STATE104;
IMED_RET("recv certificate", ret, 0);
/* fall through */
- case STATE104:
+ case STATE105:
ret = _gnutls13_recv_certificate_verify(session);
- STATE = STATE104;
+ STATE = STATE105;
IMED_RET("recv server certificate verify", ret, 0);
/* fall through */
- case STATE105:
+ case STATE106:
ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT);
- STATE = STATE105;
+ STATE = STATE106;
if (ret < 0)
return gnutls_assert_val(ret);
FALLTHROUGH;
- case STATE106:
- ret = _gnutls13_recv_finished(session);
- STATE = STATE106;
- IMED_RET("recv finished", ret, 0);
- /* fall through */
case STATE107:
- ret = _gnutls13_send_certificate(session, AGAIN(STATE107));
+ ret = _gnutls13_recv_finished(session);
STATE = STATE107;
- IMED_RET("send certificate", ret, 0);
+ IMED_RET("recv finished", ret, 0);
/* fall through */
case STATE108:
- ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE108));
+ ret = _gnutls13_send_certificate(session, AGAIN(STATE108));
STATE = STATE108;
- IMED_RET("send certificate verify", ret, 0);
+ IMED_RET("send certificate", ret, 0);
/* fall through */
case STATE109:
- ret = _gnutls13_send_finished(session, AGAIN(STATE109));
+ ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE109));
STATE = STATE109;
- IMED_RET("send finished", ret, 0);
+ IMED_RET("send certificate verify", ret, 0);
/* fall through */
case STATE110:
+ ret = _gnutls13_send_finished(session, AGAIN(STATE110));
+ STATE = STATE110;
+ IMED_RET("send finished", ret, 0);
+ /* fall through */
+ case STATE111:
ret =
generate_ap_traffic_keys(session);
- STATE = STATE110;
+ STATE = STATE111;
IMED_RET("generate app keys", ret, 0);
STATE = STATE0;
@@ -243,7 +253,7 @@ int _gnutls13_handshake_server(gnutls_session_t session)
/* this is triggered by post_client_hello, and instructs the
* handshake to proceed but be put on hold */
ret = GNUTLS_E_INTERRUPTED;
- STATE = STATE100; /* hello already parsed -> move on */
+ STATE = STATE99; /* hello already parsed -> move on */
} else {
STATE = STATE92;
}
@@ -255,62 +265,70 @@ int _gnutls13_handshake_server(gnutls_session_t session)
STATE = STATE93;
IMED_RET("send hello", ret, 0);
/* fall through */
+ case STATE99:
case STATE100:
- ret =
- generate_hs_traffic_keys(session);
+#ifdef TLS13_APPENDIX_D4
+ ret = _gnutls_send_change_cipher_spec(session, AGAIN(STATE100));
STATE = STATE100;
- IMED_RET("generate session keys", ret, 0);
+ IMED_RET("send change cipher spec", ret, 0);
+#endif
/* fall through */
case STATE101:
- ret = _gnutls13_send_encrypted_extensions(session, AGAIN(STATE101));
+ ret =
+ generate_hs_traffic_keys(session);
STATE = STATE101;
- IMED_RET("send encrypted extensions", ret, 0);
+ IMED_RET("generate session keys", ret, 0);
/* fall through */
case STATE102:
- ret = _gnutls13_send_certificate_request(session, AGAIN(STATE102));
+ ret = _gnutls13_send_encrypted_extensions(session, AGAIN(STATE102));
STATE = STATE102;
- IMED_RET("send certificate request", ret, 0);
+ IMED_RET("send encrypted extensions", ret, 0);
/* fall through */
case STATE103:
- ret = _gnutls13_send_certificate(session, AGAIN(STATE103));
+ ret = _gnutls13_send_certificate_request(session, AGAIN(STATE103));
STATE = STATE103;
- IMED_RET("send certificate", ret, 0);
+ IMED_RET("send certificate request", ret, 0);
/* fall through */
case STATE104:
- ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE104));
+ ret = _gnutls13_send_certificate(session, AGAIN(STATE104));
STATE = STATE104;
- IMED_RET("send certificate verify", ret, 0);
+ IMED_RET("send certificate", ret, 0);
/* fall through */
case STATE105:
- ret = _gnutls13_send_finished(session, AGAIN(STATE105));
+ ret = _gnutls13_send_certificate_verify(session, AGAIN(STATE105));
STATE = STATE105;
- IMED_RET("send finished", ret, 0);
+ IMED_RET("send certificate verify", ret, 0);
/* fall through */
case STATE106:
- ret = _gnutls13_recv_certificate(session);
+ ret = _gnutls13_send_finished(session, AGAIN(STATE106));
STATE = STATE106;
- IMED_RET("recv certificate", ret, 0);
+ IMED_RET("send finished", ret, 0);
/* fall through */
case STATE107:
- ret = _gnutls13_recv_certificate_verify(session);
+ ret = _gnutls13_recv_certificate(session);
STATE = STATE107;
- IMED_RET("recv certificate verify", ret, 0);
+ IMED_RET("recv certificate", ret, 0);
/* fall through */
case STATE108:
- ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT);
+ ret = _gnutls13_recv_certificate_verify(session);
STATE = STATE108;
+ IMED_RET("recv certificate verify", ret, 0);
+ /* fall through */
+ case STATE109:
+ ret = _gnutls_run_verify_callback(session, GNUTLS_CLIENT);
+ STATE = STATE109;
if (ret < 0)
return gnutls_assert_val(ret);
/* fall through */
- case STATE109:
+ case STATE110:
ret = _gnutls13_recv_finished(session);
- STATE = STATE109;
+ STATE = STATE110;
IMED_RET("recv finished", ret, 0);
/* fall through */
- case STATE110:
+ case STATE111:
ret =
generate_ap_traffic_keys(session);
- STATE = STATE110;
+ STATE = STATE111;
IMED_RET("generate app keys", ret, 0);
STATE = STATE0;
diff --git a/lib/handshake.c b/lib/handshake.c
index 179fcb8009..9b3fe6f648 100644
--- a/lib/handshake.c
+++ b/lib/handshake.c
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2000-2016 Free Software Foundation, Inc.
- * Copyright (C) 2015-2017 Red Hat, Inc.
+ * Copyright (C) 2015-2018 Red Hat, Inc.
*
* Author: Nikos Mavrogiannopoulos
*
@@ -1068,9 +1068,22 @@ inline
if ((session->internals.h_type == type
|| session->internals.h_type == GNUTLS_HANDSHAKE_ANY)
&& (session->internals.h_post == post
- || session->internals.h_post == GNUTLS_HOOK_BOTH))
+ || session->internals.h_post == GNUTLS_HOOK_BOTH)) {
+
+ /* internal API for testing: when we are expected to
+ * wait for GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC, we
+ * do so, but not when doing for all messages. The
+ * reason is that change cipher specs are not handshake
+ * messages, and we don't support waiting for them
+ * consistently (only sending is tracked, not receiving).
+ */
+ if (type == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC &&
+ session->internals.h_type != GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC)
+ return 0;
+
return session->internals.h_hook(session, type,
post, incoming, &msg);
+ }
}
return 0;
}
@@ -1553,7 +1566,7 @@ set_client_ciphersuite(gnutls_session_t session, uint8_t suite[2])
*/
static int
client_check_if_resuming(gnutls_session_t session,
- uint8_t * session_id, int session_id_len)
+ uint8_t * session_id, int session_id_len)
{
char buf[2 * GNUTLS_MAX_SESSION_ID_SIZE + 1];
int ret;
@@ -2556,7 +2569,7 @@ static int handshake_client(gnutls_session_t session)
const version_entry_st *ver;
reset:
- if (STATE >= STATE100)
+ if (STATE >= STATE99)
return _gnutls13_handshake_client(session);
switch (STATE) {
@@ -2605,7 +2618,7 @@ static int handshake_client(gnutls_session_t session)
case STATE4:
ver = get_version(session);
if (ver->tls13_sem) { /* TLS 1.3 state machine */
- STATE = STATE100;
+ STATE = STATE99;
goto reset;
}
@@ -2778,7 +2791,7 @@ static int handshake_client(gnutls_session_t session)
/* This function is to be called if the handshake was successfully
* completed. This sends a Change Cipher Spec packet to the peer.
*/
-static ssize_t send_change_cipher_spec(gnutls_session_t session, int again)
+ssize_t _gnutls_send_change_cipher_spec(gnutls_session_t session, int again)
{
uint8_t *data;
mbuffer_st *bufel;
@@ -2786,7 +2799,7 @@ static ssize_t send_change_cipher_spec(gnutls_session_t session, int again)
const version_entry_st *vers;
if (again == 0) {
- bufel = _gnutls_handshake_alloc(session, 1);
+ bufel = _gnutls_handshake_alloc(session, 3); /* max for DTLS0.9 */
if (bufel == NULL)
return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
@@ -2809,6 +2822,13 @@ static ssize_t send_change_cipher_spec(gnutls_session_t session, int again)
session->internals.dtls.hsk_write_seq++;
}
+ ret = call_hook_func(session, GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC, GNUTLS_HOOK_PRE, 0,
+ data, 1);
+ if (ret < 0) {
+ _mbuffer_xfree(&bufel);
+ return gnutls_assert_val(ret);
+ }
+
ret =
_gnutls_handshake_io_cache_int(session,
GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC,
@@ -2818,6 +2838,12 @@ static ssize_t send_change_cipher_spec(gnutls_session_t session, int again)
return gnutls_assert_val(ret);
}
+ ret = call_hook_func(session, GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC, GNUTLS_HOOK_POST, 0,
+ data, 1);
+ if (ret < 0) {
+ return gnutls_assert_val(ret);
+ }
+
_gnutls_handshake_log("REC[%p]: Sent ChangeCipherSpec\n",
session);
}
@@ -2836,7 +2862,7 @@ static int send_handshake_final(gnutls_session_t session, int init)
switch (FINAL_STATE) {
case STATE0:
case STATE1:
- ret = send_change_cipher_spec(session, FAGAIN(STATE1));
+ ret = _gnutls_send_change_cipher_spec(session, FAGAIN(STATE1));
FINAL_STATE = STATE0;
if (ret < 0) {
@@ -3021,7 +3047,7 @@ static int handshake_server(gnutls_session_t session)
ver = get_version(session);
if (ver->tls13_sem) { /* TLS 1.3 state machine */
- STATE = STATE100;
+ STATE = STATE99;
goto reset;
}
diff --git a/lib/handshake.h b/lib/handshake.h
index 109f1247c8..0084789bcd 100644
--- a/lib/handshake.h
+++ b/lib/handshake.h
@@ -64,6 +64,8 @@ int _gnutls_generate_session_id(uint8_t * session_id, uint8_t * len);
int _gnutls_gen_server_random(gnutls_session_t session, int version);
void _gnutls_set_client_random(gnutls_session_t session, uint8_t * rnd);
+ssize_t _gnutls_send_change_cipher_spec(gnutls_session_t session, int again);
+
int _gnutls_send_server_hello(gnutls_session_t session, int again);
int _gnutls_find_pk_algos_in_ciphersuites(uint8_t * data, int datalen);
diff --git a/lib/record.c b/lib/record.c
index 20dff2b3a7..a8ba45032d 100644
--- a/lib/record.c
+++ b/lib/record.c
@@ -1196,14 +1196,15 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type,
mbuffer_st *bufel = NULL, *decrypted = NULL;
gnutls_datum_t t;
int ret;
- unsigned int empty_fragments = 0;
+ unsigned int n_retries = 0;
record_parameters_st *record_params;
record_state_st *record_state;
struct tls_record_st record;
+ const version_entry_st *vers = get_version(session);
- begin:
+ begin:
- if (empty_fragments > DEFAULT_MAX_EMPTY_RECORDS) {
+ if (n_retries > DEFAULT_MAX_EMPTY_RECORDS) {
gnutls_assert();
return GNUTLS_E_TOO_MANY_EMPTY_PACKETS;
}
@@ -1264,6 +1265,18 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type,
if (bufel == NULL)
return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
+ if (vers && vers->tls13_sem && record.type == GNUTLS_CHANGE_CIPHER_SPEC &&
+ record.length == 1 && session->internals.handshake_in_progress) {
+ _gnutls_read_log("discarding change cipher spec in TLS1.3\n");
+ /* we use the same mechanism to retry as when
+ * receiving multiple empty TLS packets */
+ bufel =
+ _mbuffer_head_pop_first(&session->internals.
+ record_recv_buffer);
+ _mbuffer_xfree(&bufel);
+ n_retries++;
+ goto begin;
+ }
/* We allocate the maximum possible to allow few compressed bytes to expand to a
* full record. Moreover we add space for any pad and the MAC (in case
@@ -1363,7 +1376,7 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type,
*/
if (_mbuffer_get_udata_size(decrypted) == 0) {
_mbuffer_xfree(&decrypted);
- empty_fragments++;
+ n_retries++;
goto begin;
}
@@ -1388,7 +1401,7 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type,
return ret;
- discard:
+ discard:
session->internals.dtls.packets_dropped++;
/* discard the whole received fragment. */
@@ -1398,7 +1411,7 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type,
_mbuffer_xfree(&bufel);
return gnutls_assert_val(GNUTLS_E_AGAIN);
- sanity_check_error:
+ sanity_check_error:
if (IS_DTLS(session)) {
session->internals.dtls.packets_dropped++;
ret = gnutls_assert_val(GNUTLS_E_AGAIN);
@@ -1408,11 +1421,11 @@ _gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type,
session_unresumable(session);
session_invalidate(session);
- cleanup:
+ cleanup:
_mbuffer_xfree(&decrypted);
return ret;
- recv_error:
+ recv_error:
if (ret < 0
&& (gnutls_error_is_fatal(ret) == 0
|| ret == GNUTLS_E_TIMEDOUT))
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 66766b632c..36a493cdc7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -112,6 +112,8 @@ ctests += tls13/multi-ocsp
ctests += tls13/ocsp-client
+ctests += tls13/change_cipher_spec
+
ctests += mini-record-2 simple gnutls_hmac_fast set_pkcs12_cred cert certuniqueid tls-neg-ext-key \
mpi certificate_set_x509_crl dn parse_ca x509-dn x509-dn-decode record-sizes \
hostname-check cve-2008-4989 pkcs12_s2k chainverify record-sizes-range \
diff --git a/tests/tls13/change_cipher_spec.c b/tests/tls13/change_cipher_spec.c
new file mode 100644
index 0000000000..23519d9fd4
--- /dev/null
+++ b/tests/tls13/change_cipher_spec.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2017 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 <errno.h>
+
+#include "cert-common.h"
+#include "utils.h"
+
+/* This program tests whether the ChangeCipherSpec message
+ * is ignored during handshake.
+ */
+
+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);
+}
+
+static unsigned client_sent_ccs = 0;
+static unsigned server_sent_ccs = 0;
+
+static int cli_hsk_callback(gnutls_session_t session, unsigned int htype,
+ unsigned post, unsigned int incoming, const gnutls_datum_t *msg);
+
+static void client(int fd, unsigned ccs_check)
+{
+ int ret;
+ gnutls_certificate_credentials_t x509_cred;
+ gnutls_session_t session;
+ char buf[64];
+
+ global_init();
+ client_sent_ccs = 0;
+ server_sent_ccs = 0;
+
+ if (debug) {
+ gnutls_global_set_log_function(client_log_func);
+ gnutls_global_set_log_level(7);
+ }
+
+ gnutls_certificate_allocate_credentials(&x509_cred);
+
+ /* Initialize TLS session
+ */
+ gnutls_init(&session, GNUTLS_CLIENT|GNUTLS_POST_HANDSHAKE_AUTH);
+
+ gnutls_session_set_ptr(session, &ccs_check);
+ gnutls_handshake_set_timeout(session, 20 * 1000);
+ if (ccs_check) {
+ gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_ANY,
+ GNUTLS_HOOK_PRE,
+ cli_hsk_callback);
+ }
+
+ 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");
+
+
+ gnutls_certificate_set_x509_key_mem(x509_cred, &cli_ca3_cert,
+ &cli_ca3_key,
+ GNUTLS_X509_FMT_PEM);
+
+ /* put the anonymous credentials to the current session
+ */
+ 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));
+ success("client handshake completed\n");
+
+ do {
+ ret = gnutls_record_recv(session, buf, sizeof(buf));
+ } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+ if (ret < 0)
+ fail("client: recv did not succeed as expected: %s\n", gnutls_strerror(ret));
+
+ close(fd);
+
+ gnutls_deinit(session);
+
+ if (ccs_check) {
+ if (client_sent_ccs != 1) {
+ fail("client: did not sent CCS\n");
+ }
+ }
+
+ gnutls_certificate_free_credentials(x509_cred);
+
+ gnutls_global_deinit();
+}
+
+static int cli_hsk_callback(gnutls_session_t session, unsigned int htype,
+ unsigned post, unsigned int incoming, const gnutls_datum_t *msg)
+{
+ unsigned *p;
+ unsigned ccs_check;
+ static unsigned hello_received = 0;
+
+ p = gnutls_session_get_ptr(session);
+ ccs_check = *p;
+
+ assert(ccs_check != 0);
+ assert(post == GNUTLS_HOOK_PRE);
+
+ if (htype == GNUTLS_HANDSHAKE_CLIENT_HELLO && !incoming) {
+ hello_received = 1;
+
+ gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC,
+ GNUTLS_HOOK_PRE,
+ cli_hsk_callback);
+ }
+
+ if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC && !incoming && hello_received) {
+ client_sent_ccs++;
+ assert(msg->size == 1 && msg->data[0] == 0x01);
+ }
+
+
+ return 0;
+}
+
+static int hsk_callback(gnutls_session_t session, unsigned int htype,
+ unsigned post, unsigned int incoming, const gnutls_datum_t *msg)
+{
+ int ret;
+ int fd;
+ unsigned *p;
+ unsigned ccs_check;
+
+ p = gnutls_session_get_ptr(session);
+ ccs_check = *p;
+
+ assert(post == GNUTLS_HOOK_PRE);
+
+ if (!ccs_check) {
+ if (!incoming || htype == GNUTLS_HANDSHAKE_CLIENT_HELLO ||
+ htype == GNUTLS_HANDSHAKE_FINISHED)
+ return 0;
+
+ fd = gnutls_transport_get_int(session);
+
+ /* send change cipher spec */
+ do {
+ ret = send(fd, "\x14\x03\x03\x00\x01\x01", 6, 0);
+ } while(ret == -1 && (errno == EINTR || errno == EAGAIN));
+ } else { /* checking whether server received it */
+ if (htype == GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC && !incoming) {
+ server_sent_ccs++;
+ assert(msg->size == 1 && msg->data[0] == 0x01);
+ }
+ }
+ return 0;
+}
+
+static void server(int fd, unsigned ccs_check)
+{
+ int ret;
+ gnutls_session_t session;
+ gnutls_certificate_credentials_t x509_cred;
+
+ /* this must be called once in the program
+ */
+ global_init();
+
+ client_sent_ccs = 0;
+ server_sent_ccs = 0;
+
+ 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_init(&session, GNUTLS_SERVER);
+
+ gnutls_handshake_set_timeout(session, 20 * 1000);
+
+ if (ccs_check)
+ gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_CHANGE_CIPHER_SPEC,
+ GNUTLS_HOOK_PRE,
+ hsk_callback);
+ else
+ gnutls_handshake_set_hook_function(session, GNUTLS_HANDSHAKE_ANY,
+ GNUTLS_HOOK_PRE,
+ hsk_callback);
+
+ /* avoid calling all the priority functions, since the defaults
+ * are adequate.
+ */
+ assert(gnutls_priority_set_direct(session, "NORMAL:+VERS-TLS1.3", NULL) >= 0);
+ gnutls_session_set_ptr(session, &ccs_check);
+
+ 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));
+
+ success("server handshake completed\n");
+
+ gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
+ /* ask peer for re-authentication */
+ do {
+ ret = gnutls_record_send(session, "\x00", 1);
+ } while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
+
+ if (ret < 0)
+ fail("server: gnutls_record_send did not succeed as expected: %s\n", gnutls_strerror(ret));
+
+ close(fd);
+ gnutls_deinit(session);
+
+ gnutls_certificate_free_credentials(x509_cred);
+
+ if (ccs_check) {
+ if (server_sent_ccs != 1) {
+ fail("server: did not sent CCS\n");
+ }
+ }
+
+ gnutls_global_deinit();
+
+ if (debug)
+ success("server: client/server hello were verified\n");
+}
+
+static void ch_handler(int sig)
+{
+ int status;
+ wait(&status);
+ check_wait_status(status);
+ return;
+}
+
+static
+void start(unsigned ccs_check)
+{
+ int fd[2];
+ int ret;
+ pid_t child;
+
+ signal(SIGCHLD, ch_handler);
+
+ 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], ccs_check);
+ kill(child, SIGTERM);
+ } else {
+ close(fd[0]);
+ client(fd[1], ccs_check);
+ exit(0);
+ }
+}
+
+void doit(void)
+{
+ start(0);
+ start(1);
+}
+
+#endif /* _WIN32 */