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_pkcs7_decode_whitebox.c

2083 lines
83 KiB
C

/* test_pkcs7_decode_whitebox.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
*/
/*
* Second white-box MC/DC supplement for wolfcrypt/src/pkcs7.c (Part 5),
* targeting the ASN.1-walk "ret == 0 && Get*(...) < 0" chains inside the
* decode paths that test_pkcs7_whitebox.c explicitly left as residual:
*
* PKCS7_VerifySignedData, wc_PKCS7_DecodeAuthEnvelopedData,
* wc_PKCS7_DecodeEncryptedData, wc_PKCS7_DecodeEnvelopedData,
* wc_PKCS7_ParseToRecipientInfoSet, wc_PKCS7_GetEnvelopedDataKariRid,
* wc_PKCS7_DecryptKekri, wc_PKCS7_DecryptContentInit,
* wc_PKCS7_DecryptPwri.
*
* Technique: load a real DER/BER message (or build one with the public
* Encode/AddRecipient API), then drive the decoder across a sweep of
* progressively truncated prefixes and progressively single-byte-corrupted
* copies of the full message. Each distinct cut/corruption point stops the
* element-by-element ASN.1 walk at a different link in the chain, so a
* sweep across a whole message's length exercises most of the "Get*() < 0"
* arms without needing to hand-craft each boundary.
*
* This file does NOT duplicate the streaming-state-machine, signed-attribute,
* encode-side, or plain NULL/size-guard coverage that test_pkcs7_whitebox.c
* already drives -- only the decode ASN.1 walks (Sections 1-8) plus, from
* Section 9 on, the individual decode links that a truncate/corrupt sweep
* provably cannot steer and that therefore need a hand-built vector.
*
* ==========================================================================
* STRUCTURALLY UNREACHABLE LINKS IN THESE CHAINS (do not try to drive these)
* ==========================================================================
* The "ret == 0 && Get*(...)" idiom in this file produces a large number of
* operands that have no MC/DC independence pair for a reason in the source,
* not for want of a test. They fall into four provable families. Recording
* them here so the next pass does not re-spend effort on them; the harness
* the exclusion record carries the same arguments.
*
* (1) LEADING OPERAND OF THE FIRST LINK IN A SWITCH CASE. `ret` is a local
* initialised to 0, and the only statement before the link is a
* wc_PKCS7_AddDataToStream()/wc_PKCS7_SetMaxStream() call that breaks or
* returns on a non-zero result. `ret` is therefore provably 0 on arrival
* and the operand's false row cannot exist:
* pkcs7.c :6680 :6682 :6710 (HandleOctetStrings, after the guarded
* GetASNTag at :6675), :6962 :6976 (VerifySignedData WC_PKCS7_START),
* :7524 :7541 (VERIFY_STAGE3), :7682 (STAGE4), :8038 (STAGE6),
* :14036 :14118 (ParseToRecipientInfoSet), :15808 :15899 :16080
* :16192 :16234 (DecodeAuthEnvelopedData), :16916 :16953 :17005
* :17048 (DecodeEncryptedData) -- all condition 0.
*
* (2) TRAILING OPERAND ALREADY EXCLUDED BY AN EARLIER EXPLICIT BOUNDS CHECK.
* A preceding `if (idx + 1 > sz) ret = ...` or `if (idx >= sz) ret = ...`
* is exactly the failure precondition of the Get*() that follows, so with
* `ret == 0` the Get*() cannot fail, the decision is never true, and
* NEITHER operand has a pair:
* :6445 cond 1 and :6455 (guarded by :6441), :6452 (see family 4),
* :7230 (guarded by :7209), :7322 (guarded by :7318), :7688 cond 1
* (guarded by :7682), :16240 (duplicate of :16234 / of the
* AddDataToStream postcondition), :16116 cond 1 and :14184 cond 1
* (GetLength_ex/GetSet_ex success implies idx <= maxIdx).
*
* (3) OPERAND WHOSE VALUE IS FIXED BY THE BRANCH THAT REACHES IT.
* :6471 cond 0 -- the :6464 else is entered either with ret non-zero
* (set at :6441 by idx+1 > inSz) or on a tag mismatch;
* in the first case the GetASNTag at :6468 also fails,
* so :6471 is only reached with ret still 0.
* :7258 cond 1 -- the else-if is only reached when :7248 was false,
* which with ret 0 forces the indefinite-length byte.
* :7363 cond 0 -- :7360 can only fire on a non-OCTET-STRING tag, but
* the :7302 else-branch and the :7326 check already
* established the tag is an OCTET STRING.
* :7413 cond 2 -- the enclosing else at :7389 is entered only when ret
* is non-zero and nothing between assigns ret.
* :7537, :7541 -- VERIFY_STAGE3 is only entered with ret 0, because
* :7424 explicitly resets it on the failure path.
* :7903 cond 1 -- MAX_PKCS7_CERTS is a positive compile-time constant.
* :16451, :16480 cond 0 -- the success path returns
* `ret = encryptedContentSz`, which :15985/:15998
* force to be strictly positive, so ret is never 0 at
* these two cleanup guards.
*
* (4) OPERAND CONTRADICTED BY A CALLEE POSTCONDITION.
* :469 cond 1 -- GetSequence_ex() returns the parsed length and the
* negative case already returned, so length 0 implies
* ret 0.
* :6452 both -- GetLength() at :6449 is the bounds-CHECKING variant:
* a positive return guarantees idx + length <= inSz
* with length >= 1, hence idx + 1 <= inSz. The
* trailing operand is never true.
* :14181 both -- GetSet_ex() returns the parsed length and is only
* negative on an error that already set ret, so
* `length < 0` with ret 0 cannot happen.
* :14493 both -- the oidBlkType table only yields CBC/GCM/CCM/DES
* OIDs, and every one of them has BOTH a key size and
* a block size, so `expBlockSz < 0` implies
* `blockKeySz < 0`, which already set ret at :14488.
* :16304 both -- :16253 forces authTagSz == macSz and :15963 caps
* macSz at WC_AES_BLOCK_SIZE, which is exactly
* sizeof(authTag).
* :16313 all -- :16299 already sets ret for the same condition, and
* the length parse bounds idx by pkiMsgSz.
* :7524 cond 1 -- stream->maxLen is set to `length + localIdx` or to
* inSz at :6963/:6965 and forced to defSz by
* wc_PKCS7_SetMaxStream when it computes 0; it is
* never 0 on arrival at VERIFY_STAGE3.
*
* Added by the 2026-08-20 streaming-state wave (Sections 18-19 below), both
* filed in the exclusion record:
*
* :7516 cond 2 -- family (4). stream->content is freed and NULLed at
* :7501-:7502 before VERIFY_STAGE3 runs, so it is
* non-NULL at :7516 only because the
* wc_PKCS7_HandleOctetStrings() call two statements
* above made it so. That function stores a non-NULL
* pointer there in exactly two places, and both set
* contentSz strictly positive in the same block:
* :6648, entered only under
* `if (pkcs7->content && pkcs7->contentSz > 0)` and
* followed at :6655 by
* `stream->contentSz = pkcs7->contentSz`; and :6821,
* followed at :6849 by
* `stream->contentSz += stream->expected`, in the
* `else` of `if (currContRmnSz == 0)` where every
* assignment of expected consistent with
* currContRmnSz > 0 is
* min(currContRmnSz, MAX_PKCS7_STREAM_BUFFER) >= 1.
* The only arm that grows contentSz without allocating
* (the streamOutCb branch at :6790) leaves content
* NULL, which drives cond 1 false, not cond 2.
* :12036 cond 1 -- family (3). The branch that reaches it,
* `stream->expected == MAX_SEQ_SZ`, is what bounds
* stream->length: wc_PKCS7_AddDataToStream() grows it
* only by min(inSz - rdSz, expected - length), and the
* largest expected on any path into
* WC_PKCS7_DECRYPT_KTRI_2 is
* MAX_OID_SZ + MAX_LENGTH_SZ + ASN_TAG_SZ == 38, at
* WC_PKCS7_INFOSET_STAGE1. :12034 has just assigned
* expected = sz + MAX_ALGO_SZ + ASN_TAG_SZ +
* MAX_LENGTH_SZ + 512, i.e. at least 538, so
* `length < expected` is constant true. Section 18's
* true row shows the ceiling: the most the guard can
* ever see is MAX_SEQ_SZ (6) against expected 542.
*/
#include <wolfcrypt/src/pkcs7.c>
#include "mcdc_fault_alloc.h"
#include <stdio.h>
#include <string.h>
#include <wolfssl/certs_test.h>
static int wb_fail = 0;
#define WB_NOTE(msg) do { printf(" [wb] %s\n", (msg)); } while (0)
#define WB_CHECK(cond, msg) \
do { if (!(cond)) { printf(" [wb][FAIL] %s\n", (msg)); wb_fail = 1; } } \
while (0)
/* shared scratch buffer for corpus loads (largest corpus is ~6.2KB) */
#define WB_SCRATCH_SZ 8192
static byte wbScratch[WB_SCRATCH_SZ];
/* shared RNG for the encode-side corpus builders below */
static WC_RNG wbRng;
/* ------------------------------------------------------------------------- *
* Helpers: plain fopen/fread corpus loader, and generic truncate/corrupt
* sweep driver over a (byte*, word32) decode call.
* ------------------------------------------------------------------------- */
/* Loads a corpus file into buf (bounded by bufSz). Returns bytes read, or 0
* if the file could not be opened/read (treated as "skip", not a failure --
* some corpora are only present in certain repo checkouts). */
static word32 wb_load_file(const char* path, byte* buf, word32 bufSz)
{
FILE* f;
size_t n;
f = fopen(path, "rb");
if (f == NULL) {
printf(" [wb] corpus not found, skip: %s\n", path);
return 0;
}
n = fread(buf, 1, bufSz, f);
fclose(f);
return (word32)n;
}
typedef void (*wb_decode_fn)(byte* buf, word32 len);
/* Truncation sweep: call fn() with every prefix length from a small floor up
* to the full message (stepped, to bound run time on larger corpora), then
* once more at the full length. Corruption sweep: flip one byte at a time
* (restoring it afterward) and call fn() with the full (corrupted) length.
* Together these hit a different ASN.1 element boundary on nearly every
* call without requiring hand-crafted offsets. */
static void wb_sweep(wb_decode_fn fn, byte* buf, word32 fullLen)
{
word32 stride, i;
byte saved;
if (fullLen < 4) {
return;
}
/* bound total iterations to roughly 200 truncations + 200 corruptions
* regardless of corpus size */
stride = fullLen / 200;
if (stride == 0) {
stride = 1;
}
for (i = 4; i < fullLen; i += stride) {
fn(buf, i);
}
fn(buf, fullLen);
for (i = 0; i < fullLen; i += stride) {
saved = buf[i];
buf[i] = (byte)(saved ^ 0xFF);
fn(buf, fullLen);
buf[i] = saved;
}
}
/* ------------------------------------------------------------------------- *
* Section 1: PKCS7_VerifySignedData() decode-walk chains, via the public
* wc_PKCS7_VerifySignedData() wrapper, across three structurally different
* real SignedData corpora (degenerate/no-signer, BER-indefinite-length,
* streaming-sized-with-signer).
* ------------------------------------------------------------------------- */
#ifndef NO_RSA
static void wb_verify_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) {
(void)wc_PKCS7_VerifySignedData(p, buf, len);
}
wc_PKCS7_Free(p);
}
static void wb_verify_sweep_file(const char* path)
{
word32 fullLen = wb_load_file(path, wbScratch, sizeof(wbScratch));
if (fullLen == 0) {
return;
}
wb_sweep(wb_verify_call, wbScratch, fullLen);
}
static void wb_verify_decode_chains(void)
{
WB_NOTE("PKCS7_VerifySignedData(): decode-walk sweep, test-degenerate.p7b"
" (no signer)");
wb_verify_sweep_file("./certs/test-degenerate.p7b");
#ifdef ASN_BER_TO_DER
WB_NOTE("PKCS7_VerifySignedData(): decode-walk sweep,"
" test-ber-exp02-05-2022.p7b (BER indefinite length)");
wb_verify_sweep_file("./certs/test-ber-exp02-05-2022.p7b");
#endif
WB_NOTE("PKCS7_VerifySignedData(): decode-walk sweep, test-stream-sign.p7b"
" (signed, larger message)");
wb_verify_sweep_file("./certs/test-stream-sign.p7b");
}
#else
static void wb_verify_decode_chains(void)
{
WB_NOTE("NO_RSA; PKCS7_VerifySignedData decode-walk sweep skipped");
}
#endif /* !NO_RSA */
/* ------------------------------------------------------------------------- *
* Section 2: wc_PKCS7_DecodeEnvelopedData() KTRI decode-walk chains
* (wc_PKCS7_ParseToRecipientInfoSet() is exercised as a side effect of every
* call here too, in addition to its own direct sweep in Section 5).
* ------------------------------------------------------------------------- */
#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048)
static void wb_enveloped_ktri_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0) {
p->privateKey = (byte*)client_key_der_2048;
p->privateKeySz = sizeof_client_key_der_2048;
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
/* wc_PKCS7_DecryptKtri()'s `pkcs7->privateKey != NULL &&
* pkcs7->privateKeySz > 0` AND-guard [:12140]. wb_enveloped_ktri_call()
* above is the both-true row (real key). The three calls below supply the
* all-false baseline plus each operand's true-while-the-other-false row, by
* direct field manipulation -- the guard's own decision is under test, not
* what a real key would do afterward, so a NULL pointer paired with a
* nonzero size (and vice versa) is fine: privateKey==NULL still short-
* circuits the AND before privateKeySz is dereferenced. */
static void wb_enveloped_ktri_nokey_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0) {
/* both operands false: privateKey==NULL, privateKeySz==0 (Init's
* default). devId==INVALID_DEVID -> BAD_FUNC_ARG, clean return. */
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_enveloped_ktri_szonly_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0) {
/* 1st operand false, 2nd true: privateKey==NULL, privateKeySz>0 */
p->privateKey = NULL;
p->privateKeySz = 8;
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_enveloped_ktri_keyzerosz_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0) {
/* 1st operand true, 2nd false: real key pointer, privateKeySz==0 */
p->privateKey = (byte*)client_key_der_2048;
p->privateKeySz = 0;
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_enveloped_multi_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0 &&
wc_PKCS7_SetKey(p, (byte*)client_key_der_2048,
sizeof_client_key_der_2048) == 0) {
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_enveloped_decode_chains(void)
{
word32 fullLen;
WB_NOTE("wc_PKCS7_DecodeEnvelopedData(): KTRI decode-walk sweep,"
" ktri-keyid-cms.msg");
fullLen = wb_load_file("./certs/test/ktri-keyid-cms.msg", wbScratch,
sizeof(wbScratch));
if (fullLen > 0) {
wb_sweep(wb_enveloped_ktri_call, wbScratch, fullLen);
WB_NOTE("wc_PKCS7_DecryptKtri(): privateKey/privateKeySz guard"
" [:12140], all-false baseline + each operand isolated"
" true");
wb_enveloped_ktri_nokey_call(wbScratch, fullLen);
wb_enveloped_ktri_szonly_call(wbScratch, fullLen);
wb_enveloped_ktri_keyzerosz_call(wbScratch, fullLen);
}
WB_NOTE("wc_PKCS7_DecodeEnvelopedData(): multi-recipient decode-walk"
" sweep, test-multiple-recipients.p7b");
fullLen = wb_load_file("./certs/test-multiple-recipients.p7b", wbScratch,
sizeof(wbScratch));
if (fullLen > 0) {
wb_sweep(wb_enveloped_multi_call, wbScratch, fullLen);
}
}
#else
static void wb_enveloped_decode_chains(void)
{
WB_NOTE("NO_RSA or no 2048-bit test cert buffers; EnvelopedData"
" decode-walk sweep skipped");
}
#endif /* !NO_RSA && USE_CERT_BUFFERS_2048 */
/* ------------------------------------------------------------------------- *
* Section 3: wc_PKCS7_DecodeAuthEnvelopedData() decode-walk chains. No
* ready-made AuthEnvelopedData corpus file is listed for this module, so
* build one with the public Encode API (real AES-GCM/RSA content, not
* fabricated bytes) and sweep truncation/corruption over the result.
* ------------------------------------------------------------------------- */
#if defined(HAVE_AESGCM) && !defined(NO_RSA) && defined(WOLFSSL_AES_128) && \
defined(USE_CERT_BUFFERS_2048)
static word32 wb_build_auth_enveloped(byte* out, word32 outSz)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
byte data[] = "authEnvelopedData decode-chain corpus payload";
int sz = 0;
if (p == NULL) {
return 0;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0) {
p->content = data;
p->contentSz = (word32)sizeof(data);
p->contentOID = DATA;
p->encryptOID = AES128GCMb;
p->rng = &wbRng;
sz = wc_PKCS7_EncodeAuthEnvelopedData(p, out, outSz);
}
wc_PKCS7_Free(p);
return (sz > 0) ? (word32)sz : 0;
}
static void wb_auth_enveloped_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0) {
p->privateKey = (byte*)client_key_der_2048;
p->privateKeySz = sizeof_client_key_der_2048;
(void)wc_PKCS7_DecodeAuthEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_auth_enveloped_decode_chains(void)
{
word32 fullLen;
WB_NOTE("wc_PKCS7_DecodeAuthEnvelopedData(): decode-walk sweep over a"
" self-built AES128GCMb/RSA-KTRI message");
fullLen = wb_build_auth_enveloped(wbScratch, sizeof(wbScratch));
WB_CHECK(fullLen > 32, "self-built AuthEnvelopedData corpus encoded");
if (fullLen > 0) {
wb_sweep(wb_auth_enveloped_call, wbScratch, fullLen);
}
}
#else
static void wb_auth_enveloped_decode_chains(void)
{
WB_NOTE("no AESGCM/RSA/2048-cert-buffers; AuthEnvelopedData decode-walk"
" sweep skipped");
}
#endif
/* ------------------------------------------------------------------------- *
* Section 4: wc_PKCS7_DecodeEncryptedData() decode-walk chains, real
* encrypteddata.msg corpus plus a self-built AES-CBC EncryptedData message
* for a second, structurally different, cipher/attribs combination.
* ------------------------------------------------------------------------- */
#ifndef NO_PKCS7_ENCRYPTED_DATA
static const byte wbEncKey[] = {
0x01,0x23,0x45,0x67,0x89,0xAB,0xCD,0xEF,
0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77
};
static void wb_encrypted_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_Init(p, NULL, INVALID_DEVID) == 0) {
p->encryptionKey = (byte*)wbEncKey;
p->encryptionKeySz = (word32)sizeof(wbEncKey);
(void)wc_PKCS7_DecodeEncryptedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128)
static word32 wb_build_encrypted_aes(byte* out, word32 outSz)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
byte data[] = "encryptedData decode-chain corpus payload";
int sz = 0;
if (p == NULL) {
return 0;
}
if (wc_PKCS7_Init(p, NULL, INVALID_DEVID) == 0) {
p->content = data;
p->contentSz = (word32)sizeof(data);
p->contentOID = DATA;
p->encryptOID = AES128CBCb;
p->encryptionKey = (byte*)wbEncKey;
p->encryptionKeySz = (word32)sizeof(wbEncKey);
p->rng = &wbRng;
sz = wc_PKCS7_EncodeEncryptedData(p, out, outSz);
}
wc_PKCS7_Free(p);
return (sz > 0) ? (word32)sz : 0;
}
#endif
static void wb_encrypted_decode_chains(void)
{
word32 fullLen;
WB_NOTE("wc_PKCS7_DecodeEncryptedData(): decode-walk sweep,"
" encrypteddata.msg");
fullLen = wb_load_file("./certs/test/encrypteddata.msg", wbScratch,
sizeof(wbScratch));
if (fullLen > 0) {
wb_sweep(wb_encrypted_call, wbScratch, fullLen);
}
#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_128)
WB_NOTE("wc_PKCS7_DecodeEncryptedData(): decode-walk sweep over a"
" self-built AES128CBCb message");
fullLen = wb_build_encrypted_aes(wbScratch, sizeof(wbScratch));
WB_CHECK(fullLen > 0, "self-built AES128CBCb EncryptedData corpus"
" encoded");
if (fullLen > 0) {
wb_sweep(wb_encrypted_call, wbScratch, fullLen);
}
#endif
}
#else
static void wb_encrypted_decode_chains(void)
{
WB_NOTE("NO_PKCS7_ENCRYPTED_DATA; EncryptedData decode-walk sweep"
" skipped");
}
#endif /* !NO_PKCS7_ENCRYPTED_DATA */
/* ------------------------------------------------------------------------- *
* Section 5: wc_PKCS7_ParseToRecipientInfoSet() driven directly (it is a
* file-static, reachable because this file #includes pkcs7.c) against the
* KTRI and multi-recipient corpora, independent of the full decrypt path.
* ------------------------------------------------------------------------- */
#if !defined(NO_RSA)
static void wb_parse_ris_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
word32 idx = 0;
if (p == NULL) {
return;
}
if (wc_PKCS7_Init(p, NULL, INVALID_DEVID) == 0) {
(void)wc_PKCS7_ParseToRecipientInfoSet(p, buf, len, &idx,
ENVELOPED_DATA);
}
wc_PKCS7_Free(p);
}
static void wb_parse_ris_decode_chains(void)
{
word32 fullLen;
WB_NOTE("wc_PKCS7_ParseToRecipientInfoSet(): direct decode-walk sweep,"
" ktri-keyid-cms.msg");
fullLen = wb_load_file("./certs/test/ktri-keyid-cms.msg", wbScratch,
sizeof(wbScratch));
if (fullLen > 0) {
wb_sweep(wb_parse_ris_call, wbScratch, fullLen);
}
WB_NOTE("wc_PKCS7_ParseToRecipientInfoSet(): direct decode-walk sweep,"
" test-multiple-recipients.p7b");
fullLen = wb_load_file("./certs/test-multiple-recipients.p7b", wbScratch,
sizeof(wbScratch));
if (fullLen > 0) {
wb_sweep(wb_parse_ris_call, wbScratch, fullLen);
}
}
#else
static void wb_parse_ris_decode_chains(void)
{
WB_NOTE("NO_RSA; ParseToRecipientInfoSet decode-walk sweep skipped");
}
#endif
/* ------------------------------------------------------------------------- *
* Section 6: wc_PKCS7_GetEnvelopedDataKariRid() decode-walk chains. This is
* a standalone public parser (no wc_PKCS7 struct, no allocation, no crypto):
* safe to sweep aggressively against the real kari-keyid-cms.msg corpus.
* ------------------------------------------------------------------------- */
#if defined(HAVE_ECC) && defined(HAVE_X963_KDF)
static void wb_kari_rid_call(byte* buf, word32 len)
{
byte rid[256];
word32 ridSz = (word32)sizeof(rid);
(void)wc_PKCS7_GetEnvelopedDataKariRid(buf, len, rid, &ridSz);
}
static void wb_kari_rid_decode_chains(void)
{
word32 fullLen;
WB_NOTE("wc_PKCS7_GetEnvelopedDataKariRid(): decode-walk sweep,"
" kari-keyid-cms.msg");
fullLen = wb_load_file("./certs/test/kari-keyid-cms.msg", wbScratch,
sizeof(wbScratch));
if (fullLen > 0) {
wb_sweep(wb_kari_rid_call, wbScratch, fullLen);
}
}
#else
static void wb_kari_rid_decode_chains(void)
{
WB_NOTE("no HAVE_ECC/HAVE_X963_KDF; GetEnvelopedDataKariRid decode-walk"
" sweep skipped");
}
#endif
/* ------------------------------------------------------------------------- *
* Section 7: wc_PKCS7_DecryptKekri() and wc_PKCS7_DecryptPwri() decode-walk
* chains. Both are file-static helpers reached only through the full
* wc_PKCS7_DecodeEnvelopedData() state machine (they assume pkcs7->state and
* pkcs7->stream are already primed by the caller), so build a real KEKRI/
* PWRI EnvelopedData message with the public Encode/AddRecipient API and
* sweep the top-level decode entry point instead of calling the helpers
* out of context.
* ------------------------------------------------------------------------- */
#if !defined(NO_AES) && defined(HAVE_AES_CBC) && defined(WOLFSSL_AES_256) && \
defined(HAVE_AES_KEYWRAP)
static word32 wb_build_kekri(byte* out, word32 outSz)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
byte data[] = "kekri decode-chain corpus payload";
byte kek[32];
byte keyId[4] = { 0xAA, 0xBB, 0xCC, 0xDD };
int sz = 0, i;
if (p == NULL) {
return 0;
}
for (i = 0; i < (int)sizeof(kek); i++) {
kek[i] = (byte)i;
}
if (wc_PKCS7_Init(p, NULL, INVALID_DEVID) == 0) {
p->content = data;
p->contentSz = (word32)sizeof(data);
p->contentOID = DATA;
p->encryptOID = AES256CBCb;
p->rng = &wbRng;
if (wc_PKCS7_AddRecipient_KEKRI(p, AES256_WRAP, kek, sizeof(kek),
keyId, sizeof(keyId), NULL, NULL, 0, NULL, 0, 0) >= 0) {
sz = wc_PKCS7_EncodeEnvelopedData(p, out, outSz);
}
}
wc_PKCS7_Free(p);
return (sz > 0) ? (word32)sz : 0;
}
static void wb_kekri_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
byte kek[32];
int i;
if (p == NULL) {
return;
}
for (i = 0; i < (int)sizeof(kek); i++) {
kek[i] = (byte)i;
}
if (wc_PKCS7_Init(p, NULL, INVALID_DEVID) == 0 &&
wc_PKCS7_SetKey(p, kek, (word32)sizeof(kek)) == 0) {
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_kekri_decode_chains(void)
{
byte kekri[WB_SCRATCH_SZ];
word32 fullLen;
WB_NOTE("wc_PKCS7_DecryptKekri(): decode-walk sweep via"
" wc_PKCS7_DecodeEnvelopedData() over a self-built KEKRI message");
fullLen = wb_build_kekri(kekri, sizeof(kekri));
WB_CHECK(fullLen > 0, "self-built KEKRI EnvelopedData corpus encoded");
if (fullLen > 0) {
wb_sweep(wb_kekri_call, kekri, fullLen);
}
}
#else
static void wb_kekri_decode_chains(void)
{
WB_NOTE("no AES256CBC/AES-keywrap support; DecryptKekri decode-walk"
" sweep skipped");
}
#endif
#if !defined(NO_PWDBASED) && !defined(NO_SHA) && !defined(NO_DES3)
static word32 wb_build_pwri(byte* out, word32 outSz)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
byte data[] = "pwri decode-chain corpus payload";
byte pass[] = "wbWhiteboxPassword1";
byte salt[8] = { 1,2,3,4,5,6,7,8 };
int sz = 0;
if (p == NULL) {
return 0;
}
if (wc_PKCS7_Init(p, NULL, INVALID_DEVID) == 0) {
p->content = data;
p->contentSz = (word32)sizeof(data);
p->contentOID = DATA;
p->encryptOID = DES3b;
p->rng = &wbRng;
/* iterations kept small (5, mirroring wolfcrypt/test/test.c's PWRI
* vectors) so the truncation/corruption sweep below stays fast */
if (wc_PKCS7_AddRecipient_PWRI(p, pass, (word32)(sizeof(pass) - 1),
salt, (word32)sizeof(salt), PBKDF2_OID, WC_SHA, 5, 0, 0)
>= 0) {
sz = wc_PKCS7_EncodeEnvelopedData(p, out, outSz);
}
}
wc_PKCS7_Free(p);
return (sz > 0) ? (word32)sz : 0;
}
static void wb_pwri_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
byte pass[] = "wbWhiteboxPassword1";
if (p == NULL) {
return;
}
if (wc_PKCS7_Init(p, NULL, INVALID_DEVID) == 0 &&
wc_PKCS7_SetPassword(p, pass, (word32)(sizeof(pass) - 1)) == 0) {
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_pwri_decode_chains(void)
{
byte pwri[WB_SCRATCH_SZ];
word32 fullLen;
WB_NOTE("wc_PKCS7_DecryptPwri(): decode-walk sweep via"
" wc_PKCS7_DecodeEnvelopedData() over a self-built PWRI message");
fullLen = wb_build_pwri(pwri, sizeof(pwri));
WB_CHECK(fullLen > 0, "self-built PWRI EnvelopedData corpus encoded");
if (fullLen > 0) {
wb_sweep(wb_pwri_call, pwri, fullLen);
}
}
#else
static void wb_pwri_decode_chains(void)
{
WB_NOTE("no PWDBASED/SHA/DES3 support; DecryptPwri decode-walk sweep"
" skipped");
}
#endif
/* ------------------------------------------------------------------------- *
* Section 8: wc_PKCS7_DecryptContentInit() direct guard/branch coverage.
* Unlike DecryptKekri/DecryptPwri this file-static has no dependency on
* pkcs7->state/pkcs7->stream -- it only sets up the cipher context from an
* already-known encryptOID/key/iv -- so it is safe and cheap to call
* directly across every supported cipher OID plus the NULL/size guards.
* ------------------------------------------------------------------------- */
static void wb_decrypt_content_init_direct(void)
{
wc_PKCS7 pkcs7;
byte key32[32];
byte iv16[16];
int ret, i;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
for (i = 0; i < (int)sizeof(key32); i++) {
key32[i] = (byte)(i + 1);
}
for (i = 0; i < (int)sizeof(iv16); i++) {
iv16[i] = (byte)(i + 0x40);
}
WB_NOTE("wc_PKCS7_DecryptContentInit(): NULL iv/key guards");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES128CBCb, key32, 16, NULL, 16,
INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "iv==NULL guard");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES128CBCb, NULL, 16, iv16, 16,
INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "key==NULL guard");
#if !defined(NO_AES) && defined(HAVE_AES_CBC)
#ifdef WOLFSSL_AES_128
WB_NOTE("wc_PKCS7_DecryptContentInit(): AES128CBCb keySz/ivSz guard"
" chain");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES128CBCb, key32, 15, iv16,
sizeof(iv16), INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "AES128CBCb wrong keySz");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES128CBCb, key32, 16, iv16,
sizeof(iv16) - 1, INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "AES128CBCb wrong ivSz");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES128CBCb, key32, 16, iv16,
sizeof(iv16), INVALID_DEVID, NULL);
WB_CHECK(ret == 0, "AES128CBCb valid init");
wc_PKCS7_DecryptContentFree(&pkcs7, AES128CBCb, NULL);
#endif
#ifdef WOLFSSL_AES_256
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES256CBCb, key32,
sizeof(key32), iv16, sizeof(iv16), INVALID_DEVID, NULL);
WB_CHECK(ret == 0, "AES256CBCb valid init");
wc_PKCS7_DecryptContentFree(&pkcs7, AES256CBCb, NULL);
#endif
#endif /* !NO_AES && HAVE_AES_CBC */
#if defined(HAVE_AESGCM) && defined(WOLFSSL_AES_128)
WB_NOTE("wc_PKCS7_DecryptContentInit(): AES128GCMb valid init"
" (no keySz/ivSz guard on this branch)");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES128GCMb, key32, 16, iv16,
GCM_NONCE_MID_SZ, INVALID_DEVID, NULL);
WB_CHECK(ret == 0, "AES128GCMb valid init");
wc_PKCS7_DecryptContentFree(&pkcs7, AES128GCMb, NULL);
#endif
#if defined(HAVE_AESCCM) && defined(WOLFSSL_AES_128)
WB_NOTE("wc_PKCS7_DecryptContentInit(): AES128CCMb valid init");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, AES128CCMb, key32, 16, iv16,
GCM_NONCE_MID_SZ, INVALID_DEVID, NULL);
WB_CHECK(ret == 0, "AES128CCMb valid init");
wc_PKCS7_DecryptContentFree(&pkcs7, AES128CCMb, NULL);
#endif
#ifndef NO_DES3
WB_NOTE("wc_PKCS7_DecryptContentInit(): DESb/DES3b keySz/ivSz guard"
" chain");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, DESb, key32, DES_KEYLEN - 1,
iv16, DES_BLOCK_SIZE, INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "DESb wrong keySz");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, DESb, key32, DES_KEYLEN, iv16,
DES_BLOCK_SIZE - 1, INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "DESb wrong ivSz");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, DESb, key32, DES_KEYLEN, iv16,
DES_BLOCK_SIZE, INVALID_DEVID, NULL);
WB_CHECK(ret == 0, "DESb valid init");
wc_PKCS7_DecryptContentFree(&pkcs7, DESb, NULL);
ret = wc_PKCS7_DecryptContentInit(&pkcs7, DES3b, key32, DES3_KEYLEN - 1,
iv16, DES_BLOCK_SIZE, INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "DES3b wrong keySz");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, DES3b, key32, DES3_KEYLEN, iv16,
DES_BLOCK_SIZE, INVALID_DEVID, NULL);
WB_CHECK(ret == 0, "DES3b valid init");
wc_PKCS7_DecryptContentFree(&pkcs7, DES3b, NULL);
#endif /* !NO_DES3 */
WB_NOTE("wc_PKCS7_DecryptContentInit(): unsupported encryptOID ->"
" default/ALGO_ID_E");
ret = wc_PKCS7_DecryptContentInit(&pkcs7, 0xFFFF, key32, sizeof(key32),
iv16, sizeof(iv16), INVALID_DEVID, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(ALGO_ID_E), "unsupported encryptOID");
}
/* ------------------------------------------------------------------------- *
* Section 9: wc_PKCS7_ParseSignerInfo() version-3 SubjectKeyIdentifier walk
* [:6413,:6445,:6452,:6455,:6458,:6461,:6471,:6480,:6516].
*
* The truncate/corrupt sweeps over real bundles cannot steer this walk: a
* corpus SignerInfo is version 1, and by the time a mutation reaches the
* SignerInfo bytes the enclosing SignedData length checks have already
* rejected the message. Every link here is instead driven by a hand-built
* SignerInfo of exactly the length needed to stop the walk at that link.
* wc_PKCS7_ParseSignerInfo() is a pure function of (pkcs7->version, in, inSz)
* so the vectors are byte-deterministic.
*
* Sizing note for the version-3 arm: `ret` is set at :6441 when idx+1 exceeds
* inSz, so "the buffer ends exactly at byte N" is the lever for the leading
* `ret == 0` operand of the whole chain, and a one-byte-longer buffer is the
* lever for the trailing operand of the same link.
* ------------------------------------------------------------------------- */
/* SignerInfo bodies. Layout for the version-3 ones:
* 30 <len> 02 01 03 [ A0 <len> 04 <len> <skid> | <other> ] [ digestAlgo ] */
/* not a SEQUENCE at all -> the very first Get*() of the chain fails */
static byte wbSiNoSeq[] = { 0x02, 0x01, 0x01 };
/* ends immediately after the version: idx+1 > inSz at :6441 */
static byte wbSiCut[] = { 0x30, 0x03, 0x02, 0x01, 0x03 };
/* A0 wrapper with a zero length: GetLength() at :6449 returns 0, which is
* "<= 0", so the chain is already failed when :6452..:6461 are evaluated */
static byte wbSiA0Zero[] = { 0x30, 0x05, 0x02, 0x01, 0x03, 0xA0, 0x00 };
/* inside the A0 wrapper the tag is NULL(0x05), not OCTET STRING: :6458 */
static byte wbSiBadTag[] = { 0x30, 0x07, 0x02, 0x01, 0x03, 0xA0, 0x02,
0x05, 0x00 };
/* OCTET STRING tag present but its length byte is off the end: :6461 */
static byte wbSiNoLen[] = { 0x30, 0x06, 0x02, 0x01, 0x03, 0xA0, 0x01,
0x04 };
/* version 3 with neither A0 nor [0] primitive: falls into the
* IssuerAndSerialNumber fallback at :6475/:6480 with a non-SEQUENCE */
static byte wbSiFallBad[] = { 0x30, 0x04, 0x02, 0x01, 0x03, 0x05 };
/* same fallback but the SEQUENCE parses: the accepting row for :6480 */
static byte wbSiFallOk[] = { 0x30, 0x07, 0x02, 0x01, 0x03, 0x30, 0x02,
0xAA, 0xBB };
/* complete version-3 SKID SignerInfo followed by a SHA-256 digestAlgorithm
* that ends exactly at the buffer end, so the signedAttributes peek at :6516
* runs out of input while ret is still 0 */
static byte wbSiSkidFull[] = {
0x30, 0x18,
0x02, 0x01, 0x03,
0xA0, 0x04, 0x04, 0x02, 0xAA, 0xBB,
0x30, 0x0D,
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01,
0x05, 0x00
};
static int wb_parse_signer_info(byte* in, word32 inSz, byte sdVersion)
{
wc_PKCS7 pkcs7;
word32 idx = 0;
byte* signedAttrib = NULL;
int signedAttribSz = 0;
int ret;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
pkcs7.version = sdVersion;
ret = wc_PKCS7_ParseSignerInfo(&pkcs7, in, inSz, &idx, 0, &signedAttrib,
&signedAttribSz);
wc_PKCS7_SignerInfoFree(&pkcs7);
return ret;
}
static void wb_parse_signer_info_chains(void)
{
int ret;
WB_NOTE("wc_PKCS7_ParseSignerInfo(): SignerInfoNew failure unpins the"
" leading ret==0 operand of the whole chain [:6413]");
mcdc_fa_install();
mcdc_fa_disarm();
mcdc_fa_arm_only(1); /* the PKCS7SignerInfo allocation */
ret = wb_parse_signer_info(wbSiSkidFull, (word32)sizeof(wbSiSkidFull), 3);
mcdc_fa_disarm();
mcdc_fa_restore();
/* Any failure before the version dispatch lands in the trailing else at
* :6501, which overwrites ret, so the observable return is the version
* error rather than the error the chain actually stopped on. */
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_VERSION_E),
":6413 ret non-zero on arrival (signerInfo allocation failed)");
WB_NOTE("wc_PKCS7_ParseSignerInfo(): first SEQUENCE rejects [:6413]");
ret = wb_parse_signer_info(wbSiNoSeq, (word32)sizeof(wbSiNoSeq), 1);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_VERSION_E), ":6413 leading SEQUENCE");
WB_NOTE("wc_PKCS7_ParseSignerInfo(): buffer ends on the version, so the"
" SKID peek and the v3 IssuerAndSerial fallback both see a"
" non-zero ret [:6445,:6480]");
ret = wb_parse_signer_info(wbSiCut, (word32)sizeof(wbSiCut), 3);
WB_CHECK(ret == WC_NO_ERR_TRACE(BUFFER_E), ":6445/:6480 ret non-zero");
WB_NOTE("wc_PKCS7_ParseSignerInfo(): A0 wrapper of length zero fails the"
" chain before :6452/:6455/:6458/:6461 are evaluated");
ret = wb_parse_signer_info(wbSiA0Zero, (word32)sizeof(wbSiA0Zero), 3);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E),
":6452/:6455/:6458/:6461 ret non-zero on arrival");
WB_NOTE("wc_PKCS7_ParseSignerInfo(): SKID inner tag is not an OCTET"
" STRING [:6458 trailing operand]");
ret = wb_parse_signer_info(wbSiBadTag, (word32)sizeof(wbSiBadTag), 3);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":6458 tag mismatch");
WB_NOTE("wc_PKCS7_ParseSignerInfo(): SKID OCTET STRING tag with no length"
" byte left [:6461 trailing operand]");
ret = wb_parse_signer_info(wbSiNoLen, (word32)sizeof(wbSiNoLen), 3);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":6461 length off the end");
WB_NOTE("wc_PKCS7_ParseSignerInfo(): v3 IssuerAndSerial fallback, both"
" halves of the SignedData-version guard [:6480]");
ret = wb_parse_signer_info(wbSiFallBad, (word32)sizeof(wbSiFallBad), 1);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E),
":6480 ret non-zero on arrival (SignedData version is not 3)");
ret = wb_parse_signer_info(wbSiFallBad, (word32)sizeof(wbSiFallBad), 3);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E),
":6480 SEQUENCE rejects with ret still 0");
ret = wb_parse_signer_info(wbSiFallOk, (word32)sizeof(wbSiFallOk), 3);
WB_CHECK(ret != 0, ":6480 SEQUENCE accepts (fails later on digestAlgo)");
WB_NOTE("wc_PKCS7_ParseSignerInfo(): complete v3 SKID SignerInfo whose"
" digestAlgorithm ends the buffer, so the signedAttributes peek"
" runs out of input with ret still 0 [:6516]");
ret = wb_parse_signer_info(wbSiSkidFull, (word32)sizeof(wbSiSkidFull), 3);
WB_CHECK(ret != 0, ":6516 GetASNTag peek fails with ret still 0");
}
/* ------------------------------------------------------------------------- *
* Section 10: wc_PKCS7_GetEnvelopedDataKariRid() hand-built skeletons
* [:15056,:15078].
*
* The kari-keyid-cms.msg sweep in Section 6 walks this function but cannot
* produce either uncovered vector:
*
* - :15056 is the OPTIONAL ukm [1] probe, and it is written `>= 0`, so its
* accepting outcome needs a message that actually carries a ukm.
* kari-keyid-cms.msg has none, and no truncation or single-byte flip can
* add one, so the sweep only ever sees the rejecting outcome.
* - :15078 needs a RecipientEncryptedKey SEQUENCE of length zero sitting at
* the very end of the buffer. Truncating a real message fails the
* enclosing SEQUENCE first, because GetSequence() bounds-checks its
* declared length against the remaining input.
*
* The skeletons below are the smallest EnvelopedData shells that reach those
* two points; they are hand-built rather than encoded so the byte offsets
* that matter (ukm present / absent, trailing empty SEQUENCE) are explicit.
* ------------------------------------------------------------------------- */
/* ContentInfo { id-envelopedData, [0] { SEQ { version, SET { SEQ } } } } --
* the recipientInfos SET parses, but its first element is a SEQUENCE (a KTRI
* shape) rather than the [1] KARI header. */
static byte wbKariNotKari[] = {
0x30, 0x18,
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x03,
0xA0, 0x0B,
0x30, 0x09,
0x02, 0x01, 0x00,
0x31, 0x04,
0x30, 0x02, 0xAA, 0xBB
};
/* same shell, but recipientInfos is a SEQUENCE so the SET header rejects and
* every later link in the chain sees a non-zero ret */
static byte wbKariNoSet[] = {
0x30, 0x18,
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x03,
0xA0, 0x0B,
0x30, 0x09,
0x02, 0x01, 0x00,
0x30, 0x04,
0x30, 0x02, 0xAA, 0xBB
};
/* full KARI skeleton down to RecipientEncryptedKeys, whose single
* RecipientEncryptedKey is an empty SEQUENCE ending the buffer: the
* KeyAgreeRecipientIdentifier tag read then has no byte left to read */
static byte wbKariRidCut[] = {
0x30, 0x25,
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x03,
0xA0, 0x18,
0x30, 0x16,
0x02, 0x01, 0x02,
0x31, 0x11,
0xA1, 0x0F,
0x02, 0x01, 0x03,
0xA0, 0x02, 0xAA, 0xBB,
0x30, 0x02, 0xAA, 0xBB,
0x30, 0x02,
0x30, 0x00
};
/* same skeleton with an optional ukm [1] present between the originator [0]
* and the keyEncryptionAlgorithm: the only shape that makes the ukm probe's
* own GetASNHeader succeed. kari-keyid-cms.msg carries no ukm, so without
* this the probe only ever sees its rejecting outcome. */
static byte wbKariUkm[] = {
0x30, 0x29,
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x03,
0xA0, 0x1C,
0x30, 0x1A,
0x02, 0x01, 0x02,
0x31, 0x15,
0xA1, 0x13,
0x02, 0x01, 0x03,
0xA0, 0x02, 0xAA, 0xBB,
0xA1, 0x02, 0xAA, 0xBB,
0x30, 0x02, 0xAA, 0xBB,
0x30, 0x02,
0x30, 0x00
};
static void wb_kari_rid_crafted(void)
{
byte out[64];
word32 outSz;
int ret;
WB_NOTE("wc_PKCS7_GetEnvelopedDataKariRid(): recipientInfos SET rejects,"
" so every later link in the chain sees a non-zero ret"
" [:15056 leading operand false]");
outSz = (word32)sizeof(out);
ret = wc_PKCS7_GetEnvelopedDataKariRid(wbKariNoSet,
(word32)sizeof(wbKariNoSet), out, &outSz);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":15056 ret non-zero");
WB_NOTE("wc_PKCS7_GetEnvelopedDataKariRid(): recipientInfos SET parses but"
" holds a non-KARI element, so the kari [1] header rejects with"
" ret still 0 [:15039]");
outSz = (word32)sizeof(out);
ret = wc_PKCS7_GetEnvelopedDataKariRid(wbKariNotKari,
(word32)sizeof(wbKariNotKari), out, &outSz);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":15039 not a KARI");
WB_NOTE("wc_PKCS7_GetEnvelopedDataKariRid(): optional ukm [1] present, so"
" the ukm probe accepts [:15056 both operands true]");
outSz = (word32)sizeof(out);
ret = wc_PKCS7_GetEnvelopedDataKariRid(wbKariUkm,
(word32)sizeof(wbKariUkm), out, &outSz);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":15056 ukm consumed");
WB_NOTE("wc_PKCS7_GetEnvelopedDataKariRid(): empty RecipientEncryptedKey"
" at end of buffer, so the rid tag read runs out of input with"
" ret still 0 [:15078]");
outSz = (word32)sizeof(out);
ret = wc_PKCS7_GetEnvelopedDataKariRid(wbKariRidCut,
(word32)sizeof(wbKariRidCut), out, &outSz);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":15078 rid tag off the end");
}
/* ------------------------------------------------------------------------- *
* Section 11: wc_PKCS7_DecryptOri() callback-result guard [:12978].
*
* `ret != 0 || decryptedKey == NULL || *decryptedKeySz == 0` is entirely
* determined by what the registered ORI callback does, and no API-level test
* registers one, so all three operands need a driver here: one call per
* operand-true row plus the all-false row they pair against.
* ------------------------------------------------------------------------- */
/* OtherRecipientInfo body, positioned as wc_PKCS7_DecryptOri() expects: the
* caller has already consumed the [4] tag, so index 0 is the length byte.
* <len> 06 03 2A 03 04 04 02 AA BB */
static byte wbOri[] = { 0x09, 0x06, 0x03, 0x2A, 0x03, 0x04,
0x04, 0x02, 0xAA, 0xBB };
static int wb_ori_cb_fail(wc_PKCS7* p, byte* oriType, word32 oriTypeSz,
byte* oriValue, word32 oriValueSz, byte* decryptedKey,
word32* decryptedKeySz, void* ctx)
{
(void)p; (void)oriType; (void)oriTypeSz; (void)oriValue; (void)oriValueSz;
(void)decryptedKey; (void)decryptedKeySz; (void)ctx;
return -1;
}
static int wb_ori_cb_nowrite(wc_PKCS7* p, byte* oriType, word32 oriTypeSz,
byte* oriValue, word32 oriValueSz, byte* decryptedKey,
word32* decryptedKeySz, void* ctx)
{
(void)p; (void)oriType; (void)oriTypeSz; (void)oriValue; (void)oriValueSz;
(void)decryptedKey; (void)ctx;
*decryptedKeySz = 32;
return 0;
}
static int wb_ori_cb_empty(wc_PKCS7* p, byte* oriType, word32 oriTypeSz,
byte* oriValue, word32 oriValueSz, byte* decryptedKey,
word32* decryptedKeySz, void* ctx)
{
(void)p; (void)oriType; (void)oriTypeSz; (void)oriValue; (void)oriValueSz;
(void)decryptedKey; (void)ctx;
*decryptedKeySz = 0;
return 0;
}
static int wb_ori_cb_ok(wc_PKCS7* p, byte* oriType, word32 oriTypeSz,
byte* oriValue, word32 oriValueSz, byte* decryptedKey,
word32* decryptedKeySz, void* ctx)
{
(void)p; (void)oriType; (void)oriTypeSz; (void)oriValue; (void)oriValueSz;
(void)ctx;
XMEMSET(decryptedKey, 0x11, 32);
*decryptedKeySz = 32;
return 0;
}
static int wb_decrypt_ori_once(CallbackOriDecrypt cb, byte* decryptedKey)
{
wc_PKCS7 pkcs7;
word32 idx = 0;
word32 keySz = 32;
int recipFound = 0;
int ret;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
pkcs7.oriDecryptCb = cb;
pkcs7.state = WC_PKCS7_DECRYPT_ORI;
#ifndef NO_PKCS7_STREAM
if (wc_PKCS7_CreateStream(&pkcs7) != 0) {
return -1;
}
/* let AddDataToStream take the "input buffer already holds it" path */
pkcs7.stream->maxLen = (word32)sizeof(wbOri);
#endif
ret = wc_PKCS7_DecryptOri(&pkcs7, wbOri, (word32)sizeof(wbOri), &idx,
decryptedKey, &keySz, &recipFound);
#ifndef NO_PKCS7_STREAM
wc_PKCS7_FreeStream(&pkcs7);
#endif
return ret;
}
static void wb_decrypt_ori_guard(void)
{
byte key[32];
int ret;
WB_NOTE("wc_PKCS7_DecryptOri(): callback failure [:12978 operand 1]");
ret = wb_decrypt_ori_once(wb_ori_cb_fail, key);
WB_CHECK(ret == WC_NO_ERR_TRACE(PKCS7_RECIP_E), ":12978 callback failed");
WB_NOTE("wc_PKCS7_DecryptOri(): callback succeeds with no output buffer"
" [:12978 operand 2]");
ret = wb_decrypt_ori_once(wb_ori_cb_nowrite, NULL);
WB_CHECK(ret == WC_NO_ERR_TRACE(PKCS7_RECIP_E), ":12978 NULL key buffer");
WB_NOTE("wc_PKCS7_DecryptOri(): callback succeeds with a zero-length key"
" [:12978 operand 3]");
ret = wb_decrypt_ori_once(wb_ori_cb_empty, key);
WB_CHECK(ret == WC_NO_ERR_TRACE(PKCS7_RECIP_E), ":12978 zero-length key");
WB_NOTE("wc_PKCS7_DecryptOri(): all three operands false (the row every"
" operand-true call above pairs against) [:12978]");
ret = wb_decrypt_ori_once(wb_ori_cb_ok, key);
WB_CHECK(ret == 0, ":12978 all operands false");
}
/* ------------------------------------------------------------------------- *
* Section 12: wc_PKCS7_HandleOctetStrings() content accumulation [:6817].
*
* `tempBuf != NULL && contBufSz != 0` needs a call that arrives with a
* previously allocated stream content buffer whose accumulated size is still
* zero. That combination never arises from a single top-level decode -- the
* two fields are written together -- so it is seeded directly here, paired
* with the ordinary non-zero-accumulation call in the same binary.
* ------------------------------------------------------------------------- */
#ifndef NO_PKCS7_STREAM
static void wb_octet_accum(word32 seedAccum)
{
wc_PKCS7 pkcs7;
byte in[16];
word32 idx = 0, tmpIdx = 0;
word32 seedSz = (seedAccum > 0) ? seedAccum : 1;
int ret;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
XMEMSET(in, 0, sizeof(in));
if (wc_PKCS7_CreateStream(&pkcs7) != 0) {
return;
}
pkcs7.stream->content = (byte*)XMALLOC(seedSz, pkcs7.heap,
DYNAMIC_TYPE_PKCS7);
if (pkcs7.stream->content == NULL) {
wc_PKCS7_FreeStream(&pkcs7);
return;
}
XMEMSET(pkcs7.stream->content, 0, seedSz);
pkcs7.stream->accumContSz = seedAccum;
pkcs7.stream->currContSz = 4;
pkcs7.stream->currContRmnSz = 4;
pkcs7.stream->expected = 4;
pkcs7.stream->noContent = 0;
/* stop after the single accumulation pass rather than looking for a
* following OCTET STRING header */
pkcs7.stream->maxLen = 4;
ret = wc_PKCS7_HandleOctetStrings(&pkcs7, in, (word32)sizeof(in), &tmpIdx,
&idx, 1);
WB_CHECK(ret == 0, ":6817 accumulation pass completed");
wc_PKCS7_FreeStream(&pkcs7);
}
/* The "another OCTET STRING follows" branch, :6689. Its trailing operand needs
* an 0x04 tag whose length field is itself malformed -- a shape no encoder
* emits, since every OCTET STRING wolfSSL writes carries a well-formed length.
* Seeded directly, like wb_octet_accum() above, with currContRmnSz == 0 so the
* branch is entered on the first pass. */
static int wb_octet_next_len(byte* in, word32 inSz)
{
wc_PKCS7 pkcs7;
word32 idx = 0, tmpIdx = 0;
int ret;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
if (wc_PKCS7_CreateStream(&pkcs7) != 0) {
return BAD_FUNC_ARG;
}
pkcs7.stream->currContSz = 0;
pkcs7.stream->currContRmnSz = 0;
pkcs7.stream->expected = 1;
pkcs7.stream->noContent = 0;
pkcs7.stream->maxLen = inSz;
ret = wc_PKCS7_HandleOctetStrings(&pkcs7, in, inSz, &tmpIdx, &idx, 1);
wc_PKCS7_FreeStream(&pkcs7);
return ret;
}
static void wb_octet_accum_chains(void)
{
WB_NOTE("wc_PKCS7_HandleOctetStrings(): existing content buffer with a"
" zero accumulated size [:6817 trailing operand false]");
wb_octet_accum(0);
WB_NOTE("wc_PKCS7_HandleOctetStrings(): existing content buffer with a"
" non-zero accumulated size [:6817 trailing operand true]");
wb_octet_accum(4);
{
static byte okLen[] = { 0x04, 0x02, 0xAA, 0xBB };
static byte badLen[] = { 0x04 };
int ret;
WB_NOTE("wc_PKCS7_HandleOctetStrings(): a following OCTET STRING whose"
" length parses [:6689 trailing operand false]");
ret = wb_octet_next_len(okLen, (word32)sizeof(okLen));
WB_CHECK(ret != WC_NO_ERR_TRACE(ASN_PARSE_E),
":6689 well-formed following OCTET STRING length");
WB_NOTE("wc_PKCS7_HandleOctetStrings(): a following OCTET STRING tag"
" with no length byte behind it [:6689 trailing operand"
" true]");
ret = wb_octet_next_len(badLen, (word32)sizeof(badLen));
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E),
":6689 malformed following OCTET STRING length");
}
}
#else
static void wb_octet_accum_chains(void)
{
WB_NOTE("NO_PKCS7_STREAM; HandleOctetStrings accumulation skipped");
}
#endif /* !NO_PKCS7_STREAM */
/* ------------------------------------------------------------------------- *
* Section 13: wc_PKCS7_EcdsaVerify() verification-result guards
* [:5505,:5516].
*
* Both guards are `ret == 0 && res == 1`; their leading operand only goes
* false when wc_ecc_verify_hash() itself errors out, which a well-formed
* bundle never does (a wrong signature yields ret 0 with res 0). Feeding a
* signature that is not a valid ECDSA-Sig-Value produces the error, and the
* genuinely-verifying call in the same function supplies the row it pairs
* against.
* ------------------------------------------------------------------------- */
#if defined(HAVE_ECC) && defined(USE_CERT_BUFFERS_256)
static void wb_ecdsa_verify_results(void)
{
wc_PKCS7 pkcs7;
ecc_key key;
byte hash[32];
byte sig[144];
word32 sigSz = (word32)sizeof(sig);
word32 idx = 0;
int ret;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
XMEMSET(hash, 0x5A, sizeof(hash));
pkcs7.devId = INVALID_DEVID;
pkcs7.cert[0] = (byte*)cliecc_cert_der_256;
pkcs7.certSz[0] = (word32)sizeof_cliecc_cert_der_256;
WB_NOTE("wc_PKCS7_EcdsaVerify(): genuine signature over the supplied"
" digest [:5505,:5516 both operands true]");
if (wc_ecc_init(&key) == 0) {
if (wc_EccPrivateKeyDecode(ecc_clikey_der_256, &idx, &key,
(word32)sizeof_ecc_clikey_der_256) == 0 &&
wc_ecc_sign_hash(hash, (word32)sizeof(hash), sig, &sigSz,
&wbRng, &key) == 0) {
ret = wc_PKCS7_EcdsaVerify(&pkcs7, sig, (int)sigSz, hash,
(word32)sizeof(hash));
WB_CHECK(ret == 0, ":5505/:5516 signature verified");
}
wc_ecc_free(&key);
}
WB_NOTE("wc_PKCS7_EcdsaVerify(): signature bytes are not an"
" ECDSA-Sig-Value, so the verify call errors and both guards see"
" a non-zero ret [:5505,:5516]");
XMEMSET(sig, 0xA5, sizeof(sig));
ret = wc_PKCS7_EcdsaVerify(&pkcs7, sig, 16, hash, (word32)sizeof(hash));
WB_CHECK(ret != 0, ":5505/:5516 ret non-zero on arrival");
}
#else
static void wb_ecdsa_verify_results(void)
{
WB_NOTE("no HAVE_ECC/USE_CERT_BUFFERS_256; EcdsaVerify results skipped");
}
#endif
/* ------------------------------------------------------------------------- *
* Section 14: PKCS7_VerifySignedData() outer-shape vectors [:6980,:7068].
*
* Both are data-shape operands that a truncate/corrupt sweep over a real
* bundle cannot produce: every corpus starts `30 82 xx xx`, so the outer
* SEQUENCE length is never zero, and every corpus SignedData is version 1.
* ------------------------------------------------------------------------- */
/* definite zero-length outer SEQUENCE: length is 0 but the byte before the
* content is 0x00, not the indefinite-length marker */
static byte wbVsdEmptySeq[64];
/* SignedData whose version field is 3 (the other accepted value), and the
* same bundle with an unaccepted version, so the version guard sees both */
static byte wbVsdVer[64];
static void wb_verify_outer_shapes(void)
{
wc_PKCS7* p;
word32 i;
XMEMSET(wbVsdEmptySeq, 0, sizeof(wbVsdEmptySeq));
wbVsdEmptySeq[0] = 0x30;
wbVsdEmptySeq[1] = 0x00;
XMEMSET(wbVsdVer, 0, sizeof(wbVsdVer));
i = 0;
wbVsdVer[i++] = 0x30; wbVsdVer[i++] = 0x14;
wbVsdVer[i++] = 0x06; wbVsdVer[i++] = 0x09;
wbVsdVer[i++] = 0x2A; wbVsdVer[i++] = 0x86; wbVsdVer[i++] = 0x48;
wbVsdVer[i++] = 0x86; wbVsdVer[i++] = 0xF7; wbVsdVer[i++] = 0x0D;
wbVsdVer[i++] = 0x01; wbVsdVer[i++] = 0x07; wbVsdVer[i++] = 0x02;
wbVsdVer[i++] = 0xA0; wbVsdVer[i++] = 0x07;
wbVsdVer[i++] = 0x30; wbVsdVer[i++] = 0x05;
wbVsdVer[i++] = 0x02; wbVsdVer[i++] = 0x01; wbVsdVer[i++] = 0x03;
wbVsdVer[i++] = 0x31; wbVsdVer[i++] = 0x00;
WB_NOTE("PKCS7_VerifySignedData(): definite zero-length outer SEQUENCE,"
" so the indefinite-length probe reads a 0x00 length byte"
" [:6980 trailing operand false]");
p = wc_PKCS7_New(NULL, INVALID_DEVID);
if (p != NULL) {
if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) {
(void)wc_PKCS7_VerifySignedData(p, wbVsdEmptySeq,
(word32)sizeof(wbVsdEmptySeq));
}
wc_PKCS7_Free(p);
}
WB_NOTE("PKCS7_VerifySignedData(): SignedData version 3 [:7068 trailing"
" operand false] and an unaccepted version [:7068 true]");
p = wc_PKCS7_New(NULL, INVALID_DEVID);
if (p != NULL) {
if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) {
(void)wc_PKCS7_VerifySignedData(p, wbVsdVer,
(word32)sizeof(wbVsdVer));
}
wc_PKCS7_Free(p);
}
wbVsdVer[19] = 0x07; /* version byte: neither 1 nor 3 */
p = wc_PKCS7_New(NULL, INVALID_DEVID);
if (p != NULL) {
if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) {
(void)wc_PKCS7_VerifySignedData(p, wbVsdVer,
(word32)sizeof(wbVsdVer));
}
wc_PKCS7_Free(p);
}
wbVsdVer[19] = 0x03;
}
/* ------------------------------------------------------------------------- *
* Section 16: identity- and version-dispatch operands that neither a sweep
* nor any public encoder can produce, because no encoder in the tree ever
* emits the shape the operand tests.
*
* :6398 cond 1/2 wc_PKCS7_ParseSignerInfo()'s noDegenerate guard. The
* inner OR is `inSz == 0 || degenerate == 1`; both operands
* need a call with noDegenerate set, which the public
* decode path only ever makes with the *same* (inSz,
* degenerate) pair for a given bundle. Called directly with
* the three combinations instead.
* :5166 cond 1 wc_PKCS7_RsaVerify()'s `keyOID != RSAk && keyOID !=
* :5298 cond 1 RSAPSSk` defence-in-depth guard, and the same guard in
* wc_PKCS7_RsaPssVerify(). The false row needs an
* RSASSA-PSS SubjectPublicKeyInfo (keyOID == RSAPSSk) in
* pkcs7->cert[], which no bundle this module builds carries;
* the true row needs a non-RSA-family cert in the same
* binary. Both are supplied here from certs/.
* :14106 cond 2 wc_PKCS7_ParseToRecipientInfoSet()'s BER marker test
* `ret == 0 && length == 0 && pkiMsg[(*idx)-1] == 0x80`.
* A zero-length *definite* outer SEQUENCE (`30 00`) is the
* only input that reaches the third operand with a false
* value; the `30 80` companion in the same binary supplies
* the true row.
* :14215 cond 4 the ECDSA arm of the envelopedData version dispatch,
* `publicKeyOID == ECDSAk && (version != 0 && ...)`. The
* false row needs an ECC signer key with version 0, the
* true row the same key with a version that is none of
* 0/2/3 -- one field of one hand-built header apart.
* ------------------------------------------------------------------------- */
/* ContentInfo/EnvelopedData header, parsed as far as the RecipientInfo SET.
* Padded well past MAX_OID_SZ + MAX_LENGTH_SZ so that the streaming
* wc_PKCS7_AddDataToStream() never has to ask for more input. */
static byte wbRisHdr[96];
/* the same prefix with a zero-length definite outer SEQUENCE, and with the
* indefinite-length marker, so :14106's third operand sees both values */
static byte wbRisEmptyDef[96];
static byte wbRisEmptyIndef[96];
static word32 wb_build_ris_hdr(byte* buf, word32 bufSz, byte version)
{
word32 i = 0;
XMEMSET(buf, 0, bufSz);
buf[i++] = 0x30; buf[i++] = 0x16; /* ContentInfo SEQUENCE */
buf[i++] = 0x06; buf[i++] = 0x09; /* envelopedData OID */
buf[i++] = 0x2A; buf[i++] = 0x86; buf[i++] = 0x48; buf[i++] = 0x86;
buf[i++] = 0xF7; buf[i++] = 0x0D; buf[i++] = 0x01; buf[i++] = 0x07;
buf[i++] = 0x03;
buf[i++] = 0xA0; buf[i++] = 0x09; /* [0] EXPLICIT */
buf[i++] = 0x30; buf[i++] = 0x07; /* EnvelopedData SEQUENCE */
buf[i++] = 0x02; buf[i++] = 0x01; buf[i++] = version;
buf[i++] = 0x31; buf[i++] = 0x02; /* RecipientInfo SET */
buf[i++] = 0x30; buf[i++] = 0x00;
return bufSz;
}
static int wb_parse_ris(byte* buf, word32 bufSz, word32 pubKeyOID)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
word32 idx = 0;
int ret;
if (p == NULL) {
return MEMORY_E;
}
if (wc_PKCS7_InitWithCert(p, NULL, 0) != 0) {
wc_PKCS7_Free(p);
return BAD_FUNC_ARG;
}
p->publicKeyOID = pubKeyOID;
ret = wc_PKCS7_ParseToRecipientInfoSet(p, buf, bufSz, &idx, ENVELOPED_DATA);
wc_PKCS7_Free(p);
return ret;
}
static void wb_recipient_info_set_shapes(void)
{
int ret;
WB_NOTE("wc_PKCS7_ParseToRecipientInfoSet(): zero-length outer SEQUENCE,"
" definite vs indefinite, isolates the 0x80 marker operand"
" [:14106 cond 2]");
XMEMSET(wbRisEmptyDef, 0, sizeof(wbRisEmptyDef));
wbRisEmptyDef[0] = 0x30;
wbRisEmptyDef[1] = 0x00;
ret = wb_parse_ris(wbRisEmptyDef, (word32)sizeof(wbRisEmptyDef), RSAk);
WB_CHECK(ret != 0, ":14106 definite zero-length SEQUENCE (marker false)");
XMEMSET(wbRisEmptyIndef, 0, sizeof(wbRisEmptyIndef));
wbRisEmptyIndef[0] = 0x30;
wbRisEmptyIndef[1] = 0x80;
ret = wb_parse_ris(wbRisEmptyIndef, (word32)sizeof(wbRisEmptyIndef), RSAk);
WB_CHECK(ret != 0, ":14106 indefinite-length SEQUENCE (marker true)");
#ifdef HAVE_ECC
WB_NOTE("wc_PKCS7_ParseToRecipientInfoSet(): ECDSA signer key with"
" envelopedData version 0 and version 1 [:14215 cond 4]");
(void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x00);
ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), ECDSAk);
/* the success return is the RecipientInfo SET length, not 0 */
WB_CHECK(ret > 0, ":14215 ECDSAk with version 0 is accepted");
(void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x01);
ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), ECDSAk);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_VERSION_E),
":14215 ECDSAk with version 1 is rejected");
#endif
/* RSA companion rows, so the RSAk arm's operands are decided by the same
* two headers rather than only by real bundles. */
(void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x00);
ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), RSAk);
WB_CHECK(ret > 0, ":14215 RSAk with version 0 is accepted");
(void)wb_build_ris_hdr(wbRisHdr, (word32)sizeof(wbRisHdr), 0x01);
ret = wb_parse_ris(wbRisHdr, (word32)sizeof(wbRisHdr), RSAk);
WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_VERSION_E),
":14215 RSAk with version 1 is rejected");
}
/* wc_PKCS7_ParseSignerInfo() with pkcs7->noDegenerate set, across the three
* (inSz, degenerate) combinations the inner OR needs. */
static int wb_parse_signer_info_nodeg(byte* in, word32 inSz, int degenerate)
{
wc_PKCS7 pkcs7;
word32 idx = 0;
byte* signedAttrib = NULL;
int signedAttribSz = 0;
int ret;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
pkcs7.version = 1;
pkcs7.noDegenerate = 1;
ret = wc_PKCS7_ParseSignerInfo(&pkcs7, in, inSz, &idx, degenerate,
&signedAttrib, &signedAttribSz);
wc_PKCS7_SignerInfoFree(&pkcs7);
return ret;
}
static void wb_parse_signer_info_nodegenerate(void)
{
int ret;
WB_NOTE("wc_PKCS7_ParseSignerInfo(): noDegenerate matrix, each operand of"
" `inSz == 0 || degenerate == 1` isolated [:6398]");
/* (T,T,-): inSz == 0 decides the OR */
ret = wb_parse_signer_info_nodeg(wbSiNoSeq, 0, 0);
WB_CHECK(ret == WC_NO_ERR_TRACE(PKCS7_NO_SIGNER_E),
":6398 inSz == 0 with noDegenerate set");
/* (T,F,T): degenerate decides the OR */
ret = wb_parse_signer_info_nodeg(wbSiNoSeq, (word32)sizeof(wbSiNoSeq), 1);
WB_CHECK(ret == WC_NO_ERR_TRACE(PKCS7_NO_SIGNER_E),
":6398 degenerate == 1 with noDegenerate set");
/* (T,F,F): the guard does not fire and the parse runs on */
ret = wb_parse_signer_info_nodeg(wbSiNoSeq, (word32)sizeof(wbSiNoSeq), 0);
WB_CHECK(ret != WC_NO_ERR_TRACE(PKCS7_NO_SIGNER_E),
":6398 neither OR operand true, parse proceeds");
}
/* ------------------------------------------------------------------------- *
* The RSA-family SPKI guards. Both verifiers walk pkcs7->cert[] themselves,
* so the vector is just "put this DER in cert[0] and call".
* ------------------------------------------------------------------------- */
#ifndef NO_RSA
static byte wbSpkiCert[2048];
static void wb_rsa_spki_guards(void)
{
byte sig[256];
byte hash[32];
word32 certSz;
wc_PKCS7* p;
XMEMSET(sig, 0x5A, sizeof(sig));
XMEMSET(hash, 0x5B, sizeof(hash));
/* (a) keyOID is neither RSAk nor RSAPSSk: an ECDSA certificate. */
certSz = wb_load_file("./certs/client-ecc-cert.der", wbSpkiCert,
(word32)sizeof(wbSpkiCert));
if (certSz > 0) {
WB_NOTE("wc_PKCS7_RsaVerify(): non-RSA-family SPKI rejected"
" [:5166 both operands true]");
p = wc_PKCS7_New(NULL, INVALID_DEVID);
if (p != NULL) {
if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) {
p->cert[0] = wbSpkiCert;
p->certSz[0] = certSz;
p->hashOID = SHA256h;
WB_CHECK(wc_PKCS7_RsaVerify(p, sig, (int)sizeof(sig), hash,
(word32)sizeof(hash)) != 0,
":5166 ECDSA cert is skipped");
#ifdef WC_RSA_PSS
WB_CHECK(wc_PKCS7_RsaPssVerify(p, sig, (int)sizeof(sig), hash,
(word32)sizeof(hash)) != 0,
":5298 ECDSA cert is skipped");
#endif
}
wc_PKCS7_Free(p);
}
}
/* (b) keyOID == RSAPSSk: the second operand alone decides the guard. */
certSz = wb_load_file("./certs/rsapss/client-rsapss.der", wbSpkiCert,
(word32)sizeof(wbSpkiCert));
if (certSz > 0) {
WB_NOTE("wc_PKCS7_RsaVerify(): RSASSA-PSS SPKI accepted by the guard"
" [:5166 second operand false]");
p = wc_PKCS7_New(NULL, INVALID_DEVID);
if (p != NULL) {
if (wc_PKCS7_InitWithCert(p, NULL, 0) == 0) {
p->cert[0] = wbSpkiCert;
p->certSz[0] = certSz;
p->hashOID = SHA256h;
/* the signature is garbage, so the call still fails -- but it
* fails *after* the guard, which is the point. */
WB_CHECK(wc_PKCS7_RsaVerify(p, sig, (int)sizeof(sig), hash,
(word32)sizeof(hash)) != 0,
":5166 RSAPSS cert passes the guard, signature fails");
#ifdef WC_RSA_PSS
WB_CHECK(wc_PKCS7_RsaPssVerify(p, sig, (int)sizeof(sig), hash,
(word32)sizeof(hash)) != 0,
":5298 RSAPSS cert passes the guard, signature fails");
#endif
}
wc_PKCS7_Free(p);
}
}
}
#else
static void wb_rsa_spki_guards(void)
{
WB_NOTE("NO_RSA; RSA-family SPKI guards skipped");
}
#endif /* !NO_RSA */
/* ------------------------------------------------------------------------- *
* Section 17: the KTRI key-encryption-algorithm dispatch, :12114
* `encOID != RSAk && encOID != RSAESOAEPk`
* Every KTRI this tree can *emit* carries rsaEncryption, which short-circuits
* on the first operand. The two rows the second operand needs are one byte
* apart from that: the last arc of the 9-byte algorithm OID inside the
* KeyTransRecipientInfo is rewritten in place -- 0x07 for id-RSAES-OAEP (the
* guard's false row) and 0x0A for id-RSASSA-PSS, which is in neither arm (the
* true row). Both replacements are the same length as rsaEncryption, so no
* enclosing ASN.1 length changes.
* ------------------------------------------------------------------------- */
#if !defined(NO_RSA) && defined(USE_CERT_BUFFERS_2048)
static const byte wbRsaEncOid[] = {
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01
};
static void wb_ktri_alg_call(byte* buf, word32 len)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
static byte out[WB_SCRATCH_SZ];
if (p == NULL) {
return;
}
if (wc_PKCS7_InitWithCert(p, (byte*)client_cert_der_2048,
sizeof_client_cert_der_2048) == 0 &&
wc_PKCS7_SetKey(p, (byte*)client_key_der_2048,
sizeof_client_key_der_2048) == 0) {
(void)wc_PKCS7_DecodeEnvelopedData(p, buf, len, out, sizeof(out));
}
wc_PKCS7_Free(p);
}
static void wb_ktri_key_alg_dispatch(void)
{
word32 fullLen, i;
int found = -1;
fullLen = wb_load_file("./certs/test/ktri-keyid-cms.msg", wbScratch,
sizeof(wbScratch));
if (fullLen < sizeof(wbRsaEncOid)) {
WB_NOTE("ktri-keyid-cms.msg unavailable; KTRI algorithm dispatch"
" skipped");
return;
}
/* the LAST rsaEncryption OID in the message is the KTRI's
* keyEncryptionAlgorithm (the earlier one is the certificate's SPKI) */
for (i = 0; i + (word32)sizeof(wbRsaEncOid) <= fullLen; i++) {
if (XMEMCMP(wbScratch + i, wbRsaEncOid, sizeof(wbRsaEncOid)) == 0) {
found = (int)i;
}
}
if (found < 0) {
WB_NOTE("no rsaEncryption OID found in ktri-keyid-cms.msg; KTRI"
" algorithm dispatch skipped");
return;
}
WB_NOTE("wc_PKCS7_DecryptKtri(): rsaEncryption (first operand false)"
" [:12114]");
wb_ktri_alg_call(wbScratch, fullLen);
WB_NOTE("wc_PKCS7_DecryptKtri(): id-RSAES-OAEP (second operand false)"
" [:12114 cond 1]");
wbScratch[(word32)found + 10] = 0x07;
wb_ktri_alg_call(wbScratch, fullLen);
WB_NOTE("wc_PKCS7_DecryptKtri(): an OID in neither arm (both operands"
" true) [:12114]");
wbScratch[(word32)found + 10] = 0x0A;
wb_ktri_alg_call(wbScratch, fullLen);
wbScratch[(word32)found + 10] = 0x01;
}
#else
static void wb_ktri_key_alg_dispatch(void)
{
WB_NOTE("NO_RSA or no 2048-bit cert buffers; KTRI algorithm dispatch"
" skipped");
}
#endif
/* ------------------------------------------------------------------------- *
* Section 18: wc_PKCS7_DecryptKtri()'s WC_PKCS7_DECRYPT_KTRI_2 "peek at the
* next SEQUENCE" block, :12036 cond 0 (`pkcs7->stream->length > 0`).
*
* The block is guarded by `pkcs7->stream->expected == MAX_SEQ_SZ`, and
* `expected` on the only path into this state is the RecipientInfo SET length
* that wc_PKCS7_ParseToRecipientInfoSet() stored at :14259 (EnvelopedData then
* copies it into stream->expected at :14424 as recipientSetSz). MAX_SEQ_SZ is
* 6, so the whole block is dead for every bundle this tree can emit -- a real
* KeyTransRecipientInfo SET is hundreds of bytes -- and the measured runs
* execute :12027-:12036 exactly zero times. Reaching it needs a SET whose
* length field says 6 while the recipient behind it is full size, which
* GetSet_ex(..., NO_USER_CHECK) at :14235 does accept; rather than build that
* whole bundle, the state is seeded directly here, which is the same shape.
*
* Both rows of cond 0 in one binary, on the state the guard actually reads:
* false row -- stream->length == 0, so wc_PKCS7_AddDataToStream() hands back
* the caller's buffer (:357) and the decision is false; the KTRI parse
* then runs on and fails in the IssuerAndSerialNumber walk.
* true row -- the stream is pre-loaded with 4 buffered bytes and fed a
* 2-byte chunk, so AddDataToStream() tops the internal buffer up to
* exactly MAX_SEQ_SZ (:379-:399) and returns it; stream->length is then 6
* and the recomputed `expected` is sz + 538, so the decision is true and
* the call answers WC_PKCS7_WANT_READ_E.
*
* cond 1 (`stream->length < stream->expected`) has no false row and is filed
* in the exclusion record: `expected` was just assigned `sz + MAX_ALGO_SZ +
* ASN_TAG_SZ + MAX_LENGTH_SZ + 512` (>= 538) two statements above, while
* stream->length can never exceed the largest `expected` ever passed to
* wc_PKCS7_AddDataToStream() on the way here -- MAX_OID_SZ + MAX_LENGTH_SZ +
* ASN_TAG_SZ == 38, at WC_PKCS7_INFOSET_STAGE1 -- because AddDataToStream only
* ever grows length by min(inSz - rdSz, expected - length).
* ------------------------------------------------------------------------- */
#ifndef NO_PKCS7_STREAM
static int wb_ktri2_seq_peek(const byte* seed, word32 seedSz,
byte* in, word32 inSz)
{
wc_PKCS7 pkcs7;
byte decryptedKey[MAX_ENCRYPTED_KEY_SZ];
word32 decryptedKeySz = (word32)sizeof(decryptedKey);
word32 idx = 0;
int recipFound = 0;
int ret;
XMEMSET(&pkcs7, 0, sizeof(pkcs7));
XMEMSET(decryptedKey, 0, sizeof(decryptedKey));
pkcs7.publicKeyOID = RSAk;
if (wc_PKCS7_CreateStream(&pkcs7) != 0) {
return MEMORY_E;
}
if (seedSz > 0) {
if (wc_PKCS7_GrowStream(&pkcs7, MAX_SEQ_SZ) != 0) {
wc_PKCS7_FreeStream(&pkcs7);
return MEMORY_E;
}
XMEMCPY(pkcs7.stream->buffer, seed, seedSz);
pkcs7.stream->length = seedSz;
}
pkcs7.stream->idx = 0;
pkcs7.stream->expected = MAX_SEQ_SZ;
pkcs7.stream->varTwo = CMS_ISSUER_AND_SERIAL_NUMBER; /* sidType */
pkcs7.stream->varThree = 0; /* version */
wc_PKCS7_ChangeState(&pkcs7, WC_PKCS7_DECRYPT_KTRI_2);
ret = wc_PKCS7_DecryptKtri(&pkcs7, in, inSz, &idx, decryptedKey,
&decryptedKeySz, &recipFound);
wc_PKCS7_FreeStream(&pkcs7);
return ret;
}
static void wb_ktri2_seq_peek_rows(void)
{
/* "30 04 AA BB CC DD": a 6-byte SEQUENCE, so GetSequence()/GetLength()
* succeed against a 6-byte view and the block reaches the guard. */
static const byte seqSeed[] = { 0x30, 0x04, 0xAA, 0xBB };
static byte seqTail[] = { 0xCC, 0xDD };
static byte seqWhole[] = { 0x30, 0x04, 0xAA, 0xBB, 0xCC, 0xDD,
0x00, 0x00 };
int ret;
WB_NOTE("wc_PKCS7_DecryptKtri(): KTRI_2 SEQUENCE peek with nothing"
" buffered [:12036 cond 0 false]");
ret = wb_ktri2_seq_peek(NULL, 0, seqWhole, (word32)sizeof(seqWhole));
WB_CHECK(ret != WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E),
":12036 stream->length == 0, guard does not fire");
WB_NOTE("wc_PKCS7_DecryptKtri(): KTRI_2 SEQUENCE peek with the internal"
" buffer topped up to MAX_SEQ_SZ [:12036 cond 0 true]");
ret = wb_ktri2_seq_peek(seqSeed, (word32)sizeof(seqSeed), seqTail,
(word32)sizeof(seqTail));
WB_CHECK(ret == WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E),
":12036 stream->length > 0 and short of the new expected");
}
#else
static void wb_ktri2_seq_peek_rows(void)
{
WB_NOTE("NO_PKCS7_STREAM; KTRI_2 SEQUENCE peek skipped");
}
#endif /* !NO_PKCS7_STREAM */
/* ------------------------------------------------------------------------- *
* Section 19: PKCS7_VerifySignedData() WC_PKCS7_VERIFY_STAGE7, :8237 cond 0
* (`ret == 0` ahead of the signature OCTET STRING tag test).
*
* This is NOT family (1): the statement between the state's
* wc_PKCS7_AddDataToStream() and the link is `if (idx >= pkiMsg2Sz) ret =
* BUFFER_E;` at :8232, which does write ret. The operand's false row therefore
* exists, and needs wc_PKCS7_ParseSignerInfo() to *succeed* while landing idx
* exactly on the end of the message -- a SignerInfo whose signatureAlgorithm
* is the last element in the buffer, with no signature behind it. No encoder
* in the tree emits that (signature is mandatory), and the truncation sweeps
* in Sections 1-8 and in test_pkcs7_mutate_whitebox.c cannot produce it either
* because cutting a real bundle there also invalidates the enclosing SEQUENCE
* and SET lengths, so the parse fails one stage earlier.
*
* So the stage is entered directly with a hand-built SignerInfo:
* SEQUENCE { INTEGER 1, SEQUENCE (IssuerAndSerialNumber, opaque to the
* parser -- it only records the bytes as the SID), AlgorithmIdentifier
* (sha-256), AlgorithmIdentifier (sha256WithRSAEncryption) }
* stream->expected is 0, so AddDataToStream() hands the buffer straight back
* at idx 0 (:357); stream->varThree is 1 so the `length > 0` operand of the
* enclosing :8230 guard holds, and stream->degenerate is 0.
*
* false row -- the buffer IS exactly the SignerInfo, so the parse leaves
* idx == pkiMsg2Sz, :8232 sets BUFFER_E and the decision is false.
* true row -- the same SignerInfo followed by a 4-byte OCTET STRING
* signature, so idx stops short, ret stays 0, the tag reads 0x04 and the
* decision is true.
* ------------------------------------------------------------------------- */
#if !defined(NO_PKCS7_STREAM) && !defined(NO_RSA) && !defined(NO_SHA256)
/* offsets: [0..1] SignerInfo SEQ header, then version, SID, digestAlgorithm,
* signatureAlgorithm. 43 bytes total; the trailing 6 bytes are the OCTET
* STRING signature used only by the true row. */
static byte wbStage7Si[] = {
0x30, 0x29, /* SignerInfo SEQUENCE */
0x02, 0x01, 0x01, /* version 1 */
0x30, 0x06, /* IssuerAndSerial */
0x30, 0x00, /* issuer Name */
0x02, 0x02, 0x12, 0x34, /* serialNumber */
0x30, 0x0D, /* digestAlgorithm */
0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65,
0x03, 0x04, 0x02, 0x01, /* id-sha256 */
0x05, 0x00,
0x30, 0x0D, /* signatureAlgorithm */
0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7,
0x0D, 0x01, 0x01, 0x0B, /* sha256WithRSA */
0x05, 0x00,
0x04, 0x04, 0xDE, 0xAD, 0xBE, 0xEF /* signature (true row) */
};
#define WB_STAGE7_SI_ONLY 43
static int wb_stage7_signature(word32 inSz)
{
wc_PKCS7* p = wc_PKCS7_New(NULL, INVALID_DEVID);
int ret;
if (p == NULL) {
return MEMORY_E;
}
p->version = 1;
if (wc_PKCS7_CreateStream(p) != 0) {
wc_PKCS7_Free(p);
return MEMORY_E;
}
p->stream->expected = 0; /* AddDataToStream() returns `in` at idx 0 */
p->stream->length = 0;
p->stream->idx = 0;
p->stream->varThree = 1; /* `length` operand of :8230 */
p->stream->degenerate = 0;
p->stream->indefLen = 0;
wc_PKCS7_ChangeState(p, WC_PKCS7_VERIFY_STAGE7);
ret = PKCS7_VerifySignedData(p, NULL, 0, wbStage7Si, inSz, NULL, 0);
wc_PKCS7_Free(p);
return ret;
}
static void wb_stage7_signature_rows(void)
{
int ret;
WB_NOTE("PKCS7_VerifySignedData(): STAGE7 with the SignerInfo ending the"
" message [:8237 cond 0 false]");
ret = wb_stage7_signature(WB_STAGE7_SI_ONLY);
WB_CHECK(ret == WC_NO_ERR_TRACE(BUFFER_E),
":8232 idx == pkiMsg2Sz sets BUFFER_E before the tag test");
WB_NOTE("PKCS7_VerifySignedData(): STAGE7 with a signature OCTET STRING"
" behind the SignerInfo [:8237 cond 0 true]");
ret = wb_stage7_signature((word32)sizeof(wbStage7Si));
WB_CHECK(ret != WC_NO_ERR_TRACE(BUFFER_E),
":8237 ret == 0 on arrival, the OCTET STRING tag is read");
}
#else
static void wb_stage7_signature_rows(void)
{
WB_NOTE("NO_PKCS7_STREAM/NO_RSA/NO_SHA256; STAGE7 signature-presence"
" rows skipped");
}
#endif /* !NO_PKCS7_STREAM && !NO_RSA && !NO_SHA256 */
/* ------------------------------------------------------------------------- *
* main -- always returns 0 so the test harness keeps this variant's
* coverage even if an individual sub-section's build config disables it.
* ------------------------------------------------------------------------- */
int main(void)
{
int rngRet;
setvbuf(stdout, NULL, _IONBF, 0);
printf("=== pkcs7 decode-chain white-box (Part 5) ===\n");
rngRet = wc_InitRng(&wbRng);
WB_CHECK(rngRet == 0, "wc_InitRng for corpus builders");
wb_verify_decode_chains();
wb_enveloped_decode_chains();
wb_auth_enveloped_decode_chains();
wb_encrypted_decode_chains();
wb_parse_ris_decode_chains();
wb_kari_rid_decode_chains();
wb_kekri_decode_chains();
wb_pwri_decode_chains();
wb_decrypt_content_init_direct();
wb_parse_signer_info_chains();
wb_kari_rid_crafted();
wb_decrypt_ori_guard();
wb_octet_accum_chains();
wb_ecdsa_verify_results();
wb_verify_outer_shapes();
wb_recipient_info_set_shapes();
wb_parse_signer_info_nodegenerate();
wb_rsa_spki_guards();
wb_ktri_key_alg_dispatch();
wb_ktri2_seq_peek_rows();
wb_stage7_signature_rows();
if (rngRet == 0) {
wc_FreeRng(&wbRng);
}
if (wb_fail) {
printf("=== pkcs7 decode-chain white-box: FAIL ===\n");
}
else {
printf("=== pkcs7 decode-chain white-box: PASS ===\n");
}
return 0;
}