/*
* Copyright (C) 2008-2012 Free Software Foundation, Inc.
* Copyright (C) 2018-2020 Red Hat, Inc.
* Copyright (C) 2010 Mike Blumenkrantz
*
* 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 this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../utils.h"
#include
#include
#include
#include
static int done = 0;
EV_P;
ev_io remote_w;
gnutls_session_t session;
static const char
*SSL_GNUTLS_PRINT_HANDSHAKE_STATUS(gnutls_handshake_description_t status)
{
return gnutls_handshake_description_get_name(status);
}
/* Connects to the peer and returns a socket
* descriptor.
*/
static int tcp_connect(void)
{
const char *PORT = getenv("PORT");
const char *SERVER = "127.0.0.1"; //verisign.com
int err, sd;
int flag = 1, curstate = 0;
struct sockaddr_in sa;
/* sets some fd options such as nonblock */
sd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (const void *)&curstate,
sizeof(curstate));
memset(&sa, '\0', sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(atoi(PORT));
inet_pton(AF_INET, SERVER, &sa.sin_addr);
err = connect(sd, (struct sockaddr *)&sa, sizeof(sa));
if ((err < 0) && (errno != EINPROGRESS)) {
fprintf(stderr, "Connect error\n");
exit(1);
}
/* lower the send buffers to force EAGAIN */
assert(setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int)) >= 0);
assert(fcntl(sd, F_SETFL, O_NONBLOCK)>=0);
return sd;
}
static void tcp_close(int sd)
{
shutdown(sd, SHUT_RDWR); /* no more receptions */
close(sd);
}
/* We provide this helper to ensure that we test EAGAIN while writing
* even on a reliable connection */
static ssize_t
_client_push(gnutls_transport_ptr_t tr, const void *data, size_t len)
{
struct timeval tv;
assert(gettimeofday(&tv, NULL) >= 0);
if (tv.tv_usec % 2 == 0) {
errno = EAGAIN;
return -1;
}
return send((unsigned long)tr, data, len, 0);
}
static ssize_t
_client_pull(gnutls_transport_ptr_t tr, void *data, size_t len)
{
struct timeval tv;
assert(gettimeofday(&tv, NULL) >= 0);
if (tv.tv_usec % 2 == 0) {
errno = EAGAIN;
return -1;
}
return recv((unsigned long)tr, data, len, 0);
}
static int _client_pull_timeout(gnutls_transport_ptr_t ptr,
unsigned int ms)
{
return gnutls_system_recv_timeout(ptr, ms);
}
static void _process_data(EV_P_ ev_io * w, int revents)
{
static int ret = -1, lastret = 0;
static unsigned int count = 0;
static int prev_direction;
if (!done && (revents & (EV_WRITE|EV_READ))) {
if (lastret == GNUTLS_E_AGAIN) {
if (revents & EV_WRITE) {
assert(prev_direction == 1);
}
if (revents & EV_READ) {
assert(prev_direction == 0);
}
}
lastret = ret;
ret = gnutls_handshake(session);
count++;
if (gnutls_record_get_direction(session)) {
ev_io_stop(EV_A_ &remote_w);
ev_io_set(&remote_w, gnutls_transport_get_int(session), EV_WRITE);
ev_io_start(EV_A_ &remote_w);
prev_direction = 1;
} else {
ev_io_stop(EV_A_ &remote_w);
ev_io_set(&remote_w, gnutls_transport_get_int(session), EV_READ);
ev_io_start(EV_A_ &remote_w);
prev_direction = 0;
}
/* avoid printing messages infinity times */
if (lastret != ret && ret != 0 && ret != GNUTLS_E_AGAIN) {
fprintf(stderr, "gnutls returned with: %s - %s\n",
gnutls_strerror_name(ret),
gnutls_strerror(ret));
if ((ret == GNUTLS_E_WARNING_ALERT_RECEIVED)
|| (ret == GNUTLS_E_FATAL_ALERT_RECEIVED))
fprintf(stderr, "Also received alert: %s\n",
gnutls_alert_get_name
(gnutls_alert_get(session)));
fprintf(stderr, "last out: %s\n",
SSL_GNUTLS_PRINT_HANDSHAKE_STATUS
(gnutls_handshake_get_last_out(session)));
fprintf(stderr, "last in: %s\n",
SSL_GNUTLS_PRINT_HANDSHAKE_STATUS
(gnutls_handshake_get_last_in(session)));
}
if (gnutls_error_is_fatal(ret)) {
fprintf(stderr, "yarrr this be an error!");
exit(1);
}
}
if (ret == GNUTLS_E_SUCCESS) {
count = 0;
ret = -1;
done = 1;
lastret = 0;
ev_io_stop(EV_A_ & remote_w);
}
return;
}
static void try(const char *name, const char *prio)
{
gnutls_certificate_credentials_t c_certcred;
int sd, i, ret;
global_init();
gnutls_certificate_allocate_credentials(&c_certcred);
printf("%s: testing priority %s\n", name, prio);
loop = EV_DEFAULT;
for (i = 0; i < 4; i++) {
done = 0;
assert(gnutls_init(&session, GNUTLS_CLIENT) >= 0);
gnutls_transport_set_push_function(session, _client_push);
gnutls_transport_set_pull_function(session, _client_pull);
gnutls_transport_set_pull_timeout_function(session, _client_pull_timeout);
gnutls_handshake_set_timeout(session,
GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
assert(gnutls_priority_set_direct(session, prio, NULL) >= 0);
gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE,
c_certcred);
gnutls_server_name_set(session, GNUTLS_NAME_DNS,
"localhost", strlen("localhost"));
sd = tcp_connect();
/* associate gnutls with socket */
gnutls_transport_set_int(session, sd);
/* add a callback for data being available for send/receive on socket */
ev_io_init(&remote_w, _process_data, sd, EV_WRITE);
ev_io_start(EV_A_ & remote_w);
/* begin main loop */
ev_loop(EV_A_ 0);
do {
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
} while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
gnutls_deinit(session);
session = NULL;
tcp_close(sd);
}
ev_loop_destroy(loop);
gnutls_certificate_free_credentials(c_certcred);
return;
}
int main(void)
{
try("tls 1.2 (dhe)", "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:-KX-ALL:+DHE-RSA");
try("tls 1.2 (rsa)", "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.2:-KX-ALL:+RSA");
try("tls 1.3", "NORMAL:-VERS-TLS-ALL:+VERS-TLS1.3");
try("default", "NORMAL");
return 0;
}