Merge pull request #10804 from Frauschi/pkcs7_no_eContent

PKCS#7: support SignedData with absent eContent
This commit is contained in:
Daniel Pouzzner
2026-07-03 01:14:05 -05:00
committed by GitHub
2 changed files with 176 additions and 10 deletions
+38 -10
View File
@@ -3864,11 +3864,17 @@ 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) */
/* pre-calculate content hash for ECDSA and RSA-PSS (both sign digest
* directly).
* A detached SignedData over empty content has no eContent to drive the
* normal content-hashing pass, so its messageDigest signed attribute is
* computed here as the hash of the empty content (e.g. an SCEP CertRep
* PENDING/FAILURE per RFC 8894) */
if (pkcs7->publicKeyOID == ECDSAk
#ifdef WC_RSA_PSS
|| pkcs7->publicKeyOID == RSAPSSk
#endif
|| (pkcs7->detached && pkcs7->contentSz == 0)
) {
int hashSz;
enum wc_HashType hashType;
@@ -3890,7 +3896,9 @@ int wc_PKCS7_EncodeSignedData(wc_PKCS7* pkcs7, byte* output, word32 outputSz)
ret = wc_HashInit(hash, hashType);
if (ret == 0) {
ret = wc_HashUpdate(hash, hashType,
pkcs7->content, pkcs7->contentSz);
(pkcs7->content != NULL) ? pkcs7->content
: (const byte*)"",
(pkcs7->content != NULL) ? pkcs7->contentSz : 0);
if (ret == 0) {
ret = wc_HashFinal(hash, hashType, hashBuf);
}
@@ -5071,11 +5079,10 @@ static int wc_PKCS7_VerifyContentMessageDigest(wc_PKCS7* pkcs7,
if (pkcs7 == NULL)
return BAD_FUNC_ARG;
if ((pkcs7->content == NULL || pkcs7->contentSz == 0) &&
(hashBuf == NULL || hashSz == 0)) {
WOLFSSL_MSG("SignedData bundle has no content or hash to verify");
return BAD_FUNC_ARG;
}
/* An absent eContent with no caller-supplied hash is valid: the content
* is empty, so the messageDigest attribute is verified against the hash of
* zero-length content below (e.g. an SCEP CertRep PENDING/FAILURE per
* RFC 8894). Stripping a non-empty eContent still fails this comparison. */
/* lookup messageDigest attribute */
attrib = findAttrib(pkcs7, mdOid, sizeof(mdOid));
@@ -5112,7 +5119,14 @@ static int wc_PKCS7_VerifyContentMessageDigest(wc_PKCS7* pkcs7,
content = pkcs7->content;
contentLen = (int)pkcs7->contentSz;
if (pkcs7->contentIsPkcs7Type == 1) {
/* An absent eContent is hashed as empty content. Guard against a
* NULL content pointer paired with a non-zero size: hash zero-length
* content rather than dereferencing NULL or reading past the empty
* literal used at the wc_Hash call below. */
if (content == NULL)
contentLen = 0;
if (content != NULL && pkcs7->contentIsPkcs7Type == 1) {
/* Content follows PKCS#7 RFC, which defines type as ANY. CMS
* mandates OCTET_STRING which has already been stripped off.
* For PKCS#7 message digest calculation, digest is calculated
@@ -5130,8 +5144,10 @@ static int wc_PKCS7_VerifyContentMessageDigest(wc_PKCS7* pkcs7,
}
}
ret = wc_Hash(hashType, content + contentIdx, (word32)contentLen, digest,
MAX_PKCS7_DIGEST_SZ);
ret = wc_Hash(hashType,
(content != NULL) ? content + contentIdx
: (const byte*)"",
(word32)contentLen, digest, MAX_PKCS7_DIGEST_SZ);
if (ret < 0) {
WOLFSSL_MSG("Error hashing PKCS7 content for verification");
WC_FREE_VAR_EX(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER);
@@ -6030,6 +6046,7 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
word32 certIdx, certIdx2;
byte degenerate = 0;
byte detached = 0;
byte noContent = 0;
byte tag = 0;
word16 contentIsPkcs7Type = 0;
#ifdef ASN_BER_TO_DER
@@ -6359,6 +6376,7 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
if ((encapContentInfoLen != 0) &&
((word32)encapContentInfoLen - contentTypeSz == 0)) {
ret = ASN_PARSE_E;
noContent = 1;
#ifndef NO_PKCS7_STREAM
pkcs7->stream->noContent = 1;
#endif
@@ -6542,6 +6560,16 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf,
detached = 1;
}
/* eContent genuinely absent and no external content/hash
* supplied: verify as a detached signature over empty content.
* The messageDigest signed attribute is checked against the
* hash of zero-length content downstream, so a stripped
* non-empty eContent still fails. */
if (!degenerate && !detached && noContent) {
WOLFSSL_MSG("Processing as detached signature, no content");
detached = 1;
}
if (!degenerate && !detached && ret != 0)
break;
+138
View File
@@ -67613,6 +67613,136 @@ static wc_test_ret_t pkcs7signed_run_SingleShotVectors(
}
#if !defined(NO_RSA) && !defined(NO_SHA256)
/* Round-trip test of a CMS SignedData whose encapContentInfo carries no
* eContent: a signed-attributes-only signature over empty content, as used by
* SCEP CertRep PENDING/FAILURE (RFC 8894 section 3.2.2). The encoder must omit
* the eContent and compute the messageDigest over the empty content; the
* verifier must accept the absent eContent and check that digest without any
* caller-supplied content or hash. */
static wc_test_ret_t pkcs7_signed_no_content_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;
static const byte content[] = "non-empty content that gets stripped";
out = (byte*)XMALLOC(outSz, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
if (out == NULL)
ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl);
ret = wc_InitRng_ex(&rng, HEAP_HINT, devId);
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl);
rngInit = 1;
pkcs7 = wc_PKCS7_New(HEAP_HINT, devId);
if (pkcs7 == NULL)
ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl);
ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz);
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl);
pkcs7->rng = &rng;
pkcs7->content = NULL; /* no eContent */
pkcs7->contentSz = 0;
pkcs7->contentOID = DATA;
pkcs7->hashOID = SHA256h;
pkcs7->encryptOID = RSAk;
pkcs7->privateKey = key;
pkcs7->privateKeySz = keySz;
/* detached signature with empty content -> absent eContent on the wire */
ret = wc_PKCS7_SetDetached(pkcs7, 1);
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl);
encSz = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz);
if (encSz <= 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(encSz), out_lbl);
wc_PKCS7_Free(pkcs7);
pkcs7 = NULL;
/* Verify with no caller-supplied content or hash. */
pkcs7 = wc_PKCS7_New(HEAP_HINT, devId);
if (pkcs7 == NULL)
ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl);
ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encSz);
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl);
/* the eContent must be reported absent after decode */
if (pkcs7->content != NULL || pkcs7->contentSz != 0)
ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl);
wc_PKCS7_Free(pkcs7);
pkcs7 = NULL;
/* Negative case: a signature made over non-empty content but transmitted
* with the eContent absent (as if stripped) must be rejected. The
* messageDigest signed attribute covers the real content, so a verifier
* that treats the absent eContent as empty content gets a digest mismatch
* (SIG_VERIFY_E). This locks in the documented security property that a
* stripped non-empty eContent still fails verification. */
pkcs7 = wc_PKCS7_New(HEAP_HINT, devId);
if (pkcs7 == NULL)
ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl);
ret = wc_PKCS7_InitWithCert(pkcs7, cert, certSz);
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl);
pkcs7->rng = &rng;
pkcs7->content = (byte*)content;
pkcs7->contentSz = (word32)XSTRLEN((const char*)content);
pkcs7->contentOID = DATA;
pkcs7->hashOID = SHA256h;
pkcs7->encryptOID = RSAk;
pkcs7->privateKey = key;
pkcs7->privateKeySz = keySz;
/* detached over non-empty content -> eContent absent on the wire, while the
* messageDigest attribute still covers the real content */
ret = wc_PKCS7_SetDetached(pkcs7, 1);
if (ret != 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(ret), out_lbl);
encSz = wc_PKCS7_EncodeSignedData(pkcs7, out, outSz);
if (encSz <= 0)
ERROR_OUT(WC_TEST_RET_ENC_EC(encSz), out_lbl);
wc_PKCS7_Free(pkcs7);
pkcs7 = NULL;
/* Verify without supplying the detached content: the absent eContent is
* hashed as empty content, which must not match the messageDigest computed
* over the real content. */
pkcs7 = wc_PKCS7_New(HEAP_HINT, devId);
if (pkcs7 == NULL)
ERROR_OUT(WC_TEST_RET_ENC_ERRNO, out_lbl);
ret = wc_PKCS7_VerifySignedData(pkcs7, out, (word32)encSz);
if (ret != SIG_VERIFY_E)
ERROR_OUT(WC_TEST_RET_ENC_NC, out_lbl);
ret = 0;
out_lbl:
if (pkcs7 != NULL)
wc_PKCS7_Free(pkcs7);
if (rngInit)
wc_FreeRng(&rng);
XFREE(out, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
return ret;
}
#endif /* !NO_RSA && !NO_SHA256 */
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7signed_test(void)
{
wc_test_ret_t ret = 0;
@@ -67743,6 +67873,14 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t pkcs7signed_test(void)
rsaClientPrivKeyBuf, (word32)rsaClientPrivKeyBufSz);
#endif
#if !defined(NO_RSA) && !defined(NO_SHA256)
/* SignedData with absent eContent (detached over empty content) */
if (ret >= 0)
ret = pkcs7_signed_no_content_test(
rsaClientCertBuf, (word32)rsaClientCertBufSz,
rsaClientPrivKeyBuf, (word32)rsaClientPrivKeyBufSz);
#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);