diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index a58ebee1c6..308a2d8b07 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -3786,7 +3786,10 @@ static int wc_PKCS7_VerifyContentMessageDigest(PKCS7* pkcs7, word32 hashSz) { int ret = 0, digestSz = 0, innerAttribSz = 0; + int contentLen = 0; word32 idx = 0; + word32 contentIdx = 0; + byte* content = NULL; byte* digestBuf = NULL; #ifdef WOLFSSL_SMALL_STACK byte* digest = NULL; @@ -3845,7 +3848,29 @@ static int wc_PKCS7_VerifyContentMessageDigest(PKCS7* pkcs7, #endif XMEMSET(digest, 0, MAX_PKCS7_DIGEST_SZ); - ret = wc_Hash(hashType, pkcs7->content, pkcs7->contentSz, digest, + content = pkcs7->content; + contentLen = pkcs7->contentSz; + + if (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 + * only on the "value" of the DER encoding. As such, advance past + * the tag and length */ + if (contentLen > 1) { + contentIdx++; + } + + if (GetLength_ex(content, &contentIdx, &contentLen, + contentLen, 1) < 0) { + #ifdef WOLFSSL_SMALL_STACK + XFREE(digest, pkcs7->heap, DYNAMIC_TYPE_TMP_BUFFER); + #endif + return ASN_PARSE_E; + } + } + + ret = wc_Hash(hashType, content + contentIdx, contentLen, digest, MAX_PKCS7_DIGEST_SZ); if (ret < 0) { WOLFSSL_MSG("Error hashing PKCS7 content for verification"); @@ -4435,11 +4460,13 @@ static int PKCS7_VerifySignedData(PKCS7* pkcs7, const byte* hashBuf, byte* cert = NULL; byte* signedAttrib = NULL; byte* contentType = NULL; + int encapContentInfoLen = 0; int contentSz = 0, sigSz = 0, certSz = 0, signedAttribSz = 0; word32 localIdx, start; byte degenerate = 0; byte detached = 0; byte tag = 0; + word16 contentIsPkcs7Type = 0; #ifdef ASN_BER_TO_DER byte* der; #endif @@ -4649,7 +4676,7 @@ static int PKCS7_VerifySignedData(PKCS7* pkcs7, const byte* hashBuf, #endif /* Get the inner ContentInfo sequence */ - if (GetSequence_ex(pkiMsg, &idx, &length, pkiMsgSz, + if (GetSequence_ex(pkiMsg, &idx, &encapContentInfoLen, pkiMsgSz, NO_USER_CHECK) < 0) ret = ASN_PARSE_E; @@ -4657,7 +4684,8 @@ static int PKCS7_VerifySignedData(PKCS7* pkcs7, const byte* hashBuf, if (ret == 0) { int isIndef = 0; word32 tmpIdx = idx; - if (length == 0 && pkiMsg[idx-1] == ASN_INDEF_LENGTH) { + if (encapContentInfoLen == 0 && + pkiMsg[idx-1] == ASN_INDEF_LENGTH) { isIndef = 1; } if (GetASNObjectId(pkiMsg, &idx, &length, pkiMsgSz) == 0) { @@ -4682,7 +4710,7 @@ static int PKCS7_VerifySignedData(PKCS7* pkcs7, const byte* hashBuf, if (ret != 0) break; - /* Check for content info, it could be omitted when degenerate */ + /* Check for content, it could be omitted when degenerate */ localIdx = idx; ret = 0; if (localIdx + 1 > pkiMsgSz) { @@ -4690,75 +4718,114 @@ static int PKCS7_VerifySignedData(PKCS7* pkcs7, const byte* hashBuf, break; } + /* Set error state if no more data left in ContentInfo, meaning + * no content - may be detached. Will recover from error below */ + if ((encapContentInfoLen != 0) && + (encapContentInfoLen - contentTypeSz == 0)) { + ret = ASN_PARSE_E; + } + + /* PKCS#7 spec: + * content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL + * CMS spec: + * eContent [0] EXPLICIT OCTET STRING OPTIONAL + */ if (ret == 0 && GetASNTag(pkiMsg, &localIdx, &tag, pkiMsgSz) != 0) ret = ASN_PARSE_E; if (ret == 0 && tag != (ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC | 0)) ret = ASN_PARSE_E; + /* Get length of inner eContent payload. For CMS, spec defines + * OCTET_STRING will be next. If so, we use the length retrieved + * there. PKCS#7 spec defines ANY as eContent type. In this case + * we fall back and save this content length for use later */ if (ret == 0 && GetLength_ex(pkiMsg, &localIdx, &length, pkiMsgSz, - NO_USER_CHECK) <= 0) + NO_USER_CHECK) <= 0) { ret = ASN_PARSE_E; + } if (localIdx >= pkiMsgSz) { ret = BUFFER_E; } + /* Save idx to back up in case of PKCS#7 eContent */ + start = localIdx; + /* get length of content in the case that there is multiple parts */ if (ret == 0 && GetASNTag(pkiMsg, &localIdx, &tag, pkiMsgSz) < 0) ret = ASN_PARSE_E; - if (ret == 0 && tag == (ASN_OCTET_STRING | ASN_CONSTRUCTED)) { - multiPart = 1; + if (ret == 0 && + (tag != (ASN_OCTET_STRING | ASN_CONSTRUCTED) && + (tag != ASN_OCTET_STRING))) { - /* Get length of all OCTET_STRINGs. */ - if (GetLength_ex(pkiMsg, &localIdx, &contentLen, pkiMsgSz, - NO_USER_CHECK) < 0) + /* If reached end of ContentInfo, or we see the next element + * ([0] IMPLICIT CertificateSet), set error state. Either + * true error or detached */ + if (tag == (ASN_CONSTRUCTED | ASN_CONTEXT_SPECIFIC | 0)) { ret = ASN_PARSE_E; - - /* Check whether there is one OCTET_STRING inside. */ - start = localIdx; - if (localIdx >= pkiMsgSz) { - ret = BUFFER_E; } - if (ret == 0 && GetASNTag(pkiMsg, &localIdx, &tag, pkiMsgSz) - != 0) - ret = ASN_PARSE_E; + /* Back up before getting tag, process as PKCS#7 ANY and use + * this as start of content. */ + localIdx = start; + pkcs7->contentIsPkcs7Type = 1; + } + else { + /* CMS eContent OCTET_STRING */ + if (ret == 0 && tag == (ASN_OCTET_STRING | ASN_CONSTRUCTED)) { + multiPart = 1; - if (ret == 0 && tag != ASN_OCTET_STRING) - ret = ASN_PARSE_E; + /* Get length of all OCTET_STRINGs. */ + if (GetLength_ex(pkiMsg, &localIdx, &contentLen, pkiMsgSz, + NO_USER_CHECK) < 0) + ret = ASN_PARSE_E; - if (ret == 0 && GetLength_ex(pkiMsg, &localIdx, &length, - pkiMsgSz, NO_USER_CHECK) < 0) - ret = ASN_PARSE_E; + /* Check whether there is one OCTET_STRING inside. */ + start = localIdx; + if (localIdx >= pkiMsgSz) { + ret = BUFFER_E; + } - if (ret == 0) { - /* Use single OCTET_STRING directly, or reset length. */ - if (localIdx - start + length == (word32)contentLen) { - multiPart = 0; - } else { - /* reset length to outer OCTET_STRING for bundle size - * check below */ - length = contentLen; + if (ret == 0 && GetASNTag(pkiMsg, &localIdx, &tag, pkiMsgSz) + != 0) + ret = ASN_PARSE_E; + + if (ret == 0 && tag != ASN_OCTET_STRING) + ret = ASN_PARSE_E; + + if (ret == 0 && GetLength_ex(pkiMsg, &localIdx, &length, + pkiMsgSz, NO_USER_CHECK) < 0) + ret = ASN_PARSE_E; + + if (ret == 0) { + /* Use single OCTET_STRING directly, or reset length. */ + if (localIdx - start + length == (word32)contentLen) { + multiPart = 0; + } else { + /* reset length to outer OCTET_STRING for bundle + * size check below */ + length = contentLen; + } + localIdx = start; } - localIdx = start; - } - if (ret != 0) { - /* failed ASN1 parsing during OCTET_STRING checks */ - break; + if (ret != 0) { + /* failed ASN1 parsing during OCTET_STRING checks */ + break; + } } - } - /* get length of content in case of single part */ - if (ret == 0 && !multiPart) { - if (tag != ASN_OCTET_STRING) - ret = ASN_PARSE_E; + /* get length of content in case of single part */ + if (ret == 0 && !multiPart) { + if (tag != ASN_OCTET_STRING) + ret = ASN_PARSE_E; - if (ret == 0 && GetLength_ex(pkiMsg, &localIdx, - &length, pkiMsgSz, NO_USER_CHECK) < 0) - ret = ASN_PARSE_E; + if (ret == 0 && GetLength_ex(pkiMsg, &localIdx, + &length, pkiMsgSz, NO_USER_CHECK) < 0) + ret = ASN_PARSE_E; + } } /* update idx if successful */ @@ -5111,6 +5178,7 @@ static int PKCS7_VerifySignedData(PKCS7* pkcs7, const byte* hashBuf, pkcs7->der = NULL; #endif version = pkcs7->version; + contentIsPkcs7Type = pkcs7->contentIsPkcs7Type; if (ret == 0) { byte isDynamic = (byte)pkcs7->isDynamic; @@ -5146,6 +5214,9 @@ static int PKCS7_VerifySignedData(PKCS7* pkcs7, const byte* hashBuf, contentDynamic = NULL; } + /* Restore content is PKCS#7 flag */ + pkcs7->contentIsPkcs7Type = contentIsPkcs7Type; + #ifndef NO_PKCS7_STREAM pkcs7->stream = stream; #endif diff --git a/wolfssl/wolfcrypt/pkcs7.h b/wolfssl/wolfcrypt/pkcs7.h index 8f28fdbae2..1e2733c83a 100644 --- a/wolfssl/wolfcrypt/pkcs7.h +++ b/wolfssl/wolfcrypt/pkcs7.h @@ -341,6 +341,7 @@ struct PKCS7 { byte* cachedEncryptedContent; word32 cachedEncryptedContentSz; word16 contentCRLF:1; /* have content line endings been converted to CRLF */ + word16 contentIsPkcs7Type:1; /* eContent follows PKCS#7 RFC not CMS */ /* !! NEW DATA MEMBERS MUST BE ADDED AT END !! */ };