net: advance TLS handshakes without blocking socket I/O

Add a TLS-only step API across the native, Mbed TLS and BearSSL backends. Preserve handshake state and pending ciphertext across calls, report readiness interests, and close stalled connections without waiting.

Snapshot resumption keys with the offered session and skip contended cache updates in nonblocking calls. Exercise fragmented records, backpressure, cancellation, cache replacement and contention, plus real server verification through the existing network CI jobs.
This commit is contained in:
Codex committed 2026-11-10 20:07:06 +01:00
1 parent 798dcd7f33
commit 58ccb33e28
15 files changed
+1294 -290

No files matched your search

@@ -171,6 +171,9 @@ jobs:
make -C ../tls_retro clean
SSL_BACKEND=$be ../tls_retro/verify_hook_test.sh
make -C ../tls_retro clean
SSL_BACKEND=$be CFLAGS='-O1 -g1 -std=gnu89 -Wall -Wextra -Werror=declaration-after-statement -fsanitize=address,undefined -fno-omit-frame-pointer' \
sh ../tls_retro/handshake_test.sh
make -C ../tls_retro clean
- name: Operate the libsmb2 backend (SMB VFS against a local smbd)
if: matrix.lane == 'libsmb2'
@@ -91,6 +91,12 @@ jobs:
# line has to carry Winsock, which nothing on Linux checks.
make -C samples/network/tls_retro clean
- name: Incremental built-in TLS handshakes
shell: bash
run: |
set -eu
TLS_TEST_HANDSHAKE_STEP=1 samples/network/tls_retro/local_server_test.sh
make -C samples/network/tls_retro clean
- name: Built-in SMB2/3 client against a local smbd
shell: bash
+11 -1
View File
@@ -43,6 +43,13 @@ void* ssl_socket_init(int fd, const char *domain);
int ssl_socket_connect(void *state_data, void *data, bool timeout_enable, bool nonblock);
/* TLS only, on an already-connected descriptor. Enables nonblocking I/O.
* 1: complete, -1: failed, 0: progress; read/write name the next readiness
* interests. Neither means internal work remains: schedule another step.
* The caller owns scheduling, deadlines and cancellation (close then free).
* Do not mix this with ssl_socket_connect() on the same state. */
int ssl_socket_handshake_step(void *state_data, bool *read, bool *write);
/* The TLS library's own code for the most recent failure in
* ssl_socket_init()/ssl_socket_connect(), as the library reports it
* (mbedtls: negative, so -0x7780 style; BearSSL: its BR_ERR_* value),
@@ -72,6 +79,8 @@ int ssl_socket_receive_all_blocking(void *state_data, void *data_, size_t len);
* records/control work use ssl_socket_retro_pending for readiness. */
ssize_t ssl_socket_receive_all_nonblocking(void *state_data, bool *error, void *data_, size_t len);
/* Closes immediately; flush pending application bytes before closing when
* delivery matters. The close notification is best effort and never waits. */
void ssl_socket_close(void *state_data);
void ssl_socket_free(void *state_data);
@@ -114,7 +123,8 @@ struct rsa_private_key;
/* net_socket_ssl_retro.c only: one connection's own settings, for
* clients that pin their own CAs or present a certificate. Every
* pointer is borrowed until ssl_socket_connect() returns. */
* pointer is borrowed until ssl_socket_connect() returns, or until the
* incremental handshake completes or the connection is freed. */
struct ssl_socket_retro_config
{
/* Trust anchors, DER, in place of the built-in bundle; NULL for
+66 -32
View File
@@ -34,6 +34,7 @@
struct ssl_state
{
int fd;
int handshake_state;
char domain[256];
br_ssl_client_context sc;
br_x509_minimal_context xc;
@@ -280,7 +281,7 @@ void* ssl_socket_init(int fd, const char *domain)
static bool process_inner(struct ssl_state *state, bool blocking)
{
bool dummy;
bool dummy = false;
size_t buflen;
ssize_t bytes;
uint8_t *buf = br_ssl_engine_sendrec_buf(&state->sc.eng, &buflen);
@@ -321,7 +322,7 @@ static bool process_inner(struct ssl_state *state, bool blocking)
bytes = socket_receive_all_nonblocking(state->fd, &dummy, buf, buflen);
if (bytes > 0)
br_ssl_engine_recvrec_ack(&state->sc.eng, bytes);
if (bytes < 0)
if (bytes < 0 || dummy)
return false;
}
@@ -381,44 +382,73 @@ static void ssl_bear_report_verify(struct ssl_state *state)
ssl_socket_log_verify_fail(1, state->domain, info);
}
int ssl_socket_handshake_step(void *state_data, bool *read, bool *write)
{
struct ssl_state *state = (struct ssl_state*)state_data;
unsigned status;
if (read)
*read = false;
if (write)
*write = false;
if (!state || state->handshake_state < 0)
return -1;
if (state->handshake_state == 2)
return 1;
if (!state->handshake_state)
{
if (!socket_set_block(state->fd, false))
goto error;
state->handshake_state = 1;
}
if (!process_inner(state, false))
goto error;
status = br_ssl_engine_current_state(&state->sc.eng);
if (status & BR_SSL_CLOSED)
goto error;
if (status & BR_SSL_SENDAPP)
{
state->handshake_state = 2;
return 1;
}
if (read)
*read = (status & BR_SSL_RECVREC) != 0;
if (write)
*write = (status & BR_SSL_SENDREC) != 0;
return 0;
error:
state->handshake_state = -1;
ssl_bear_report_verify(state);
return -1;
}
int ssl_socket_connect(void *state_data,
void *data, bool timeout_enable, bool nonblock)
{
struct ssl_state *state = (struct ssl_state*)state_data;
unsigned bearstate;
int result;
bool read, write;
(void)nonblock;
if (!state)
return -1;
if (timeout_enable)
{
if (!socket_connect_with_timeout(state->fd, data, 5000))
return -1;
/* socket_connect_with_timeout makes the socket non-blocking. */
if (!socket_set_block(state->fd, true))
return -1;
}
else
else if (socket_connect(state->fd, data))
return -1;
while ((result = ssl_socket_handshake_step(state, &read, &write)) == 0)
{
if (socket_connect(state->fd, data))
return -1;
}
for (;;)
{
if (!process_inner(state, true))
if ((read || write)
&& (!socket_wait(state->fd, &read, &write, SSL_SOCKET_IO_TIMEOUT_MS)
|| !(read || write)))
{
ssl_bear_report_verify(state);
state->handshake_state = -1;
return -1;
}
bearstate = br_ssl_engine_current_state(&state->sc.eng);
if (bearstate & BR_SSL_SENDAPP)
break; /* handshake done */
if (bearstate & BR_SSL_CLOSED)
{
ssl_bear_report_verify(state);
return -1; /* failed */
}
}
if (result < 0 || !socket_set_block(state->fd, true))
return -1;
return 1;
}
@@ -574,12 +604,16 @@ int ssl_socket_flush_nonblocking(void *state_data)
void ssl_socket_close(void *state_data)
{
struct ssl_state *state = (struct ssl_state*)state_data;
br_ssl_engine_close(&state->sc.eng);
process_inner(state, false); /* send close notification */
socket_close(state->fd); /* but immediately close socket
and don't worry about recipient
getting our message */
if (!state || state->fd < 0)
return;
if (state->handshake_state == 2 && socket_set_block(state->fd, false))
{
br_ssl_engine_close(&state->sc.eng);
process_inner(state, false);
}
socket_close(state->fd);
state->fd = -1;
state->handshake_state = -1;
}
void ssl_socket_free(void *state_data)
+117 -68
View File
@@ -43,6 +43,11 @@
#include <mbedtls/platform.h>
#include <mbedtls/net_sockets.h>
#include <mbedtls/ssl.h>
#if MBEDTLS_VERSION_MAJOR == 3 && MBEDTLS_VERSION_MINOR == 0
#define SSL_MBED_FD(ctx) ((ctx)->MBEDTLS_PRIVATE(fd))
#else
#define SSL_MBED_FD(ctx) ((ctx)->fd)
#endif
#if MBEDTLS_VERSION_MAJOR >= 4
#include <psa/crypto.h>
#else
@@ -108,6 +113,7 @@ struct ssl_state
* holds that record half-sent and must be called again with the same
* buffer and length to finish it. */
size_t wpend;
int handshake_state; /* 0: setup, 1: active, 2: complete, -1: failed */
};
static void ssl_debug(void *ctx, int level,
@@ -180,7 +186,7 @@ void* ssl_socket_init(int fd, const char *domain)
mbedtls_entropy_init(&state->entropy);
#endif
state->net_ctx.fd = fd;
SSL_MBED_FD(&state->net_ctx) = fd;
#if SSL_MBED_LEGACY_RNG
if (mbedtls_ctr_drbg_seed(&state->ctr_drbg,
@@ -247,7 +253,7 @@ error:
* Mirrors the normal-teardown path in ssl_socket_free below
* (lines 355-361) in reverse of init order. mbedtls_net_free
* is intentionally not called for net_ctx (line 111):
* - init sets state->net_ctx.fd from the caller's fd
* - init borrows the caller's descriptor
* parameter at line 120, and the caller still owns that
* fd on this error exit.
* - ssl_socket_free similarly never calls mbedtls_net_free;
@@ -319,27 +325,9 @@ void ssl_socket_log_verify_disabled(const char *domain)
}
#endif
int ssl_socket_connect(void *state_data,
void *data, bool timeout_enable, bool nonblock)
static int ssl_mbed_start(struct ssl_state *state)
{
int ret, flags;
int authmode;
struct ssl_state *state = (struct ssl_state*)state_data;
if (timeout_enable)
{
if (!socket_connect_with_timeout(state->net_ctx.fd, data, 5000))
return -1;
/* socket_connect_with_timeout makes the socket non-blocking. */
if (!socket_set_block(state->net_ctx.fd, true))
return -1;
}
else
{
if (socket_connect(state->net_ctx.fd, data))
return -1;
}
int ret, authmode;
state->last_err = 0;
if ((ret = mbedtls_ssl_config_defaults(&state->conf,
@@ -386,55 +374,113 @@ int ssl_socket_connect(void *state_data,
mbedtls_ssl_set_bio(&state->ctx, &state->net_ctx, mbedtls_net_send, mbedtls_net_recv, NULL);
/* The handshake runs on a non-blocking socket, each wait for the
* server bounded: on a blocking one, a server that goes silent holds
* recv() for good. */
if (!socket_set_block(state->net_ctx.fd, false))
if (!socket_set_block(SSL_MBED_FD(&state->net_ctx), false))
return -1;
while ((ret = mbedtls_ssl_handshake(&state->ctx)) != 0)
state->handshake_state = 1;
return 0;
}
static void ssl_mbed_report_verify(struct ssl_state *state, bool required)
{
char info[512];
uint32_t flags = mbedtls_ssl_get_verify_result(&state->ctx);
mbedtls_x509_crt_verify_info(info, sizeof(info), " ! ", flags);
ssl_socket_log_verify_fail(required ? 1 : 0, state->domain, info);
}
int ssl_socket_handshake_step(void *state_data, bool *read, bool *write)
{
struct ssl_state *state = (struct ssl_state*)state_data;
int result;
bool complete;
if (read)
*read = false;
if (write)
*write = false;
if (!state || state->handshake_state < 0)
return -1;
if (state->handshake_state == 2)
return 1;
if (!state->handshake_state && ssl_mbed_start(state) != 0)
{
if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE)
state->handshake_state = -1;
return -1;
}
result = mbedtls_ssl_handshake_step(&state->ctx);
if (result == MBEDTLS_ERR_SSL_WANT_READ)
{
if (read)
*read = true;
return 0;
}
if (result == MBEDTLS_ERR_SSL_WANT_WRITE)
{
if (write)
*write = true;
return 0;
}
#ifdef MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS
if (result == MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS)
return 0;
#endif
#ifdef MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS
if (result == MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS)
return 0;
#endif
if (result)
{
state->last_err = result;
state->handshake_state = -1;
if (result == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
ssl_mbed_report_verify(state, true);
return -1;
}
#if MBEDTLS_VERSION_NUMBER >= 0x03020000
complete = mbedtls_ssl_is_handshake_over(&state->ctx) != 0;
#elif MBEDTLS_VERSION_MAJOR >= 3
/* 3.0/3.1 predate the completion accessor. */
complete = state->ctx.MBEDTLS_PRIVATE(state) == MBEDTLS_SSL_HANDSHAKE_OVER;
#else
complete = state->ctx.state == MBEDTLS_SSL_HANDSHAKE_OVER;
#endif
if (!complete)
return 0;
state->handshake_state = 2;
if (mbedtls_ssl_get_verify_result(&state->ctx))
ssl_mbed_report_verify(state, false);
return 1;
}
int ssl_socket_connect(void *state_data,
void *data, bool timeout_enable, bool nonblock)
{
struct ssl_state *state = (struct ssl_state*)state_data;
int result;
bool read, write;
(void)nonblock;
if (!state)
return -1;
if (timeout_enable)
{
if (!socket_connect_with_timeout(SSL_MBED_FD(&state->net_ctx), data, 5000))
return -1;
}
else if (socket_connect(SSL_MBED_FD(&state->net_ctx), data))
return -1;
while ((result = ssl_socket_handshake_step(state, &read, &write)) == 0)
{
if ((read || write)
&& (!socket_wait(SSL_MBED_FD(&state->net_ctx), &read, &write, SSL_SOCKET_IO_TIMEOUT_MS)
|| !(read || write)))
{
bool rd = (ret == MBEDTLS_ERR_SSL_WANT_READ);
bool wr = !rd;
if ( !socket_wait(state->net_ctx.fd, &rd, &wr, SSL_SOCKET_IO_TIMEOUT_MS)
|| !(rd || wr))
{
state->last_err = MBEDTLS_ERR_SSL_TIMEOUT;
return -1;
}
}
else
{
state->last_err = ret;
/* Fail-closed: under REQUIRED a bad certificate makes the
* handshake return here (MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
* before we reach the verify-result block below. Surface the
* reason, then bail. */
if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED)
{
char vrfy_buf[512];
uint32_t vflags = mbedtls_ssl_get_verify_result(&state->ctx);
mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", vflags);
ssl_socket_log_verify_fail(1, state->domain, vrfy_buf);
}
state->last_err = MBEDTLS_ERR_SSL_TIMEOUT;
state->handshake_state = -1;
return -1;
}
}
if (!socket_set_block(state->net_ctx.fd, true))
if (result < 0 || !socket_set_block(SSL_MBED_FD(&state->net_ctx), true))
return -1;
if ((flags = mbedtls_ssl_get_verify_result(&state->ctx)) != 0)
{
char vrfy_buf[512];
mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", flags);
/* Reached only under OPTIONAL/DISABLED: the handshake succeeded
* despite a verification failure. Log the soft-fail and let the
* connection proceed (the mode's documented behaviour). */
ssl_socket_log_verify_fail(0, state->domain, vrfy_buf);
}
return state->net_ctx.fd;
return SSL_MBED_FD(&state->net_ctx);
}
int ssl_socket_last_error(void *state_data)
@@ -617,10 +663,13 @@ int ssl_socket_flush_nonblocking(void *state_data)
void ssl_socket_close(void *state_data)
{
struct ssl_state *state = (struct ssl_state*)state_data;
mbedtls_ssl_close_notify(&state->ctx);
socket_close(state->net_ctx.fd);
if (!state || SSL_MBED_FD(&state->net_ctx) < 0)
return;
if (state->handshake_state == 2 && socket_set_block(SSL_MBED_FD(&state->net_ctx), false))
mbedtls_ssl_close_notify(&state->ctx);
socket_close(SSL_MBED_FD(&state->net_ctx));
SSL_MBED_FD(&state->net_ctx) = -1;
state->handshake_state = -1;
}
void ssl_socket_free(void *state_data)
+460 -177
View File
@@ -241,6 +241,18 @@ static void tls_cache_lock(void)
#endif
#endif
static bool tls_cache_begin(bool nonblock)
{
#if defined(HAVE_THREADS) && !defined(TLS_CACHE_LOCK_FREE)
if (nonblock)
return retro_atomic_cas_int(&tls_cache_busy, 0, 1);
#else
(void)nonblock;
#endif
TLS_CACHE_READ_BEGIN();
return true;
}
/* Between TLS_CACHE_READ_BEGIN() and _END(): the host's live entry. */
static struct tls_session *tls_cache_find(const char *host)
{
@@ -286,7 +298,8 @@ static unsigned tls_cache_slot_for(const char *host)
}
/* @put into @host's entry, made if it has none. */
static void tls_cache_put(const char *host, const struct tls_session_put *put)
static void tls_cache_put(const char *host, const struct tls_session_put *put,
bool nonblock)
{
#ifdef TLS_CACHE_LOCK_FREE
if (!host || !*host)
@@ -331,12 +344,15 @@ static void tls_cache_put(const char *host, const struct tls_session_put *put)
return;
}
tls_session_free(&e->node);
if (nonblock)
return;
}
#else
struct tls_session *e;
if (!host || !*host)
return;
TLS_CACHE_READ_BEGIN();
if (!tls_cache_begin(nonblock))
return;
e = tls_cache_at(tls_cache_slot_for(host));
if (!e->when || strcmp(e->host, host))
{
@@ -368,10 +384,46 @@ static int tls_trust_load(void)
CACERT_DER_PARTS);
}
enum tls_connect_phase
{
TLS_CONNECT_START = 0,
TLS_CONNECT_HELLO,
TLS_CONNECT_CERT12,
TLS_CONNECT_KEX12,
TLS_CONNECT_DONE12,
TLS_CONNECT_SEND_CERT12,
TLS_CONNECT_SEND_KEX12,
TLS_CONNECT_SEND_VERIFY12,
TLS_CONNECT_SEND_CCS12,
TLS_CONNECT_SEND_FIN12,
TLS_CONNECT_TICKET12,
TLS_CONNECT_CCS12,
TLS_CONNECT_FIN12,
TLS_CONNECT_COMPLETE12,
TLS_CONNECT_KEYS13,
TLS_CONNECT_EXT13,
TLS_CONNECT_CERT13,
TLS_CONNECT_VERIFY13,
TLS_CONNECT_FIN13,
TLS_CONNECT_SEND_CCS13,
TLS_CONNECT_SEND_FIN13,
TLS_CONNECT_COMPLETE13,
TLS_CONNECT_FAILED
};
struct tls_handshake_progress
{
struct x509_cert leaf; /* borrows DER in the handshake accumulator */
size_t peer_len;
enum tls_connect_phase phase;
uint8_t peer[97];
};
struct ssl_state
{
uint8_t *rx; /* raw record buffer */
uint8_t *hs; /* handshake message accumulator */
uint8_t *connect_message; /* encoded handshake, owned until sealed */
/* decrypted application data pending: a record's plaintext where
* it was decrypted, in rx - the next record read comes only once
* this is drained, so it is handed out with no copy of its own */
@@ -382,6 +434,8 @@ struct ssl_state
size_t hs_off;
size_t pt_len;
size_t pt_off;
size_t connect_length;
size_t connect_offset;
uint64_t cseq;
uint64_t sseq;
struct sha256_state transcript;
@@ -411,6 +465,9 @@ struct ssl_state
uint8_t tx_encrypted;
uint8_t handshake_done;
uint8_t closed;
bool connect_nonblock;
bool connect_read;
bool connect_write;
/* Handshake flights are corked: the client's records go out in one
* write, since sending ClientKeyExchange, ChangeCipherSpec and
* Finished as three small writes trips Nagle against the server's
@@ -456,6 +513,7 @@ struct ssl_state
uint8_t offer13; /* the ClientHello offers TLS 1.3 */
unsigned offered[24]; /* suites offered, for the ServerHello check */
unsigned offered_count;
unsigned cached_suite;
/* CertificateRequest: 1 to answer with our chain, 2 with none */
uint8_t cert_requested;
unsigned cv_alg; /* the scheme our CertificateVerify signs with */
@@ -470,6 +528,7 @@ struct ssl_state
uint8_t key[32];
unsigned key_len;
} wcbc, rcbc;
struct tls_handshake_progress connect;
};
/* ---- byte helpers ------------------------------------------------- */
@@ -755,7 +814,7 @@ static size_t tls_transcript(const struct ssl_state *s, uint8_t *out)
static void tls_cork(struct ssl_state *s)
{
s->corked = 1;
s->cork_len = 0;
s->cork_len = s->cork_off = 0;
}
/* Sends what was corked in one write. */
@@ -763,6 +822,8 @@ static int tls_uncork(struct ssl_state *s)
{
int ok = 1;
s->corked = 0;
if (s->connect_nonblock)
return 0;
if (s->cork_len)
ok = socket_send_all_blocking(s->fd, s->cork, s->cork_len, true);
s->cork_len = 0;
@@ -924,6 +985,84 @@ static int tls_tx_drain_blocking(struct ssl_state *s)
return result < 0 ? -1 : 0;
}
/* Seal into the cork or the application's reusable record buffer. */
static int tls_connect_record(struct ssl_state *s, uint8_t type,
const uint8_t *data, size_t len)
{
size_t sealed;
if (5 + len + TLS_REC_OVERHEAD <= TLS_CORK_MAX - s->cork_len)
{
if (tls_seal_record(s, type, data, len,
s->cork + s->cork_len, &sealed) != 0)
return -1;
s->cork_len += sealed;
return 0;
}
if (s->tx_off < s->tx_len)
{
s->last_err = TLS_ERR_HANDSHAKE;
return -1;
}
if (!s->tx && !(s->tx = (uint8_t*)malloc(5 + TLS_REC_MAX + TLS_REC_OVERHEAD)))
{
s->last_err = TLS_ERR_MEMORY;
return -1;
}
if (tls_seal_record(s, type, data, len, s->tx, &s->tx_len) != 0)
return -1;
s->tx_off = 0;
return 0;
}
static int tls_connect_push(struct ssl_state *s, bool cork)
{
uint8_t *data = cork ? s->cork : s->tx;
size_t *offset = cork ? &s->cork_off : &s->tx_off;
size_t *length = cork ? &s->cork_len : &s->tx_len;
ssize_t n = socket_send_all_nonblocking(s->fd, data + *offset,
*length - *offset, true);
if (n < 0)
{
s->last_err = TLS_ERR_SOCKET;
return -1;
}
*offset += (size_t)n;
if (*offset == *length)
*offset = *length = 0;
else
s->connect_write = true;
return 0;
}
/* One record is sealed or one pending span is written per call. */
static int tls_connect_output(struct ssl_state *s)
{
if (s->tx_off < s->tx_len)
return tls_connect_push(s, s->cork_off < s->cork_len);
if (s->connect_message)
{
size_t length = s->connect_length - s->connect_offset;
if (length > TLS_REC_MAX)
length = TLS_REC_MAX;
if (s->cork_len && 5 + length + TLS_REC_OVERHEAD > TLS_CORK_MAX - s->cork_len)
return tls_connect_push(s, true);
if (tls_connect_record(s, TLS_CT_HANDSHAKE,
s->connect_message + s->connect_offset, length) != 0)
return -1;
s->connect_offset += length;
if (s->connect_offset == s->connect_length)
{
free(s->connect_message);
s->connect_message = NULL;
s->connect_length = s->connect_offset = 0;
}
return 0;
}
if (!s->corked && s->cork_off < s->cork_len)
return tls_connect_push(s, true);
return 1;
}
static int tls_send_record(struct ssl_state *s, uint8_t type,
const uint8_t *data, size_t len)
{
@@ -933,6 +1072,8 @@ static int tls_send_record(struct ssl_state *s, uint8_t type,
if (len > TLS_REC_MAX)
return -1;
if (s->connect_nonblock)
return tls_connect_record(s, type, data, len);
if (!s->corked && tls_tx_drain_blocking(s) != 0)
return -1;
@@ -1207,7 +1348,10 @@ bad:
static int tls_read_record(struct ssl_state *s, uint8_t *type,
const uint8_t **body, size_t *len)
{
return tls_read_record_to(s, type, body, len, NULL, 0, false);
int result = tls_read_record_to(s, type, body, len, NULL, 0, s->connect_nonblock);
if (result > 0)
s->connect_read = true;
return result;
}
static int tls_send_alert(struct ssl_state *s, uint8_t level, uint8_t desc)
@@ -1225,6 +1369,7 @@ static int tls_send_alert(struct ssl_state *s, uint8_t level, uint8_t desc)
static int tls_read_handshake(struct ssl_state *s, uint8_t *msg_type,
const uint8_t **body, size_t *len)
{
bool record_read = false;
for (;;)
{
uint8_t type;
@@ -1252,8 +1397,14 @@ static int tls_read_handshake(struct ssl_state *s, uint8_t *msg_type,
/* Pull another record. Consumed messages stay where they are:
* the parsed Certificate points into this buffer for the rest
* of the handshake, and a whole handshake fits in it. */
if (s->connect_nonblock && record_read)
{
s->connect_read = true;
return -1;
}
if (tls_read_record(s, &type, &rb, &rl) != 0)
return -1;
record_read = true;
/* a 1.3 server's compatibility ChangeCipherSpec carries nothing */
if (s->v13 && type == TLS_CT_CCS)
continue;
@@ -1278,6 +1429,11 @@ static int tls_send_handshake(struct ssl_state *s, uint8_t msg_type,
uint8_t *m;
size_t off;
int ret;
if (len > 0xffffffU || (s->connect_nonblock && s->connect_message))
{
s->last_err = TLS_ERR_HANDSHAKE;
return -1;
}
if (!(m = (uint8_t*)malloc(4 + len)))
{
s->last_err = TLS_ERR_MEMORY;
@@ -1287,11 +1443,19 @@ static int tls_send_handshake(struct ssl_state *s, uint8_t msg_type,
m[1] = (uint8_t)(len >> 16);
m[2] = (uint8_t)(len >> 8);
m[3] = (uint8_t)len;
memcpy(m + 4, body, len);
if (len)
memcpy(m + 4, body, len);
sha256_stream_update(&s->transcript, m, 4 + len);
sha512_stream_update(&s->transcript384, m, 4 + len);
if (s->cfg.chain_count)
sha1_stream_update(&s->transcript1, m, 4 + len);
if (s->connect_nonblock)
{
s->connect_message = m;
s->connect_length = 4 + len;
s->connect_offset = 0;
return 0;
}
/* a certificate chain may need more than one record */
ret = 0;
for (off = 0; ret == 0 && off < 4 + len; off += TLS_REC_MAX)
@@ -1335,7 +1499,8 @@ static int tls_send_client_hello(struct ssl_state *s)
? strlen(s->domain) : 0;
size_t ext_len;
int ret;
const struct tls_session *cached;
const struct tls_session *cached = NULL;
bool cache_open;
size_t ticket_len;
uint8_t *ticket = NULL;
size_t cached_sid_len = 0;
@@ -1385,8 +1550,9 @@ static int tls_send_client_hello(struct ssl_state *s)
/* the cache entry is copied out here, so nothing below reads it
* once another thread may be rewriting the slot */
TLS_CACHE_READ_BEGIN();
cached = s->configured ? NULL : tls_cache_find(s->domain);
cache_open = !s->configured && tls_cache_begin(s->connect_nonblock);
if (cache_open)
cached = tls_cache_find(s->domain);
ticket_len = (cached && cached->ticket) ? cached->ticket_len : 0;
if (ticket_len && (ticket = (uint8_t*)malloc(ticket_len)))
memcpy(ticket, cached->ticket, ticket_len);
@@ -1397,6 +1563,12 @@ static int tls_send_client_hello(struct ssl_state *s)
memcpy(cached_sid, cached->sid, cached->sid_len);
cached_sid_len = cached->sid_len;
}
s->cached_suite = 0;
if (cached && !cached->is13)
{
memcpy(s->master, cached->master, sizeof(s->master));
s->cached_suite = cached->suite;
}
if (cached && cached->is13 && ticket_len && s->offer13)
{
cached_is13 = 1;
@@ -1404,7 +1576,8 @@ static int tls_send_client_hello(struct ssl_state *s)
memcpy(s->psk, cached->psk, 48);
obf_age = (uint32_t)((time(NULL) - cached->when) * 1000) + cached->age_add;
}
TLS_CACHE_READ_END();
if (cache_open)
TLS_CACHE_READ_END();
s->psk_offered = 0;
s->psk_accepted = 0;
@@ -1659,6 +1832,8 @@ static int tls_recv_server_hello(struct ssl_state *s)
|| sid != s->offered_sid_len || memcmp(b + 35, s->offered_sid, sid) != 0)
goto bad;
s->resumed = 0;
crypto_memzero(s->master, sizeof(s->master));
s->cached_suite = 0;
s->expect_ticket = 0;
return 0;
}
@@ -1670,18 +1845,13 @@ static int tls_recv_server_hello(struct ssl_state *s)
if (sid && sid == s->offered_sid_len && memcmp(b + 35, s->offered_sid, sid) == 0
&& !s->configured)
{
const struct tls_session *cached;
TLS_CACHE_READ_BEGIN();
cached = tls_cache_find(s->domain);
if (cached && cached->suite == suite)
{
memcpy(s->master, cached->master, 48);
s->resumed = 1;
}
TLS_CACHE_READ_END();
if (!s->resumed)
if (s->cached_suite != suite)
goto bad;
s->resumed = 1;
}
if (!s->resumed)
crypto_memzero(s->master, sizeof(s->master));
s->cached_suite = 0;
/* extensions: session_ticket (35) present means a ticket follows */
s->expect_ticket = 0;
if (len > 35 + sid + 3 + 2)
@@ -1929,12 +2099,12 @@ static int tls_recv_server_done(struct ssl_state *s)
return -1;
if (type == TLS_HS_CERT_REQUEST)
{
if (tls_parse_cert_request(s, b, len) != 0
|| tls_read_handshake(s, &type, &b, &len) != 0)
if (s->cert_requested || tls_parse_cert_request(s, b, len) != 0)
{
s->last_err = TLS_ERR_HANDSHAKE;
return -1;
}
return 1;
}
if (type != TLS_HS_SERVER_DONE || len != 0)
{
@@ -2040,7 +2210,15 @@ static int tls_send_client_cert(struct ssl_state *s)
int ret;
for (i = 0; i < n; i++)
total += 3 + s->cfg.chain_lens[i];
{
size_t length = s->cfg.chain_lens[i];
if (length > 0xffffffU - 6 || total > 0xffffffU - 6 - length)
{
s->last_err = TLS_ERR_MEMORY;
return -1;
}
total += 3 + length;
}
if (total > 0xffffff - 3 || !(m = (uint8_t*)malloc(3 + total)))
{
s->last_err = TLS_ERR_MEMORY;
@@ -2138,7 +2316,7 @@ static int tls_recv_new_session_ticket(struct ssl_state *s)
put.what = TLS_PUT_TICKET;
put.ticket = b + 6;
put.ticket_len = tlen;
tls_cache_put(s->domain, &put);
tls_cache_put(s->domain, &put, s->connect_nonblock);
return 0;
}
@@ -2155,7 +2333,7 @@ static void tls_cache_store(struct ssl_state *s)
put.suite = s->suite;
put.sid = s->offered_sid;
put.sid_len = s->offered_sid_len;
tls_cache_put(s->domain, &put);
tls_cache_put(s->domain, &put, s->connect_nonblock);
}
/* Key block from a master secret already in s->master. */
@@ -2544,74 +2722,6 @@ static int tls13_send_finished(struct ssl_state *s)
return tls_send_handshake(s, TLS_HS_FINISHED, mac, tls13_hlen(s));
}
/* The whole 1.3 handshake after the ServerHello has been read and its
* key share handed over. */
static int tls13_handshake(struct ssl_state *s, const uint8_t *peer, size_t peer_len)
{
struct x509_cert leaf;
uint8_t type;
const uint8_t *b;
size_t len;
static const uint8_t ccs = 1;
if (tls13_handshake_keys(s, peer, peer_len) != 0)
{
s->last_err = TLS_ERR_KEX;
return -1;
}
/* EncryptedExtensions: nothing we act on */
if (tls_read_handshake(s, &type, &b, &len) != 0)
return -1;
if (type != TLS_HS_ENCRYPTED_EXTENSIONS)
{
s->last_err = TLS_ERR_HANDSHAKE;
return -1;
}
if (s->psk_accepted)
{
/* resumed: the server proves the PSK with its Finished alone */
s->resumed = 1;
if (tls13_recv_finished(s) != 0) return -1;
}
else
{
if (tls13_recv_certificate(s, &leaf) != 0) return -1;
if (tls13_recv_certificate_verify(s, &leaf) != 0) return -1;
if (tls13_recv_finished(s) != 0) return -1;
}
/* application secrets come from the transcript through the server
* Finished; our own Finished goes out under the handshake keys */
if (tls13_application_secrets(s) != 0) return -1;
tls_cork(s);
/* middlebox-compatibility ChangeCipherSpec, in the clear */
s->tx_encrypted = 0;
if (tls_send_record(s, TLS_CT_CCS, &ccs, 1) != 0) return -1;
if (tls13_traffic_keys(s, s->c_hs, s->cwk, s->civ) != 0) return -1;
s->tx_encrypted = 1;
s->cseq = 0;
if (tls13_send_finished(s) != 0) return -1;
if (tls_uncork(s) != 0) return -1;
/* resumption master secret: the transcript through our Finished */
if (tls13_derive_secret(s, s->master13, "res master", s->res_master) != 0)
return -1;
crypto_memzero(s->master13, sizeof(s->master13));
crypto_memzero(s->psk, sizeof(s->psk));
/* both sides on application keys */
if (tls13_traffic_keys(s, s->c_app, s->cwk, s->civ) != 0
|| tls13_traffic_keys(s, s->s_app, s->swk, s->siv) != 0)
return -1;
s->cseq = s->sseq = 0;
crypto_memzero(s->hs_secret, sizeof(s->hs_secret));
crypto_memzero(s->c_hs, sizeof(s->c_hs));
crypto_memzero(s->s_hs, sizeof(s->s_hs));
s->handshake_done = 1;
s->hs_len = s->hs_off = 0;
return 0;
}
/* A post-handshake message on the application stream: tickets are
* dropped (no PSK resumption), a KeyUpdate rotates the server keys and
* is answered with our own when asked. */
@@ -2651,7 +2761,7 @@ static int tls13_post_handshake(struct ssl_state *s, const uint8_t *msg, size_t
put.suite = s->suite;
put.age_add = age_add;
put.psk = psk;
tls_cache_put(s->domain, &put);
tls_cache_put(s->domain, &put, nonblock);
crypto_memzero(psk, sizeof(psk));
return 0;
}
@@ -2692,97 +2802,229 @@ static int tls13_post_handshake(struct ssl_state *s, const uint8_t *msg, size_t
}
}
static int tls_handshake(struct ssl_state *s)
static int tls_handshake_step(struct ssl_state *s)
{
struct x509_cert leaf;
uint8_t peer[97];
size_t peer_len = 0;
struct tls_handshake_progress *progress = &s->connect;
uint8_t type;
const uint8_t *b;
size_t len;
int rsa_kx;
size_t len;
int result;
static const uint8_t ccs = 1;
sha256_stream_init(&s->transcript, 0);
sha512_stream_init(&s->transcript384, 1);
sha1_stream_init(&s->transcript1);
if (crypto_random_bytes(s->client_random, 32) != 0)
{
s->last_err = TLS_ERR_KEX;
if (s->handshake_done)
return 1;
if (progress->phase == TLS_CONNECT_FAILED)
return -1;
}
if (tls_send_client_hello(s) != 0) return -1;
if (tls_recv_server_hello(s) != 0) return -1;
if (s->v13)
return tls13_handshake(s, s->tls13_peer, s->tls13_peer_len);
if (s->resumed)
s->connect_read = s->connect_write = false;
if (s->connect_nonblock)
{
/* Abbreviated: the server goes first with the cached master. */
tls_expand_keys(s);
if (s->expect_ticket && tls_recv_new_session_ticket(s) != 0)
result = tls_connect_output(s);
if (result < 0)
goto error;
if (!result)
return 0;
}
switch (progress->phase)
{
case TLS_CONNECT_START:
sha256_stream_init(&s->transcript, 0);
sha512_stream_init(&s->transcript384, 1);
sha1_stream_init(&s->transcript1);
if (crypto_random_bytes(s->client_random, 32) != 0)
{
s->last_err = TLS_ERR_KEX;
goto error;
}
if (tls_send_client_hello(s) != 0)
goto error;
progress->phase = TLS_CONNECT_HELLO;
break;
case TLS_CONNECT_HELLO:
if (tls_recv_server_hello(s) != 0)
goto error;
if (s->v13)
progress->phase = TLS_CONNECT_KEYS13;
else if (s->resumed)
{
tls_expand_keys(s);
progress->phase = s->expect_ticket
? TLS_CONNECT_TICKET12 : TLS_CONNECT_CCS12;
}
else
progress->phase = TLS_CONNECT_CERT12;
break;
case TLS_CONNECT_CERT12:
if (tls_recv_certificate(s, &progress->leaf) != 0)
goto error;
progress->phase = tls_suite(s->suite)
&& tls_suite(s->suite)->kx == TLS_KX_RSA
? TLS_CONNECT_DONE12 : TLS_CONNECT_KEX12;
break;
case TLS_CONNECT_KEX12:
if (tls_recv_server_kex(s, &progress->leaf,
progress->peer, &progress->peer_len) != 0)
goto error;
progress->phase = TLS_CONNECT_DONE12;
break;
case TLS_CONNECT_DONE12:
result = tls_recv_server_done(s);
if (result < 0)
goto error;
if (!result)
progress->phase = TLS_CONNECT_SEND_CERT12;
break;
case TLS_CONNECT_SEND_CERT12:
tls_cork(s);
if (s->cert_requested && tls_send_client_cert(s) != 0)
goto error;
progress->phase = TLS_CONNECT_SEND_KEX12;
break;
case TLS_CONNECT_SEND_KEX12:
if (tls_suite(s->suite) && tls_suite(s->suite)->kx == TLS_KX_RSA)
result = tls_send_client_kex_rsa(s, &progress->leaf);
else
result = tls_send_client_kex(s, progress->peer, progress->peer_len);
if (result != 0)
goto error;
progress->phase = TLS_CONNECT_SEND_VERIFY12;
break;
case TLS_CONNECT_SEND_VERIFY12:
if (s->cert_requested == 1 && tls_send_cert_verify(s) != 0)
goto error;
tls_derive_keys(s);
progress->phase = TLS_CONNECT_SEND_CCS12;
break;
case TLS_CONNECT_SEND_CCS12:
if (s->resumed)
tls_cork(s);
if (tls_send_record(s, TLS_CT_CCS, &ccs, 1) != 0)
goto error;
s->tx_encrypted = 1;
s->cseq = 0;
progress->phase = TLS_CONNECT_SEND_FIN12;
break;
case TLS_CONNECT_SEND_FIN12:
if (tls_send_finished(s) != 0 || tls_uncork(s) != 0)
goto error;
progress->phase = s->resumed ? TLS_CONNECT_COMPLETE12
: (s->expect_ticket ? TLS_CONNECT_TICKET12 : TLS_CONNECT_CCS12);
break;
case TLS_CONNECT_TICKET12:
if (tls_recv_new_session_ticket(s) != 0)
goto error;
progress->phase = TLS_CONNECT_CCS12;
break;
case TLS_CONNECT_CCS12:
if (tls_read_record(s, &type, &b, &len) != 0)
goto error;
if (type != TLS_CT_CCS || len != 1 || b[0] != 1)
{
s->last_err = TLS_ERR_HANDSHAKE;
goto error;
}
s->rx_encrypted = 1;
s->sseq = 0;
progress->phase = TLS_CONNECT_FIN12;
break;
case TLS_CONNECT_FIN12:
if (tls_recv_finished(s) != 0)
goto error;
progress->phase = s->resumed
? TLS_CONNECT_SEND_CCS12 : TLS_CONNECT_COMPLETE12;
break;
case TLS_CONNECT_COMPLETE12:
tls_cache_store(s);
s->handshake_done = 1;
break;
case TLS_CONNECT_KEYS13:
if (tls13_handshake_keys(s, s->tls13_peer, s->tls13_peer_len) != 0)
{
s->last_err = TLS_ERR_KEX;
goto error;
}
progress->phase = TLS_CONNECT_EXT13;
break;
case TLS_CONNECT_EXT13:
if (tls_read_handshake(s, &type, &b, &len) != 0)
goto error;
if (type != TLS_HS_ENCRYPTED_EXTENSIONS)
{
s->last_err = TLS_ERR_HANDSHAKE;
goto error;
}
s->resumed = s->psk_accepted ? 1 : 0;
progress->phase = s->psk_accepted
? TLS_CONNECT_FIN13 : TLS_CONNECT_CERT13;
break;
case TLS_CONNECT_CERT13:
if (tls13_recv_certificate(s, &progress->leaf) != 0)
goto error;
progress->phase = TLS_CONNECT_VERIFY13;
break;
case TLS_CONNECT_VERIFY13:
if (tls13_recv_certificate_verify(s, &progress->leaf) != 0)
goto error;
progress->phase = TLS_CONNECT_FIN13;
break;
case TLS_CONNECT_FIN13:
if (tls13_recv_finished(s) != 0 || tls13_application_secrets(s) != 0)
goto error;
progress->phase = TLS_CONNECT_SEND_CCS13;
break;
case TLS_CONNECT_SEND_CCS13:
tls_cork(s);
s->tx_encrypted = 0;
if (tls_send_record(s, TLS_CT_CCS, &ccs, 1) != 0
|| tls13_traffic_keys(s, s->c_hs, s->cwk, s->civ) != 0)
goto error;
s->tx_encrypted = 1;
s->cseq = 0;
progress->phase = TLS_CONNECT_SEND_FIN13;
break;
case TLS_CONNECT_SEND_FIN13:
if (tls13_send_finished(s) != 0 || tls_uncork(s) != 0)
goto error;
progress->phase = TLS_CONNECT_COMPLETE13;
break;
case TLS_CONNECT_COMPLETE13:
if (tls13_derive_secret(s, s->master13, "res master", s->res_master) != 0)
goto error;
crypto_memzero(s->master13, sizeof(s->master13));
crypto_memzero(s->psk, sizeof(s->psk));
if (tls13_traffic_keys(s, s->c_app, s->cwk, s->civ) != 0
|| tls13_traffic_keys(s, s->s_app, s->swk, s->siv) != 0)
goto error;
s->cseq = s->sseq = 0;
crypto_memzero(s->hs_secret, sizeof(s->hs_secret));
crypto_memzero(s->c_hs, sizeof(s->c_hs));
crypto_memzero(s->s_hs, sizeof(s->s_hs));
s->handshake_done = 1;
break;
case TLS_CONNECT_FAILED:
return -1;
if (tls_read_record(s, &type, &b, &len) != 0) return -1;
if (type != TLS_CT_CCS || len != 1 || b[0] != 1)
{
default:
s->last_err = TLS_ERR_HANDSHAKE;
return -1;
}
s->rx_encrypted = 1;
s->sseq = 0;
if (tls_recv_finished(s) != 0) return -1;
tls_cork(s);
if (tls_send_record(s, TLS_CT_CCS, &ccs, 1) != 0) return -1;
s->tx_encrypted = 1;
s->cseq = 0;
if (tls_send_finished(s) != 0) return -1;
if (tls_uncork(s) != 0) return -1;
tls_cache_store(s);
s->handshake_done = 1;
s->hs_len = s->hs_off = 0;
goto error;
}
if (!s->handshake_done)
return 0;
}
rsa_kx = tls_suite(s->suite) && tls_suite(s->suite)->kx == TLS_KX_RSA;
if (tls_recv_certificate(s, &leaf) != 0) return -1;
if (!rsa_kx && tls_recv_server_kex(s, &leaf, peer, &peer_len) != 0)
return -1;
if (tls_recv_server_done(s) != 0) return -1;
tls_cork(s);
if (s->cert_requested && tls_send_client_cert(s) != 0) return -1;
if (rsa_kx ? tls_send_client_kex_rsa(s, &leaf) != 0
: tls_send_client_kex(s, peer, peer_len) != 0)
return -1;
if (s->cert_requested == 1 && tls_send_cert_verify(s) != 0) return -1;
tls_derive_keys(s);
if (tls_send_record(s, TLS_CT_CCS, &ccs, 1) != 0) return -1;
s->tx_encrypted = 1;
s->cseq = 0;
if (tls_send_finished(s) != 0) return -1;
if (tls_uncork(s) != 0) return -1;
/* NewSessionTicket when announced, then the server's
* ChangeCipherSpec and its Finished under the new keys. */
if (s->expect_ticket && tls_recv_new_session_ticket(s) != 0)
return -1;
if (tls_read_record(s, &type, &b, &len) != 0) return -1;
if (type != TLS_CT_CCS || len != 1 || b[0] != 1)
{
s->last_err = TLS_ERR_HANDSHAKE;
return -1;
}
s->rx_encrypted = 1;
s->sseq = 0;
if (tls_recv_finished(s) != 0) return -1;
tls_cache_store(s);
s->handshake_done = 1;
/* The accumulator is done; application data has its own buffer. */
s->hs_len = s->hs_off = 0;
return 0;
memset(progress, 0, sizeof(*progress));
return 1;
error:
if (s->connect_read)
return 0;
progress->phase = TLS_CONNECT_FAILED;
return -1;
}
static int tls_handshake(struct ssl_state *s)
{
int result;
do
result = tls_handshake_step(s);
while (result == 0);
return result < 0 ? -1 : 0;
}
/* ---- public API --------------------------------------------------- */
@@ -2876,6 +3118,38 @@ int ssl_socket_last_error(void *state_data)
return s ? s->last_err : 0;
}
int ssl_socket_handshake_step(void *state_data, bool *read, bool *write)
{
struct ssl_state *s = (struct ssl_state*)state_data;
int result;
if (read)
*read = false;
if (write)
*write = false;
if (!s || s->closed || s->connect.phase == TLS_CONNECT_FAILED)
return -1;
if (s->handshake_done)
return 1;
if (!s->connect_nonblock)
{
if (!socket_set_block(s->fd, false))
{
s->last_err = TLS_ERR_SOCKET;
s->connect.phase = TLS_CONNECT_FAILED;
return -1;
}
s->connect_nonblock = true;
}
result = tls_handshake_step(s);
if (result > 0)
s->connect_nonblock = false;
if (read)
*read = s->connect_read;
if (write)
*write = s->connect_write;
return result;
}
int ssl_socket_connect(void *state_data,
void *data, bool timeout_enable, bool nonblock)
{
@@ -3240,13 +3514,21 @@ int ssl_socket_flush_nonblocking(void *state_data)
void ssl_socket_close(void *state_data)
{
struct ssl_state *s = (struct ssl_state*)state_data;
/* Not behind a half-sent record: the alert would have to wait for
* it, and a peer that stopped reading is exactly when a transfer is
* abandoned mid-upload. */
if (!s || s->fd < 0)
return;
if (s->handshake_done && !s->closed && s->tx_off >= s->tx_len
&& !s->key_update && s->cork_off >= s->cork_len)
tls_send_alert(s, 1, 0); /* warning close_notify */
{
static const uint8_t alert[2] = {1, 0};
size_t length;
if (socket_set_block(s->fd, false)
&& tls_seal_record(s, TLS_CT_ALERT, alert, sizeof(alert),
s->cork, &length) == 0)
socket_send_all_nonblocking(s->fd, s->cork, length, true);
}
socket_close(s->fd);
s->fd = -1;
s->closed = 1;
}
void ssl_socket_free(void *state_data)
@@ -3259,6 +3541,7 @@ void ssl_socket_free(void *state_data)
crypto_memzero(s->swk, sizeof(s->swk));
crypto_memzero(&s->wgcm, sizeof(s->wgcm));
crypto_memzero(&s->rgcm, sizeof(s->rgcm));
free(s->connect_message);
free(s->rx);
free(s->hs);
free(s->cork);
+6
View File
@@ -1,2 +1,8 @@
tls_config
tls_config.exe
tls_handshake
tls_handshake.exe
tls_partial_flag
tls_partial_flag.exe
tls_partial_threads
tls_partial_threads.exe
+19 -5
View File
@@ -9,6 +9,8 @@ SRC = tls_fetch.c $(COMMON)/net/net_socket_ssl_retro.c $(COMMON)/net/net_socket.
$(COMMON)/file/file_path_io.c $(COMMON)/features/features_cpu.c $(COMMON)/compat/compat_strl.c \
$(COMMON)/compat/fopen_utf8.c \
$(COMMON)/time/rtime.c $(COMMON)/string/stdstring.c $(COMMON)/encodings/encoding_utf.c
THR_SRC = tls_threads.c $(filter-out tls_fetch.c,$(SRC)) $(COMMON)/rthreads/rthreads.c \
$(COMMON)/rthreads/retro_eventcount.c
# Winsock on Windows (MSYS2 / MinGW, or a cross build with a *-w64-mingw32 CC).
ifneq (,$(findstring mingw,$(CC))$(findstring MINGW,$(MSYSTEM))$(findstring Windows,$(OS)))
@@ -20,15 +22,21 @@ all: tls_fetch tls_http tls_threads
tls_partial: tls_partial.c $(COMMON)/net/net_socket_ssl_retro.c $(COMMON)/include/net/net_socket_ssl.h $(filter-out tls_fetch.c $(COMMON)/net/net_socket_ssl_retro.c,$(SRC))
$(CC) $(CPPFLAGS) $(CFLAGS) tls_partial.c $(filter-out tls_fetch.c $(COMMON)/net/net_socket_ssl_retro.c,$(SRC)) $(LDLIBS) -o $@
tls_fetch: $(SRC)
tls_partial_flag: tls_partial.c $(COMMON)/net/net_socket_ssl_retro.c $(COMMON)/include/net/net_socket_ssl.h $(filter-out tls_threads.c $(COMMON)/net/net_socket_ssl_retro.c,$(THR_SRC))
$(CC) $(CPPFLAGS) -DHAVE_THREADS -DTLS_CACHE_TEST_FLAG $(CFLAGS) tls_partial.c $(filter-out tls_threads.c $(COMMON)/net/net_socket_ssl_retro.c,$(THR_SRC)) $(LDLIBS) -lpthread -o $@
tls_partial_threads: tls_partial.c $(COMMON)/net/net_socket_ssl_retro.c $(COMMON)/include/net/net_socket_ssl.h $(filter-out tls_threads.c $(COMMON)/net/net_socket_ssl_retro.c,$(THR_SRC))
$(CC) $(CPPFLAGS) -DHAVE_THREADS $(CFLAGS) tls_partial.c $(filter-out tls_threads.c $(COMMON)/net/net_socket_ssl_retro.c,$(THR_SRC)) $(LDLIBS) -lpthread -o $@
tls_fetch: tls_handshake_driver.h $(SRC)
$(CC) $(CPPFLAGS) $(CFLAGS) $(SRC) $(LDLIBS) -o $@
# the same tool with no AES instructions to use, as on a 3DS or a Vita
tls_fetch_noaes: $(SRC)
tls_fetch_noaes: tls_handshake_driver.h $(SRC)
$(CC) $(CPPFLAGS) -DAES_NO_X86 -DAES_NO_ARM $(CFLAGS) $(SRC) $(LDLIBS) -o $@
tls_bench: tls_bench.c $(filter-out tls_fetch.c,$(SRC))
$(CC) $(CPPFLAGS) $(CFLAGS) tls_bench.c $(filter-out tls_fetch.c,$(SRC)) $(LDLIBS) -o $@
# one connection under ssl_socket_retro_configure()
tls_config: tls_config.c $(filter-out tls_fetch.c,$(SRC))
tls_config: tls_handshake_driver.h tls_config.c $(filter-out tls_fetch.c,$(SRC))
$(CC) $(CPPFLAGS) $(CFLAGS) tls_config.c $(filter-out tls_fetch.c,$(SRC)) $(LDLIBS) -o $@
tls_keyupdate: tls_keyupdate.c $(COMMON)/include/net/net_socket_ssl.h $(filter-out tls_fetch.c,$(SRC))
$(CC) $(CPPFLAGS) $(CFLAGS) tls_keyupdate.c $(filter-out tls_fetch.c,$(SRC)) $(LDLIBS) -o $@
@@ -62,17 +70,23 @@ else
$(COMMON)/rthreads/retro_eventcount.c
STALL_FLAGS = $(CPPFLAGS)
endif
HANDSHAKE_SRC = $(patsubst tls_stall.c,tls_handshake.c,$(STALL_SRC))
HANDSHAKE_IMPL = $(filter $(COMMON)/net/net_socket_ssl_%.c,$(HANDSHAKE_SRC))
tls_handshake: $(HANDSHAKE_SRC) tls_handshake_driver.h
$(CC) $(STALL_FLAGS) -DHAVE_THREADS $(CFLAGS) $(filter-out $(HANDSHAKE_IMPL),$(HANDSHAKE_SRC)) $(STALL_LIBS) $(LDLIBS) -lpthread -o $@
tls_stall: $(STALL_SRC)
$(CC) $(STALL_FLAGS) -DHAVE_THREADS -DSSL_SOCKET_IO_TIMEOUT_MS=500 $(CFLAGS) $(STALL_SRC) $(STALL_LIBS) $(LDLIBS) -lpthread -o $@
# a refused certificate reaches the verify hook, on the same backends
HOOK_SRC = $(patsubst tls_stall.c,tls_verify_hook.c,$(STALL_SRC))
tls_verify_hook: $(HOOK_SRC)
$(CC) $(STALL_FLAGS) -DHAVE_THREADS $(CFLAGS) $(HOOK_SRC) $(STALL_LIBS) $(LDLIBS) -lpthread -o $@
THR_SRC = tls_threads.c $(filter-out tls_fetch.c,$(SRC)) $(COMMON)/rthreads/rthreads.c \
$(COMMON)/rthreads/retro_eventcount.c
tls_threads: $(THR_SRC)
$(CC) $(CPPFLAGS) -DHAVE_THREADS $(CFLAGS) $(THR_SRC) $(LDLIBS) -lpthread -o $@
clean:
rm -f tls_partial_threads tls_partial_threads.exe
rm -f tls_partial_flag tls_partial_flag.exe
rm -f tls_handshake tls_handshake.exe
rm -f tls_partial tls_partial.exe
rm -f tls_verify_hook tls_verify_hook.exe tls_stall tls_stall.exe tls_fetch_noaes tls_fetch_noaes.exe tls_http tls_http.exe tls_fetch tls_fetch.exe tls_bench tls_bench.exe tls_keyupdate tls_keyupdate.exe tls_config tls_config.exe tls_threads tls_threads.exe
.PHONY: all clean
+52
View File
@@ -0,0 +1,52 @@
#!/bin/sh
set -eu
cd "$(dirname "$0")"
backend=${SSL_BACKEND:-retro}
port=${TLS_HANDSHAKE_PORT:-44531}
case ${TLS_PROTOCOL:-1.2} in
1.2) protocol=-tls1_2 ;;
1.3) protocol=-tls1_3 ;;
*) exit 2 ;;
esac
D=$(mktemp -d)
SRV=
cleanup() {
if [ -n "$SRV" ]; then
kill "$SRV" 2>/dev/null || true
wait "$SRV" 2>/dev/null || true
fi
rm -rf "$D"
}
trap cleanup EXIT
make -s -B tls_handshake SSL_BACKEND="$backend"
openssl req -x509 -newkey rsa:2048 -nodes -keyout "$D/ca.key" -out "$D/ca.pem" \
-days 2 -subj '/CN=TLS step test CA' 2>/dev/null
openssl req -newkey rsa:2048 -nodes -keyout "$D/server.key" -out "$D/server.csr" \
-subj '/CN=localhost' 2>/dev/null
printf 'subjectAltName=DNS:localhost\nbasicConstraints=CA:FALSE\n' > "$D/server.ext"
openssl x509 -req -in "$D/server.csr" -CA "$D/ca.pem" -CAkey "$D/ca.key" \
-CAcreateserial -out "$D/server.pem" -days 2 -extfile "$D/server.ext" 2>/dev/null
openssl s_server -accept "$port" -cert "$D/server.pem" -key "$D/server.key" \
"$protocol" -www > "$D/server.log" 2>&1 &
SRV=$!
if ! python3 - "$port" <<'PY'
import socket
import sys
import time
deadline = time.monotonic() + 5
while True:
try:
with socket.create_connection(('127.0.0.1', int(sys.argv[1])), 0.1):
break
except OSError:
if time.monotonic() >= deadline:
sys.exit('TLS test server did not start')
time.sleep(0.02)
PY
then
cat "$D/server.log"
exit 1
fi
${RUNNER:-} ./tls_handshake${EXE:-} "$port" "$D/ca.pem"
${RUNNER:-} ./tls_handshake${EXE:-} "$port" "$D/ca.pem" reject
@@ -14,6 +14,9 @@ RUN=${RUNNER:-}
EXE=${EXE:-}
make -s tls_partial
$RUN ./tls_partial$EXE
make -s tls_partial_threads tls_partial_flag
$RUN ./tls_partial_threads$EXE
$RUN ./tls_partial_flag$EXE
# Until a server listens on port $1, five seconds at most. It is asked of
# the socket table, not by connecting: a server that counts its
# connections would count a probe. Without ss, the old fixed wait.
+2 -1
View File
@@ -38,6 +38,7 @@
#include <net/net_compat.h>
#include <net/net_socket.h>
#include <net/net_socket_ssl.h>
#include "tls_handshake_driver.h"
#include <crypto/pk.h>
#include <crypto/x509.h>
#include <lrc_hash.h>
@@ -160,7 +161,7 @@ int main(int argc, char **argv)
fprintf(stderr, "configuration refused\n");
goto done;
}
if (ssl_socket_connect(ssl, addr, true, false) < 0)
if (tls_test_connect(ssl, fd, addr, getenv("TLS_TEST_HANDSHAKE_STEP") != NULL) < 0)
{
fprintf(stderr, "TLS connect failed (%d)\n", ssl_socket_last_error(ssl));
goto done;
+2 -1
View File
@@ -10,6 +10,7 @@
#include <net/net_compat.h>
#include <net/net_socket.h>
#include <net/net_socket_ssl.h>
#include "tls_handshake_driver.h"
void ssl_socket_log_verify_fail(int mode_required, const char *domain, const char *info)
{
@@ -66,7 +67,7 @@ int main(int argc, char **argv)
ssl = ssl_socket_init(fd, host);
if (!ssl)
return 2;
if (ssl_socket_connect(ssl, addr, true, false) < 0)
if (tls_test_connect(ssl, fd, addr, getenv("TLS_TEST_HANDSHAKE_STEP") != NULL) < 0)
{
fprintf(stderr, "TLS connect failed (%d)\n", ssl_socket_last_error(ssl));
ssl_socket_free(ssl);
+159
View File
@@ -0,0 +1,159 @@
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/net_socket.h>
#include <net/net_socket_ssl.h>
#include <streams/file_stream.h>
static bool checked_nonblock(int fd, bool block)
{
assert(!block);
return socket_set_block(fd, block);
}
static bool forbidden_wait(int fd, bool *read, bool *write, int timeout)
{
(void)fd; (void)read; (void)write; (void)timeout;
assert(!"TLS handshake/close entered a socket wait");
return false;
}
#if defined(HAVE_RETROSSL) || defined(HAVE_BEARSSL)
static bool forbidden_send(int fd, const void *data, size_t length, bool no_signal)
{
(void)fd; (void)data; (void)length; (void)no_signal;
assert(!"TLS handshake/close entered a blocking send");
return false;
}
#define socket_send_all_blocking forbidden_send
#endif
#define HAVE_GRIFFIN
#define socket_set_block checked_nonblock
#define socket_wait forbidden_wait
#if defined(HAVE_RETROSSL)
#include "../../../libretro-common/net/net_socket_ssl_retro.c"
#define TEST_BACKEND "retro"
#elif defined(HAVE_BEARSSL)
#include "../../../libretro-common/net/net_socket_ssl_bear.c"
#define TEST_BACKEND "bear"
#else
#include "../../../libretro-common/net/net_socket_ssl_mbed.c"
#define TEST_BACKEND "mbed"
#endif
#undef socket_set_block
#undef socket_wait
#undef socket_send_all_blocking
#undef HAVE_GRIFFIN
#include "tls_handshake_driver.h"
static unsigned verify_failures;
void ssl_socket_log_verify_fail(int required, const char *domain, const char *info)
{
(void)domain; (void)info;
assert(required);
verify_failures++;
}
void ssl_socket_log_verify_disabled(const char *domain)
{
(void)domain;
assert(!"verification must remain required");
}
static bool wait_for(int fd, bool write, retro_time_t deadline)
{
bool read = !write;
retro_time_t left = deadline - cpu_features_get_time_usec();
return left > 0 && socket_wait(fd, &read, &write, (int)((left + 999) / 1000));
}
int main(int argc, char **argv)
{
static const char request[] = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n";
struct addrinfo *address = NULL;
struct ssl_state *ssl;
char *pem = NULL;
char response[128];
int64_t pem_length = 0;
int fd, result;
size_t sent = 0, received = 0;
retro_time_t deadline;
bool error = false;
bool reject = argc > 3;
if (argc < 3)
return 2;
setvbuf(stdout, NULL, _IONBF, 0);
network_init();
assert(filestream_read_file(argv[2], (void**)&pem, &pem_length));
assert(pem && pem_length > 0);
#if defined(HAVE_RETROSSL)
ssl_socket_retro_set_trust_pem(pem, (size_t)pem_length);
#elif defined(HAVE_BEARSSL)
append_certs_pem_x509(pem);
assert(TAs_NUM);
#endif
ssl_socket_set_verify_mode(0);
fd = socket_init((void**)&address, (uint16_t)atoi(argv[1]), "localhost",
SOCKET_TYPE_STREAM, AF_INET);
assert(fd >= 0 && address);
ssl = (struct ssl_state*)ssl_socket_init(fd, reject ? "wrong.invalid" : "localhost");
assert(ssl);
#if !defined(HAVE_RETROSSL) && !defined(HAVE_BEARSSL)
assert(!mbedtls_x509_crt_parse(&ssl->ca, (unsigned char*)pem, (size_t)pem_length + 1));
#endif
result = tls_test_connect(ssl, fd, address, true);
freeaddrinfo(address);
if (reject)
{
assert(result == -1 && verify_failures == 1);
ssl_socket_close(ssl);
ssl_socket_free(ssl);
free(pem);
printf("PASS %s: incremental handshake rejects the wrong host\n", TEST_BACKEND);
return 0;
}
assert(result == 1 && !verify_failures);
assert(ssl_socket_handshake_step(ssl, NULL, NULL) == 1);
deadline = cpu_features_get_time_usec() + 5000000;
while (sent < sizeof(request) - 1)
{
ssize_t n = ssl_socket_send_all_nonblocking(ssl, request + sent,
sizeof(request) - 1 - sent, true);
assert(n >= 0);
sent += (size_t)n;
if (!n)
assert(wait_for(fd, true, deadline));
}
while ((result = ssl_socket_flush_nonblocking(ssl)) == 0)
assert(wait_for(fd, true, deadline));
assert(result == 1);
while (received + 1 < sizeof(response))
{
ssize_t n = ssl_socket_receive_all_nonblocking(ssl, &error,
response + received, 1);
assert(n >= 0 && !error);
if (!n)
{
bool write = false;
#if defined(HAVE_RETROSSL)
if (ssl_socket_retro_pending(ssl, &write))
continue;
#endif
assert(wait_for(fd, write, deadline));
continue;
}
received += (size_t)n;
if (response[received - 1] == '\n')
break;
}
response[received] = '\0';
assert(!strncmp(response, "HTTP/1.", 7) && strstr(response, " 200 "));
ssl_socket_close(ssl);
ssl_socket_close(ssl);
assert(ssl_socket_handshake_step(ssl, NULL, NULL) == -1);
ssl_socket_free(ssl);
free(pem);
printf("PASS %s: incremental handshake, application records and nonblocking close\n", TEST_BACKEND);
return 0;
}
@@ -0,0 +1,33 @@
/* Copyright (C) 2026 The RetroArch team. SPDX-License-Identifier: MIT */
#ifndef TLS_HANDSHAKE_DRIVER_H
#define TLS_HANDSHAKE_DRIVER_H
#include <features/features_cpu.h>
/* The fixture owns waits and its deadline; the TLS step owns neither. */
static int tls_test_connect(void *ssl, int fd, void *address, bool incremental)
{
retro_time_t deadline;
if (!incremental)
return ssl_socket_connect(ssl, address, true, false);
if (!socket_connect_with_timeout(fd, address, 5000))
return -1;
deadline = cpu_features_get_time_usec()
+ (retro_time_t)SSL_SOCKET_IO_TIMEOUT_MS * 1000;
for (;;)
{
bool read = false, write = false;
int result = ssl_socket_handshake_step(ssl, &read, &write);
retro_time_t remaining = deadline - cpu_features_get_time_usec();
if (result)
return result;
if (remaining <= 0)
return -1;
if (read || write)
{
int timeout = (int)((remaining + 999) / 1000);
if (!socket_wait(fd, &read, &write, timeout))
return -1;
}
}
}
#endif
+355 -5
View File
@@ -4,11 +4,35 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#include <queues/retro_retire.h>
#if defined(RETRO_RETIRE_LOCK_FREE) && !defined(TLS_CACHE_TEST_FLAG)
static bool cache_collision;
static unsigned cache_attempts;
static int test_cache_cas(retro_atomic_ptr_t *p, void *expected, void *desired)
{
if (cache_collision)
{
assert(++cache_attempts == 1);
return 0;
}
return retro_atomic_cas_ptr(p, expected, desired);
}
#undef retro_atomic_cas_ptr
#define retro_atomic_cas_ptr test_cache_cas
#endif
static void test_yield(void)
{
assert(!"incremental handshake waited for the session cache");
}
#define sthread_yield test_yield
#endif
static uint8_t input[65536], output[65536];
static size_t input_size, input_at, available, output_size, write_quota;
static unsigned reads, writes;
static bool ended, allow_blocking, mode_failed;
static unsigned reads, writes, closes;
static bool ended, allow_blocking, mode_failed, send_failed;
static bool test_set_block(int fd, bool block)
{
@@ -44,6 +68,8 @@ static ssize_t test_send(int fd, const void *data, size_t size, bool no_signal)
size_t n = size < write_quota ? size : write_quota;
(void)fd; (void)no_signal;
writes++;
if (send_failed)
return -1;
assert(n <= sizeof(output) - output_size);
memcpy(output + output_size, data, n);
output_size += n;
@@ -51,6 +77,14 @@ static ssize_t test_send(int fd, const void *data, size_t size, bool no_signal)
return (ssize_t)n;
}
static int test_close(int fd)
{
(void)fd;
closes++;
return 0;
}
#define socket_close test_close
#define socket_set_block test_set_block
#define socket_wait test_wait
#define socket_receive_all_nonblocking test_receive
@@ -61,10 +95,10 @@ static void reset(void)
{
input_size = input_at = available = output_size = 0;
write_quota = sizeof(output);
reads = writes = 0;
reads = writes = closes = 0;
ended = false;
allow_blocking = false;
mode_failed = false;
mode_failed = send_failed = false;
}
static struct ssl_state *state(bool v13)
@@ -283,14 +317,330 @@ static void socket_failures(void)
ssl_socket_free(receiver);
}
static struct ssl_state *handshake_hello(const char *host)
{
struct ssl_state *s = (struct ssl_state*)ssl_socket_init(-1, host);
unsigned step;
bool read, write;
assert(s);
for (step = 0; step < 2000; step++)
{
unsigned before = writes;
write_quota = 7;
assert(!ssl_socket_handshake_step(s, &read, &write));
assert(writes - before <= 1);
if (read)
break;
}
assert(step < 2000 && !write);
assert(output_size > 9 && output[0] == TLS_CT_HANDSHAKE);
assert(output[5] == TLS_HS_CLIENT_HELLO);
assert(tls_get16(output + 3) == output_size - 5);
assert(s->connect.phase == TLS_CONNECT_HELLO);
return s;
}
static void handshake_busy_cache(void)
{
#ifdef HAVE_THREADS
struct tls_session_put put;
#ifndef TLS_CACHE_LOCK_FREE
struct ssl_state *s;
#endif
reset();
memset(&put, 0, sizeof(put));
#ifdef TLS_CACHE_LOCK_FREE
cache_attempts = 0;
cache_collision = true;
tls_cache_put("busy.test", &put, true);
assert(cache_attempts == 1);
cache_collision = false;
#else
retro_atomic_store_release_int(&tls_cache_busy, 1);
tls_cache_put("busy.test", &put, true);
s = handshake_hello("busy.test");
assert(!s->cached_suite);
retro_atomic_store_release_int(&tls_cache_busy, 0);
ssl_socket_free(s);
#endif
#endif
}
static void handshake_cached_keys(void)
{
uint8_t master[48], changed[48], sid[32];
struct tls_session_put put;
struct ssl_state *s;
size_t length;
bool read, write;
reset();
memset(master, 0xa5, sizeof(master));
memset(changed, 0x5a, sizeof(changed));
memset(sid, 0x31, sizeof(sid));
memset(&put, 0, sizeof(put));
put.what = TLS_PUT_MASTER;
put.master = master;
put.sid = sid;
put.sid_len = sizeof(sid);
put.suite = 0xc02f;
tls_cache_put("snapshot.test", &put, false);
s = handshake_hello("snapshot.test");
put.master = changed;
memset(sid, 0x32, sizeof(sid));
tls_cache_put("snapshot.test", &put, false);
length = s->offered_sid_len;
input[0] = TLS_CT_HANDSHAKE;
input[1] = input[2] = 3;
tls_put16(input + 3, (unsigned)(42 + length));
input[5] = TLS_HS_SERVER_HELLO;
input[6] = input[7] = 0;
input[8] = (uint8_t)(38 + length);
input[9] = input[10] = 3;
memset(input + 11, 0x11, 32);
input[43] = (uint8_t)length;
memcpy(input + 44, s->offered_sid, length);
input[44 + length] = 0xc0;
input[45 + length] = 0x2f;
input[46 + length] = 0;
available = input_size = 47 + length;
assert(!ssl_socket_handshake_step(s, &read, &write));
assert(s->resumed && !memcmp(s->master, master, sizeof(master)));
ssl_socket_free(s);
}
static void handshake_fragments(void)
{
struct ssl_state *s;
size_t i;
unsigned before;
bool read, write;
reset();
s = handshake_hello("localhost");
input[0] = TLS_CT_HANDSHAKE;
input[1] = input[2] = 3;
tls_put16(input + 3, 42);
input[5] = TLS_HS_SERVER_HELLO;
input[6] = input[7] = 0;
input[8] = 38;
input[9] = input[10] = 3;
memset(input + 11, 0x11, 32);
input[43] = 0;
input[44] = 0xc0;
input[45] = 0x2f;
input[46] = 0;
input_size = 47;
for (i = 1; i <= input_size; i++)
{
available = i;
before = reads;
assert(!ssl_socket_handshake_step(s, &read, &write));
assert(reads - before <= 2 && !write);
}
assert(s->connect.phase == TLS_CONNECT_CERT12);
assert(s->hs_off == 42 && s->suite == 0xc02f);
assert(!ssl_socket_handshake_step(s, &read, &write) && read && !write);
ended = true;
assert(ssl_socket_handshake_step(s, &read, &write) == -1);
before = reads;
assert(ssl_socket_handshake_step(s, &read, &write) == -1);
assert(reads == before && !read && !write);
ssl_socket_free(s);
}
static void handshake_backpressure(void)
{
struct ssl_state *s;
unsigned step, before;
bool read, write;
uint8_t queued[TLS_CORK_MAX];
size_t length;
reset();
s = (struct ssl_state*)ssl_socket_init(-1, "localhost");
assert(s);
assert(!ssl_socket_handshake_step(s, &read, &write));
assert(s->connect_message && !reads && !writes);
write_quota = 0;
assert(!ssl_socket_handshake_step(s, &read, &write));
assert(!s->connect_message && s->cork_len && !writes);
length = s->cork_len;
memcpy(queued, s->cork, length);
for (step = 0; step < 10; step++)
{
before = writes;
assert(!ssl_socket_handshake_step(s, &read, &write) && write && !read);
assert(writes == before + 1 && !output_size);
assert(s->cork_len == length && !memcmp(s->cork, queued, length));
}
for (step = 0; step < 2000; step++)
{
write_quota = 7;
assert(!ssl_socket_handshake_step(s, &read, &write));
if (read)
break;
}
assert(step < 2000 && output_size == length);
assert(!memcmp(output, queued, length));
ssl_socket_free(s);
reset();
s = (struct ssl_state*)ssl_socket_init(-1, "localhost");
assert(s);
assert(!ssl_socket_handshake_step(s, &read, &write));
assert(!ssl_socket_handshake_step(s, &read, &write));
send_failed = true;
assert(ssl_socket_handshake_step(s, &read, &write) == -1);
before = writes;
assert(ssl_socket_handshake_step(s, &read, &write) == -1);
assert(writes == before);
ssl_socket_free(s);
for (step = 0; step < 12; step++)
{
unsigned tick;
reset();
write_quota = 0;
s = (struct ssl_state*)ssl_socket_init(-1, "localhost");
assert(s);
for (tick = 0; tick < step; tick++)
assert(!ssl_socket_handshake_step(s, &read, &write));
ssl_socket_free(s);
}
}
static void handshake_output(void)
{
static uint8_t body[TLS_REC_MAX + 37];
static const uint8_t ccs = 1;
struct ssl_state *s;
unsigned step, before, count;
size_t at, payload;
bool read, write;
reset();
s = (struct ssl_state*)ssl_socket_init(-1, "localhost");
assert(s);
s->connect_nonblock = true;
tls_cork(s);
memset(body, 0x5a, sizeof(body));
assert(!tls_send_handshake(s, TLS_HS_CERTIFICATE, body, sizeof(body)));
for (step = 0; step < 10000; step++)
{
int result;
write_quota = 7;
before = writes;
result = tls_connect_output(s);
assert(result >= 0 && writes - before <= 1);
if (result == 1)
break;
}
assert(step < 10000 && !s->connect_message && s->cork_len);
assert(!tls_send_record(s, TLS_CT_CCS, &ccs, 1));
assert(!tls_uncork(s));
for (step = 0; step < 10000; step++)
{
write_quota = 7;
if (tls_connect_output(s) == 1)
break;
}
assert(step < 10000);
count = 0;
payload = 0;
for (at = 0; at < output_size;)
{
size_t length = tls_get16(output + at + 3);
assert(at + 5 + length <= output_size && length <= TLS_REC_MAX);
if (output[at] == TLS_CT_HANDSHAKE)
{
size_t offset = 0;
if (!payload)
{
assert(output[at + 5] == TLS_HS_CERTIFICATE);
assert(tls_get24(output + at + 6) == sizeof(body));
offset = 4;
}
assert(!memcmp(output + at + 5 + offset, body + payload,
length - offset));
payload += length - offset;
}
else
assert(output[at] == TLS_CT_CCS && length == 1 && output[at + 5] == 1);
at += 5 + length;
count++;
}
assert(payload == sizeof(body) && count == 3);
ssl_socket_free(s);
reset();
s = (struct ssl_state*)ssl_socket_init(-1, "localhost");
assert(s);
s->connect_nonblock = true;
tls_cork(s);
assert(!tls_send_handshake(s, TLS_HS_CERTIFICATE, body, 935));
assert(!tls_connect_output(s) && s->cork_len == 944);
assert(!tls_send_record(s, TLS_CT_CCS, &ccs, 1) && s->tx_len == 6);
assert(!tls_uncork(s));
for (step = 0; step < 1000; step++)
{
write_quota = 7;
if (tls_connect_output(s) == 1)
break;
}
assert(step < 1000 && output_size == 950);
assert(output[0] == TLS_CT_HANDSHAKE && output[944] == TLS_CT_CCS);
ssl_socket_free(s);
read = write = true;
assert(ssl_socket_handshake_step(NULL, &read, &write) == -1 && !read && !write);
}
static void nonblocking_close(void)
{
struct ssl_state *s;
unsigned before;
bool read, write;
reset();
s = state(true);
s->fd = 7;
write_quota = 0;
ssl_socket_close(s);
assert(closes == 1 && writes == 1 && s->fd == -1);
ssl_socket_close(s);
assert(closes == 1 && writes == 1);
ssl_socket_free(s);
reset();
s = state(true);
s->fd = 7;
write_quota = 0;
assert(ssl_socket_send_all_nonblocking(s, "data", 4, true) == 4);
before = writes;
ssl_socket_close(s);
assert(closes == 1 && writes == before);
ssl_socket_free(s);
reset();
s = (struct ssl_state*)ssl_socket_init(-1, "localhost");
assert(s);
s->fd = 7;
assert(!ssl_socket_handshake_step(s, &read, &write));
before = writes;
ssl_socket_close(s);
assert(closes == 1 && writes == before);
ssl_socket_free(s);
}
int main(void)
{
handshake_busy_cache();
nonblocking_close();
handshake_cached_keys();
handshake_fragments();
handshake_backpressure();
handshake_output();
partial_records(false);
partial_records(true);
fragmented_update();
buffered_tickets();
mixed_and_failed_records();
socket_failures();
puts("PASS partial TLS records, direct decrypt and backpressured KeyUpdate");
puts("PASS incremental TLS handshake, partial records and backpressured KeyUpdate");
return 0;
}