mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 10:40:52 +02:00
Merge pull request #10804 from Frauschi/pkcs7_no_eContent
PKCS#7: support SignedData with absent eContent
This commit is contained in:
+38
-10
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user