Files
wolfssl/tests/api/test_coding.c
T
Daniele Lacamera afe37aabd1 tests: MC/DC gap-closing for the deferred Part-3 modules
Close API-reachable and file-static gaps in the smaller modules (+52 union
conditions): kdf 75->91, coding 48->64, wolfentropy 9->13, curve448 54->58
(100%), chacha20_poly1305 43->46 (100%), hash 6->7 (100%), wc_encrypt 16->19,
pwdbased 14->17, signature 30->32. Additive cases in the existing tests (one
new test_wc_HashTypeConvert), plus wolfentropy/random white-box drivers.
Remaining are justified residuals (WOLFSSL_LOCAL wc_CryptKey, dead defensive
branches, alloc/crypto-failure err-chains, platform seed sources).
2026-07-17 08:56:03 +02:00

471 lines
19 KiB
C

/* test_coding.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 <wolfssl/wolfcrypt/coding.h>
#include <tests/api/api.h>
#include <tests/api/test_coding.h>
/*
* MC/DC DecisionCoverage for wolfcrypt/src/coding.c (Base64 / Base16).
*
* Base64_SkipNewline() is WOLFSSL_LOCAL (hidden visibility): it is NOT called
* directly here (that would break the shared shared-lib CI), it is exercised
* only indirectly via Base64_Decode() on inputs containing embedded
* whitespace / CR / LF, in test_wc_Base64_DecodeWhitespaceCoverage below.
*/
/* Exercises the argument-check OR, the padding logic, the bad-character and
* short-output-buffer branches of Base64_Decode() (constant-time path) and
* Base64_Decode_nonCT() (table path). */
int test_wc_Base64_DecodeDecisionCoverage(void)
{
EXPECT_DECLS;
#if !defined(NO_CODING) && defined(WOLFSSL_BASE64_DECODE)
/* "TWFu" -> "Man" */
const byte good[] = { 'T', 'W', 'F', 'u' };
byte out[8];
word32 outLen;
/* --- argument-check decision: (in==NULL && inLen>0) || out==NULL ||
* outLen==NULL --- independence pair for each operand */
/* in==NULL, inLen>0 -> true (BAD_FUNC_ARG) */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(NULL, 4, out, &outLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* in!=NULL, inLen>0 -> the (in==NULL && inLen>0) sub-term is false */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(good, (word32)sizeof(good), out, &outLen), 0);
/* in==NULL, inLen==0 -> (in==NULL && inLen>0) false; other operands false */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(NULL, 0, out, &outLen), 0);
/* out==NULL alone -> true */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(good, (word32)sizeof(good), NULL, &outLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* outLen==NULL alone -> true */
ExpectIntEQ(Base64_Decode(good, (word32)sizeof(good), out, NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* --- valid decode, verify result --- */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(good, (word32)sizeof(good), out, &outLen), 0);
ExpectIntEQ(outLen, 3);
ExpectIntEQ(XMEMCMP(out, "Man", 3), 0);
/* --- padding decisions --- */
/* pad3 && pad4 : "TQ==" -> "M" (1 byte) */
{
const byte p2[] = { 'T', 'Q', '=', '=' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(p2, (word32)sizeof(p2), out, &outLen), 0);
ExpectIntEQ(outLen, 1);
ExpectIntEQ(out[0], 'M');
}
/* !pad3 && pad4 : "TWE=" -> "Ma" (2 bytes) */
{
const byte p1[] = { 'T', 'W', 'E', '=' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(p1, (word32)sizeof(p1), out, &outLen), 0);
ExpectIntEQ(outLen, 2);
ExpectIntEQ(XMEMCMP(out, "Ma", 2), 0);
}
/* pad3 && !pad4 : illegal -> ASN_INPUT_E */
{
const byte bad[] = { 'T', 'W', '=', 'u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(bad, (word32)sizeof(bad), out, &outLen),
WC_NO_ERR_TRACE(ASN_INPUT_E));
}
/* --- bad character (not in alphabet) -> ASN_INPUT_E --- */
{
const byte bad[] = { 'T', '@', 'F', 'u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(bad, (word32)sizeof(bad), out, &outLen),
WC_NO_ERR_TRACE(ASN_INPUT_E));
}
/* --- output buffer too small -> BUFFER_E --- */
outLen = 1;
ExpectIntEQ(Base64_Decode(good, (word32)sizeof(good), out, &outLen),
WC_NO_ERR_TRACE(BUFFER_E));
/* --- trailing non-whitespace after a full quantum -> ASN_INPUT_E --- */
{
const byte tr[] = { 'T', 'W', 'F', 'u', '!' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(tr, (word32)sizeof(tr), out, &outLen),
WC_NO_ERR_TRACE(ASN_INPUT_E));
}
/* --- table (non constant-time) decode path --- */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode_nonCT(good, (word32)sizeof(good), out, &outLen),
0);
ExpectIntEQ(outLen, 3);
ExpectIntEQ(XMEMCMP(out, "Man", 3), 0);
/* --- Base64_Decode_nonCT argument-check decision (own MC/DC, table path
* is a distinct function from the constant-time one above):
* (in==NULL && inLen>0) || out==NULL || outLen==NULL --- */
/* in==NULL, inLen>0 -> true (BAD_FUNC_ARG) */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode_nonCT(NULL, 4, out, &outLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* in==NULL, inLen==0 -> (in==NULL && inLen>0) false; other operands
* false too -> overall false, succeeds with empty output */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode_nonCT(NULL, 0, out, &outLen), 0);
ExpectIntEQ(outLen, 0);
/* out==NULL alone -> true */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode_nonCT(good, (word32)sizeof(good), NULL,
&outLen), WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* outLen==NULL alone -> true */
ExpectIntEQ(Base64_Decode_nonCT(good, (word32)sizeof(good), out, NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* --- Base64_Decode_nonCT bad-character OR (e1==BAD||e2==BAD||e3==BAD||
* e4==BAD): each position isolated so its independence pair is shown
* against the all-good decode above. ':' (0x3A) is in the table's
* range but maps to BAD, and unlike '=' it can't be mistaken for
* padding, so it isolates e2/e3/e4 without tripping the pad3/pad4
* logic. */
{
const byte badE2[] = { 'T', ':', 'F', 'u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode_nonCT(badE2, (word32)sizeof(badE2), out,
&outLen), WC_NO_ERR_TRACE(ASN_INPUT_E));
}
{
const byte badE3[] = { 'T', 'W', ':', 'u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode_nonCT(badE3, (word32)sizeof(badE3), out,
&outLen), WC_NO_ERR_TRACE(ASN_INPUT_E));
}
{
const byte badE4[] = { 'T', 'W', 'F', ':' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode_nonCT(badE4, (word32)sizeof(badE4), out,
&outLen), WC_NO_ERR_TRACE(ASN_INPUT_E));
}
#endif
return EXPECT_RESULT();
}
/* Drives Base64_SkipNewline() (indirectly) through Base64_Decode(): embedded
* spaces, LF, CR LF, and the "not enough buffer" / bad-EOL branches. */
int test_wc_Base64_DecodeWhitespaceCoverage(void)
{
EXPECT_DECLS;
#if !defined(NO_CODING) && defined(WOLFSSL_BASE64_DECODE)
byte out[16];
word32 outLen;
/* LF between quanta: "TWFu\nTWFu" -> "ManMan" */
{
const byte in[] = { 'T','W','F','u','\n','T','W','F','u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(in, (word32)sizeof(in), out, &outLen), 0);
ExpectIntEQ(outLen, 6);
ExpectIntEQ(XMEMCMP(out, "ManMan", 6), 0);
}
/* CR LF sequence between quanta */
{
const byte in[] = { 'T','W','F','u','\r','\n','T','W','F','u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(in, (word32)sizeof(in), out, &outLen), 0);
ExpectIntEQ(outLen, 6);
ExpectIntEQ(XMEMCMP(out, "ManMan", 6), 0);
}
/* embedded spaces (skipped as whitespace) */
{
const byte in[] = { 'T','W','F','u',' ',' ','T','W','F','u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(in, (word32)sizeof(in), out, &outLen), 0);
ExpectIntEQ(outLen, 6);
}
/* CR not followed by LF -> bad end of line -> ASN_INPUT_E */
{
const byte in[] = { 'T','W','F','u','\r','x','T','u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(in, (word32)sizeof(in), out, &outLen),
WC_NO_ERR_TRACE(ASN_INPUT_E));
}
/* trailing whitespace after final quantum is tolerated */
{
const byte in[] = { 'T','W','F','u','\n' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(in, (word32)sizeof(in), out, &outLen), 0);
ExpectIntEQ(outLen, 3);
}
/* LF followed by a space, followed by more data: exercises the
* "while (len && curChar == ' ')" trailing-space-skip loop in
* Base64_SkipNewline() with BOTH independence pairs in one call --
* the loop first sees (len>0, curChar==' ') -> true (enters, consumes
* the space) and then, once curChar becomes the next real character
* 'T', sees (len>0, curChar==' ') -> false (exits without a further
* iteration). */
{
const byte in[] = { 'T','W','F','u','\n',' ','T','W','F','u' };
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Decode(in, (word32)sizeof(in), out, &outLen), 0);
ExpectIntEQ(outLen, 6);
ExpectIntEQ(XMEMCMP(out, "ManMan", 6), 0);
}
#endif
return EXPECT_RESULT();
}
/* Base64_Encode / Base64_EncodeEsc / Base64_Encode_NoNl:
* arg checks, length-only (out==NULL) path, buffer-too-small, escape logic. */
int test_wc_Base64_EncodeDecisionCoverage(void)
{
EXPECT_DECLS;
#if !defined(NO_CODING) && defined(WOLFSSL_BASE64_ENCODE)
const byte in[] = { 'M', 'a', 'n' };
byte out[64];
word32 outLen;
/* in==NULL && inLen>0 -> BAD_FUNC_ARG */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Encode(NULL, 3, out, &outLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* outLen==NULL -> BAD_FUNC_ARG */
ExpectIntEQ(Base64_Encode(in, (word32)sizeof(in), out, NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* out==NULL -> length-only query returns LENGTH_ONLY_E and sets outLen */
outLen = 0;
ExpectIntEQ(Base64_Encode(in, (word32)sizeof(in), NULL, &outLen),
WC_NO_ERR_TRACE(LENGTH_ONLY_E));
ExpectIntGT(outLen, 0);
/* output buffer too small -> BAD_FUNC_ARG */
outLen = 2;
ExpectIntEQ(Base64_Encode(in, (word32)sizeof(in), out, &outLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* valid encode with trailing newline (WC_STD_ENC) */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Encode(in, (word32)sizeof(in), out, &outLen), 0);
ExpectIntGT(outLen, 0);
/* valid encode without newline (WC_NO_NL_ENC) -> "TWFu" */
outLen = (word32)sizeof(out);
ExpectIntEQ(Base64_Encode_NoNl(in, (word32)sizeof(in), out, &outLen), 0);
ExpectIntEQ(outLen, 4);
ExpectIntEQ(XMEMCMP(out, "TWFu", 4), 0);
/* escaped-newline encode (WC_ESC_NL_ENC): a payload long enough to force a
* line break drives the CEscape() '\n' -> "%0A" and '+' / '=' escapes. */
{
byte big[80];
byte enc[512];
word32 i;
for (i = 0; i < (word32)sizeof(big); i++)
big[i] = (byte)(0xF8 + (i & 0x7)); /* yields '+' and '/' indices */
outLen = (word32)sizeof(enc);
ExpectIntEQ(Base64_EncodeEsc(big, (word32)sizeof(big), enc, &outLen), 0);
ExpectIntGT(outLen, 0);
}
/* --- DoBase64_Encode() "(in==NULL && inLen>0)" independence: hold
* in==NULL fixed, vary inLen. With inLen==0 the sub-term is false, so
* (unlike the inLen>0 case above) argument validation does not reject
* it; the call proceeds all the way to the final "i != outSz" sanity
* check (see below), which non-obviously trips for an empty, WC_STD_ENC
* encode because a lone trailing '\n' is still appended while the
* pre-computed outSz for zero input is 0. */
{
byte enc[8];
outLen = (word32)sizeof(enc);
ExpectIntEQ(Base64_Encode(NULL, 0, enc, &outLen),
WC_NO_ERR_TRACE(ASN_INPUT_E));
}
/* Same empty-input edge case, but WC_ESC_NL_ENC: the trailing '\n' is
* escaped to the 3-byte "%0A", which happens to match outSz's escaped
* bookkeeping, so this succeeds -- independence pair for the
* "escaped != 1" operand of the final sanity check (holding "i !=
* outSz" and "ret == 0" at the same values as the case directly
* above). */
{
byte enc[8];
outLen = (word32)sizeof(enc);
ExpectIntEQ(Base64_EncodeEsc(NULL, 0, enc, &outLen), 0);
ExpectIntEQ(outLen, 3);
ExpectIntEQ(XMEMCMP(enc, "%0A", 3), 0);
}
/* --- exact-multiple-of-line-length input (48 bytes == 16 groups of 3,
* BASE64_LINE_SZ/4 == 16 groups per line): the newline-insertion check
* "escaped != WC_NO_NL_ENC && (++n % (BASE64_LINE_SZ/4)) == 0 && inLen"
* hits the line boundary exactly when there is no input left, so its
* "inLen" operand is false here (no embedded newline is inserted --
* only the single unconditional trailing one). Pairs with the
* mid-buffer newlines already produced by the "big" EncodeEsc case
* above, where inLen is still nonzero at the line boundary. */
{
byte in48[48];
byte enc[128];
word32 i;
int nlCount = 0;
for (i = 0; i < (word32)sizeof(in48); i++)
in48[i] = (byte)(i + 1);
outLen = (word32)sizeof(enc);
ExpectIntEQ(Base64_Encode(in48, (word32)sizeof(in48), enc, &outLen),
0);
for (i = 0; i < outLen; i++) {
if (enc[i] == '\n')
nlCount++;
}
/* exactly one (trailing) newline -- none inserted mid-stream */
ExpectIntEQ(nlCount, 1);
ExpectIntEQ(enc[outLen - 1], '\n');
}
/* --- force a BUFFER_E from CEscape() inside the *main* while loop
* (not the tail), so DoBase64_Encode()'s "if (inLen && ret == 0)"
* decision is reached with inLen still nonzero (the failing group's
* 3 bytes were never subtracted from inLen) and ret != 0: the
* independence pair for "ret == 0", holding "inLen" true, against the
* "big" EncodeEsc case above (inLen true, ret == 0 true). Four
* repeats of a 3-byte pattern chosen so every group encodes to
* '+','+','A','+' (three escapes needing 3 bytes each): outLen is
* sized to the *unescaped* precomputed size (which under-counts the
* '+' escape overhead), so the second group's 4th escape overflows
* mid-group. */
{
const byte plusIn[12] = { 0xFB, 0xE0, 0x3E, 0xFB, 0xE0, 0x3E,
0xFB, 0xE0, 0x3E, 0xFB, 0xE0, 0x3E };
byte enc[32];
outLen = 19; /* == precomputed outSz for 12 bytes, WC_ESC_NL_ENC */
ExpectIntEQ(Base64_EncodeEsc(plusIn, (word32)sizeof(plusIn), enc,
&outLen), WC_NO_ERR_TRACE(BUFFER_E));
}
#endif
return EXPECT_RESULT();
}
/* Base16_Encode / Base16_Decode: arg checks, single-byte decode, odd-length,
* bad-character, short-buffer, and a round-trip. */
int test_wc_Base16DecisionCoverage(void)
{
EXPECT_DECLS;
#if !defined(NO_CODING) && defined(WOLFSSL_BASE16)
const byte raw[] = { 0x0A, 0xB3, 0xFF };
byte hex[8];
byte back[8];
word32 hexLen;
word32 backLen;
/* --- Base16_Encode arg checks --- */
hexLen = (word32)sizeof(hex);
ExpectIntEQ(Base16_Encode(NULL, 3, hex, &hexLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
hexLen = (word32)sizeof(hex);
ExpectIntEQ(Base16_Encode(raw, 3, NULL, &hexLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(Base16_Encode(raw, 3, hex, NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* output too small (*outLen < 2*inLen) -> BAD_FUNC_ARG */
hexLen = 2;
ExpectIntEQ(Base16_Encode(raw, 3, hex, &hexLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* --- valid encode (buffer has room, so a NUL is appended and counted) --- */
hexLen = (word32)sizeof(hex);
ExpectIntEQ(Base16_Encode(raw, (word32)sizeof(raw), hex, &hexLen), 0);
ExpectIntEQ(hexLen, 7); /* 6 hex chars + appended NUL terminator */
ExpectIntEQ(XMEMCMP(hex, "0AB3FF", 6), 0);
/* --- Base16_Decode arg checks --- */
backLen = (word32)sizeof(back);
ExpectIntEQ(Base16_Decode(NULL, 6, back, &backLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
backLen = (word32)sizeof(back);
ExpectIntEQ(Base16_Decode(hex, 6, NULL, &backLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(Base16_Decode(hex, 6, back, NULL),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* odd length (>1) -> BAD_FUNC_ARG */
backLen = (word32)sizeof(back);
ExpectIntEQ(Base16_Decode(hex, 3, back, &backLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
/* --- "inLen == 1 && *outLen && in" independence for *outLen: hold
* inLen==1 (true) fixed, force *outLen == 0 (false); the single-byte
* fast path above already showed inLen==1 && *outLen(true) && in
* -> true (decoded 'A' directly), so this closes the *outLen operand.
* Falls through to the "inLen % 2" check, where inLen==1 is odd. */
{
const byte one[] = { 'A' };
backLen = 0;
ExpectIntEQ(Base16_Decode(one, 1, back, &backLen),
WC_NO_ERR_TRACE(BAD_FUNC_ARG));
}
/* output too small -> BUFFER_E */
backLen = 1;
ExpectIntEQ(Base16_Decode(hex, 6, back, &backLen),
WC_NO_ERR_TRACE(BUFFER_E));
/* single-byte decode path: "A" -> 0x0A */
{
const byte one[] = { 'A' };
backLen = (word32)sizeof(back);
ExpectIntEQ(Base16_Decode(one, 1, back, &backLen), 0);
ExpectIntEQ(backLen, 1);
ExpectIntEQ(back[0], 0x0A);
}
/* bad character -> ASN_INPUT_E ('G' is out of the hex alphabet) */
{
const byte bad[] = { 'G', 'G' };
backLen = (word32)sizeof(back);
ExpectIntEQ(Base16_Decode(bad, 2, back, &backLen),
WC_NO_ERR_TRACE(ASN_INPUT_E));
}
/* --- "b == BAD || b2 == BAD" independence for b2: the case above has
* BOTH nibbles bad (b == BAD already true, masking b2); pair a good
* first nibble with a bad second nibble so only b2's operand drives
* the (still true) decision, against the all-good round-trip decode
* below where both are false. */
{
const byte bad[] = { '0', 'G' };
backLen = (word32)sizeof(back);
ExpectIntEQ(Base16_Decode(bad, 2, back, &backLen),
WC_NO_ERR_TRACE(ASN_INPUT_E));
}
/* --- round-trip decode of the earlier encode --- */
backLen = (word32)sizeof(back);
ExpectIntEQ(Base16_Decode(hex, 6, back, &backLen), 0);
ExpectIntEQ(backLen, 3);
ExpectIntEQ(XMEMCMP(back, raw, 3), 0);
#endif
return EXPECT_RESULT();
}