diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index b04504a0054..8178c4363e8 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -1618,7 +1618,14 @@ typedef struct ESD { byte digEncAlgoId[MAX_ALGO_SZ]; #endif byte signedAttribSet[MAX_SET_SZ]; - EncodedAttrib signedAttribs[7]; + /* Working attribute array. signedAttribs points at + * the inline buffer for the common case (no heap use, + * important for no-malloc/static-memory builds) and is + * redirected to a heap allocation only when the + * attribute count exceeds MAX_SIGNED_ATTRIBS_SZ. + * signedAttribsCap holds the usable entry count. */ + EncodedAttrib signedAttribsInline[MAX_SIGNED_ATTRIBS_SZ]; + EncodedAttrib* signedAttribs; byte signerDigest[MAX_OCTET_STR_SZ]; word32 innerOctetsSz, innerContSeqSz, contentInfoSeqSz; word32 outerSeqSz, outerContentSz, innerSeqSz, versionSz, digAlgoIdSetSz, @@ -1627,7 +1634,7 @@ typedef struct ESD { issuerSnSeqSz, issuerNameSz, issuerSnSz, issuerSKIDSz, issuerSKIDSeqSz, signerDigAlgoIdSz, digEncAlgoIdSz, signerDigestSz; word32 encContentDigestSz, signedAttribsSz, signedAttribsCount, - signedAttribSetSz; + signedAttribSetSz, signedAttribsCap; } ESD; @@ -2238,11 +2245,46 @@ static int wc_PKCS7_GetSignSize(wc_PKCS7* pkcs7) } +/* Number of default ("canned") signed attributes that + * wc_PKCS7_BuildSignedAttributes() will emit for the current + * pkcs7->defaultSignedAttribs selection. This is the single source of truth for + * that count: it must stay in lock step with the emission logic in + * wc_PKCS7_BuildSignedAttributes() below, and is used by PKCS7_EncodeSigned() to + * size the working attribute array to the exact count. */ +static word32 wc_PKCS7_GetDefaultSignedAttribCount(wc_PKCS7* pkcs7) +{ + word32 cnt = 0; + word16 flags; + + if (pkcs7 == NULL) + return 0; + + flags = pkcs7->defaultSignedAttribs; + if (flags == WOLFSSL_NO_ATTRIBUTES) + return 0; + + if ((flags & WOLFSSL_CONTENT_TYPE_ATTRIBUTE) || flags == 0) + cnt++; +#ifndef NO_ASN_TIME + if ((flags & WOLFSSL_SIGNING_TIME_ATTRIBUTE) || flags == 0) + cnt++; +#endif + if ((flags & WOLFSSL_MESSAGE_DIGEST_ATTRIBUTE) || flags == 0) + cnt++; + + return cnt; +} + + /* builds up SignedData signed attributes, including default ones. * * pkcs7 - pointer to initialized PKCS7 structure * esd - pointer to initialized ESD structure, used for output * + * The number of default attributes emitted here must match + * wc_PKCS7_GetDefaultSignedAttribCount(), which sizes the working array; the + * runtime bound-check below turns any drift into BUFFER_E rather than overflow. + * * return 0 on success, negative on error */ static int wc_PKCS7_BuildSignedAttributes(wc_PKCS7* pkcs7, ESD* esd, const byte* contentType, word32 contentTypeSz, @@ -2315,6 +2357,13 @@ static int wc_PKCS7_BuildSignedAttributes(wc_PKCS7* pkcs7, ESD* esd, idx++; } + /* the working array is sized for the canned count by PKCS7_EncodeSigned() + * via wc_PKCS7_GetDefaultSignedAttribCount(); bound-check here so a + * future drift between the two can never overflow it */ + if (esd->signedAttribs == NULL || + atrIdx + idx > esd->signedAttribsCap) + return BUFFER_E; + esd->signedAttribsCount += idx; encAttribsSz = EncodeAttributes(&esd->signedAttribs[atrIdx], (int)idx, cannedAttribs, (int)idx); @@ -2329,9 +2378,13 @@ static int wc_PKCS7_BuildSignedAttributes(wc_PKCS7* pkcs7, ESD* esd, /* add custom signed attributes if set */ if (pkcs7->signedAttribsSz > 0 && pkcs7->signedAttribs != NULL) { - word32 availableSpace = MAX_SIGNED_ATTRIBS_SZ - atrIdx; + /* esd->signedAttribs was allocated to hold all attributes, but guard + * against writing past it in case the working array was undersized */ + word32 availableSpace = (esd->signedAttribsCap > atrIdx) ? + (esd->signedAttribsCap - atrIdx) : 0; - if (pkcs7->signedAttribsSz > availableSpace) + if (esd->signedAttribs == NULL || + pkcs7->signedAttribsSz > availableSpace) return BUFFER_E; esd->signedAttribsCount += pkcs7->signedAttribsSz; @@ -3054,7 +3107,10 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, byte signingTime[MAX_TIME_STRING_SZ]; - if (pkcs7 == NULL || pkcs7->hashOID == 0 || + /* hashOID is unused on the degenerate (certs-only) path, so only require + * it when an actual signer is present */ + if (pkcs7 == NULL || + (pkcs7->hashOID == 0 && pkcs7->sidType != DEGENERATE_SID) || outputSz == NULL) { WOLFSSL_MSG("PKCS7 struct / outputSz null, or hashOID is 0"); return BAD_FUNC_ARG; @@ -3065,9 +3121,12 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, } /* signature size varies with ECDSA; RSA-PSS signs digest directly like - * ECDSA. For both, content hash must be known to build ASN.1 before signing */ + * ECDSA. For both, content hash must be known to build ASN.1 before signing. + * The degenerate (certs-only) path has no signer, so this does not apply + * even though InitWithCert may have parsed an ECDSA/RSA-PSS cert and left + * publicKeyOID set. */ #if defined(HAVE_ECC) || defined(WC_RSA_PSS) - if (hashBuf == NULL && + if (pkcs7->sidType != DEGENERATE_SID && hashBuf == NULL && (pkcs7->publicKeyOID == ECDSAk #ifdef WC_RSA_PSS || pkcs7->publicKeyOID == RSAPSSk @@ -3260,6 +3319,53 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, } signerInfoSz += esd->digEncAlgoIdSz; + /* Point the working attribute array at the inline buffer, sized for the + * actual attribute count: the user-supplied attributes plus the exact + * number of CMS auto-defaults that will be emitted for this + * pkcs7->defaultSignedAttribs selection. Only fall back to a heap + * allocation when more attributes are needed than fit inline, so the + * common case stays allocation-free. */ + { + word32 defaultAttribCap = + wc_PKCS7_GetDefaultSignedAttribCount(pkcs7); + word32 neededCap = defaultAttribCap + pkcs7->signedAttribsSz; + + /* detect addition overflow */ + if (neededCap < pkcs7->signedAttribsSz) { + idx = BUFFER_E; + goto out; + } + + if (neededCap <= MAX_SIGNED_ATTRIBS_SZ) { + esd->signedAttribs = esd->signedAttribsInline; + esd->signedAttribsCap = MAX_SIGNED_ATTRIBS_SZ; + } + else { + #ifdef WOLFSSL_NO_MALLOC + /* cannot grow beyond the inline array without a heap */ + idx = BUFFER_E; + goto out; + #else + /* detect multiplication overflow */ + if (neededCap > ((word32)WC_MAX_SINT_OF(int) / + (word32)sizeof(EncodedAttrib))) { + idx = BUFFER_E; + goto out; + } + esd->signedAttribs = (EncodedAttrib*)XMALLOC( + neededCap * (word32)sizeof(EncodedAttrib), + pkcs7->heap, DYNAMIC_TYPE_PKCS7); + if (esd->signedAttribs == NULL) { + idx = MEMORY_E; + goto out; + } + esd->signedAttribsCap = neededCap; + #endif + } + XMEMSET(esd->signedAttribs, 0, + esd->signedAttribsCap * (word32)sizeof(EncodedAttrib)); + } + /* build up signed attributes, include contentType, signingTime, and messageDigest by default */ ret = wc_PKCS7_BuildSignedAttributes(pkcs7, esd, pkcs7->contentType, @@ -3688,6 +3794,20 @@ static int PKCS7_EncodeSigned(wc_PKCS7* pkcs7, XFREE(flatSignedAttribs, pkcs7->heap, DYNAMIC_TYPE_PKCS7); + /* free the working attribute array only if it was heap-allocated (i.e. it + * is not the inline buffer) before freeing esd. In small-stack builds esd + * is heap-allocated and may be NULL here. */ +#ifdef WOLFSSL_SMALL_STACK + if (esd != NULL) +#endif + { + if (esd->signedAttribs != NULL && + esd->signedAttribs != esd->signedAttribsInline) { + XFREE(esd->signedAttribs, pkcs7->heap, DYNAMIC_TYPE_PKCS7); + } + esd->signedAttribs = NULL; + } + WC_FREE_VAR_EX(esd, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); WC_FREE_VAR_EX(signedDataOid, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -3858,12 +3978,16 @@ int wc_PKCS7_EncodeSignedData(wc_PKCS7* pkcs7, byte* output, word32 outputSz) return BAD_FUNC_ARG; } - /* pre-calculate content hash for ECDSA and RSA-PSS (both sign digest directly) */ - if (pkcs7->publicKeyOID == ECDSAk + /* pre-calculate content hash for ECDSA and RSA-PSS (both sign digest + * directly). Skipped on the degenerate (certs-only) path: there is no + * signer to hash for, and hashOID is 0 so deriving a hash type would fail + * even though InitWithCert may have left publicKeyOID set to the cert's. */ + if (pkcs7->sidType != DEGENERATE_SID && + (pkcs7->publicKeyOID == ECDSAk #ifdef WC_RSA_PSS || pkcs7->publicKeyOID == RSAPSSk #endif - ) { + )) { int hashSz; enum wc_HashType hashType; byte hashBuf[WC_MAX_DIGEST_SIZE]; @@ -7021,6 +7145,19 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf, if (ret == 0 && MAX_PKCS7_CERTS > 0) { int sz = 0; int i; + /* Absolute end of the certificate set within pkiMsg2. + * idx is the start of the set, so the set spans + * [idx, idx + length). In non-streaming mode idx is the + * absolute offset into the message; in streaming mode it + * is typically 0 (the set was copied to a standalone + * buffer). Bounding the loop with the relative length + * alone stops short by idx bytes in non-streaming mode + * and can drop the last certificate. Clamp to pkiMsg2Sz + * to guard against overflow/over-long length (reads stay + * bounded by the certIdx + 1 < pkiMsg2Sz check below). */ + word32 certSetEnd = idx + (word32)length; + if (certSetEnd < idx || certSetEnd > pkiMsg2Sz) + certSetEnd = pkiMsg2Sz; pkcs7->cert[0] = cert; pkcs7->certSz[0] = (word32)certSz; @@ -7028,7 +7165,7 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf, for (i = 1; i < MAX_PKCS7_CERTS && certIdx + 1 < pkiMsg2Sz && - certIdx + 1 < (word32)length; i++) { + certIdx + 1 < certSetEnd; i++) { localIdx = certIdx; if (ret == 0 && GetASNTag(pkiMsg2, &certIdx, &tag, diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e95bba5ae5e..b3acd05d081 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -67438,6 +67438,395 @@ static wc_test_ret_t pkcs7signed_run_SingleShotVectors( } +#if !defined(NO_RSA) || defined(HAVE_ECC) +/* Exercise the degenerate (certs-only) encode path through the public API: a + * SignedData with DEGENERATE_SID and no signer (hashOID left 0). This covers + * the hashOID==0 relaxation in PKCS7_EncodeSigned(); the output round-trips + * through wc_PKCS7_VerifySignedData(), which repopulates pkcs7->cert[]. + * + * Called for both an RSA and an ECDSA cert: InitWithCert parses the cert and + * sets pkcs7->publicKeyOID, and the ECDSA case additionally covers the + * DEGENERATE_SID exclusion of the "pre-calculated content hash is needed" + * publicKeyOID==ECDSAk/RSAPSSk check (a degenerate bundle has no signer). */ +static wc_test_ret_t pkcs7_degenerate_encode_test(byte* cert, word32 certSz) +{ + wc_test_ret_t ret = 0; + wc_PKCS7* pkcs7 = NULL; + byte* out = NULL; + int encSz = 0; + const word32 outSz = FOURK_BUF * 2; + + out = (byte*)XMALLOC(outSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return WC_TEST_RET_ENC_ERRNO; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + + ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* degenerate: no signer, no eContent, hashOID deliberately left 0 */ + ret = wc_PKCS7_SetSignerIdentifierType(pkcs7, DEGENERATE_SID); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + pkcs7->detached = 1; + pkcs7->contentOID = DATA; + + encSz = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz); + if (encSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(encSz), out); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* round-trip: degenerate verify repopulates the certificate */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + wc_PKCS7_AllowDegenerate(pkcs7, 1); + ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + if (pkcs7->certSz[0] != certSz || + XMEMCMP(pkcs7->cert[0], cert, certSz) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + ret = 0; + +out: + if (pkcs7 != NULL) + wc_PKCS7_Free(pkcs7); + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* !NO_RSA || HAVE_ECC */ + + +#if !defined(NO_RSA) && !defined(NO_SHA256) +/* The nine-attribute case below needs more slots than the inline array holds + * (MAX_SIGNED_ATTRIBS_SZ), which requires a heap allocation; skip it on + * no-malloc builds unless the inline array was enlarged at build time. */ +#if !defined(WOLFSSL_NO_MALLOC) || (MAX_SIGNED_ATTRIBS_SZ >= 9) +/* Test that a SignedData carrying six user signed attributes plus the three + * CMS auto-defaults (nine total) encodes and verifies. This exceeds the + * default inline capacity (MAX_SIGNED_ATTRIBS_SZ == 7). */ +static wc_test_ret_t pkcs7_signed_attribs_count_test(byte* cert, word32 certSz, + byte* key, word32 keySz) +{ + wc_test_ret_t ret = 0; + wc_PKCS7* pkcs7 = NULL; + WC_RNG rng; + int rngInit = 0; + byte* out = NULL; + int encSz = 0; + const word32 outSz = FOURK_BUF * 2; + int i; + byte content[] = "signed attribs count test"; + + /* six distinct user attribute OIDs (arbitrary, well-formed OID TLVs) and + * PrintableString values */ + static const byte oid0[] = { 0x06,0x03, 0x55,0x04,0x03 }; + static const byte oid1[] = { 0x06,0x03, 0x55,0x04,0x04 }; + static const byte oid2[] = { 0x06,0x03, 0x55,0x04,0x05 }; + static const byte oid3[] = { 0x06,0x03, 0x55,0x04,0x06 }; + static const byte oid4[] = { 0x06,0x03, 0x55,0x04,0x07 }; + static const byte oid5[] = { 0x06,0x03, 0x55,0x04,0x08 }; + static const byte val0[] = { 0x13,0x01, 0x30 }; + static const byte val1[] = { 0x13,0x01, 0x31 }; + static const byte val2[] = { 0x13,0x01, 0x32 }; + static const byte val3[] = { 0x13,0x01, 0x33 }; + static const byte val4[] = { 0x13,0x01, 0x34 }; + static const byte val5[] = { 0x13,0x01, 0x35 }; + + PKCS7Attrib attribs[6] = { + { oid0, sizeof(oid0), val0, sizeof(val0) }, + { oid1, sizeof(oid1), val1, sizeof(val1) }, + { oid2, sizeof(oid2), val2, sizeof(val2) }, + { oid3, sizeof(oid3), val3, sizeof(val3) }, + { oid4, sizeof(oid4), val4, sizeof(val4) }, + { oid5, sizeof(oid5), val5, sizeof(val5) } + }; + + out = (byte*)XMALLOC(outSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return WC_TEST_RET_ENC_ERRNO; + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + rngInit = 1; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + + ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + pkcs7->rng = &rng; + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->hashOID = SHA256h; + pkcs7->encryptOID = RSAk; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->signedAttribs = attribs; + pkcs7->signedAttribsSz = (word32)(sizeof(attribs) / sizeof(attribs[0])); + + encSz = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz); + if (encSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(encSz), out); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* verify */ + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* all six user attributes should be present in the decoded list. + * wc_PKCS7_GetAttributeValue() matches on the OID content (no tag/len), + * so skip the 2-byte OID TLV header. */ + for (i = 0; i < 6; i++) { + word32 vSz = 0; + ret = wc_PKCS7_GetAttributeValue(pkcs7, attribs[i].oid + 2, + attribs[i].oidSz - 2, NULL, &vSz); + if (ret != WC_NO_ERR_TRACE(LENGTH_ONLY_E) || vSz == 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + } + + ret = 0; + +out: + if (pkcs7 != NULL) + wc_PKCS7_Free(pkcs7); + if (rngInit) + wc_FreeRng(&rng); + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* !WOLFSSL_NO_MALLOC || MAX_SIGNED_ATTRIBS_SZ >= 9 */ + +/* Test that decoded signed-attribute values follow the documented, stable + * shape (the contents of the SET OF AttributeValue, outer SET tag stripped) + * for both PrintableString and OCTET STRING value types, as fetched via + * wc_PKCS7_GetAttributeValue(). */ +static wc_test_ret_t pkcs7_decoded_attrib_shape_test(byte* cert, word32 certSz, + byte* key, word32 keySz) +{ + wc_test_ret_t ret = 0; + wc_PKCS7* pkcs7 = NULL; + WC_RNG rng; + int rngInit = 0; + byte* out = NULL; + int encSz = 0; + const word32 outSz = FOURK_BUF * 2; + byte getBuf[32]; + word32 getBufSz; + byte content[] = "decoded attrib shape test"; + + /* one PrintableString-valued and one OCTET STRING-valued attribute */ + static const byte oidPS[] = { 0x06,0x03, 0x55,0x04,0x0A }; + static const byte oidOS[] = { 0x06,0x03, 0x55,0x04,0x0B }; + static const byte valPS[] = { 0x13,0x03, 0x61,0x62,0x63 }; /* "abc" */ + static const byte valOS[] = { 0x04,0x04, 0xDE,0xAD,0xBE,0xEF }; + + PKCS7Attrib attribs[2] = { + { oidPS, sizeof(oidPS), valPS, sizeof(valPS) }, + { oidOS, sizeof(oidOS), valOS, sizeof(valOS) } + }; + + out = (byte*)XMALLOC(outSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (out == NULL) + return WC_TEST_RET_ENC_ERRNO; + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + rngInit = 1; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + + ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + pkcs7->rng = &rng; + pkcs7->content = content; + pkcs7->contentSz = (word32)XSTRLEN((char*)content); + pkcs7->contentOID = DATA; + pkcs7->hashOID = SHA256h; + pkcs7->encryptOID = RSAk; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->signedAttribs = attribs; + pkcs7->signedAttribsSz = 2; + + encSz = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz); + if (encSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(encSz), out); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* PrintableString attribute: decoded value is the SET contents, i.e. the + * AttributeValue TLV (0x13 ...), NOT the outer SET (0x31 ...). Lookups + * match on the OID content, so skip the 2-byte OID TLV header. */ + getBufSz = sizeof(getBuf); + /* return value is the value length; *outSz is only set on the size probe */ + ret = wc_PKCS7_GetAttributeValue(pkcs7, oidPS + 2, sizeof(oidPS) - 2, + getBuf, &getBufSz); + if (ret != (int)sizeof(valPS) || XMEMCMP(getBuf, valPS, sizeof(valPS)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (getBuf[0] != 0x13) /* value tag, not 0x31 SET */ + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + /* OCTET STRING attribute */ + getBufSz = sizeof(getBuf); + ret = wc_PKCS7_GetAttributeValue(pkcs7, oidOS + 2, sizeof(oidOS) - 2, + getBuf, &getBufSz); + if (ret != (int)sizeof(valOS) || XMEMCMP(getBuf, valOS, sizeof(valOS)) != 0) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + if (getBuf[0] != 0x04) /* value tag, not 0x31 SET */ + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + ret = 0; + +out: + if (pkcs7 != NULL) + wc_PKCS7_Free(pkcs7); + if (rngInit) + wc_FreeRng(&rng); + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +/* Regression test for the multi-certificate decode bound. A SignedData whose + * eContent is larger than the certificate set pushes the cert set to a large + * offset within the message; the decoder must still parse every certificate. + * + * IMPORTANT - build dependency: this only guards the bound fix under + * NO_PKCS7_STREAM. In the default streaming build wc_PKCS7_VerifySignedData() + * copies the cert set into a standalone buffer and resets idx to 0, so + * certSetEnd == length and both the pre-fix and post-fix code parse every + * certificate - the test passes either way and is just a general multi-cert + * round-trip sanity check. Only a NO_PKCS7_STREAM build actually exercises + * the off-by-idx bound this fix corrects. */ +static wc_test_ret_t pkcs7_signed_multi_cert_test( + byte* cert1, word32 cert1Sz, byte* key, word32 keySz, + byte* cert2, word32 cert2Sz, byte* cert3, word32 cert3Sz) +{ +#if MAX_PKCS7_CERTS < 3 + /* needs at least three certificate slots to exercise the bound */ + (void)cert1; (void)cert1Sz; (void)key; (void)keySz; + (void)cert2; (void)cert2Sz; (void)cert3; (void)cert3Sz; + return 0; +#else + wc_test_ret_t ret = 0; + wc_PKCS7* pkcs7 = NULL; + WC_RNG rng; + int rngInit = 0; + byte* out = NULL; + byte* content = NULL; + int encSz = 0; + const word32 outSz = FOURK_BUF * 4; + const word32 contentSz = FOURK_BUF; /* larger than the certificate set */ + int found1 = 0, found2 = 0, found3 = 0, j; + + content = (byte*)XMALLOC(contentSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + out = (byte*)XMALLOC(outSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (content == NULL || out == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + XMEMSET(content, 0xA5, contentSz); + + ret = wc_InitRng_ex(&rng, HEAP_HINT, devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + rngInit = 1; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + + ret = wc_PKCS7_InitWithCert(pkcs7, cert1, cert1Sz); + if (ret == 0) + ret = wc_PKCS7_AddCertificate(pkcs7, cert2, cert2Sz); + if (ret == 0) + ret = wc_PKCS7_AddCertificate(pkcs7, cert3, cert3Sz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + pkcs7->rng = &rng; + pkcs7->content = content; + pkcs7->contentSz = contentSz; + pkcs7->contentOID = DATA; + pkcs7->hashOID = SHA256h; + pkcs7->encryptOID = RSAk; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + + encSz = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz); + if (encSz <= 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(encSz), out); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + pkcs7 = wc_PKCS7_New(HEAP_HINT, devId); + if (pkcs7 == NULL) + ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out); + ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encSz); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out); + + /* all three certificates must be decoded (SET OF is unordered) */ + for (j = 0; j < MAX_PKCS7_CERTS; j++) { + if (pkcs7->certSz[j] == cert1Sz && + XMEMCMP(pkcs7->cert[j], cert1, cert1Sz) == 0) + found1 = 1; + if (pkcs7->certSz[j] == cert2Sz && + XMEMCMP(pkcs7->cert[j], cert2, cert2Sz) == 0) + found2 = 1; + if (pkcs7->certSz[j] == cert3Sz && + XMEMCMP(pkcs7->cert[j], cert3, cert3Sz) == 0) + found3 = 1; + } + if (!found1 || !found2 || !found3) + ERROR_OUT(WC_TEST_RET_ENC_NC, out); + + ret = 0; + +out: + if (pkcs7 != NULL) + wc_PKCS7_Free(pkcs7); + if (rngInit) + wc_FreeRng(&rng); + XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(content, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +#endif /* MAX_PKCS7_CERTS < 3 */ +} +#endif /* !NO_RSA && !NO_SHA256 */ + + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7signed_test(void) { wc_test_ret_t ret = 0; @@ -67568,6 +67957,46 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7signed_test(void) rsaClientPrivKeyBuf, (word32)rsaClientPrivKeyBufSz); #endif +#ifndef NO_RSA + /* degenerate (certs-only) encode via the public API */ + if (ret >= 0) + ret = pkcs7_degenerate_encode_test( + rsaClientCertBuf, (word32)rsaClientCertBufSz); +#endif +#ifdef HAVE_ECC + /* same path with an ECDSA cert: covers the DEGENERATE_SID exclusion of the + * ECDSAk/RSAPSSk pre-hash check that InitWithCert would otherwise trip */ + if (ret >= 0) + ret = pkcs7_degenerate_encode_test( + eccClientCertBuf, (word32)eccClientCertBufSz); +#endif + +#if !defined(NO_RSA) && !defined(NO_SHA256) +#if !defined(WOLFSSL_NO_MALLOC) || (MAX_SIGNED_ATTRIBS_SZ >= 9) + /* signed-attribute count beyond the inline capacity */ + if (ret >= 0) + ret = pkcs7_signed_attribs_count_test( + rsaClientCertBuf, (word32)rsaClientCertBufSz, + rsaClientPrivKeyBuf, (word32)rsaClientPrivKeyBufSz); +#endif + + /* decoded signed-attribute value shape */ + if (ret >= 0) + ret = pkcs7_decoded_attrib_shape_test( + rsaClientCertBuf, (word32)rsaClientCertBufSz, + rsaClientPrivKeyBuf, (word32)rsaClientPrivKeyBufSz); + + /* multi-certificate decode with large content (cert-set bound). NOTE: only + * validates the bound fix under NO_PKCS7_STREAM; in streaming builds it is a + * general multi-cert round-trip check (see the function comment). */ + if (ret >= 0) + ret = pkcs7_signed_multi_cert_test( + rsaClientCertBuf, (word32)rsaClientCertBufSz, + rsaClientPrivKeyBuf, (word32)rsaClientPrivKeyBufSz, + rsaServerCertBuf, (word32)rsaServerCertBufSz, + rsaCaCertBuf, (word32)rsaCaCertBufSz); +#endif + XFREE(rsaClientCertBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaClientPrivKeyBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); XFREE(rsaServerCertBuf, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); diff --git a/wolfssl/wolfcrypt/pkcs7.h b/wolfssl/wolfcrypt/pkcs7.h index 93c9c6a5d79..43d305597d7 100644 --- a/wolfssl/wolfcrypt/pkcs7.h +++ b/wolfssl/wolfcrypt/pkcs7.h @@ -63,6 +63,15 @@ #define MAX_ORI_VALUE_SZ 512 #endif +/* Bound on the number of signed attributes the encoder can place in a + * SignedData SignerInfo without a heap allocation. At encode time the working + * attribute array is sized to the actual attribute count (user-supplied + * attributes plus up to three CMS auto-defaults: contentType, messageDigest, + * signingTime); counts up to this bound use an inline buffer, and only larger + * counts fall back to a heap allocation (or fail under WOLFSSL_NO_MALLOC). + * The macro therefore no longer caps the attribute count, but it does set the + * size of that inline working buffer (in the transient encode-time ESD + * structure, not in wc_PKCS7), so raising it increases that buffer. */ #ifndef MAX_SIGNED_ATTRIBS_SZ #define MAX_SIGNED_ATTRIBS_SZ 7 #endif @@ -175,6 +184,22 @@ typedef struct PKCS7Attrib { } PKCS7Attrib; +/* A single decoded signed/unsigned attribute, as produced when verifying a + * SignedData. The fields follow a stable, guaranteed shape: + * + * oid, oidSz: + * The attribute OID, encoded as (the full OBJECT + * IDENTIFIER TLV). + * + * value, valueSz: + * The contents of the SET OF AttributeValue, i.e. the bytes inside the + * attribute's SET with the outer SET (tag 0x31) and its length removed. + * A CMS attribute is a SET OF AttributeValue and may legitimately carry + * more than one value, so this is the concatenation of all AttributeValue + * TLVs. For the common single-valued case it is exactly one AttributeValue + * encoded as (for example 0x13 PrintableString or + * 0x04 OCTET STRING). + */ typedef struct PKCS7DecodedAttrib { struct PKCS7DecodedAttrib* next; byte* oid;