1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00
Files
wolfssl/tests/unit-mcdc/test_internal_clienthello_whitebox.c
Daniele Lacamera c4bab8d292 tests: name the renegotiation_info extension and widen two skip messages
The ClientHello builder wrote the renegotiation_info extension type as a
conditional whose two branches were the same constant, which is a constant
expression some -Wextra builds reject and which hid what 0xFF01 is. It is
TLSX_RENEGOTIATION_INFO; use that.

The CRL and OCSP skip stubs are guarded on their feature macro plus certs plus
not-WOLFCRYPT_ONLY. Their printed messages already said so; the #else comments
still named only the feature macro, so a reader chasing a skip saw the wrong
reason.

No coverage change: tls_core 810/1748 and revocation 28/47 + 31/48 both
re-measured identical, gates green.
2026-09-14 17:44:45 +02:00

370 lines
14 KiB
C

/* test_internal_clienthello_whitebox.c -- MC/DC white-box driver for
* DoClientHello in src/internal.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 2 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-1301, USA
*/
/* FORGED FRAMES INSTEAD OF A HANDSHAKE.
*
* The leaf-function white-boxes in this directory have run out of leaves. What
* is left in internal.c is message handling, and the obvious way to reach it
* -- stand up two endpoints and run handshakes -- is both expensive and, for
* MC/DC, mostly useless: two conforming endpoints produce one ClientHello
* shape, so every operand that distinguishes a hostile hello from a friendly
* one is evaluated the same way every time.
*
* The cheaper move is to forge the frame. DoClientHello takes a byte buffer
* and a length; it does not take a socket, a peer, or a handshake. So the
* fixture is a REAL server WOLFSSL -- from a CTX with a certificate loaded, so
* suites, hashes and buffers are all properly constructed -- fed a
* hand-assembled ClientHello body. Real endpoint, forged input. Nothing on the
* wire, nothing to synchronise, and each vector differs from its partner in
* exactly the byte or flag under test.
*
* That is what reaches the decisions the handshake tests cannot: a version
* below the configured minimum, a compression list with no null method, a
* session id where a cookie is expected, a renegotiation info extension on a
* connection that never renegotiated, an extension set that contradicts the
* options the server was configured with.
*
* A send callback is installed because several of these paths answer with a
* fatal alert; without one the alert path dereferences a NULL CBIOSend.
*
* Rules, as for the sibling drivers:
* - options.h FIRST, or the smoke build compiles this with the feature
* macros undefined and it silently becomes a no-op that still exits 0.
* - main() ALWAYS returns 0; a non-zero exit discards the whole variant.
* - Bail paths print, so "covered nothing" differs from "nothing to say".
*/
#include <wolfssl/options.h>
#include <src/internal.c>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if !defined(WOLFCRYPT_ONLY) && !defined(NO_TLS) && \
!defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \
!defined(NO_CERTS) && !defined(NO_FILESYSTEM)
static int g_calls;
static int wb_send_sink(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
(void)ssl; (void)buf; (void)ctx;
return sz; /* swallow alerts, report them fully written */
}
static int wb_recv_none(WOLFSSL* ssl, char* buf, int sz, void* ctx)
{
(void)ssl; (void)buf; (void)sz; (void)ctx;
return WOLFSSL_CBIO_ERR_WANT_READ;
}
/* ------------------------------------------------------------- the forger */
typedef struct {
byte buf[768];
word32 len;
} Frame;
static void fr_reset(Frame* f) { XMEMSET(f, 0, sizeof(*f)); }
static void fr_u8(Frame* f, byte v)
{
if (f->len < sizeof(f->buf)) f->buf[f->len++] = v;
}
static void fr_u16(Frame* f, word16 v) { fr_u8(f, (byte)(v >> 8));
fr_u8(f, (byte)(v & 0xff)); }
static void fr_fill(Frame* f, byte v, int n)
{
int i; for (i = 0; i < n; i++) fr_u8(f, v);
}
/* What a forged ClientHello may differ in. Each field exists because some
* decision in DoClientHello reads it. */
typedef struct {
byte major, minor; /* the offered version */
int sessionIdLen; /* 0, 32, or an illegal length */
int cookieLen; /* DTLS only */
int nSuites; /* cipher suite count (in suites, not bytes)*/
int suiteBogus; /* offer suites the server cannot match */
int compNo; /* offer the null compression method */
int compZlib; /* offer zlib */
int extReneg; /* renegotiation_info */
int extTicket; /* session_ticket */
int extEtm; /* encrypt_then_mac */
int extEms; /* extended_master_secret */
int extTruncate; /* declare more extension bytes than follow */
const char* what;
} Hello;
/* The suites the SERVER was configured with, echoed back. A forged hello with
* invented suite bytes fails MatchSuite and returns before the compression,
* extension and downgrade logic is ever reached -- which is what the first
* version of this driver did, and why it measured almost nothing. Reading
* ssl->suites is the difference between a frame that is refused at the door
* and one that gets deep enough for its one forged field to matter. */
static void wb_build(Frame* f, const Hello* h, int dtls, const WOLFSSL* ssl)
{
fr_reset(f);
fr_u8(f, h->major);
fr_u8(f, h->minor);
fr_fill(f, 0xAB, RAN_LEN); /* client random */
fr_u8(f, (byte)h->sessionIdLen);
fr_fill(f, 0xCD, h->sessionIdLen);
if (dtls) {
fr_u8(f, (byte)h->cookieLen);
fr_fill(f, 0xEF, h->cookieLen);
}
{
int have = (ssl->suites != NULL) ? ssl->suites->suiteSz / 2 : 0;
int n = h->nSuites;
int i;
if (!h->suiteBogus && n > have)
n = have;
fr_u16(f, (word16)(n * 2));
for (i = 0; i < n; i++) {
if (h->suiteBogus) {
fr_u8(f, 0x00); fr_u8(f, (byte)(0xF0 + i));
}
else {
fr_u8(f, ssl->suites->suites[i * 2]);
fr_u8(f, ssl->suites->suites[i * 2 + 1]);
}
}
}
{
int n = (h->compNo ? 1 : 0) + (h->compZlib ? 1 : 0);
fr_u8(f, (byte)n);
if (h->compZlib) fr_u8(f, ZLIB_COMPRESSION);
if (h->compNo) fr_u8(f, NO_COMPRESSION);
}
/* extensions, assembled into a scratch frame first so the length is
* known before it is written */
{
Frame ext;
fr_reset(&ext);
if (h->extReneg) {
fr_u16(&ext, TLSX_RENEGOTIATION_INFO);
fr_u16(&ext, 1);
fr_u8(&ext, 0); /* empty renegotiated_connection */
}
if (h->extTicket) {
fr_u16(&ext, TLSX_SESSION_TICKET);
fr_u16(&ext, 0);
}
if (h->extEtm) {
fr_u16(&ext, TLSX_ENCRYPT_THEN_MAC);
fr_u16(&ext, 0);
}
if (h->extEms) {
fr_u16(&ext, HELLO_EXT_EXTMS);
fr_u16(&ext, 0);
}
if (ext.len > 0 || h->extTruncate) {
word32 i;
fr_u16(f, (word16)(h->extTruncate ? ext.len + 16 : ext.len));
for (i = 0; i < ext.len; i++)
fr_u8(f, ext.buf[i]);
}
}
}
/* -------------------------------------------------------------- one vector */
/* The ssl is rebuilt per vector: DoClientHello writes session state, suites
* and extension lists into it, and a second hello on the same object would be
* a renegotiation rather than the case under test. */
static void wb_hello(WOLFSSL_CTX* ctx, const Hello* h, int dtls,
int usingCompression, int useTicket, int encThenMac,
int downgrade, byte minDowngrade)
{
WOLFSSL* ssl = wolfSSL_new(ctx);
Frame f;
word32 idx = 0;
if (ssl == NULL)
return;
wolfSSL_SSLSetIORecv(ssl, wb_recv_none);
wolfSSL_SSLSetIOSend(ssl, wb_send_sink);
ssl->options.side = WOLFSSL_SERVER_END;
ssl->options.usingCompression = (byte)usingCompression;
ssl->options.useTicket = (byte)useTicket;
ssl->options.encThenMac = (byte)encThenMac;
ssl->options.downgrade = (byte)downgrade;
ssl->options.minDowngrade = minDowngrade;
#ifdef WOLFSSL_DTLS
ssl->options.dtls = (byte)dtls;
if (dtls) {
ssl->version.major = DTLS_MAJOR;
ssl->version.minor = DTLSv1_2_MINOR;
ssl->options.dtlsStateful = 1;
}
#endif
wb_build(&f, h, dtls, ssl);
{
int r = DoClientHello(ssl, f.buf, &idx, f.len);
if (getenv("WB_TRACE"))
printf(" %-52s -> %d (idx %u of %u)\n", h->what, r,
(unsigned)idx, (unsigned)f.len);
}
g_calls++;
wolfSSL_free(ssl);
}
/* ---------------------------------------------------------------- main */
int main(void)
{
WOLFSSL_CTX* ctx = NULL;
size_t i;
int dtls;
/* The accepting baseline, then one field changed per row. A conforming
* client sends the first row and nothing else; every other row is a hello
* a server must survive but no test peer produces. */
static const Hello rows[] = {
/* maj min sid ck ns bog no zl rg tk em ems tr what */
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"baseline TLS 1.2" },
{ SSLv3_MAJOR, TLSv1_3_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"offers 1.3 in the legacy field" },
{ SSLv3_MAJOR, TLSv1_1_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"offers 1.1" },
{ SSLv3_MAJOR, TLSv1_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"offers 1.0" },
{ SSLv3_MAJOR, SSLv3_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"offers SSLv3" },
{ 0x02, 0x00, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"a major version from no protocol" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 32, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"carries a session id" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 64, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"session id longer than the field allows" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
"offers only suites the server has not got" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
"offers no cipher suites at all" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0,64, 0, 1, 0, 0, 0, 0, 0, 0,
"offers sixty-four suites" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0,
"zlib only: no null compression method" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0,
"both compression methods" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
"an empty compression list" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0,
"renegotiation_info on a fresh connection" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0,
"asks for a session ticket" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0,
"asks for encrypt-then-mac" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0,
"asks for extended master secret" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0,
"asks for everything at once" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1,
"extension length longer than the extensions" },
{ SSLv3_MAJOR, TLSv1_2_MINOR, 32, 8, 1, 0, 1, 0, 0, 0, 0, 0, 0,
"session id and a cookie" },
};
if (wolfSSL_Init() != WOLFSSL_SUCCESS) {
printf("internal clienthello white-box: wolfSSL_Init failed\n");
goto done;
}
/* A server CTX with a real certificate: wolfSSL_new returns NULL for a
* server with no certificate loaded, and a NULL ssl is how an earlier
* white-box in this campaign silently covered nothing while exiting 0. */
ctx = wolfSSL_CTX_new(wolfSSLv23_server_method());
if (ctx == NULL) {
printf("internal clienthello white-box: CTX_new failed\n");
goto done;
}
if (wolfSSL_CTX_use_certificate_file(ctx, "certs/server-cert.pem",
WOLFSSL_FILETYPE_PEM)
!= WOLFSSL_SUCCESS ||
wolfSSL_CTX_use_PrivateKey_file(ctx, "certs/server-key.pem",
WOLFSSL_FILETYPE_PEM)
!= WOLFSSL_SUCCESS) {
printf("internal clienthello white-box: no server credentials, "
"nothing driven\n");
goto done;
}
/* Every hello against both compression settings and both downgrade
* policies, so the server-side operands of each decision get their pair
* as well as the client-side bytes. */
for (i = 0; i < sizeof(rows) / sizeof(rows[0]); i++) {
/* downgrade on for the bulk of the sweep: with it off, any hello
* whose version differs from the server's is refused at the version
* check and the rest of the function is never entered. Both settings
* still appear, so that operand keeps its pair. */
wb_hello(ctx, &rows[i], 0, 0, 0, 0, 1, SSLv3_MINOR);
wb_hello(ctx, &rows[i], 0, 1, 0, 0, 1, SSLv3_MINOR);
wb_hello(ctx, &rows[i], 0, 0, 1, 1, 1, SSLv3_MINOR);
wb_hello(ctx, &rows[i], 0, 1, 1, 1, 1, SSLv3_MINOR);
wb_hello(ctx, &rows[i], 0, 0, 0, 0, 0, TLSv1_MINOR);
/* downgrade allowed, with a floor above what the row offers, which
* is what belowMinDowngrade exists to detect */
wb_hello(ctx, &rows[i], 0, 0, 0, 0, 1, TLSv1_2_MINOR);
wb_hello(ctx, &rows[i], 0, 0, 0, 0, 1, SSLv3_MINOR);
}
#ifdef WOLFSSL_DTLS
for (dtls = 1; dtls < 2; dtls++)
for (i = 0; i < sizeof(rows) / sizeof(rows[0]); i++)
wb_hello(ctx, &rows[i], dtls, 0, 0, 0, 1, SSLv3_MINOR);
#else
(void)dtls;
#endif
printf("internal clienthello white-box: %d forged hellos\n", g_calls);
done:
if (ctx != NULL)
wolfSSL_CTX_free(ctx);
wolfSSL_Cleanup();
return 0; /* always 0: a non-zero exit discards the variant */
}
#else
int main(void)
{
printf("internal clienthello white-box: skipped (server TLS 1.2 or "
"certs not built)\n");
return 0;
}
#endif