mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-10-24 04:09:07 +03:00
SendData() refused to write whenever options.isClosed was set, to stop application data after a fatal alert (RFC 8446 Section 6.2). isClosed is also set when the transport reports a plain EOF, so a server could no longer answer a request whose client had already closed its socket. The write failed without reaching the socket, which reports SSL_ERROR_SYSCALL with a stale errno instead of EPIPE or ECONNRESET. nginx logs that at crit level, which made the ssl_stapling.t "no crit" check in the nginx CI fail intermittently. Track a fatal alert, sent or received, in a new options.fatalAlert bit and check that in SendData() instead. A transport EOF goes back to letting the write reach the socket, as it did before. Also refuse writes once our own close_notify has been sent, as OpenSSL does. The isClosed check only caught that when an EOF happened to arrive after the close_notify. The refused write sets SSL_SHUTDOWN_ALREADY_DONE_E, which ReceiveData() clears, so the application can still read the peer's remaining data and its close_notify. Session export writes fatalAlert as its own options byte. Export version 7 has not been in a release yet, so it takes the byte without a new version number; version 7 blobs written by master before this change no longer import. A version 6 blob only has isClosed, so the importer takes it as the fatal alert state too, as SendData() did before.
953 lines
28 KiB
C
953 lines
28 KiB
C
/* utils.c
|
|
*
|
|
* Copyright (C) 2006-2026 wolfSSL Inc.
|
|
*
|
|
* This file is part of wolfSSL.
|
|
*
|
|
* wolfSSL 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.
|
|
*
|
|
* wolfSSL 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
|
|
#include <tests/unit.h>
|
|
#include <tests/utils.h>
|
|
#include <wolfssl/wolfcrypt/error-crypt.h>
|
|
#include <wolfssl/internal.h>
|
|
|
|
#ifdef HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES_BUILD
|
|
|
|
/* This set of memio functions allows for more fine tuned control of the TLS
|
|
* connection operations. For new tests, try to use ssl_memio first. */
|
|
|
|
/* To dump the memory in gdb use
|
|
* dump memory client.bin test_ctx.c_buff test_ctx.c_buff+test_ctx.c_len
|
|
* dump memory server.bin test_ctx.s_buff test_ctx.s_buff+test_ctx.s_len
|
|
* This can be imported into Wireshark by transforming the file with
|
|
* od -Ax -tx1 -v client.bin > client.hex
|
|
* od -Ax -tx1 -v server.bin > server.hex
|
|
* Then transform the files into pcap (use -u instead of -T for UDP)
|
|
* text2pcap -T 50,60 client.hex client.pcap
|
|
* text2pcap -T 50,60 server.hex server.pcap
|
|
* Then open in wireshark
|
|
* wireshark client.pcap
|
|
* wireshark server.pcap
|
|
*/
|
|
|
|
int test_memio_write_cb(WOLFSSL *ssl, char *data, int sz, void *ctx)
|
|
{
|
|
struct test_memio_ctx *test_ctx;
|
|
byte *buf;
|
|
int *len;
|
|
int *msg_sizes;
|
|
int *msg_count;
|
|
int *forceWantWrite;
|
|
|
|
test_ctx = (struct test_memio_ctx*)ctx;
|
|
|
|
if (wolfSSL_GetSide(ssl) == WOLFSSL_SERVER_END) {
|
|
buf = test_ctx->c_buff;
|
|
len = &test_ctx->c_len;
|
|
msg_sizes = test_ctx->c_msg_sizes;
|
|
msg_count = &test_ctx->c_msg_count;
|
|
forceWantWrite = &test_ctx->s_force_want_write;
|
|
}
|
|
else {
|
|
buf = test_ctx->s_buff;
|
|
len = &test_ctx->s_len;
|
|
msg_sizes = test_ctx->s_msg_sizes;
|
|
msg_count = &test_ctx->s_msg_count;
|
|
forceWantWrite = &test_ctx->c_force_want_write;
|
|
}
|
|
|
|
if (*forceWantWrite)
|
|
return WOLFSSL_CBIO_ERR_WANT_WRITE;
|
|
|
|
if ((unsigned)(*len + sz) > TEST_MEMIO_BUF_SZ)
|
|
return WOLFSSL_CBIO_ERR_WANT_WRITE;
|
|
|
|
if (*msg_count >= TEST_MEMIO_MAX_MSGS)
|
|
return WOLFSSL_CBIO_ERR_WANT_WRITE;
|
|
|
|
#ifdef WOLFSSL_DUMP_MEMIO_STREAM
|
|
{
|
|
char dump_file_name[64];
|
|
XFILE dump_file;
|
|
sprintf(dump_file_name, "%s/%s.dump", tmpDirName, currentTestName);
|
|
dump_file = XFOPEN(dump_file_name, "ab");
|
|
if (dump_file != XBADFILE) {
|
|
(void)XFWRITE(data, 1, (size_t)sz, dump_file);
|
|
XFCLOSE(dump_file);
|
|
}
|
|
}
|
|
#endif
|
|
XMEMCPY(buf + *len, data, (size_t)sz);
|
|
msg_sizes[*msg_count] = sz;
|
|
(*msg_count)++;
|
|
*len += sz;
|
|
|
|
return sz;
|
|
}
|
|
|
|
int test_memio_read_cb(WOLFSSL *ssl, char *data, int sz, void *ctx)
|
|
{
|
|
struct test_memio_ctx *test_ctx;
|
|
int read_sz;
|
|
byte *buf;
|
|
int *len;
|
|
int *msg_sizes;
|
|
int *msg_count;
|
|
int *msg_pos;
|
|
int is_dtls;
|
|
|
|
test_ctx = (struct test_memio_ctx*)ctx;
|
|
is_dtls = wolfSSL_dtls(ssl) && !test_ctx->sctp;
|
|
|
|
if (wolfSSL_GetSide(ssl) == WOLFSSL_SERVER_END) {
|
|
buf = test_ctx->s_buff;
|
|
len = &test_ctx->s_len;
|
|
msg_sizes = test_ctx->s_msg_sizes;
|
|
msg_count = &test_ctx->s_msg_count;
|
|
msg_pos = &test_ctx->s_msg_pos;
|
|
}
|
|
else {
|
|
buf = test_ctx->c_buff;
|
|
len = &test_ctx->c_len;
|
|
msg_sizes = test_ctx->c_msg_sizes;
|
|
msg_count = &test_ctx->c_msg_count;
|
|
msg_pos = &test_ctx->c_msg_pos;
|
|
}
|
|
|
|
if (*len == 0 || *msg_pos >= *msg_count)
|
|
return WOLFSSL_CBIO_ERR_WANT_READ;
|
|
|
|
if (!is_dtls) {
|
|
/* TLS is a byte stream: serve across message boundaries so
|
|
* pending-crypto re-read patterns cannot desync the slots. */
|
|
int rem;
|
|
|
|
read_sz = *len;
|
|
if (read_sz > sz)
|
|
read_sz = sz;
|
|
|
|
XMEMCPY(data, buf, (size_t)read_sz);
|
|
XMEMMOVE(buf, buf + read_sz, (size_t)(*len - read_sz));
|
|
*len -= read_sz;
|
|
|
|
rem = read_sz;
|
|
while (rem > 0 && *msg_pos < *msg_count) {
|
|
if (msg_sizes[*msg_pos] > rem) {
|
|
msg_sizes[*msg_pos] -= rem;
|
|
rem = 0;
|
|
}
|
|
else {
|
|
rem -= msg_sizes[*msg_pos];
|
|
msg_sizes[*msg_pos] = 0;
|
|
(*msg_pos)++;
|
|
}
|
|
}
|
|
if (rem != 0) {
|
|
/* Slot accounting desynced from the byte count; fail loudly. */
|
|
return WOLFSSL_CBIO_ERR_GENERAL;
|
|
}
|
|
if (*msg_pos >= *msg_count && *len == 0) {
|
|
*msg_pos = 0;
|
|
*msg_count = 0;
|
|
}
|
|
|
|
return read_sz;
|
|
}
|
|
|
|
/* DTLS: datagram boundaries matter, serve one message at a time. */
|
|
read_sz = msg_sizes[*msg_pos];
|
|
if (read_sz > sz)
|
|
read_sz = sz;
|
|
|
|
if (read_sz > *len) {
|
|
return WOLFSSL_CBIO_ERR_GENERAL;
|
|
}
|
|
|
|
/* Copy data from current message */
|
|
XMEMCPY(data, buf, (size_t)read_sz);
|
|
/* remove the read data from the buffer */
|
|
XMEMMOVE(buf, buf + read_sz, (size_t)(*len - read_sz));
|
|
*len -= read_sz;
|
|
msg_sizes[*msg_pos] -= read_sz;
|
|
|
|
/* if we are on dtls, discard the rest of the message */
|
|
if (is_dtls && msg_sizes[*msg_pos] > 0) {
|
|
XMEMMOVE(buf, buf + msg_sizes[*msg_pos], (size_t)(*len - msg_sizes[*msg_pos]));
|
|
*len -= msg_sizes[*msg_pos];
|
|
msg_sizes[*msg_pos] = 0;
|
|
}
|
|
|
|
/* If we've read the entire message */
|
|
if (msg_sizes[*msg_pos] == 0) {
|
|
/* Move to next message */
|
|
(*msg_pos)++;
|
|
if (*msg_pos >= *msg_count) {
|
|
*msg_pos = 0;
|
|
*msg_count = 0;
|
|
}
|
|
}
|
|
|
|
return read_sz;
|
|
}
|
|
|
|
int test_memio_do_handshake(WOLFSSL *ssl_c, WOLFSSL *ssl_s,
|
|
int max_rounds, int *rounds)
|
|
{
|
|
byte handshake_complete = 0, hs_c = 0, hs_s = 0;
|
|
int ret, err;
|
|
|
|
if (rounds != NULL)
|
|
*rounds = 0;
|
|
while (!handshake_complete && max_rounds > 0) {
|
|
if (!hs_c) {
|
|
wolfSSL_SetLoggingPrefix("client");
|
|
ret = wolfSSL_connect(ssl_c);
|
|
wolfSSL_SetLoggingPrefix(NULL);
|
|
if (ret == WOLFSSL_SUCCESS) {
|
|
hs_c = 1;
|
|
}
|
|
else {
|
|
err = wolfSSL_get_error(ssl_c, ret);
|
|
if (err == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) {
|
|
/* retry non-blocking math */
|
|
}
|
|
#ifdef WOLFSSL_ASYNC_CRYPT
|
|
else if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) {
|
|
ret = wolfSSL_AsyncPoll(ssl_c, WOLF_POLL_FLAG_CHECK_HW);
|
|
if (ret < 0)
|
|
return -1;
|
|
}
|
|
#endif
|
|
#ifdef WOLFSSL_CHAIN_VERIFY_CB
|
|
else if (err == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) {
|
|
/* application has not reached a verdict yet; retry */
|
|
}
|
|
#endif
|
|
else if (err != WOLFSSL_ERROR_WANT_READ &&
|
|
err != WOLFSSL_ERROR_WANT_WRITE) {
|
|
char buff[WOLFSSL_MAX_ERROR_SZ];
|
|
fprintf(stderr, "memio client error = %d, %s\n", err,
|
|
wolfSSL_ERR_error_string((word32)err, buff));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
if (!hs_s) {
|
|
wolfSSL_SetLoggingPrefix("server");
|
|
ret = wolfSSL_accept(ssl_s);
|
|
wolfSSL_SetLoggingPrefix(NULL);
|
|
if (ret == WOLFSSL_SUCCESS) {
|
|
hs_s = 1;
|
|
}
|
|
else {
|
|
err = wolfSSL_get_error(ssl_s, ret);
|
|
if (err == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) {
|
|
/* retry non-blocking math */
|
|
}
|
|
#ifdef WOLFSSL_ASYNC_CRYPT
|
|
else if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) {
|
|
ret = wolfSSL_AsyncPoll(ssl_s, WOLF_POLL_FLAG_CHECK_HW);
|
|
if (ret < 0)
|
|
return -1;
|
|
}
|
|
#endif
|
|
#ifdef WOLFSSL_CHAIN_VERIFY_CB
|
|
else if (err == WC_NO_ERR_TRACE(CHAIN_VERIFY_WANT_E)) {
|
|
/* application has not reached a verdict yet; retry */
|
|
}
|
|
#endif
|
|
else if (err != WOLFSSL_ERROR_WANT_READ &&
|
|
err != WOLFSSL_ERROR_WANT_WRITE) {
|
|
char buff[WOLFSSL_MAX_ERROR_SZ];
|
|
fprintf(stderr, "memio server error = %d, %s\n", err,
|
|
wolfSSL_ERR_error_string((word32)err, buff));
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
handshake_complete = hs_c && hs_s;
|
|
max_rounds--;
|
|
if (rounds != NULL)
|
|
*rounds = *rounds + 1;
|
|
}
|
|
|
|
if (!handshake_complete)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int test_memio_setup_ex(struct test_memio_ctx *ctx,
|
|
WOLFSSL_CTX **ctx_c, WOLFSSL_CTX **ctx_s, WOLFSSL **ssl_c, WOLFSSL **ssl_s,
|
|
method_provider method_c, method_provider method_s,
|
|
byte *caCert, int caCertSz, byte *serverCert, int serverCertSz,
|
|
byte *serverKey, int serverKeySz)
|
|
{
|
|
int ret;
|
|
(void)caCert;
|
|
(void)caCertSz;
|
|
(void)serverCert;
|
|
(void)serverCertSz;
|
|
(void)serverKey;
|
|
(void)serverKeySz;
|
|
|
|
if (ctx_c != NULL && *ctx_c == NULL) {
|
|
*ctx_c = wolfSSL_CTX_new(method_c());
|
|
if (*ctx_c == NULL)
|
|
return -1;
|
|
#ifndef NO_CERTS
|
|
if (caCert == NULL) {
|
|
ret = wolfSSL_CTX_load_verify_locations(*ctx_c, caCertFile, 0);
|
|
}
|
|
else {
|
|
ret = wolfSSL_CTX_load_verify_buffer(*ctx_c, caCert, (long)caCertSz,
|
|
WOLFSSL_FILETYPE_ASN1);
|
|
}
|
|
if (ret != WOLFSSL_SUCCESS) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
return -1;
|
|
}
|
|
#endif /* NO_CERTS */
|
|
wolfSSL_SetIORecv(*ctx_c, test_memio_read_cb);
|
|
wolfSSL_SetIOSend(*ctx_c, test_memio_write_cb);
|
|
if (ctx->c_ciphers != NULL) {
|
|
ret = wolfSSL_CTX_set_cipher_list(*ctx_c, ctx->c_ciphers);
|
|
if (ret != WOLFSSL_SUCCESS) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ctx_s != NULL && *ctx_s == NULL) {
|
|
*ctx_s = wolfSSL_CTX_new(method_s());
|
|
if (*ctx_s == NULL) {
|
|
if (ctx_c != NULL) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
#ifndef NO_CERTS
|
|
if (serverKey == NULL) {
|
|
ret = wolfSSL_CTX_use_PrivateKey_file(*ctx_s, svrKeyFile,
|
|
CERT_FILETYPE);
|
|
}
|
|
else {
|
|
ret = wolfSSL_CTX_use_PrivateKey_buffer(*ctx_s, serverKey,
|
|
(long)serverKeySz, WOLFSSL_FILETYPE_ASN1);
|
|
}
|
|
if (ret != WOLFSSL_SUCCESS) {
|
|
if (ctx_s != NULL) {
|
|
wolfSSL_CTX_free(*ctx_s);
|
|
*ctx_s = NULL;
|
|
}
|
|
if (ctx_c != NULL) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (serverCert == NULL) {
|
|
ret = wolfSSL_CTX_use_certificate_file(*ctx_s, svrCertFile,
|
|
CERT_FILETYPE);
|
|
}
|
|
else {
|
|
ret = wolfSSL_CTX_use_certificate_chain_buffer_format(*ctx_s,
|
|
serverCert, (long)serverCertSz, WOLFSSL_FILETYPE_ASN1);
|
|
}
|
|
if (ret != WOLFSSL_SUCCESS) {
|
|
if (ctx_s != NULL) {
|
|
wolfSSL_CTX_free(*ctx_s);
|
|
*ctx_s = NULL;
|
|
}
|
|
if (ctx_c != NULL) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
#endif /* NO_CERTS */
|
|
wolfSSL_SetIORecv(*ctx_s, test_memio_read_cb);
|
|
wolfSSL_SetIOSend(*ctx_s, test_memio_write_cb);
|
|
if (ctx->s_ciphers != NULL) {
|
|
ret = wolfSSL_CTX_set_cipher_list(*ctx_s, ctx->s_ciphers);
|
|
if (ret != WOLFSSL_SUCCESS) {
|
|
if (ctx_s != NULL) {
|
|
wolfSSL_CTX_free(*ctx_s);
|
|
*ctx_s = NULL;
|
|
}
|
|
if (ctx_c != NULL) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ctx_c != NULL && ssl_c != NULL) {
|
|
*ssl_c = wolfSSL_new(*ctx_c);
|
|
if (*ssl_c == NULL) {
|
|
if (ctx_s != NULL) {
|
|
wolfSSL_CTX_free(*ctx_s);
|
|
*ctx_s = NULL;
|
|
}
|
|
if (ctx_c != NULL) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
wolfSSL_SetIOWriteCtx(*ssl_c, ctx);
|
|
wolfSSL_SetIOReadCtx(*ssl_c, ctx);
|
|
}
|
|
if (ctx_s != NULL && ssl_s != NULL) {
|
|
*ssl_s = wolfSSL_new(*ctx_s);
|
|
if (*ssl_s == NULL) {
|
|
if (ssl_c != NULL) {
|
|
wolfSSL_free(*ssl_c);
|
|
*ssl_c = NULL;
|
|
}
|
|
if (ctx_s != NULL) {
|
|
wolfSSL_CTX_free(*ctx_s);
|
|
*ctx_s = NULL;
|
|
}
|
|
if (ctx_c != NULL) {
|
|
wolfSSL_CTX_free(*ctx_c);
|
|
*ctx_c = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
wolfSSL_SetIOWriteCtx(*ssl_s, ctx);
|
|
wolfSSL_SetIOReadCtx(*ssl_s, ctx);
|
|
#if !defined(NO_DH)
|
|
SetDH(*ssl_s);
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void test_memio_simulate_want_write(struct test_memio_ctx *ctx, int is_client,
|
|
int enable)
|
|
{
|
|
if (ctx == NULL)
|
|
return;
|
|
|
|
if (is_client)
|
|
ctx->c_force_want_write = (enable != 0);
|
|
else
|
|
ctx->s_force_want_write = (enable != 0);
|
|
}
|
|
|
|
void test_memio_clear_buffer(struct test_memio_ctx *ctx, int is_client)
|
|
{
|
|
if (is_client) {
|
|
ctx->c_len = 0;
|
|
ctx->c_msg_pos = 0;
|
|
ctx->c_msg_count = 0;
|
|
ctx->c_force_want_write = 0;
|
|
} else {
|
|
ctx->s_len = 0;
|
|
ctx->s_msg_pos = 0;
|
|
ctx->s_msg_count = 0;
|
|
ctx->s_force_want_write = 0;
|
|
}
|
|
}
|
|
|
|
/* Inject a message into the buffer for client or server */
|
|
int test_memio_inject_message(struct test_memio_ctx* ctx, int client,
|
|
const char* data, int sz)
|
|
{
|
|
int* len;
|
|
int* msg_count;
|
|
int* msg_sizes;
|
|
byte* buff;
|
|
|
|
/* Select buffer and metadata for client or server */
|
|
if (client) {
|
|
buff = ctx->c_buff;
|
|
len = &ctx->c_len;
|
|
msg_count = &ctx->c_msg_count;
|
|
msg_sizes = ctx->c_msg_sizes;
|
|
}
|
|
else {
|
|
buff = ctx->s_buff;
|
|
len = &ctx->s_len;
|
|
msg_count = &ctx->s_msg_count;
|
|
msg_sizes = ctx->s_msg_sizes;
|
|
}
|
|
|
|
/* Check if buffer has enough space for new message */
|
|
if (*len + sz > TEST_MEMIO_BUF_SZ) {
|
|
return -1;
|
|
}
|
|
/* Check if message count does not exceed maximum allowed */
|
|
if (*msg_count >= TEST_MEMIO_MAX_MSGS) {
|
|
return -1;
|
|
}
|
|
|
|
/* Copy message data into buffer */
|
|
XMEMCPY(buff + *len, data, (size_t)sz);
|
|
|
|
/* Record message size and increment message count */
|
|
msg_sizes[*msg_count] = sz;
|
|
(*msg_count)++;
|
|
*len += sz;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Copy a message from the buffer to an output buffer */
|
|
int test_memio_copy_message(const struct test_memio_ctx *ctx, int client,
|
|
char *out, int *out_sz, int msg_pos)
|
|
{
|
|
const char* buff = NULL;
|
|
int buff_sz = 0;
|
|
|
|
/* Retrieve message pointer and size for given position */
|
|
if (test_memio_get_message(ctx, client, &buff, &buff_sz, msg_pos) != 0)
|
|
return -1;
|
|
|
|
/* Ensure output buffer is large enough */
|
|
if (*out_sz < buff_sz)
|
|
return -1;
|
|
|
|
/* Copy message to output buffer */
|
|
XMEMCPY(out, buff, (size_t)buff_sz);
|
|
*out_sz = buff_sz;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Get a pointer and size to a message in the buffer */
|
|
int test_memio_get_message(const struct test_memio_ctx *ctx, int client,
|
|
const char **out, int *out_sz, int msg_pos)
|
|
{
|
|
int msg_count;
|
|
const int* msg_sizes;
|
|
int i;
|
|
const byte* buff;
|
|
|
|
/* Select buffer and message metadata for client or server */
|
|
if (client) {
|
|
buff = ctx->c_buff;
|
|
msg_count = ctx->c_msg_count;
|
|
msg_sizes = ctx->c_msg_sizes;
|
|
}
|
|
else {
|
|
buff = ctx->s_buff;
|
|
msg_count = ctx->s_msg_count;
|
|
msg_sizes = ctx->s_msg_sizes;
|
|
}
|
|
|
|
/* Validate message position */
|
|
if (msg_pos < 0 || msg_pos >= msg_count) {
|
|
return -1;
|
|
}
|
|
|
|
/* Find start of the message in the buffer */
|
|
for (i = 0; i < msg_pos; i++) {
|
|
buff += msg_sizes[i];
|
|
}
|
|
|
|
/* Set output pointers to message data and size */
|
|
*out = (const char*)buff;
|
|
*out_sz = msg_sizes[msg_pos];
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The random value placed in a ServerHello to mark it as a HelloRetryRequest.
|
|
* See RFC 8446 Section 4.1.3. */
|
|
static const byte test_hello_retry_request_random[32] = {
|
|
0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11,
|
|
0xBE, 0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91,
|
|
0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E,
|
|
0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C
|
|
};
|
|
|
|
/* Returns 1 if the first server->client record buffered in ctx is a
|
|
* HelloRetryRequest, 0 otherwise. A HelloRetryRequest is sent as a ServerHello
|
|
* (handshake type server_hello) carrying the special random above. */
|
|
int test_memio_msg_is_hello_retry_request(const struct test_memio_ctx *ctx)
|
|
{
|
|
const char* msg = NULL;
|
|
int msg_sz = 0;
|
|
/* TLS record header (5) + handshake header (4) + legacy_version (2) is the
|
|
* offset of the 32-byte ServerHello random within the record. */
|
|
const int random_off = 5 + 4 + 2;
|
|
|
|
/* The server's flight is buffered for the client (client = 1). */
|
|
if (test_memio_get_message(ctx, 1, &msg, &msg_sz, 0) != 0)
|
|
return 0;
|
|
/* Need a handshake record (0x16) holding a server_hello (0x02) with a full
|
|
* random. */
|
|
if (msg_sz < random_off + (int)sizeof(test_hello_retry_request_random))
|
|
return 0;
|
|
if ((byte)msg[0] != 0x16 || (byte)msg[5] != 0x02)
|
|
return 0;
|
|
|
|
return XMEMCMP(msg + random_off, test_hello_retry_request_random,
|
|
sizeof(test_hello_retry_request_random)) == 0;
|
|
}
|
|
|
|
int test_memio_move_message(struct test_memio_ctx *ctx, int client,
|
|
int msg_pos_in, int msg_pos_out)
|
|
{
|
|
int msg_count;
|
|
int* msg_sizes;
|
|
int i;
|
|
byte* buff;
|
|
byte* buff_in;
|
|
byte* buff_out;
|
|
int total_size = 0;
|
|
int msg_in_size;
|
|
|
|
/* Select buffer and message metadata for client or server */
|
|
if (client) {
|
|
buff = buff_in = buff_out = ctx->c_buff;
|
|
msg_count = ctx->c_msg_count;
|
|
msg_sizes = ctx->c_msg_sizes;
|
|
}
|
|
else {
|
|
buff = buff_in = buff_out = ctx->s_buff;
|
|
msg_count = ctx->s_msg_count;
|
|
msg_sizes = ctx->s_msg_sizes;
|
|
}
|
|
|
|
/* Validate input and output message positions */
|
|
if (msg_pos_in < 0 || msg_pos_in >= msg_count)
|
|
return -1;
|
|
if (msg_pos_out < 0 || msg_pos_out >= msg_count)
|
|
msg_pos_out = msg_count-1;
|
|
if (msg_pos_in == msg_pos_out)
|
|
return 0;
|
|
|
|
/* Get the size of the message to move */
|
|
msg_in_size = msg_sizes[msg_pos_in];
|
|
|
|
/* Calculate the total size of all messages */
|
|
for (i = 0; i < msg_count; i++)
|
|
total_size += msg_sizes[i];
|
|
|
|
/* Check if buffer has enough space for the move */
|
|
if (total_size + msg_in_size > TEST_MEMIO_BUF_SZ)
|
|
return -1;
|
|
|
|
/* Find the start of the input message in the buffer */
|
|
for (i = 0; i < msg_pos_in; i++)
|
|
buff_in += msg_sizes[i];
|
|
|
|
/* Find the position to move the message to in the buffer */
|
|
for (i = 0; i < msg_pos_out + (msg_pos_out > msg_pos_in ? 1 : 0); i++)
|
|
buff_out += msg_sizes[i];
|
|
|
|
/* Make space for the moved message at the output position */
|
|
XMEMMOVE(buff_out + msg_in_size, buff_out,
|
|
total_size - (buff_out - buff));
|
|
total_size += msg_in_size;
|
|
|
|
/* Adjust input pointer if it was after the output position */
|
|
if (buff_in > buff_out)
|
|
buff_in += msg_in_size;
|
|
|
|
/* Copy the message to its new position */
|
|
XMEMCPY(buff_out, buff_in, msg_in_size);
|
|
|
|
/* Remove the original message from its old position */
|
|
XMEMMOVE(buff_in, buff_in + msg_in_size,
|
|
total_size - (buff_in - buff) - msg_in_size);
|
|
|
|
/* Update the message sizes array to reflect the move */
|
|
if (msg_pos_in < msg_pos_out) {
|
|
XMEMMOVE(msg_sizes + msg_pos_in, msg_sizes + msg_pos_in + 1,
|
|
sizeof(*msg_sizes) *
|
|
((msg_sizes + msg_pos_out) - (msg_sizes + msg_pos_in)));
|
|
msg_sizes[msg_pos_out] = msg_in_size;
|
|
}
|
|
else {
|
|
XMEMMOVE(msg_sizes + msg_pos_out + 1, msg_sizes + msg_pos_out,
|
|
sizeof(*msg_sizes) *
|
|
((msg_sizes + msg_pos_in) - (msg_sizes + msg_pos_out)));
|
|
msg_sizes[msg_pos_out] = msg_in_size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Drop (remove) a message from the buffer and update metadata */
|
|
int test_memio_drop_message(struct test_memio_ctx *ctx, int client, int msg_pos)
|
|
{
|
|
int *len;
|
|
int *msg_count;
|
|
int *msg_sizes;
|
|
int msg_off, msg_sz;
|
|
int i;
|
|
byte *buff;
|
|
|
|
/* Select buffer and metadata for client or server */
|
|
if (client) {
|
|
buff = ctx->c_buff;
|
|
len = &ctx->c_len;
|
|
msg_count = &ctx->c_msg_count;
|
|
msg_sizes = ctx->c_msg_sizes;
|
|
} else {
|
|
buff = ctx->s_buff;
|
|
len = &ctx->s_len;
|
|
msg_count = &ctx->s_msg_count;
|
|
msg_sizes = ctx->s_msg_sizes;
|
|
}
|
|
|
|
/* Check for empty message list */
|
|
if (*msg_count == 0) {
|
|
return -1;
|
|
}
|
|
|
|
msg_off = 0;
|
|
/* Validate message position */
|
|
if (msg_pos >= *msg_count) {
|
|
return -1;
|
|
}
|
|
|
|
/* Find offset and size of message to drop */
|
|
msg_sz = msg_sizes[msg_pos];
|
|
for (i = 0; i < msg_pos; i++) {
|
|
msg_off += msg_sizes[i];
|
|
}
|
|
|
|
/* Remove message from buffer by shifting remaining data */
|
|
XMEMMOVE(buff + msg_off, buff + msg_off + msg_sz, *len - msg_off - msg_sz);
|
|
|
|
/* Update message sizes array */
|
|
for (i = msg_pos; i < *msg_count - 1; i++) {
|
|
msg_sizes[i] = msg_sizes[i + 1];
|
|
}
|
|
|
|
/* Update buffer length and message count */
|
|
*len -= msg_sz;
|
|
(*msg_count)--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Remove a region from the buffer, possibly dropping or shrinking a message */
|
|
int test_memio_remove_from_buffer(struct test_memio_ctx* ctx, int client,
|
|
int off, int sz)
|
|
{
|
|
int* len;
|
|
int* msg_count;
|
|
int* msg_sizes;
|
|
int msg_off;
|
|
int i;
|
|
byte* buff;
|
|
|
|
/* Select buffer and metadata for client or server */
|
|
if (client) {
|
|
buff = ctx->c_buff;
|
|
len = &ctx->c_len;
|
|
msg_count = &ctx->c_msg_count;
|
|
msg_sizes = ctx->c_msg_sizes;
|
|
}
|
|
else {
|
|
buff = ctx->s_buff;
|
|
len = &ctx->s_len;
|
|
msg_count = &ctx->s_msg_count;
|
|
msg_sizes = ctx->s_msg_sizes;
|
|
}
|
|
|
|
/* Validate buffer and offset */
|
|
if (*len == 0) {
|
|
return -1;
|
|
}
|
|
if (off >= *len) {
|
|
return -1;
|
|
}
|
|
if (off + sz > *len) {
|
|
return -1;
|
|
}
|
|
|
|
/* Find which message the offset is in */
|
|
msg_off = 0;
|
|
for (i = 0; i < *msg_count; i++) {
|
|
if (off >= msg_off && off < msg_off + msg_sizes[i]) {
|
|
break;
|
|
}
|
|
msg_off += msg_sizes[i];
|
|
}
|
|
|
|
/* Don't support records split across messages */
|
|
if (off + sz > msg_off + msg_sizes[i]) {
|
|
return -1;
|
|
}
|
|
if (i == *msg_count) {
|
|
return -1;
|
|
}
|
|
|
|
/* If removing entire message, drop it */
|
|
if (sz == msg_sizes[i]) {
|
|
return test_memio_drop_message(ctx, client, i);
|
|
}
|
|
|
|
/* Remove part of message by shifting buffer and updating size */
|
|
XMEMMOVE(buff + off, buff + off + sz, *len - off - sz);
|
|
msg_sizes[i] -= sz;
|
|
*len -= sz;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Modify the length of a message in the buffer, shifting data as needed */
|
|
int test_memio_modify_message_len(struct test_memio_ctx* ctx, int client,
|
|
int msg_pos, int new_len)
|
|
{
|
|
int* len;
|
|
int* msg_count;
|
|
int* msg_sizes;
|
|
int msg_off, msg_sz;
|
|
int i;
|
|
byte* buff;
|
|
|
|
/* Select buffer and metadata for client or server */
|
|
if (client) {
|
|
buff = ctx->c_buff;
|
|
len = &ctx->c_len;
|
|
msg_count = &ctx->c_msg_count;
|
|
msg_sizes = ctx->c_msg_sizes;
|
|
}
|
|
else {
|
|
buff = ctx->s_buff;
|
|
len = &ctx->s_len;
|
|
msg_count = &ctx->s_msg_count;
|
|
msg_sizes = ctx->s_msg_sizes;
|
|
}
|
|
|
|
/* Validate message count and position */
|
|
if (*msg_count == 0) {
|
|
return -1;
|
|
}
|
|
if (msg_pos >= *msg_count) {
|
|
return -1;
|
|
}
|
|
|
|
/* Find offset and size of message to modify */
|
|
msg_off = 0;
|
|
for (i = 0; i < msg_pos; i++) {
|
|
msg_off += msg_sizes[i];
|
|
}
|
|
msg_sz = msg_sizes[msg_pos];
|
|
|
|
/* Check if buffer has enough space for length increase */
|
|
if (new_len > msg_sz) {
|
|
if (*len + (new_len - msg_sz) > TEST_MEMIO_BUF_SZ) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Shift buffer contents to accommodate new message length */
|
|
XMEMMOVE(buff + msg_off + new_len, buff + msg_off + msg_sz,
|
|
*len - msg_off - msg_sz);
|
|
|
|
/* Update message size and buffer length */
|
|
msg_sizes[msg_pos] = new_len;
|
|
*len = *len - msg_sz + new_len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int test_memio_setup(struct test_memio_ctx *ctx,
|
|
WOLFSSL_CTX **ctx_c, WOLFSSL_CTX **ctx_s, WOLFSSL **ssl_c, WOLFSSL **ssl_s,
|
|
method_provider method_c, method_provider method_s)
|
|
{
|
|
return test_memio_setup_ex(ctx, ctx_c, ctx_s, ssl_c, ssl_s, method_c,
|
|
method_s, NULL, 0, NULL, 0, NULL, 0);
|
|
}
|
|
|
|
#ifdef WOLFSSL_SESSION_EXPORT
|
|
/* Relabel a session export as version 6 and drop fatalAlert, the options byte
|
|
* ahead of the two connection version bytes that version 6 does not have. */
|
|
void test_export_options_v6(unsigned char* session, unsigned int* sessionSz)
|
|
{
|
|
const unsigned int totalLenAt = WOLFSSL_EXPORT_LEN;
|
|
const unsigned int optLenAt = 2 * WOLFSSL_EXPORT_LEN;
|
|
const unsigned int optAt = 3 * WOLFSSL_EXPORT_LEN;
|
|
unsigned int totalLen;
|
|
unsigned int optSz;
|
|
unsigned int at;
|
|
|
|
totalLen = ((unsigned int)session[totalLenAt] << 8) |
|
|
session[totalLenAt + 1];
|
|
optSz = ((unsigned int)session[optLenAt] << 8) | session[optLenAt + 1];
|
|
at = optAt + optSz - 3;
|
|
|
|
XMEMMOVE(session + at, session + at + 1, *sessionSz - at - 1);
|
|
*sessionSz -= 1;
|
|
totalLen -= 1;
|
|
optSz -= 1;
|
|
session[1] = (byte)((session[1] & 0xF0) | WOLFSSL_EXPORT_VERSION_6);
|
|
session[totalLenAt] = (byte)(totalLen >> 8);
|
|
session[totalLenAt + 1] = (byte)totalLen;
|
|
session[optLenAt] = (byte)(optSz >> 8);
|
|
session[optLenAt + 1] = (byte)optSz;
|
|
}
|
|
#endif /* WOLFSSL_SESSION_EXPORT */
|
|
|
|
#endif /* HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES_BUILD */
|
|
|
|
#if defined(OPENSSL_EXTRA) && !defined(NO_BIO)
|
|
/* Copy a memory BIO into buf as a string. Returns length, or -1. */
|
|
int test_bio_mem_to_str(WOLFSSL_BIO* bio, char* buf, int bufSz)
|
|
{
|
|
char* data = NULL;
|
|
int len = wolfSSL_BIO_get_mem_data(bio, &data);
|
|
|
|
buf[0] = '\0';
|
|
if ((data == NULL) || (len <= 0) || (len >= bufSz)) {
|
|
return -1;
|
|
}
|
|
XMEMCPY(buf, data, (size_t)len);
|
|
buf[len] = '\0';
|
|
|
|
return len;
|
|
}
|
|
#endif
|
|
|
|
#if !defined(NO_FILESYSTEM) && defined(OPENSSL_EXTRA) && \
|
|
defined(DEBUG_UNIT_TEST_CERTS)
|
|
/* Used when debugging name constraint tests. Not static to allow use in
|
|
* multiple locations with complex define guards. */
|
|
void DEBUG_WRITE_CERT_X509(WOLFSSL_X509* x509, const char* fileName)
|
|
{
|
|
BIO* out = BIO_new_file(fileName, "wb");
|
|
if (out != NULL) {
|
|
PEM_write_bio_X509(out, x509);
|
|
BIO_free(out);
|
|
}
|
|
}
|
|
void DEBUG_WRITE_DER(const byte* der, int derSz, const char* fileName)
|
|
{
|
|
BIO* out = BIO_new_file(fileName, "wb");
|
|
if (out != NULL) {
|
|
BIO_write(out, der, derSz);
|
|
BIO_free(out);
|
|
}
|
|
}
|
|
#endif
|