From 994cf9c4c9f9b3f7fbd3cfc0bac0c005aa876607 Mon Sep 17 00:00:00 2001 From: night1rider Date: Wed, 4 Nov 2026 00:52:17 -0700 Subject: [PATCH] PKCS7: reject ANY-typed eContent with trailing bytes after first element A legacy PKCS#7 ANY-typed eContent (non-OCTET STRING, e.g. a SEQUENCE) must be a single DER element spanning the whole content; a message with unsigned bytes appended after that element is now rejected with ASN_PARSE_E instead of verifying and returning the appended bytes as content. contentIsPkcs7Type is reset per verify so a reused object parses each message afresh. --- doc/dox_comments/header_files/pkcs7.h | 18 +- doc/dox_comments/header_files/tsp.h | 8 +- tests/api/test_pkcs7.c | 821 ++++++++++++++++++++++ tests/api/test_pkcs7.h | 2 + tests/api/test_tsp.c | 114 +++ tests/api/test_tsp.h | 2 + tests/unit-mcdc/test_pkcs7_arg_whitebox.c | 113 ++- tests/unit-mcdc/test_pkcs7_whitebox.c | 52 +- wolfcrypt/src/pkcs7.c | 28 + wolfcrypt/src/tsp.c | 5 +- 10 files changed, 1126 insertions(+), 37 deletions(-) diff --git a/doc/dox_comments/header_files/pkcs7.h b/doc/dox_comments/header_files/pkcs7.h index 09ac05672ac..f18c3f75e57 100644 --- a/doc/dox_comments/header_files/pkcs7.h +++ b/doc/dox_comments/header_files/pkcs7.h @@ -327,7 +327,20 @@ int wc_PKCS7_EncodeSignedData_ex(wc_PKCS7* pkcs7, const byte* hashBuf, \brief This function takes in a transmitted PKCS7 signed data message, extracts the certificate list and certificate revocation list, and then verifies the signature. It stores the extracted content in the given - PKCS7 structure. + PKCS7 structure. For legacy PKCS#7 ANY-typed eContent (a non-OCTET STRING + element, e.g. a SEQUENCE as RFC 2315 permits), pkcs7->content is the whole + DER element (so contentSz includes the tag and length octets), whereas a + CMS OCTET STRING eContent reports the value only. The element shape is + enforced only when the signer uses signed attributes: in that case it must + be a single DER element spanning the whole eContent, and an element shorter + than two octets, trailing bytes after it, an indefinite length, a + non-minimal or out-of-range length, the end-of-contents identifier (0x00), + or a high-tag-number identifier are + rejected with ASN_PARSE_E; with no signed attributes no shape check runs + and the signature covers the eContent region as received. pkcs7->content + is valid only until the next verify on this structure or wc_PKCS7_Free(); + when the message is verified from a single input buffer it may point into + that buffer, so it is also invalid once the input buffer is freed or reused. \return 0 Returned on successfully extracting the information from the message @@ -404,7 +417,8 @@ int wc_PKCS7_VerifySignedData(wc_PKCS7* pkcs7, \brief This function takes in a transmitted PKCS7 signed data message as hash/header/footer, then extracts the certificate list and certificate revocation list, and then verifies the signature. It stores the extracted - content in the given PKCS7 structure. + content in the given PKCS7 structure, in the same form as + wc_PKCS7_VerifySignedData() reports it. \return 0 Returned on successfully extracting the information from the message diff --git a/doc/dox_comments/header_files/tsp.h b/doc/dox_comments/header_files/tsp.h index e4292d0e693..64724558ffa 100644 --- a/doc/dox_comments/header_files/tsp.h +++ b/doc/dox_comments/header_files/tsp.h @@ -1621,7 +1621,13 @@ int wc_TspTstInfo_SignWithPkcs7(const TspTstInfo* tstInfo, wc_PKCS7* pkcs7, signer's certificate. \return HASH_TYPE_E Returned when a hash algorithm is not available or below WC_TSP_MIN_HASH_STRENGTH_BITS. - \return ASN_PARSE_E Returned when an encoding is invalid. + \return ASN_PARSE_E Returned when an encoding is invalid, including a + legacy ANY eContent that is not a single DER element spanning the whole + content (an element shorter than two octets, trailing bytes, a non-minimal, + out-of-range or indefinite length, the end-of-contents identifier (0x00), + or a high-tag-number identifier). A + well-formed ANY eContent that is not a TSTInfo SEQUENCE is also rejected + with ASN_PARSE_E when the content is decoded as a TSTInfo. \return MEMORY_E Returned on dynamic memory allocation failure. \param [in] pkcs7 Pointer to an initialized PKCS7 object. diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index dee8e5c67c2..63bf1b8b317 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -8181,6 +8181,827 @@ int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void) return EXPECT_RESULT(); } +/* + * Legacy PKCS#7 "ANY" eContent form: eContent begins with a SEQUENCE rather + * than an OCTET STRING (as RFC 2315 allows and Authenticode uses). The ANY + * eContent is a single element that must span the whole content. A message + * whose first element spans the content verifies, and the whole element + * (tag, length and value) is reported to the caller as the content; one whose + * first element stops short of the content (leaving trailing, undigested + * bytes) is rejected during content verification. + * + * The message is produced at run time: a normal CMS SignedData (OCTET STRING + * eContent) is signed with the test RSA credentials, then its eContent inner + * tag is changed from OCTET STRING (0x04) to SEQUENCE (0x30) in place to reach + * the ANY path. For the negative case that element's length is also shortened + * so it no longer spans the content. + */ +int test_wc_PKCS7_VerifySignedData_eContentAnySpan(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && !defined(NO_RSA) && !defined(NO_SHA256) && \ + (defined(USE_CERT_BUFFERS_2048) || defined(USE_CERT_BUFFERS_1024)) + PKCS7* pkcs7 = NULL; + WC_RNG rng; + byte output[FOURK_BUF]; + word32 outputSz = (word32)sizeof(output); + byte any[FOURK_BUF]; + int anySz = 0; + int i; + int octetOff = -1; + const char contentStr[] = "pkcs7 econtent test"; + word32 contentSz = (word32)XSTRLEN(contentStr); + + /* Crafted PKCS#7 SignedData with signed attributes whose ANY-typed + * eContent is a single malformed element; the span check rejects it + * before any signature or digest work, so the signature need not be + * valid. onebyteEContent: a one-octet [0] value (no length octet). + * indefEContent: a SEQUENCE using the indefinite-length form (0x80). */ + static const byte onebyteEContent[] = { + 0x30, 0x82, 0x02, 0x86, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x02, 0xa0, 0x82, 0x02, 0x77, 0x30, 0x82, 0x02, 0x73, 0x02, + 0x01, 0x01, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0e, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x01, 0x00, 0x31, 0x82, 0x02, + 0x4d, 0x30, 0x82, 0x02, 0x49, 0x02, 0x01, 0x01, 0x30, 0x81, 0xb8, 0x30, + 0x81, 0x9f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, + 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x07, 0x4d, 0x6f, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x31, 0x10, 0x30, + 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, 0x42, 0x6f, 0x7a, 0x65, + 0x6d, 0x61, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, + 0x0c, 0x0c, 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x5f, 0x32, 0x30, + 0x34, 0x38, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, + 0x10, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, 0x67, + 0x2d, 0x32, 0x30, 0x34, 0x38, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x0c, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x6f, 0x6c, 0x66, + 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x11, + 0x66, 0x61, 0x63, 0x74, 0x73, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, 0x73, + 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x02, 0x14, 0x75, 0x90, 0x2e, 0xa0, 0xc3, + 0xe1, 0x96, 0xe1, 0x92, 0x48, 0xb7, 0xa5, 0xc5, 0x83, 0xa1, 0xc9, 0xa7, + 0x3e, 0xe9, 0x5f, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x02, 0x01, 0xa0, 0x69, 0x30, 0x18, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03, 0x31, 0x0b, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0x30, 0x1c, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x05, 0x31, 0x0f, 0x17, + 0x0d, 0x32, 0x36, 0x31, 0x31, 0x30, 0x33, 0x30, 0x31, 0x30, 0x34, 0x31, + 0x38, 0x5a, 0x30, 0x2f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x09, 0x04, 0x31, 0x22, 0x04, 0x20, 0x14, 0x75, 0x34, 0x9f, 0x89, + 0xdc, 0xdf, 0x00, 0x02, 0x20, 0xbe, 0xea, 0xfa, 0x17, 0xde, 0x82, 0x4d, + 0x0f, 0x79, 0x20, 0xe9, 0x6e, 0x35, 0x41, 0x2b, 0x6f, 0xf1, 0x15, 0x93, + 0xa9, 0x86, 0xd9, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x00, 0x3d, 0x13, + 0x76, 0x29, 0xa1, 0xd7, 0xf7, 0xb4, 0xad, 0x5e, 0xf6, 0xad, 0x6a, 0xbb, + 0x0a, 0x47, 0x84, 0xd7, 0x9c, 0x8e, 0xb3, 0xf1, 0x61, 0xe5, 0xaf, 0x6b, + 0x26, 0x9a, 0xb2, 0x40, 0x8f, 0xf2, 0xe6, 0x81, 0x01, 0xe3, 0x58, 0xbc, + 0xe1, 0x1d, 0xeb, 0xf6, 0x2d, 0x95, 0xf9, 0x4a, 0xe3, 0x57, 0x43, 0xba, + 0x86, 0xa1, 0x13, 0xef, 0xa7, 0xad, 0x7e, 0xfe, 0x6f, 0xb9, 0xde, 0x04, + 0x05, 0xe1, 0x57, 0x59, 0x8a, 0x72, 0x73, 0x2f, 0x14, 0xbb, 0x59, 0xe3, + 0xc1, 0xb6, 0xe2, 0x7c, 0x20, 0xb6, 0x4a, 0x92, 0xad, 0x27, 0xa3, 0x7f, + 0xcb, 0x53, 0x02, 0xc5, 0x24, 0x77, 0x49, 0x86, 0xfa, 0xf4, 0x69, 0xb3, + 0x21, 0x3c, 0x1e, 0x11, 0xfd, 0x6a, 0x58, 0x3b, 0xb6, 0x50, 0xf2, 0xbe, + 0x94, 0x25, 0x9a, 0x56, 0x43, 0x28, 0x7f, 0xc9, 0xb7, 0xdc, 0x58, 0xde, + 0xd2, 0xed, 0xc6, 0xe2, 0x14, 0xa8, 0x68, 0xe2, 0x59, 0xdb, 0x02, 0x57, + 0xe1, 0x96, 0xcb, 0x1e, 0x7f, 0x71, 0x61, 0xd3, 0xa3, 0xc5, 0x5b, 0x71, + 0xc0, 0x5f, 0xc6, 0x69, 0x4d, 0x3b, 0x0d, 0xe0, 0xde, 0xbb, 0xe0, 0xee, + 0xa5, 0xf1, 0xed, 0x20, 0xfc, 0x5d, 0x98, 0x85, 0xa1, 0xb9, 0x0b, 0x93, + 0x6d, 0xe1, 0x14, 0x61, 0x6a, 0x11, 0x37, 0x27, 0x65, 0xcb, 0xd2, 0x13, + 0x91, 0x35, 0x7c, 0x8f, 0x98, 0xe4, 0x0b, 0xf7, 0x5b, 0xfb, 0x1b, 0x48, + 0x94, 0x53, 0x97, 0x24, 0x6a, 0xf6, 0x8e, 0x77, 0xf3, 0xe7, 0x71, 0x54, + 0xae, 0x0f, 0xfe, 0xfb, 0x78, 0x6f, 0x12, 0x31, 0x56, 0xeb, 0x47, 0x90, + 0x45, 0x34, 0xfb, 0xab, 0x4a, 0x0f, 0xf9, 0x03, 0x49, 0x3c, 0x1b, 0x0d, + 0x94, 0x9c, 0x24, 0x84, 0x7c, 0xd9, 0x34, 0x67, 0xee, 0xad, 0x72, 0xea, + 0x65, 0xdf, 0xde, 0x10, 0x44, 0xbc, 0x5b, 0x76, 0xeb, 0x89, 0xa7, 0xba, + 0x19, 0x2e, + }; + + static const byte indefEContent[] = { + 0x30, 0x82, 0x02, 0x87, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x02, 0xa0, 0x82, 0x02, 0x78, 0x30, 0x82, 0x02, 0x74, 0x02, + 0x01, 0x01, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x02, 0x30, 0x80, 0x31, 0x82, + 0x02, 0x4d, 0x30, 0x82, 0x02, 0x49, 0x02, 0x01, 0x01, 0x30, 0x81, 0xb8, + 0x30, 0x81, 0x9f, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, + 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, + 0x08, 0x0c, 0x07, 0x4d, 0x6f, 0x6e, 0x74, 0x61, 0x6e, 0x61, 0x31, 0x10, + 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, 0x42, 0x6f, 0x7a, + 0x65, 0x6d, 0x61, 0x6e, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x0c, 0x0c, 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x5f, 0x32, + 0x30, 0x34, 0x38, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x0c, 0x10, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x6d, 0x69, 0x6e, + 0x67, 0x2d, 0x32, 0x30, 0x34, 0x38, 0x31, 0x18, 0x30, 0x16, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x0f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x6f, 0x6c, + 0x66, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x31, 0x20, 0x30, 0x1e, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, + 0x11, 0x66, 0x61, 0x63, 0x74, 0x73, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, + 0x73, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x02, 0x14, 0x75, 0x90, 0x2e, 0xa0, + 0xc3, 0xe1, 0x96, 0xe1, 0x92, 0x48, 0xb7, 0xa5, 0xc5, 0x83, 0xa1, 0xc9, + 0xa7, 0x3e, 0xe9, 0x5f, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x01, 0xa0, 0x69, 0x30, 0x18, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x03, 0x31, 0x0b, 0x06, 0x09, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0x30, 0x1c, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x05, 0x31, 0x0f, + 0x17, 0x0d, 0x32, 0x36, 0x31, 0x31, 0x30, 0x33, 0x30, 0x31, 0x30, 0x34, + 0x31, 0x38, 0x5a, 0x30, 0x2f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x04, 0x31, 0x22, 0x04, 0x20, 0x14, 0x75, 0x34, 0x9f, + 0x89, 0xdc, 0xdf, 0x00, 0x02, 0x20, 0xbe, 0xea, 0xfa, 0x17, 0xde, 0x82, + 0x4d, 0x0f, 0x79, 0x20, 0xe9, 0x6e, 0x35, 0x41, 0x2b, 0x6f, 0xf1, 0x15, + 0x93, 0xa9, 0x86, 0xd9, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x01, 0x00, 0x3d, + 0x13, 0x76, 0x29, 0xa1, 0xd7, 0xf7, 0xb4, 0xad, 0x5e, 0xf6, 0xad, 0x6a, + 0xbb, 0x0a, 0x47, 0x84, 0xd7, 0x9c, 0x8e, 0xb3, 0xf1, 0x61, 0xe5, 0xaf, + 0x6b, 0x26, 0x9a, 0xb2, 0x40, 0x8f, 0xf2, 0xe6, 0x81, 0x01, 0xe3, 0x58, + 0xbc, 0xe1, 0x1d, 0xeb, 0xf6, 0x2d, 0x95, 0xf9, 0x4a, 0xe3, 0x57, 0x43, + 0xba, 0x86, 0xa1, 0x13, 0xef, 0xa7, 0xad, 0x7e, 0xfe, 0x6f, 0xb9, 0xde, + 0x04, 0x05, 0xe1, 0x57, 0x59, 0x8a, 0x72, 0x73, 0x2f, 0x14, 0xbb, 0x59, + 0xe3, 0xc1, 0xb6, 0xe2, 0x7c, 0x20, 0xb6, 0x4a, 0x92, 0xad, 0x27, 0xa3, + 0x7f, 0xcb, 0x53, 0x02, 0xc5, 0x24, 0x77, 0x49, 0x86, 0xfa, 0xf4, 0x69, + 0xb3, 0x21, 0x3c, 0x1e, 0x11, 0xfd, 0x6a, 0x58, 0x3b, 0xb6, 0x50, 0xf2, + 0xbe, 0x94, 0x25, 0x9a, 0x56, 0x43, 0x28, 0x7f, 0xc9, 0xb7, 0xdc, 0x58, + 0xde, 0xd2, 0xed, 0xc6, 0xe2, 0x14, 0xa8, 0x68, 0xe2, 0x59, 0xdb, 0x02, + 0x57, 0xe1, 0x96, 0xcb, 0x1e, 0x7f, 0x71, 0x61, 0xd3, 0xa3, 0xc5, 0x5b, + 0x71, 0xc0, 0x5f, 0xc6, 0x69, 0x4d, 0x3b, 0x0d, 0xe0, 0xde, 0xbb, 0xe0, + 0xee, 0xa5, 0xf1, 0xed, 0x20, 0xfc, 0x5d, 0x98, 0x85, 0xa1, 0xb9, 0x0b, + 0x93, 0x6d, 0xe1, 0x14, 0x61, 0x6a, 0x11, 0x37, 0x27, 0x65, 0xcb, 0xd2, + 0x13, 0x91, 0x35, 0x7c, 0x8f, 0x98, 0xe4, 0x0b, 0xf7, 0x5b, 0xfb, 0x1b, + 0x48, 0x94, 0x53, 0x97, 0x24, 0x6a, 0xf6, 0x8e, 0x77, 0xf3, 0xe7, 0x71, + 0x54, 0xae, 0x0f, 0xfe, 0xfb, 0x78, 0x6f, 0x12, 0x31, 0x56, 0xeb, 0x47, + 0x90, 0x45, 0x34, 0xfb, 0xab, 0x4a, 0x0f, 0xf9, 0x03, 0x49, 0x3c, 0x1b, + 0x0d, 0x94, 0x9c, 0x24, 0x84, 0x7c, 0xd9, 0x34, 0x67, 0xee, 0xad, 0x72, + 0xea, 0x65, 0xdf, 0xde, 0x10, 0x44, 0xbc, 0x5b, 0x76, 0xeb, 0x89, 0xa7, + 0xba, 0x19, 0x2e, + }; +#if defined(USE_CERT_BUFFERS_2048) + byte key[sizeof(client_key_der_2048)]; + byte cert[sizeof(client_cert_der_2048)]; + word32 keySz = (word32)sizeof(key); + word32 certSz = (word32)sizeof(cert); + XMEMCPY(key, client_key_der_2048, keySz); + XMEMCPY(cert, client_cert_der_2048, certSz); +#else + byte key[sizeof_client_key_der_1024]; + byte cert[sizeof_client_cert_der_1024]; + word32 keySz = (word32)sizeof(key); + word32 certSz = (word32)sizeof(cert); + XMEMCPY(key, client_key_der_1024, keySz); + XMEMCPY(cert, client_cert_der_1024, certSz); +#endif + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(output, 0, outputSz); + ExpectIntEQ(wc_InitRng(&rng), 0); + + /* Sign a normal CMS SignedData (OCTET STRING eContent) over contentStr. */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + if (pkcs7 != NULL) { + pkcs7->content = (byte*)contentStr; + pkcs7->contentSz = contentSz; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->encryptOID = RSAk; + pkcs7->hashOID = SHA256h; + pkcs7->rng = &rng; + } + ExpectIntGT(anySz = wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* Locate the eContent OCTET STRING: the two bytes 0x04,len precede the + * signed content inside the eContent [0] wrapper. */ + if (anySz > 0) { + for (i = 0; i + 2 + (int)contentSz <= anySz; i++) { + if (output[i] == 0x04 && + output[i + 1] == (byte)contentSz && + XMEMCMP(output + i + 2, contentStr, contentSz) == 0) { + octetOff = i; + break; + } + } + } + ExpectIntGE(octetOff, 0); + + /* Positive: retag the element to SEQUENCE so it spans the whole content. */ + if (octetOff >= 0) { + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; /* OCTET STRING (0x04) -> SEQUENCE (0x30) */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), 0); + if (pkcs7 != NULL) { + ExpectIntEQ(pkcs7->contentIsPkcs7Type, 1); + /* the whole ANY element is reported: tag (0x30), length, value. */ + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz + 2); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) { + ExpectIntEQ(pkcs7->content[0], 0x30); + ExpectIntEQ(pkcs7->content[1], (int)contentSz); + ExpectIntEQ(XMEMCMP(pkcs7->content + 2, contentStr, + contentSz), 0); + } + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Negative: retag, and shorten the element's length so it stops before the + * end of the content, leaving trailing bytes the digest never covered. */ + if (octetOff >= 0) { + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = (byte)(contentSz - 4); /* short-form length */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Negative: a high-tag-number identifier (multi-byte tag, low five bits + * all set) is not a supported eContent element form and is rejected. */ + if (octetOff >= 0) { + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x9f; /* high-tag-number identifier */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Negative: the end-of-contents identifier (0x00) is not a valid element + * tag. The length octet is left unchanged so the element still spans the + * whole content; only the tag rule can reject it. */ + if (octetOff >= 0) { + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x00; /* end-of-contents identifier */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Same two cases through the precomputed-hash entry point + * (wc_PKCS7_VerifySignedData_ex, hashBuf != NULL): the value is still + * reported, and trailing data is still rejected, on that path too. */ + if (octetOff >= 0) { + byte hash[WC_SHA256_DIGEST_SIZE]; + word32 hashSz = (word32)sizeof(hash); + ExpectIntEQ(wc_Hash(WC_HASH_TYPE_SHA256, (const byte*)contentStr, + contentSz, hash, hashSz), 0); + + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData_ex(pkcs7, hash, hashSz, + any, (word32)anySz, NULL, 0), 0); + if (pkcs7 != NULL) { + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz + 2); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) { + ExpectIntEQ(pkcs7->content[0], 0x30); + ExpectIntEQ(XMEMCMP(pkcs7->content + 2, contentStr, + contentSz), 0); + } + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = (byte)(contentSz - 4); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData_ex(pkcs7, hash, hashSz, + any, (word32)anySz, NULL, 0), WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Reuse: after an ANY message, a CMS message verifies on the same object + * with no re-init, via both entry points, and reports the CMS content. */ + if (octetOff >= 0) { + byte hash[WC_SHA256_DIGEST_SIZE]; + word32 hashSz = (word32)sizeof(hash); + ExpectIntEQ(wc_Hash(WC_HASH_TYPE_SHA256, (const byte*)contentStr, + contentSz, hash, hashSz), 0); + + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), 0); + if (pkcs7 != NULL) { + ExpectIntEQ(pkcs7->contentIsPkcs7Type, 1); + /* Content lifetime: this verify's content is valid until the next + * verify on the object. Read it here, before the second verify + * below, to pin that the first verify's content is still intact. */ + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz + 2); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) { + ExpectIntEQ(pkcs7->content[0], 0x30); + ExpectIntEQ(XMEMCMP(pkcs7->content + 2, contentStr, + contentSz), 0); + } + } + /* same object, CMS message, ordinary entry point */ + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, output, (word32)anySz), + 0); + if (pkcs7 != NULL) { + ExpectIntEQ(pkcs7->contentIsPkcs7Type, 0); + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz); + ExpectIntEQ(XMEMCMP(pkcs7->content, contentStr, contentSz), 0); + } + /* ANY again, then CMS through the precomputed-hash entry point */ + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData_ex(pkcs7, hash, hashSz, + output, (word32)anySz, NULL, 0), 0); + if (pkcs7 != NULL) { + ExpectIntEQ(pkcs7->contentIsPkcs7Type, 0); + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz); + ExpectIntEQ(XMEMCMP(pkcs7->content, contentStr, contentSz), 0); + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Reuse after a FAILED ANY verify: a malformed ANY (trailing bytes) is + * rejected with ASN_PARSE_E, during parsing of which contentIsPkcs7Type + * is set; the next verify on the same object must clear it. A following + * CMS verify parses fresh and reports flag 0, pinning the per-verify + * reset on the failure path. */ + if (octetOff >= 0) { + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = (byte)(contentSz - 4); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, output, (word32)anySz), 0); + if (pkcs7 != NULL) { + ExpectIntEQ(pkcs7->contentIsPkcs7Type, 0); + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz); + ExpectIntEQ(XMEMCMP(pkcs7->content, contentStr, contentSz), 0); + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + +#ifndef NO_PKCS7_STREAM + /* Trailing data is also rejected when the message is fed one byte at a + * time, exercising the streaming parse path. */ + if (octetOff >= 0) { + word32 idx; + int ret = WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E); + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = (byte)(contentSz - 4); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + for (idx = 0; idx < (word32)anySz && ret != 0; idx++) { + ret = wc_PKCS7_VerifySignedData(pkcs7, any + idx, 1); + if (ret < 0 && ret != WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E)) + break; + } + ExpectIntEQ(ret, WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } +#endif /* !NO_PKCS7_STREAM */ + + /* No-certificate bundle: verification returns PKCS7_SIGNEEDS_CHECK (the + * caller verifies the signature itself), and the reported content is still + * narrowed to the signed value. */ + if (octetOff >= 0) { + byte nc[FOURK_BUF]; + int ncSz = 0; + int o2 = -1; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + ExpectIntEQ(wc_PKCS7_SetNoCerts(pkcs7, 1), 0); + if (pkcs7 != NULL) { + pkcs7->content = (byte*)contentStr; + pkcs7->contentSz = contentSz; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->encryptOID = RSAk; + pkcs7->hashOID = SHA256h; + pkcs7->rng = &rng; + } + ExpectIntGT(ncSz = wc_PKCS7_EncodeSignedData(pkcs7, nc, sizeof(nc)), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + for (i = 0; ncSz > 0 && i + 2 + (int)contentSz <= ncSz; i++) { + if (nc[i] == 0x04 && nc[i + 1] == (byte)contentSz && + XMEMCMP(nc + i + 2, contentStr, contentSz) == 0) { + o2 = i; + break; + } + } + ExpectIntGE(o2, 0); + if (o2 >= 0) { + nc[o2] = 0x30; /* retag to a spanning SEQUENCE */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, nc, (word32)ncSz), + WC_NO_ERR_TRACE(PKCS7_SIGNEEDS_CHECK)); + if (pkcs7 != NULL) { + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz + 2); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) { + ExpectIntEQ(pkcs7->content[0], 0x30); + ExpectIntEQ(XMEMCMP(pkcs7->content + 2, contentStr, + contentSz), 0); + } + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + } + + + /* Negative (crafted vectors): the span check rejects a one-octet element + * (no length octet) and the indefinite-length form, before any signature + * or digest work. */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, (byte*)onebyteEContent, + (word32)sizeof(onebyteEContent)), WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, (byte*)indefEContent, + (word32)sizeof(indefEContent)), WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* Negative: a declared length past the content is rejected on both the + * one-shot and precomputed-hash entry points. */ + if (octetOff >= 0) { + byte hash[WC_SHA256_DIGEST_SIZE]; + word32 hashSz = (word32)sizeof(hash); + ExpectIntEQ(wc_Hash(WC_HASH_TYPE_SHA256, (const byte*)contentStr, + contentSz, hash, hashSz), 0); + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = 0x7f; /* length past the element's bytes */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData_ex(pkcs7, hash, hashSz, + any, (word32)anySz, NULL, 0), WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* Non-minimal long-form length (long form for a value that fits short + * form) is rejected on both entry points. The element still spans the + * whole content, so only the minimal-length rule can reject it. */ + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = 0x81; /* long form, one length octet */ + any[octetOff + 2] = (byte)(contentSz - 1); /* 0x30 0x81 L + (len-1) value */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData_ex(pkcs7, hash, hashSz, + any, (word32)anySz, NULL, 0), WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Negative: multi-octet long-form lengths rejected by GetLength_ex before + * the span comparison. Each overwrites only the length octets, so the tag + * stays a SEQUENCE and only the length rule can reject it: + * 0x82 0x00 0x13 non-minimal two-octet length (value fits one octet) + * 0x84 0x80 ... four-octet length with the high bit set (out of range) + * 0x85 ... overlong length (more than four octets). */ + if (octetOff >= 0) { + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = 0x82; + any[octetOff + 2] = 0x00; + any[octetOff + 3] = 0x13; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = 0x84; + any[octetOff + 2] = 0x80; + any[octetOff + 3] = 0x00; + any[octetOff + 4] = 0x00; + any[octetOff + 5] = 0x00; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + any[octetOff + 1] = 0x85; + any[octetOff + 2] = 0x00; + any[octetOff + 3] = 0x00; + any[octetOff + 4] = 0x00; + any[octetOff + 5] = 0x00; + any[octetOff + 6] = 0x13; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)anySz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + +#ifndef NO_PKCS7_STREAM + /* Positive: a spanning element also verifies fed one byte at a time. */ + if (octetOff >= 0) { + word32 idx; + int ret = WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E); + XMEMCPY(any, output, (word32)anySz); + any[octetOff] = 0x30; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + for (idx = 0; idx < (word32)anySz && ret != 0; idx++) { + ret = wc_PKCS7_VerifySignedData(pkcs7, any + idx, 1); + if (ret < 0 && ret != WC_NO_ERR_TRACE(WC_PKCS7_WANT_READ_E)) + break; + } + ExpectIntEQ(ret, 0); + if (pkcs7 != NULL) { + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz + 2); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) + ExpectIntEQ(XMEMCMP(pkcs7->content + 2, contentStr, + contentSz), 0); + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } +#endif /* !NO_PKCS7_STREAM */ + + /* Positive: a long-form (>=128-byte) element length spans the content. */ + if (octetOff >= 0) { + byte lcont[200]; + int lanySz = 0; + int loff = -1; + int j; + for (j = 0; j < (int)sizeof(lcont); j++) + lcont[j] = (byte)('A' + (j % 26)); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + if (pkcs7 != NULL) { + pkcs7->content = lcont; + pkcs7->contentSz = (word32)sizeof(lcont); + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->encryptOID = RSAk; + pkcs7->hashOID = SHA256h; + pkcs7->rng = &rng; + } + ExpectIntGT(lanySz = wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz), + 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + /* eContent OCTET STRING is long-form (04 81 c8) for a 200-byte value */ + for (i = 0; lanySz > 0 && i + 3 + (int)sizeof(lcont) <= lanySz; i++) { + if (output[i] == 0x04 && output[i + 1] == 0x81 && + output[i + 2] == (byte)sizeof(lcont) && + XMEMCMP(output + i + 3, lcont, sizeof(lcont)) == 0) { + loff = i; + break; + } + } + ExpectIntGE(loff, 0); + if (loff >= 0) { + XMEMCPY(any, output, (word32)lanySz); + any[loff] = 0x30; /* OCTET STRING -> spanning SEQUENCE */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)lanySz), + 0); + if (pkcs7 != NULL) { + ExpectIntEQ((int)pkcs7->contentSz, (int)sizeof(lcont) + 3); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) + ExpectIntEQ(XMEMCMP(pkcs7->content + 3, lcont, + sizeof(lcont)), 0); + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + } + + /* Positive: a two-length-octet (>=256-byte) element length spans the + * content, exercising the 0x82 long-form length in the span check and in + * the value-skip digest (a slip here would reject every large real token + * while the short-length tests still pass). */ + if (octetOff >= 0) { + byte bcont[300]; + int banySz = 0; + int boff = -1; + int j; + for (j = 0; j < (int)sizeof(bcont); j++) + bcont[j] = (byte)('A' + (j % 26)); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + if (pkcs7 != NULL) { + pkcs7->content = bcont; + pkcs7->contentSz = (word32)sizeof(bcont); + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->encryptOID = RSAk; + pkcs7->hashOID = SHA256h; + pkcs7->rng = &rng; + } + ExpectIntGT(banySz = wc_PKCS7_EncodeSignedData(pkcs7, output, outputSz), + 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + /* eContent OCTET STRING is two-length-octet form (04 82 01 2c) */ + for (i = 0; banySz > 0 && i + 4 + (int)sizeof(bcont) <= banySz; i++) { + if (output[i] == 0x04 && output[i + 1] == 0x82 && + output[i + 2] == (byte)(sizeof(bcont) >> 8) && + output[i + 3] == (byte)(sizeof(bcont) & 0xff) && + XMEMCMP(output + i + 4, bcont, sizeof(bcont)) == 0) { + boff = i; + break; + } + } + ExpectIntGE(boff, 0); + if (boff >= 0) { + XMEMCPY(any, output, (word32)banySz); + any[boff] = 0x30; /* OCTET STRING -> spanning SEQUENCE */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, any, (word32)banySz), + 0); + if (pkcs7 != NULL) { + ExpectIntEQ((int)pkcs7->contentSz, (int)sizeof(bcont) + 4); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) + ExpectIntEQ(XMEMCMP(pkcs7->content + 4, bcont, + sizeof(bcont)), 0); + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + } + + /* Accept side of the contentSz<2 boundary: a 2-byte element (30 00) passes + * the span check and the digest of its empty value. No-cert bundle (no + * signature needed); patch messageDigest to the SHA-256 of the empty + * value, which the encoder leaves zero. */ + if (octetOff >= 0) { + static const byte emptyHash[WC_SHA256_DIGEST_SIZE] = { + 0xe3,0xb0,0xc4,0x42,0x98,0xfc,0x1c,0x14,0x9a,0xfb,0xf4,0xc8, + 0x99,0x6f,0xb9,0x24,0x27,0xae,0x41,0xe4,0x64,0x9b,0x93,0x4c, + 0xa4,0x95,0x99,0x1b,0x78,0x52,0xb8,0x55 }; + static const byte mdOid[] = { + 0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x09,0x04 }; + byte eb[FOURK_BUF]; + int ebSz = 0, eoff = -1, moff = -1; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + ExpectIntEQ(wc_PKCS7_SetNoCerts(pkcs7, 1), 0); + if (pkcs7 != NULL) { + pkcs7->content = (byte*)contentStr; + pkcs7->contentSz = 0; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->encryptOID = RSAk; + pkcs7->hashOID = SHA256h; + pkcs7->rng = &rng; + } + ExpectIntGT(ebSz = wc_PKCS7_EncodeSignedData(pkcs7, eb, sizeof(eb)), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + /* eContent [0] wraps an empty OCTET STRING (a0 02 04 00) */ + for (i = 0; ebSz > 3 && i + 4 <= ebSz; i++) { + if (eb[i] == 0xa0 && eb[i + 1] == 0x02 && eb[i + 2] == 0x04 && + eb[i + 3] == 0x00) { + eoff = i + 2; + break; + } + } + /* messageDigest value follows its OID, then 31 22 04 20 */ + for (i = 0; i + (int)sizeof(mdOid) + 4 + WC_SHA256_DIGEST_SIZE <= ebSz; + i++) { + if (XMEMCMP(eb + i, mdOid, sizeof(mdOid)) == 0) { + moff = i + (int)sizeof(mdOid) + 4; + break; + } + } + ExpectIntGE(eoff, 0); + ExpectIntGE(moff, 0); + if (eoff >= 0 && moff >= 0) { + eb[eoff] = 0x30; /* 04 00 -> 30 00 */ + XMEMCPY(eb + moff, emptyHash, WC_SHA256_DIGEST_SIZE); + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, eb, (word32)ebSz), + WC_NO_ERR_TRACE(PKCS7_SIGNEEDS_CHECK)); + if (pkcs7 != NULL) { + ExpectIntEQ((int)pkcs7->contentSz, 2); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) + ExpectIntEQ(pkcs7->content[0], 0x30); + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + } + + /* No signed attributes: the span check is gated on the signed-attributes + * path, so it never runs here. The identical malformed ANY element that the + * signed-attributes path rejects with ASN_PARSE_E is accepted, and the + * whole eContent region (trailing bytes included) is reported as content. + * No certificates, so verification returns PKCS7_SIGNEEDS_CHECK without a + * signature check; the signature covers the region as received, so the + * shape is the signer's choice, not the verifier's guarantee. */ + if (octetOff >= 0) { + byte na[FOURK_BUF]; + int naSz = 0; + int o3 = -1; + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, cert, certSz), 0); + ExpectIntEQ(wc_PKCS7_NoDefaultSignedAttribs(pkcs7), 0); + ExpectIntEQ(wc_PKCS7_SetNoCerts(pkcs7, 1), 0); + if (pkcs7 != NULL) { + pkcs7->content = (byte*)contentStr; + pkcs7->contentSz = contentSz; + pkcs7->privateKey = key; + pkcs7->privateKeySz = keySz; + pkcs7->encryptOID = RSAk; + pkcs7->hashOID = SHA256h; + pkcs7->rng = &rng; + } + ExpectIntGT(naSz = wc_PKCS7_EncodeSignedData(pkcs7, na, sizeof(na)), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + for (i = 0; naSz > 0 && i + 2 + (int)contentSz <= naSz; i++) { + if (na[i] == 0x04 && na[i + 1] == (byte)contentSz && + XMEMCMP(na + i + 2, contentStr, contentSz) == 0) { + o3 = i; + break; + } + } + ExpectIntGE(o3, 0); + if (o3 >= 0) { + na[o3] = 0x30; /* retag to a SEQUENCE */ + na[o3 + 1] = (byte)(contentSz - 4); /* shorten: 4 trailing bytes */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, na, (word32)naSz), + WC_NO_ERR_TRACE(PKCS7_SIGNEEDS_CHECK)); + if (pkcs7 != NULL) { + ExpectIntEQ(pkcs7->contentIsPkcs7Type, 1); + ExpectIntEQ((int)pkcs7->contentSz, (int)contentSz + 2); + ExpectNotNull(pkcs7->content); + if (pkcs7->content != NULL) { + ExpectIntEQ(pkcs7->content[0], 0x30); + ExpectIntEQ(pkcs7->content[1], (int)contentSz - 4); + } + } + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + } + + ForceZero(key, sizeof(key)); + wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + + + /* * Test PKCS7 VerifySignedData with indefinite-length BER-encoded SignedData * containing mismatched nesting depth. Verifies bounds checking in the diff --git a/tests/api/test_pkcs7.h b/tests/api/test_pkcs7.h index e4794d616aa..0f171699f01 100644 --- a/tests/api/test_pkcs7.h +++ b/tests/api/test_pkcs7.h @@ -89,6 +89,7 @@ int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagCcm(void); int test_wc_PKCS7_DecodeAuthEnvelopedData_shortTagChunked(void); int test_wc_PKCS7_AuthEnvelopedData_stream_leak(void); int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void); +int test_wc_PKCS7_VerifySignedData_eContentAnySpan(void); int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void); int test_wc_PKCS7_VerifySignedData_TruncEContentTag(void); int test_wc_PKCS7_VerifySignedData_TruncCertSetTag(void); @@ -153,6 +154,7 @@ int test_wc_PKCS7_VerifySignedData_NoDigestParams(void); TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_BER), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_NoDefaultSignedAttribs), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq), \ + TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_eContentAnySpan), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_IndefLenOOB), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_TruncEContentTag), \ TEST_DECL_GROUP("pkcs7_sd", test_wc_PKCS7_VerifySignedData_TruncCertSetTag), \ diff --git a/tests/api/test_tsp.c b/tests/api/test_tsp.c index 299f0fa7f75..96e83f9d5b1 100644 --- a/tests/api/test_tsp.c +++ b/tests/api/test_tsp.c @@ -2145,6 +2145,120 @@ int test_wc_TspTstInfo_SignWithPkcs7_create(void) return EXPECT_RESULT(); } +int test_wc_TspTstInfo_VerifyWithPKCS7_legacyAny(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TSP) && defined(HAVE_PKCS7) && !defined(NO_RSA) && \ + !defined(NO_SHA256) && !defined(WC_NO_RNG) && \ + defined(WOLFSSL_TSP_REQUESTER) && defined(WOLFSSL_TSP_RESPONDER) + wc_PKCS7* pkcs7 = NULL; + WC_RNG rng; + TspTstInfo tstDec; + byte token[3072]; + byte mod[3072]; + word32 tokenSz; + int i; + int off = -1; + int lenOff = -1; + + ExpectIntEQ(wc_InitRng(&rng), 0); + tokenSz = (word32)sizeof(token); + ExpectIntEQ(test_tsp_make_token(token, &tokenSz, tsa_cert_der_2048, + sizeof_tsa_cert_der_2048, tsa_key_der_2048, sizeof_tsa_key_der_2048, + RSAk, &rng), TEST_SUCCESS); + ExpectIntGT(tokenSz, 0); + + /* A wolfSSL-encoded token always has a CMS OCTET STRING eContent wrapping + * the TstInfo SEQUENCE; the well-formed success path is covered by + * test_wc_TspTstInfo_SignWithPkcs7_create. Here that eContent is retagged + * to an ANY SEQUENCE to drive the new span check on the TSP verify call. A + * true legacy token (the TstInfo SEQUENCE as the eContent directly) cannot + * be produced by retagging this token: the wolfSSL signer digested the + * OCTET STRING value, i.e. the whole TstInfo SEQUENCE TLV, while after + * retagging the ANY value-skip hashes only the SEQUENCE value, so the + * digests never match. + * + * Locate the eContent OCTET STRING: it wraps the TstInfo SEQUENCE, whose + * first field is the version INTEGER 1 (02 01 01). Short-form and one-byte + * long-form (0x81) OCTET STRING lengths are handled, covering a TstInfo up + * to 255 octets; the fixed test vectors stay within that, so a two-byte + * (0x82) long form does not arise here. */ + for (i = 0; off < 0 && i + 8 <= (int)tokenSz; i++) { + int vOff, j; + if (token[i] != 0x04) + continue; + if (token[i + 1] < 0x80) { + lenOff = i + 1; + vOff = i + 2; + } + else if (token[i + 1] == 0x81) { + lenOff = i + 2; + vOff = i + 3; + } + else { + continue; + } + if (vOff >= (int)tokenSz || token[vOff] != 0x30) + continue; + for (j = vOff + 1; j + 2 < (int)tokenSz && j <= vOff + 3; j++) { + if (token[j] == 0x02 && token[j + 1] == 0x01 && + token[j + 2] == 0x01) { + off = i; + break; + } + } + } + ExpectIntGE(off, 0); + + /* Retag the OCTET STRING to a SEQUENCE. The element now spans the eContent + * (its value is the original TstInfo SEQUENCE), so the value-skip digest + * still matches and the span check accepts it: wc_PKCS7_VerifySignedData + * passes. wc_TspTstInfo_VerifyWithPKCS7 then returns ASN_PARSE_E because the + * decoder sees a SEQUENCE wrapping a SEQUENCE, not a TstInfo. */ + if (off >= 0) { + XMEMCPY(mod, token, tokenSz); + mod[off] = 0x30; + ExpectNotNull(pkcs7 = wc_PKCS7_New(NULL, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, mod, tokenSz), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + ExpectNotNull(pkcs7 = wc_PKCS7_New(NULL, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_TspTstInfo_VerifyWithPKCS7(pkcs7, mod, tokenSz, &tstDec), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + /* Trailing bytes: retag and shorten the element so four bytes trail it. The + * span check runs on the TSP verify call and rejects it with ASN_PARSE_E + * before the TSP decode is reached. */ + if (off >= 0) { + XMEMCPY(mod, token, tokenSz); + mod[off] = 0x30; + mod[lenOff] = (byte)(token[lenOff] - 4); + ExpectNotNull(pkcs7 = wc_PKCS7_New(NULL, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_PKCS7_VerifySignedData(pkcs7, mod, tokenSz), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + ExpectNotNull(pkcs7 = wc_PKCS7_New(NULL, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, NULL, 0), 0); + ExpectIntEQ(wc_TspTstInfo_VerifyWithPKCS7(pkcs7, mod, tokenSz, &tstDec), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + } + + wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} + int test_wc_TspTstInfo_SignWithPkcs7_signer_required(void) { EXPECT_DECLS; diff --git a/tests/api/test_tsp.h b/tests/api/test_tsp.h index a1968d8c198..48d43d696e5 100644 --- a/tests/api/test_tsp.h +++ b/tests/api/test_tsp.h @@ -51,6 +51,7 @@ int test_wc_TspTstInfo_CheckRequest(void); int test_wc_TspTstInfo_CheckTsaName(void); int test_wc_TspTstInfo_SignWithPkcs7(void); int test_wc_TspTstInfo_SignWithPkcs7_create(void); +int test_wc_TspTstInfo_VerifyWithPKCS7_legacyAny(void); int test_wc_TspTstInfo_SignWithPkcs7_signer_required(void); int test_wc_TspTstInfo_SignWithPkcs7_hash_and_buffer(void); int test_wc_TspTstInfo_Sign(void); @@ -117,6 +118,7 @@ int test_wc_TspTstInfo_SetFromRequest(void); TEST_DECL_GROUP("tsp", test_wc_TspTstInfo_CheckTsaName), \ TEST_DECL_GROUP("tsp", test_wc_TspTstInfo_SignWithPkcs7), \ TEST_DECL_GROUP("tsp", test_wc_TspTstInfo_SignWithPkcs7_create), \ + TEST_DECL_GROUP("tsp", test_wc_TspTstInfo_VerifyWithPKCS7_legacyAny), \ TEST_DECL_GROUP("tsp", test_wc_TspTstInfo_SignWithPkcs7_signer_required), \ TEST_DECL_GROUP("tsp", test_wc_TspTstInfo_SignWithPkcs7_hash_and_buffer), \ TEST_DECL_GROUP("tsp", test_wc_TspTstInfo_Sign), \ diff --git a/tests/unit-mcdc/test_pkcs7_arg_whitebox.c b/tests/unit-mcdc/test_pkcs7_arg_whitebox.c index 2217b60a038..09ea3daa5b2 100644 --- a/tests/unit-mcdc/test_pkcs7_arg_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_arg_whitebox.c @@ -566,7 +566,7 @@ static void wb_signerinfo_binding(void) /* ------------------------------------------------------------------------- * * Section 4: wc_PKCS7_VerifyContentMessageDigest() attribute/content shapes - * [:5857, :5890, :5893] + * [:6012, :6069, :6075] plus the ANY-span check [:6030, :6037, :6046] * ------------------------------------------------------------------------- */ static void wb_verify_content_msgdigest(void) { @@ -577,11 +577,41 @@ static void wb_verify_content_msgdigest(void) byte content[8]; byte pkcs7Content[8]; byte hash[32]; + byte anySpan[2]; /* 30 00: single element spanning the content */ + byte anyTrail[3]; /* 30 00 00: element plus a trailing byte */ + byte anyHighTag[2]; /* 1f 00: high-tag-number identifier */ + byte anyIndef[2]; /* 30 80: indefinite length */ + byte anyBadLen[3]; /* 30 81 7f: non-minimal 1-byte long-form length */ + byte anyBufExc[3]; /* 30 81 80: long-form length runs past the element */ + byte anyLen2NM[4]; /* 30 82 00 13: non-minimal 2-byte long-form length */ + byte anyLen4MSB[6]; /* 30 84 80 00 00 00: 4-byte length with the MSB set */ + byte anyLenOvr[7]; /* 30 85 ...: overlong length (more than four octets) */ + byte anyEoc[3]; /* 00 01 78: end-of-contents identifier as element tag */ + byte anyLen3NM[5]; /* 30 83 00 00 13: non-minimal 3-byte long-form length */ + byte anyLen4NM[6]; /* 30 84 00 00 00 13: non-minimal 4-byte long-form len */ XMEMSET(&attrib, 0, sizeof(attrib)); XMEMCPY(mdOidDer, wbMessageDigestOid, sizeof(wbMessageDigestOid)); XMEMSET(content, 0x31, sizeof(content)); XMEMSET(hash, 0, sizeof(hash)); + anySpan[0] = 0x30; anySpan[1] = 0x00; + anyTrail[0] = 0x30; anyTrail[1] = 0x00; anyTrail[2] = 0x00; + anyHighTag[0] = 0x1f; anyHighTag[1] = 0x00; + anyIndef[0] = 0x30; anyIndef[1] = 0x80; + anyBadLen[0] = 0x30; anyBadLen[1] = 0x81; anyBadLen[2] = 0x7f; + anyBufExc[0] = 0x30; anyBufExc[1] = 0x81; anyBufExc[2] = 0x80; + anyLen2NM[0] = 0x30; anyLen2NM[1] = 0x82; anyLen2NM[2] = 0x00; + anyLen2NM[3] = 0x13; + anyLen4MSB[0] = 0x30; anyLen4MSB[1] = 0x84; anyLen4MSB[2] = 0x80; + anyLen4MSB[3] = 0x00; anyLen4MSB[4] = 0x00; anyLen4MSB[5] = 0x00; + anyLenOvr[0] = 0x30; anyLenOvr[1] = 0x85; anyLenOvr[2] = 0x00; + anyLenOvr[3] = 0x00; anyLenOvr[4] = 0x00; anyLenOvr[5] = 0x00; + anyLenOvr[6] = 0x13; + anyEoc[0] = 0x00; anyEoc[1] = 0x01; anyEoc[2] = 0x78; + anyLen3NM[0] = 0x30; anyLen3NM[1] = 0x83; anyLen3NM[2] = 0x00; + anyLen3NM[3] = 0x00; anyLen3NM[4] = 0x13; + anyLen4NM[0] = 0x30; anyLen4NM[1] = 0x84; anyLen4NM[2] = 0x00; + anyLen4NM[3] = 0x00; anyLen4NM[4] = 0x00; anyLen4NM[5] = 0x13; /* OCTET STRING wrapping a 32-byte digest */ mdValue[0] = ASN_OCTET_STRING; @@ -608,7 +638,7 @@ static void wb_verify_content_msgdigest(void) attrib.valueSz = (word32)sizeof(mdValue); p.decodedAttrib = &attrib; - WB_NOTE("VerifyContentMessageDigest attrib->value guards [:5857]"); + WB_NOTE("VerifyContentMessageDigest attrib->value guards [:6012]"); /* 1st operand true */ attrib.value = NULL; (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); @@ -618,23 +648,23 @@ static void wb_verify_content_msgdigest(void) (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); attrib.valueSz = (word32)sizeof(mdValue); - WB_NOTE("VerifyContentMessageDigest content shape [:5890,:5893]"); - /* all false at :5857; :5890 1st operand false (content == NULL) */ + WB_NOTE("VerifyContentMessageDigest content shape [:6069,:6075]"); + /* all false at :6012; :6069 1st operand false (content == NULL) */ p.content = NULL; p.contentSz = 0; p.contentIsPkcs7Type = 1; (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); - /* :5890 2nd operand false (content set, not PKCS#7-typed) */ + /* :6069 2nd operand false (content set, not PKCS#7-typed) */ p.content = content; p.contentSz = (word32)sizeof(content); p.contentIsPkcs7Type = 0; (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); - /* :5890 both true, :5893 contentLen > 1 */ + /* :6069 both true, :6075 contentLen > 1 */ p.content = pkcs7Content; p.contentSz = (word32)sizeof(pkcs7Content); p.contentIsPkcs7Type = 1; (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); - /* :5893 contentLen <= 1 */ + /* :6037 contentSz < 2 operand (intercepts the old contentLen <= 1 drive) */ p.contentSz = 1; (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); /* caller-supplied hash path */ @@ -643,6 +673,75 @@ static void wb_verify_content_msgdigest(void) p.contentIsPkcs7Type = 0; (void)wc_PKCS7_VerifyContentMessageDigest(&p, hash, (word32)sizeof(hash)); + /* ANY-span check [:6030-:6050], inserted ahead of the decisions above and + * reached with the valid messageDigest attribute set here. */ + WB_NOTE("VerifyContentMessageDigest ANY-span check [:6030,:6037,:6046]"); + attrib.value = mdValue; + attrib.valueSz = (word32)sizeof(mdValue); + p.contentIsPkcs7Type = 1; + /* :6030 contentSz > 0 false operand (flag set, content set, size 0); this + * also drives the :6075 contentLen <= 1 arm, unreachable once the span + * check runs (it requires contentSz >= 2). */ + p.content = anySpan; + p.contentSz = 0; + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6030 content != NULL operand varies alone: content NULL with + * contentSz > 0 (MCDC pair; the parse code never produces this state, + * so it is a defensive guard). */ + p.content = NULL; + p.contentSz = (word32)sizeof(content); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6037 high-tag-number identifier operand */ + p.content = anyHighTag; + p.contentSz = (word32)sizeof(anyHighTag); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6037 end-of-contents (0x00) operand: not a valid element tag, the + * length still spans so only this operand rejects */ + p.content = anyEoc; + p.contentSz = (word32)sizeof(anyEoc); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6037 indefinite-length operand */ + p.content = anyIndef; + p.contentSz = (word32)sizeof(anyIndef); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6043 GetLength_ex failure: non-minimal 1-byte long-form length + * (0x7f is below the 0x80 minimum, not a run past the end) */ + p.content = anyBadLen; + p.contentSz = (word32)sizeof(anyBadLen); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6043 GetLength_ex failure: long-form length runs past the element */ + p.content = anyBufExc; + p.contentSz = (word32)sizeof(anyBufExc); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6043 GetLength_ex failure: non-minimal 2-byte long-form length */ + p.content = anyLen2NM; + p.contentSz = (word32)sizeof(anyLen2NM); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6043 GetLength_ex failure: non-minimal 3-byte long-form length */ + p.content = anyLen3NM; + p.contentSz = (word32)sizeof(anyLen3NM); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6043 GetLength_ex failure: non-minimal 4-byte long-form length */ + p.content = anyLen4NM; + p.contentSz = (word32)sizeof(anyLen4NM); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6043 GetLength_ex failure: 4-byte length with the high bit set */ + p.content = anyLen4MSB; + p.contentSz = (word32)sizeof(anyLen4MSB); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6043 GetLength_ex failure: overlong length (more than four octets) */ + p.content = anyLenOvr; + p.contentSz = (word32)sizeof(anyLenOvr); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6046 element does not span the content (trailing byte) */ + p.content = anyTrail; + p.contentSz = (word32)sizeof(anyTrail); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + /* :6030-:6046 all pass: a single element spanning the content */ + p.content = anySpan; + p.contentSz = (word32)sizeof(anySpan); + (void)wc_PKCS7_VerifyContentMessageDigest(&p, NULL, 0); + p.decodedAttrib = NULL; p.content = NULL; p.contentSz = 0; diff --git a/tests/unit-mcdc/test_pkcs7_whitebox.c b/tests/unit-mcdc/test_pkcs7_whitebox.c index de9bf89df4c..e34c2d52030 100644 --- a/tests/unit-mcdc/test_pkcs7_whitebox.c +++ b/tests/unit-mcdc/test_pkcs7_whitebox.c @@ -860,7 +860,7 @@ static void wb_verify_guards(void) /* ------------------------------------------------------------------------- * * Section 9: BuildSignedDataDigest/VerifyContentMessageDigest guards and - * internal branches [:5706,:5717,:5722,:5742,:5836,:5853,:5886,:5929], + * internal branches [:5865,:5876,:5881,:5901,:5995,:6012,:6069,:6112], * SignedDataVerifySignature guard [:5964-area], SetPublicKeyOID [:6170 area], * GetSignerSID [:8271-area, already simple]. * ------------------------------------------------------------------------- */ @@ -876,23 +876,23 @@ static void wb_digest_verify(void) XMEMSET(&pkcs7, 0, sizeof(pkcs7)); pkcs7.hashOID = SHA256h; - WB_NOTE("wc_PKCS7_BuildSignedDataDigest(): NULL guard [:5706]"); + WB_NOTE("wc_PKCS7_BuildSignedDataDigest(): NULL guard [:5865]"); ret = wc_PKCS7_BuildSignedDataDigest(NULL, NULL, 0, pkcs7Digest, &pkcs7DigestSz, &plainDigest, &plainDigestSz, NULL, 0, 0); - WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5706 pkcs7==NULL"); + WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5865 pkcs7==NULL"); ret = wc_PKCS7_BuildSignedDataDigest(&pkcs7, NULL, 0, NULL, &pkcs7DigestSz, &plainDigest, &plainDigestSz, NULL, 0, 0); - WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5706 pkcs7Digest==NULL"); + WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5865 pkcs7Digest==NULL"); ret = wc_PKCS7_BuildSignedDataDigest(&pkcs7, NULL, 0, pkcs7Digest, NULL, &plainDigest, &plainDigestSz, NULL, 0, 0); - WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5706 pkcs7DigestSz==NULL"); + WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5865 pkcs7DigestSz==NULL"); ret = wc_PKCS7_BuildSignedDataDigest(&pkcs7, NULL, 0, pkcs7Digest, &pkcs7DigestSz, NULL, &plainDigestSz, NULL, 0, 0); - WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5706 plainDigest==NULL"); + WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5865 plainDigest==NULL"); WB_NOTE("wc_PKCS7_BuildSignedDataDigest(): signedAttribSz==0 branch" - " [:5722,:5742]"); - /* hashBuf given, no content: :5722 both true, hashSz mismatch check */ + " [:5881,:5901]"); + /* hashBuf given, no content: :5881 both true, hashSz mismatch check */ { byte userHash[8]; XMEMSET(userHash, 0xAB, sizeof(userHash)); @@ -900,7 +900,7 @@ static void wb_digest_verify(void) &pkcs7DigestSz, &plainDigest, &plainDigestSz, userHash, sizeof(userHash), 0); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), - ":5722 both true, hashSz mismatch (WC_SHA256_DIGEST_SIZE!=8)"); + ":5881 both true, hashSz mismatch (WC_SHA256_DIGEST_SIZE!=8)"); } { byte userHash[WC_SHA256_DIGEST_SIZE]; @@ -910,17 +910,17 @@ static void wb_digest_verify(void) &pkcs7DigestSz, &plainDigest, &plainDigestSz, userHash, sizeof(userHash), 0); WB_CHECK(ret == 0, - ":5722 both true, hashSz matches -> :5742 all true (copy path)"); + ":5881 both true, hashSz matches -> :5901 all true (copy path)"); } - /* :5722 1st operand false (hashBuf==NULL), pkcs7->content==NULL -> - * BAD_FUNC_ARG at the else-if; :5742 1st operand false (hashBuf==NULL). */ + /* :5881 1st operand false (hashBuf==NULL), pkcs7->content==NULL -> + * BAD_FUNC_ARG at the else-if; :5901 1st operand false (hashBuf==NULL). */ pkcs7.content = NULL; pkcs7DigestSz = sizeof(pkcs7Digest); ret = wc_PKCS7_BuildSignedDataDigest(&pkcs7, NULL, 0, pkcs7Digest, &pkcs7DigestSz, &plainDigest, &plainDigestSz, NULL, 0, 0); WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), - ":5722/:5742 1st operand false (hashBuf==NULL), content==NULL"); - /* :5742 3rd operand false: hashBuf valid but signedAttribSz>0 -- goes to + ":5881/:5901 1st operand false (hashBuf==NULL), content==NULL"); + /* :5901 3rd operand false: hashBuf valid but signedAttribSz>0 -- goes to * hash-over-signedAttrib path instead (needs signedAttrib!=NULL). */ { byte userHash[WC_SHA256_DIGEST_SIZE]; @@ -931,18 +931,18 @@ static void wb_digest_verify(void) pkcs7Digest, &pkcs7DigestSz, &plainDigest, &plainDigestSz, userHash, sizeof(userHash), 0); WB_CHECK(ret == 0, - ":5742 3rd operand false (signedAttribSz>0, hash-over-attrib path)"); + ":5901 3rd operand false (signedAttribSz>0, hash-over-attrib path)"); } - WB_NOTE("wc_PKCS7_VerifyContentMessageDigest(): NULL guard [:5836] and" - " attrib->value checks [:5853] via real ParseAttribs()"); + WB_NOTE("wc_PKCS7_VerifyContentMessageDigest(): NULL guard [:5995] and" + " attrib->value checks [:6012] via real ParseAttribs()"); XMEMSET(&pkcs7, 0, sizeof(pkcs7)); pkcs7.hashOID = SHA256h; ret = wc_PKCS7_VerifyContentMessageDigest(NULL, NULL, 0); - WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5836 pkcs7==NULL"); + WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), ":5995 pkcs7==NULL"); /* no messageDigest attrib in bundle at all: findAttrib returns NULL, - * ASN_PARSE_E before reaching :5853 -- not the gap; build one instead - * with an empty OCTET STRING value to hit :5853 true. */ + * ASN_PARSE_E before reaching :6012 -- not the gap; build one instead + * with an empty OCTET STRING value to hit :6012 true. */ { /* messageDigest attrib SEQ { OID mdOid, SET { OCTET_STRING(empty) } } */ static const byte mdOid[] = @@ -963,13 +963,13 @@ static void wb_digest_verify(void) ret = wc_PKCS7_ParseAttribs(&pkcs7, attrBuf, (int)idx); WB_CHECK(ret == 1, "ParseAttribs found 1 attrib (messageDigest, empty value)"); ret = wc_PKCS7_VerifyContentMessageDigest(&pkcs7, NULL, 0); - WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":5853 attrib->valueSz==0 true"); + WB_CHECK(ret == WC_NO_ERR_TRACE(ASN_PARSE_E), ":6012 attrib->valueSz==0 true"); } wc_PKCS7_FreeDecodedAttrib(pkcs7.decodedAttrib, NULL); pkcs7.decodedAttrib = NULL; WB_NOTE("wc_PKCS7_VerifyContentMessageDigest(): content-is-pkcs7-type OR" - " [:5886] and mismatch compare [:5929]"); + " [:6069] and mismatch compare [:6112]"); { static const byte mdOid[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x04 }; @@ -997,12 +997,12 @@ static void wb_digest_verify(void) ret = wc_PKCS7_ParseAttribs(&pkcs7, attrBuf, (int)idx); WB_CHECK(ret == 1, "ParseAttribs found messageDigest w/ correct hash"); - pkcs7.content = NULL; /* :5886 1st operand false (content==NULL) */ + pkcs7.content = NULL; /* :6069 1st operand false (content==NULL) */ pkcs7.contentIsPkcs7Type = 0; ret = wc_PKCS7_VerifyContentMessageDigest(&pkcs7, NULL, 0); - WB_CHECK(ret == 0, ":5886 both false (empty content, hash matches -> :5929 false)"); + WB_CHECK(ret == 0, ":6069 both false (empty content, hash matches -> :6112 false)"); - /* :5929 true: wrong-length messageDigest attrib value (mismatch). */ + /* :6112 true: wrong-length messageDigest attrib value (mismatch). */ wc_PKCS7_FreeDecodedAttrib(pkcs7.decodedAttrib, NULL); pkcs7.decodedAttrib = NULL; idx = 0; @@ -1020,7 +1020,7 @@ static void wb_digest_verify(void) ret = wc_PKCS7_ParseAttribs(&pkcs7, attrBuf, (int)idx); WB_CHECK(ret == 1, "ParseAttribs found messageDigest w/ wrong-size hash"); ret = wc_PKCS7_VerifyContentMessageDigest(&pkcs7, NULL, 0); - WB_CHECK(ret == WC_NO_ERR_TRACE(SIG_VERIFY_E), ":5929 1st operand true (size mismatch)"); + WB_CHECK(ret == WC_NO_ERR_TRACE(SIG_VERIFY_E), ":6112 1st operand true (size mismatch)"); wc_PKCS7_FreeDecodedAttrib(pkcs7.decodedAttrib, NULL); pkcs7.decodedAttrib = NULL; } diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 10eba731316..14d33632087 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -6025,6 +6025,32 @@ static int wc_PKCS7_VerifyContentMessageDigest(wc_PKCS7* pkcs7, return BAD_FUNC_ARG; } + /* ANY-typed eContent must be a single element spanning content; checked + * here so it applies whether or not a precomputed hash is supplied. */ + if (pkcs7->contentIsPkcs7Type && pkcs7->content != NULL && + pkcs7->contentSz > 0) { + word32 spanIdx; + int spanLen = 0; /* set by GetLength_ex below */ + /* Need an identifier octet and a length. Reject the high-tag-number + * form (low five bits all set) so one identifier octet is correct, + * the end-of-contents identifier (0x00), which is not an element tag, + * and the indefinite length (0x80), which GetLength_ex reads as 0. */ + if (pkcs7->contentSz < 2 || pkcs7->content[0] == 0x00 || + (pkcs7->content[0] & 0x1F) == 0x1F || + pkcs7->content[1] == ASN_INDEF_LENGTH) { + WOLFSSL_MSG("Unsupported PKCS7 eContent element encoding"); + return ASN_PARSE_E; + } + spanIdx = 1; + if (GetLength_ex(pkcs7->content, &spanIdx, &spanLen, + pkcs7->contentSz, 1) < 0) + return ASN_PARSE_E; + if (spanIdx + (word32)spanLen != pkcs7->contentSz) { + WOLFSSL_MSG("PKCS7 eContent has data after the first element"); + return ASN_PARSE_E; + } + } + /* build content hash if needed, or use existing hash value */ if (hashBuf == NULL) { @@ -7115,6 +7141,8 @@ static int PKCS7_VerifySignedData(wc_PKCS7* pkcs7, const byte* hashBuf, switch (pkcs7->state) { case WC_PKCS7_START: + /* per-message flag, clear any value left by a previous verify */ + pkcs7->contentIsPkcs7Type = 0; #ifndef NO_PKCS7_STREAM /* The expected size calculation originally assumed digest OID * with NULL params, -2 to also accept with absent params */ diff --git a/wolfcrypt/src/tsp.c b/wolfcrypt/src/tsp.c index 71b34f3cbd0..81fa9b0443c 100644 --- a/wolfcrypt/src/tsp.c +++ b/wolfcrypt/src/tsp.c @@ -1861,7 +1861,10 @@ static int Tsp_CheckSignerCert(const byte* cert, word32 certSz, * @return HASH_TYPE_E when the signing certificate attribute's hash * algorithm is not available or a hash algorithm is below * WC_TSP_MIN_HASH_STRENGTH_BITS. - * @return ASN_PARSE_E when an encoding is invalid. + * @return ASN_PARSE_E when an encoding is invalid, including a legacy ANY + * eContent that is not a single DER element spanning the whole + * content, or a well-formed ANY eContent that does not decode as a + * TSTInfo SEQUENCE. * @return MEMORY_E on dynamic memory allocation failure. */ int wc_TspTstInfo_VerifyWithPKCS7(wc_PKCS7* pkcs7, byte* token, word32 tokenSz,