// Copyright (C) 2022 Red Hat, Inc.
//
// Author: Frantisek Krenzelok
//
// 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, see .
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "cert-common.h"
#include "utils.h"
#if defined(_WIN32)
int main(void)
{
exit(77);
}
#else
#define MAX_BUF 1024
#define MSG "Hello world!"
#define HANDSHAKE(session, name, ret) \
{ \
do { \
ret = gnutls_handshake(session); \
} while (ret < 0 && gnutls_error_is_fatal(ret) == 0); \
if (ret < 0) { \
fail("%s: Handshake failed\n", name); \
goto end; \
} \
}
#define SEND_MSG(session, name, ret) \
{ \
do { \
ret = gnutls_record_send(session, MSG, \
strlen(MSG) + 1); \
} while (ret == GNUTLS_E_AGAIN || \
ret == GNUTLS_E_INTERRUPTED); \
if (ret < 0) { \
fail("%s: data sending has failed (%s)\n", name, \
gnutls_strerror(ret)); \
goto end; \
} \
}
#define RECV_MSG(session, name, buffer, buffer_len, ret) \
{ \
memset(buffer, 0, sizeof(buffer)); \
do { \
ret = gnutls_record_recv(session, buffer, \
sizeof(buffer)); \
} while (ret == GNUTLS_E_AGAIN || \
ret == GNUTLS_E_INTERRUPTED); \
if (ret == 0) { \
success("%s: Peer has closed the TLS connection\n", \
name); \
goto end; \
} else if (ret < 0) { \
fail("%s: Error -> %s\n", name, gnutls_strerror(ret)); \
goto end; \
} \
if (strncmp(buffer, MSG, ret)) { \
fail("%s: Message doesn't match\n", name); \
goto end; \
} \
}
#define KEY_UPDATE(session, name, peer_req, ret) \
{ \
do { \
ret = gnutls_session_key_update(session, peer_req); \
} while (ret == GNUTLS_E_AGAIN || \
ret == GNUTLS_E_INTERRUPTED); \
if (ret < 0) { \
fail("%s: key update has failed (%s)\n", name, \
gnutls_strerror(ret)); \
goto end; \
} \
}
#define CHECK_KTLS_ENABLED(session, ret) \
{ \
ret = gnutls_transport_is_ktls_enabled(session); \
if (!(ret & GNUTLS_KTLS_RECV)) { \
fail("client: KTLS was not properly initialized\n"); \
goto end; \
} \
}
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 void client(int fd, const char *prio, int pipe)
{
const char *name = "client";
int ret;
char foo;
char buffer[MAX_BUF + 1];
gnutls_certificate_credentials_t x509_cred;
gnutls_session_t session;
global_init();
if (debug) {
gnutls_global_set_log_function(client_log_func);
gnutls_global_set_log_level(7);
}
gnutls_certificate_allocate_credentials(&x509_cred);
gnutls_init(&session, GNUTLS_CLIENT);
gnutls_handshake_set_timeout(session, 0);
assert(gnutls_priority_set_direct(session, prio, NULL) >= 0);
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
gnutls_transport_set_int(session, fd);
HANDSHAKE(session, name, ret);
CHECK_KTLS_ENABLED(session, ret)
// Test 0: Try sending/receiving data
RECV_MSG(session, name, buffer, MAX_BUF + 1, ret)
SEND_MSG(session, name, ret)
CHECK_KTLS_ENABLED(session, ret)
// Test 1: Servers does key update
read(pipe, &foo, 1);
RECV_MSG(session, name, buffer, MAX_BUF + 1, ret)
SEND_MSG(session, name, ret)
CHECK_KTLS_ENABLED(session, ret)
// Test 2: Does key update witch request
read(pipe, &foo, 1);
RECV_MSG(session, name, buffer, MAX_BUF + 1, ret)
SEND_MSG(session, name, ret)
CHECK_KTLS_ENABLED(session, ret)
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
if (ret < 0) {
fail("client: error in closing session: %s\n",
gnutls_strerror(ret));
}
ret = 0;
end:
close(fd);
gnutls_deinit(session);
gnutls_certificate_free_credentials(x509_cred);
gnutls_global_deinit();
if (ret != 0)
exit(1);
}
pid_t child;
static void terminate(void)
{
assert(child);
kill(child, SIGTERM);
exit(1);
}
static void server(int fd, const char *prio, int pipe)
{
const char *name = "server";
int ret;
char bar = 0;
char buffer[MAX_BUF + 1];
gnutls_certificate_credentials_t x509_cred;
gnutls_session_t session;
global_init();
if (debug) {
gnutls_global_set_log_function(server_log_func);
gnutls_global_set_log_level(7);
}
gnutls_certificate_allocate_credentials(&x509_cred);
ret = gnutls_certificate_set_x509_key_mem(
x509_cred, &server_cert, &server_key, GNUTLS_X509_FMT_PEM);
if (ret < 0)
exit(1);
gnutls_init(&session, GNUTLS_SERVER);
gnutls_handshake_set_timeout(session, 0);
assert(gnutls_priority_set_direct(session, prio, NULL) >= 0);
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
gnutls_transport_set_int(session, fd);
HANDSHAKE(session, name, ret)
CHECK_KTLS_ENABLED(session, ret)
success("Test 0: sending/receiving data\n");
SEND_MSG(session, name, ret)
RECV_MSG(session, name, buffer, MAX_BUF + 1, ret)
CHECK_KTLS_ENABLED(session, ret)
success("Test 1: server key update without request\n");
KEY_UPDATE(session, name, 0, ret)
write(pipe, &bar, 1);
SEND_MSG(session, name, ret)
RECV_MSG(session, name, buffer, MAX_BUF + 1, ret)
CHECK_KTLS_ENABLED(session, ret)
success("Test 2: server key update with request\n");
KEY_UPDATE(session, name, GNUTLS_KU_PEER, ret)
write(pipe, &bar, 1);
SEND_MSG(session, name, ret)
RECV_MSG(session, name, buffer, MAX_BUF + 1, ret)
CHECK_KTLS_ENABLED(session, ret)
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
if (ret < 0) {
fail("server: error in closing session: %s\n",
gnutls_strerror(ret));
}
ret = 0;
end:
close(fd);
gnutls_deinit(session);
gnutls_certificate_free_credentials(x509_cred);
gnutls_global_deinit();
if (ret) {
terminate();
}
if (debug)
success("server: finished\n");
}
static void ch_handler(int sig)
{
return;
}
static void run(const char *prio)
{
int ret;
struct sockaddr_in saddr;
socklen_t addrlen;
int listener;
int fd;
int sync_pipe[2]; //used for synchronization
pipe(sync_pipe);
success("running ktls test with %s\n", prio);
signal(SIGCHLD, ch_handler);
signal(SIGPIPE, SIG_IGN);
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == -1) {
fail("error in listener(): %s\n", strerror(errno));
}
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
saddr.sin_port = 0;
ret = bind(listener, (struct sockaddr *)&saddr, sizeof(saddr));
if (ret == -1) {
fail("error in bind(): %s\n", strerror(errno));
}
addrlen = sizeof(saddr);
ret = getsockname(listener, (struct sockaddr *)&saddr, &addrlen);
if (ret == -1) {
fail("error in getsockname(): %s\n", strerror(errno));
}
child = fork();
if (child < 0) {
fail("error in fork(): %s\n", strerror(errno));
exit(1);
}
if (child) {
int status;
/* parent */
ret = listen(listener, 1);
if (ret == -1) {
fail("error in listen(): %s\n", strerror(errno));
}
fd = accept(listener, NULL, NULL);
if (fd == -1) {
fail("error in accept(): %s\n", strerror(errno));
}
close(sync_pipe[0]);
server(fd, prio, sync_pipe[1]);
wait(&status);
check_wait_status(status);
} else {
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1) {
fail("error in socket(): %s\n", strerror(errno));
exit(1);
}
usleep(1000000);
connect(fd, (struct sockaddr *)&saddr, addrlen);
close(sync_pipe[1]);
client(fd, prio, sync_pipe[0]);
exit(0);
}
}
void doit(void)
{
run("NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM");
run("NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-256-GCM");
}
#endif /* _WIN32 */