diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index e2e268a7d7..f796acd2a2 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -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; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 0bf7595b06..9e026fc5b4 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -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);