/** * Verifies a counter signature. * * @returns IPRT status code. * @param pCounterSignerInfo The counter signature. * @param pPrimarySignerInfo The primary signature (can be a counter * signature too if nested). * @param pSignedData The SignedData. * @param fFlags Verficiation flags. * @param hAdditionalCerts Store containing optional certificates, * optional. * @param hTrustedCerts Store containing trusted certificates, required. * @param pValidationTime The time we're supposed to validate the * certificates chains at. * @param pfnVerifyCert Signing certificate verification callback. * @param fVccFlags Signing certificate verification callback flags. * @param pvUser Callback parameter. * @param pErrInfo Where to store additional error details, * optional. */ static int rtCrPkcs7VerifyCounterSignerInfo(PCRTCRPKCS7SIGNERINFO pCounterSignerInfo, PCRTCRPKCS7SIGNERINFO pPrimarySignerInfo, PCRTCRPKCS7SIGNEDDATA pSignedData, uint32_t fFlags, RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, PCRTTIMESPEC pValidationTime, PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, uint32_t fVccFlags, void *pvUser, PRTERRINFO pErrInfo) { /* * Calculate the digest we need to verify. */ RTCRDIGEST hDigest; int rc = RTCrDigestCreateByObjId(&hDigest, &pCounterSignerInfo->DigestAlgorithm.Algorithm); if (RT_SUCCESS(rc)) { rc = RTCrDigestUpdate(hDigest, pPrimarySignerInfo->EncryptedDigest.Asn1Core.uData.pv, pPrimarySignerInfo->EncryptedDigest.Asn1Core.cb); if (RT_SUCCESS(rc)) rc = RTCrDigestFinal(hDigest, NULL, 0); if (RT_SUCCESS(rc)) { /* * Pass it on to the common SignerInfo verifier function. */ rc = rtCrPkcs7VerifySignerInfo(pCounterSignerInfo, pSignedData, hDigest, fFlags | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE, hAdditionalCerts, hTrustedCerts, pValidationTime, pfnVerifyCert, fVccFlags, pvUser, pErrInfo); } else rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CALC_ERROR, "Hashing for counter signature failed unexpectedly: %Rrc", rc); RTCrDigestRelease(hDigest); } else rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CREATE_ERROR, "Error creating digest for '%s': %Rrc", pCounterSignerInfo->DigestAlgorithm.Algorithm.szObjId, rc); return rc; }
RTDECL(int) RTCrPkixPubKeyVerifySignature(PCRTASN1OBJID pAlgorithm, PCRTASN1DYNTYPE pParameters, PCRTASN1BITSTRING pPublicKey, PCRTASN1BITSTRING pSignatureValue, const void *pvData, size_t cbData, PRTERRINFO pErrInfo) { /* * Valid input. */ AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER); AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER); if (pParameters) { AssertPtrReturn(pParameters, VERR_INVALID_POINTER); if (pParameters->enmType == RTASN1TYPE_NULL) pParameters = NULL; } AssertPtrReturn(pPublicKey, VERR_INVALID_POINTER); AssertReturn(RTAsn1BitString_IsPresent(pPublicKey), VERR_INVALID_POINTER); AssertPtrReturn(pSignatureValue, VERR_INVALID_POINTER); AssertReturn(RTAsn1BitString_IsPresent(pSignatureValue), VERR_INVALID_POINTER); AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertReturn(cbData > 0, VERR_INVALID_PARAMETER); /* * Parameters are not currently supported (openssl code path). */ if (pParameters) return RTErrInfoSet(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_PARAMS_NOT_IMPL, "Cipher algorithm parameters are not yet supported."); /* * Validate using IPRT. */ RTCRPKIXSIGNATURE hSignature; int rcIprt = RTCrPkixSignatureCreateByObjId(&hSignature, pAlgorithm, false /*fSigning*/, pPublicKey, pParameters); if (RT_FAILURE(rcIprt)) return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, "Unknown public key algorithm [IPRT]: %s", pAlgorithm->szObjId); RTCRDIGEST hDigest; rcIprt = RTCrDigestCreateByObjId(&hDigest, pAlgorithm); if (RT_SUCCESS(rcIprt)) { /* Calculate the digest. */ rcIprt = RTCrDigestUpdate(hDigest, pvData, cbData); if (RT_SUCCESS(rcIprt)) { rcIprt = RTCrPkixSignatureVerifyBitString(hSignature, hDigest, pSignatureValue); if (RT_FAILURE(rcIprt)) RTErrInfoSet(pErrInfo, rcIprt, "RTCrPkixSignatureVerifyBitString failed"); } else RTErrInfoSet(pErrInfo, rcIprt, "RTCrDigestUpdate failed"); RTCrDigestRelease(hDigest); } else RTErrInfoSetF(pErrInfo, rcIprt, "Unknown digest algorithm [IPRT]: %s", pAlgorithm->szObjId); RTCrPkixSignatureRelease(hSignature); #ifdef IPRT_WITH_OPENSSL /* * Validate using OpenSSL EVP. */ rtCrOpenSslInit(); /* Translate the algorithm ID into a EVP message digest type pointer. */ int iAlgoNid = OBJ_txt2nid(pAlgorithm->szObjId); if (iAlgoNid == NID_undef) return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN, "Unknown public key algorithm [OpenSSL]: %s", pAlgorithm->szObjId); const char *pszAlogSn = OBJ_nid2sn(iAlgoNid); const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlogSn); if (!pEvpMdType) return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP, "EVP_get_digestbyname failed on %s (%s)", pszAlogSn, pAlgorithm->szObjId); /* Initialize the EVP message digest context. */ EVP_MD_CTX EvpMdCtx; EVP_MD_CTX_init(&EvpMdCtx); if (!EVP_VerifyInit_ex(&EvpMdCtx, pEvpMdType, NULL /*engine*/)) return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALOG_INIT_FAILED, "EVP_VerifyInit_ex failed (algorithm type is %s / %s)", pszAlogSn, pAlgorithm->szObjId); /* Create an EVP public key. */ int rcOssl; EVP_PKEY *pEvpPublicKey = EVP_PKEY_new(); if (pEvpPublicKey) { pEvpPublicKey->type = EVP_PKEY_type(pEvpMdType->required_pkey_type[0]); if (pEvpPublicKey->type != NID_undef) { const unsigned char *puchPublicKey = RTASN1BITSTRING_GET_BIT0_PTR(pPublicKey); if (d2i_PublicKey(pEvpPublicKey->type, &pEvpPublicKey, &puchPublicKey, RTASN1BITSTRING_GET_BYTE_SIZE(pPublicKey))) { /* Digest the data. */ EVP_VerifyUpdate(&EvpMdCtx, pvData, cbData); /* Verify the signature. */ if (EVP_VerifyFinal(&EvpMdCtx, RTASN1BITSTRING_GET_BIT0_PTR(pSignatureValue), RTASN1BITSTRING_GET_BYTE_SIZE(pSignatureValue), pEvpPublicKey) > 0) rcOssl = VINF_SUCCESS; else rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_VERIFY_FINAL_FAILED, "EVP_VerifyFinal failed"); } else rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_D2I_PUBLIC_KEY_FAILED, "d2i_PublicKey failed"); } else rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_type(%d) failed", pEvpMdType->required_pkey_type[0]); /* Cleanup and return.*/ EVP_PKEY_free(pEvpPublicKey); } else rcOssl = RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "EVP_PKEY_new(%d) failed", pEvpMdType->required_pkey_type[0]); EVP_MD_CTX_cleanup(&EvpMdCtx); /* * Check the result. */ if (RT_SUCCESS(rcIprt) && RT_SUCCESS(rcOssl)) return VINF_SUCCESS; if (RT_FAILURE_NP(rcIprt) && RT_FAILURE_NP(rcOssl)) return rcIprt; AssertMsgFailed(("rcIprt=%Rrc rcOssl=%Rrc\n", rcIprt, rcOssl)); if (RT_FAILURE_NP(rcOssl)) return rcOssl; #endif /* IPRT_WITH_OPENSSL */ return rcIprt; }
int main(int argc, char **argv) { RTR3InitExe(argc, &argv, 0); RTDIGESTTYPE enmDigestType = RTDIGESTTYPE_INVALID; const char *pszDigestType = "NotSpecified"; enum { kMethod_Full, kMethod_Block, kMethod_File, kMethod_CVAS } enmMethod = kMethod_Block; uint64_t offStart = 0; uint64_t cbMax = UINT64_MAX; bool fTestcase = false; static const RTGETOPTDEF s_aOptions[] = { { "--type", 't', RTGETOPT_REQ_STRING }, { "--method", 'm', RTGETOPT_REQ_STRING }, { "--help", 'h', RTGETOPT_REQ_NOTHING }, { "--length", 'l', RTGETOPT_REQ_UINT64 }, { "--offset", 'o', RTGETOPT_REQ_UINT64 }, { "--testcase", 'x', RTGETOPT_REQ_NOTHING }, }; int ch; RTGETOPTUNION ValueUnion; RTGETOPTSTATE GetState; RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, RTGETOPTINIT_FLAGS_OPTS_FIRST); while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 't': if (!RTStrICmp(ValueUnion.psz, "crc32")) { pszDigestType = "CRC32"; enmDigestType = RTDIGESTTYPE_CRC32; } else if (!RTStrICmp(ValueUnion.psz, "crc64")) { pszDigestType = "CRC64"; enmDigestType = RTDIGESTTYPE_CRC64; } else if (!RTStrICmp(ValueUnion.psz, "md2")) { pszDigestType = "MD2"; enmDigestType = RTDIGESTTYPE_MD2; } else if (!RTStrICmp(ValueUnion.psz, "md5")) { pszDigestType = "MD5"; enmDigestType = RTDIGESTTYPE_MD5; } else if (!RTStrICmp(ValueUnion.psz, "sha1")) { pszDigestType = "SHA-1"; enmDigestType = RTDIGESTTYPE_SHA1; } else if (!RTStrICmp(ValueUnion.psz, "sha224")) { pszDigestType = "SHA-224"; enmDigestType = RTDIGESTTYPE_SHA224; } else if (!RTStrICmp(ValueUnion.psz, "sha256")) { pszDigestType = "SHA-256"; enmDigestType = RTDIGESTTYPE_SHA256; } else if (!RTStrICmp(ValueUnion.psz, "sha384")) { pszDigestType = "SHA-384"; enmDigestType = RTDIGESTTYPE_SHA384; } else if (!RTStrICmp(ValueUnion.psz, "sha512")) { pszDigestType = "SHA-512"; enmDigestType = RTDIGESTTYPE_SHA512; } else if (!RTStrICmp(ValueUnion.psz, "sha512/224")) { pszDigestType = "SHA-512/224"; enmDigestType = RTDIGESTTYPE_SHA512T224; } else if (!RTStrICmp(ValueUnion.psz, "sha512/256")) { pszDigestType = "SHA-512/256"; enmDigestType = RTDIGESTTYPE_SHA512T256; } else { Error("Invalid digest type: %s\n", ValueUnion.psz); return 1; } break; case 'm': if (!RTStrICmp(ValueUnion.psz, "full")) enmMethod = kMethod_Full; else if (!RTStrICmp(ValueUnion.psz, "block")) enmMethod = kMethod_Block; else if (!RTStrICmp(ValueUnion.psz, "file")) enmMethod = kMethod_File; else if (!RTStrICmp(ValueUnion.psz, "cvas")) enmMethod = kMethod_CVAS; else { Error("Invalid digest method: %s\n", ValueUnion.psz); return 1; } break; case 'l': cbMax = ValueUnion.u64; break; case 'o': offStart = ValueUnion.u64; break; case 'x': fTestcase = true; break; case 'h': RTPrintf("usage: tstRTDigest -t <digest-type> [-o <offset>] [-l <length>] [-x] file [file2 [..]]\n"); return 1; case VINF_GETOPT_NOT_OPTION: { if (enmDigestType == RTDIGESTTYPE_INVALID) return Error("No digest type was specified\n"); switch (enmMethod) { case kMethod_Full: return Error("Full file method is not implemented\n"); case kMethod_File: if (offStart != 0 || cbMax != UINT64_MAX) return Error("The -l and -o options do not work with the 'file' method."); switch (enmDigestType) { case RTDIGESTTYPE_SHA1: { char *pszDigest; int rc = RTSha1DigestFromFile(ValueUnion.psz, &pszDigest, NULL, NULL); if (RT_FAILURE(rc)) return Error("RTSha1Digest(%s,) -> %Rrc\n", ValueUnion.psz, rc); RTPrintf("%s %s\n", pszDigest, ValueUnion.psz); RTStrFree(pszDigest); break; } case RTDIGESTTYPE_SHA256: { char *pszDigest; int rc = RTSha256DigestFromFile(ValueUnion.psz, &pszDigest, NULL, NULL); if (RT_FAILURE(rc)) return Error("RTSha256Digest(%s,) -> %Rrc\n", ValueUnion.psz, rc); RTPrintf("%s %s\n", pszDigest, ValueUnion.psz); RTStrFree(pszDigest); break; } default: return Error("The file method isn't implemented for this digest\n"); } break; case kMethod_Block: { RTFILE hFile; int rc = RTFileOpen(&hFile, ValueUnion.psz, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); if (RT_FAILURE(rc)) return Error("RTFileOpen(,%s,) -> %Rrc\n", ValueUnion.psz, rc); if (offStart != 0) { rc = RTFileSeek(hFile, offStart, RTFILE_SEEK_BEGIN, NULL); if (RT_FAILURE(rc)) return Error("RTFileSeek(%s,%ull) -> %Rrc\n", ValueUnion.psz, offStart, rc); } uint64_t cbMaxLeft = cbMax; size_t cbRead; uint8_t abBuf[_64K]; char *pszDigest = (char *)&abBuf[0]; switch (enmDigestType) { case RTDIGESTTYPE_CRC32: { uint32_t uCRC32 = RTCrc32Start(); for (;;) { rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); if (RT_FAILURE(rc) || !cbRead) break; uCRC32 = RTCrc32Process(uCRC32, abBuf, cbRead); } uCRC32 = RTCrc32Finish(uCRC32); RTStrPrintf(pszDigest, sizeof(abBuf), "%08RX32", uCRC32); break; } case RTDIGESTTYPE_CRC64: { uint64_t uCRC64 = RTCrc64Start(); for (;;) { rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); if (RT_FAILURE(rc) || !cbRead) break; uCRC64 = RTCrc64Process(uCRC64, abBuf, cbRead); } uCRC64 = RTCrc64Finish(uCRC64); RTStrPrintf(pszDigest, sizeof(abBuf), "%016RX64", uCRC64); break; } case RTDIGESTTYPE_MD2: { RTMD2CONTEXT Ctx; RTMd2Init(&Ctx); for (;;) { rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); if (RT_FAILURE(rc) || !cbRead) break; RTMd2Update(&Ctx, abBuf, cbRead); } uint8_t abDigest[RTMD2_HASH_SIZE]; RTMd2Final(&Ctx, abDigest); RTMd2ToString(abDigest, pszDigest, sizeof(abBuf)); break; } case RTDIGESTTYPE_MD5: { RTMD5CONTEXT Ctx; RTMd5Init(&Ctx); for (;;) { rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); if (RT_FAILURE(rc) || !cbRead) break; RTMd5Update(&Ctx, abBuf, cbRead); } uint8_t abDigest[RTMD5HASHSIZE]; RTMd5Final(abDigest, &Ctx); RTMd5ToString(abDigest, pszDigest, sizeof(abBuf)); break; } case RTDIGESTTYPE_SHA1: { RTSHA1CONTEXT Ctx; RTSha1Init(&Ctx); for (;;) { rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); if (RT_FAILURE(rc) || !cbRead) break; RTSha1Update(&Ctx, abBuf, cbRead); } uint8_t abDigest[RTSHA1_HASH_SIZE]; RTSha1Final(&Ctx, abDigest); RTSha1ToString(abDigest, pszDigest, sizeof(abBuf)); break; } case RTDIGESTTYPE_SHA256: { RTSHA256CONTEXT Ctx; RTSha256Init(&Ctx); for (;;) { rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); if (RT_FAILURE(rc) || !cbRead) break; RTSha256Update(&Ctx, abBuf, cbRead); } uint8_t abDigest[RTSHA256_HASH_SIZE]; RTSha256Final(&Ctx, abDigest); RTSha256ToString(abDigest, pszDigest, sizeof(abBuf)); break; } case RTDIGESTTYPE_SHA512: { RTSHA512CONTEXT Ctx; RTSha512Init(&Ctx); for (;;) { rc = MyReadFile(hFile, abBuf, sizeof(abBuf), &cbRead, &cbMaxLeft); if (RT_FAILURE(rc) || !cbRead) break; RTSha512Update(&Ctx, abBuf, cbRead); } uint8_t abDigest[RTSHA512_HASH_SIZE]; RTSha512Final(&Ctx, abDigest); RTSha512ToString(abDigest, pszDigest, sizeof(abBuf)); break; } default: return Error("Internal error #1\n"); } RTFileClose(hFile); if (RT_FAILURE(rc) && rc != VERR_EOF) { RTPrintf("Partial: %s %s\n", pszDigest, ValueUnion.psz); return Error("RTFileRead(%s) -> %Rrc\n", ValueUnion.psz, rc); } if (!fTestcase) RTPrintf("%s %s\n", pszDigest, ValueUnion.psz); else if (offStart) RTPrintf(" { &g_abRandom72KB[%#4llx], %5llu, \"%s\", \"%s %llu bytes @%llu\" },\n", offStart, cbMax - cbMaxLeft, pszDigest, pszDigestType, offStart, cbMax - cbMaxLeft); else RTPrintf(" { &g_abRandom72KB[0], %5llu, \"%s\", \"%s %llu bytes\" },\n", cbMax - cbMaxLeft, pszDigest, pszDigestType, cbMax - cbMaxLeft); break; } /* * Process a SHS response file: * http://csrc.nist.gov/groups/STM/cavp/index.html#03 */ case kMethod_CVAS: { RTCRDIGEST hDigest; int rc = RTCrDigestCreateByType(&hDigest, enmDigestType); if (RT_FAILURE(rc)) return Error("Failed to create digest calculator for %s: %Rrc", pszDigestType, rc); uint32_t const cbDigest = RTCrDigestGetHashSize(hDigest); if (!cbDigest || cbDigest >= _1K) return Error("Unexpected hash size: %#x\n", cbDigest); PRTSTREAM pFile; rc = RTStrmOpen(ValueUnion.psz, "r", &pFile); if (RT_FAILURE(rc)) return Error("Failed to open CVAS file '%s': %Rrc", ValueUnion.psz, rc); /* * Parse the input file. * ASSUME order: Len, Msg, MD. */ static char s_szLine[_256K]; char *psz; uint32_t cPassed = 0; uint32_t cErrors = 0; uint32_t iLine = 1; for (;;) { psz = MyGetNextSignificantLine(pFile, s_szLine, sizeof(s_szLine), &iLine, &rc); if (!psz) break; /* Skip [L = 20] stuff. */ if (*psz == '[') continue; /* Message length. */ uint64_t cMessageBits; if (RTStrNICmp(psz, RT_STR_TUPLE("Len ="))) return Error("%s(%d): Expected 'Len =' found '%.10s...'", ValueUnion.psz, iLine, psz); psz = RTStrStripL(psz + 5); rc = RTStrToUInt64Full(psz, 0, &cMessageBits); if (rc != VINF_SUCCESS) return Error("%s(%d): Error parsing length '%s': %Rrc\n", ValueUnion.psz, iLine, psz, rc); /* The message text. */ psz = MyGetNextSignificantLine(pFile, s_szLine, sizeof(s_szLine), &iLine, &rc); if (!psz) return Error("%s(%d): Expected message text not EOF.", ValueUnion.psz, iLine); if (RTStrNICmp(psz, RT_STR_TUPLE("Msg ="))) return Error("%s(%d): Expected 'Msg =' found '%.10s...'", ValueUnion.psz, iLine, psz); psz = RTStrStripL(psz + 5); size_t const cbMessage = (cMessageBits + 7) / 8; static uint8_t s_abMessage[sizeof(s_szLine) / 2]; if (cbMessage > 0) { rc = RTStrConvertHexBytes(psz, s_abMessage, cbMessage, 0 /*fFlags*/); if (rc != VINF_SUCCESS) return Error("%s(%d): Error parsing message '%.10s...': %Rrc\n", ValueUnion.psz, iLine, psz, rc); } /* The message digest. */ psz = MyGetNextSignificantLine(pFile, s_szLine, sizeof(s_szLine), &iLine, &rc); if (!psz) return Error("%s(%d): Expected message digest not EOF.", ValueUnion.psz, iLine); if (RTStrNICmp(psz, RT_STR_TUPLE("MD ="))) return Error("%s(%d): Expected 'MD =' found '%.10s...'", ValueUnion.psz, iLine, psz); psz = RTStrStripL(psz + 4); static uint8_t s_abExpectedDigest[_1K]; rc = RTStrConvertHexBytes(psz, s_abExpectedDigest, cbDigest, 0 /*fFlags*/); if (rc != VINF_SUCCESS) return Error("%s(%d): Error parsing message digest '%.10s...': %Rrc\n", ValueUnion.psz, iLine, psz, rc); /* * Do the testing. */ rc = RTCrDigestReset(hDigest); if (rc != VINF_SUCCESS) return Error("RTCrDigestReset failed: %Rrc", rc); rc = RTCrDigestUpdate(hDigest, s_abMessage, cbMessage); if (rc != VINF_SUCCESS) return Error("RTCrDigestUpdate failed: %Rrc", rc); static uint8_t s_abActualDigest[_1K]; rc = RTCrDigestFinal(hDigest, s_abActualDigest, cbDigest); if (rc != VINF_SUCCESS) return Error("RTCrDigestFinal failed: %Rrc", rc); if (memcmp(s_abActualDigest, s_abExpectedDigest, cbDigest) == 0) cPassed++; else { Error("%s(%d): Message digest mismatch. Expected %.*RThxs, got %.*RThxs.", ValueUnion.psz, iLine, cbDigest, s_abExpectedDigest, cbDigest, s_abActualDigest); cErrors++; } } RTStrmClose(pFile); if (cErrors > 0) return Error("Failed: %u error%s (%u passed)", cErrors, cErrors == 1 ? "" : "s", cPassed); RTPrintf("Passed %u test%s.\n", cPassed, cPassed == 1 ? "" : "s"); if (RT_FAILURE(rc)) return Error("Failed: %Rrc", rc); break; } default: return Error("Internal error #2\n"); } break; } default: return RTGetOptPrintError(ch, &ValueUnion); } } return 0; }
RTDECL(int) RTCrPkcs7VerifySignedData(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags, RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, PCRTTIMESPEC pValidationTime, PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser, PRTERRINFO pErrInfo) { /* * Check the input. */ if (pfnVerifyCert) AssertPtrReturn(pfnVerifyCert, VERR_INVALID_POINTER); else pfnVerifyCert = RTCrPkcs7VerifyCertCallbackDefault; if (!RTCrPkcs7ContentInfo_IsSignedData(pContentInfo)) return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_NOT_SIGNED_DATA, "Not PKCS #7 SignedData."); PCRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData; int rc = RTCrPkcs7SignedData_CheckSanity(pSignedData, 0, pErrInfo, ""); if (RT_FAILURE(rc)) return rc; /* * Hash the content info. */ /* Exactly what the content is, for some stupid reason unnecessarily complicated. Figure it out here as we'll need it for the OpenSSL code path as well. */ void const *pvContent = pSignedData->ContentInfo.Content.Asn1Core.uData.pv; uint32_t cbContent = pSignedData->ContentInfo.Content.Asn1Core.cb; if (pSignedData->ContentInfo.Content.pEncapsulated) { pvContent = pSignedData->ContentInfo.Content.pEncapsulated->uData.pv; cbContent = pSignedData->ContentInfo.Content.pEncapsulated->cb; } /* Check that there aren't too many or too few hash algorithms for our implementation and purposes. */ RTCRDIGEST ahDigests[2]; uint32_t const cDigests = pSignedData->DigestAlgorithms.cItems; if (!cDigests) /** @todo we might have to support this... */ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NO_DIGEST_ALGORITHMS, "No digest algorithms"); if (cDigests > RT_ELEMENTS(ahDigests)) return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_TOO_MANY_DIGEST_ALGORITHMS, "Too many digest algorithm: cAlgorithms=%u", cDigests); /* Create the message digest calculators. */ rc = VERR_CR_PKCS7_NO_DIGEST_ALGORITHMS; uint32_t i; for (i = 0; i < cDigests; i++) { rc = RTCrDigestCreateByObjId(&ahDigests[i], &pSignedData->DigestAlgorithms.paItems[i].Algorithm); if (RT_FAILURE(rc)) { rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CREATE_ERROR, "Error creating digest for '%s': %Rrc", pSignedData->DigestAlgorithms.paItems[i].Algorithm.szObjId, rc); break; } } if (RT_SUCCESS(rc)) { /* Hash the content. */ for (i = 0; i < cDigests && RT_SUCCESS(rc); i++) { rc = RTCrDigestUpdate(ahDigests[i], pvContent, cbContent); if (RT_SUCCESS(rc)) rc = RTCrDigestFinal(ahDigests[i], NULL, 0); } if (RT_SUCCESS(rc)) { /* * Validate the signed infos. */ uint32_t fPrimaryVccFlags = !(fFlags & RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING) ? RTCRPKCS7VCC_F_SIGNED_DATA : RTCRPKCS7VCC_F_TIMESTAMP; rc = VERR_CR_PKCS7_NO_SIGNER_INFOS; for (i = 0; i < pSignedData->SignerInfos.cItems; i++) { PCRTCRPKCS7SIGNERINFO pSignerInfo = &pSignedData->SignerInfos.paItems[i]; RTCRDIGEST hThisDigest = NIL_RTCRDIGEST; /* (gcc maybe incredible stupid.) */ rc = rtCrPkcs7VerifyFindDigest(&hThisDigest, pSignedData, pSignerInfo, ahDigests, pErrInfo); if (RT_FAILURE(rc)) break; /* * See if we can find a trusted signing time. * (Note that while it would make sense splitting up this function, * we need to carry a lot of arguments around, so better not.) */ bool fDone = false; PCRTCRPKCS7SIGNERINFO pSigningTimeSigner = NULL; PCRTASN1TIME pSignedTime; while ( !fDone && (pSignedTime = RTCrPkcs7SignerInfo_GetSigningTime(pSignerInfo, &pSigningTimeSigner)) != NULL) { RTTIMESPEC ThisValidationTime; if (RT_LIKELY(RTTimeImplode(&ThisValidationTime, &pSignedTime->Time))) { if (pSigningTimeSigner == pSignerInfo) { if (fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY) continue; rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, hTrustedCerts, &ThisValidationTime, pfnVerifyCert, fPrimaryVccFlags | RTCRPKCS7VCC_F_TIMESTAMP, pvUser, pErrInfo); } else { rc = VINF_SUCCESS; if (!(fFlags & RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED)) rc = rtCrPkcs7VerifyCounterSignerInfo(pSigningTimeSigner, pSignerInfo, pSignedData, fFlags, hAdditionalCerts, hTrustedCerts, &ThisValidationTime, pfnVerifyCert, RTCRPKCS7VCC_F_TIMESTAMP, pvUser, pErrInfo); if (RT_SUCCESS(rc)) rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, hTrustedCerts, &ThisValidationTime, pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo); } fDone = RT_SUCCESS(rc) || (fFlags & RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT); } else { rc = RTErrInfoSet(pErrInfo, VERR_INTERNAL_ERROR_3, "RTTimeImplode failed"); fDone = true; } } /* * If not luck, check for microsoft timestamp counter signatures. */ if (!fDone && !(fFlags & RTCRPKCS7VERIFY_SD_F_IGNORE_MS_TIMESTAMP)) { PCRTCRPKCS7CONTENTINFO pSignedTimestamp = NULL; pSignedTime = RTCrPkcs7SignerInfo_GetMsTimestamp(pSignerInfo, &pSignedTimestamp); if (pSignedTime) { RTTIMESPEC ThisValidationTime; if (RT_LIKELY(RTTimeImplode(&ThisValidationTime, &pSignedTime->Time))) { rc = VINF_SUCCESS; if (!(fFlags & RTCRPKCS7VERIFY_SD_F_USE_MS_TIMESTAMP_UNVERIFIED)) rc = RTCrPkcs7VerifySignedData(pSignedTimestamp, fFlags | RTCRPKCS7VERIFY_SD_F_IGNORE_MS_TIMESTAMP | RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING, hAdditionalCerts, hTrustedCerts, &ThisValidationTime, pfnVerifyCert, pvUser, pErrInfo); if (RT_SUCCESS(rc)) rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, hTrustedCerts, &ThisValidationTime, pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo); fDone = RT_SUCCESS(rc) || (fFlags & RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT); } else { rc = RTErrInfoSet(pErrInfo, VERR_INTERNAL_ERROR_3, "RTTimeImplode failed"); fDone = true; } } } /* * No valid signing time found, use the one specified instead. */ if (!fDone) rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, hTrustedCerts, pValidationTime, pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo); RTCrDigestRelease(hThisDigest); if (RT_FAILURE(rc)) break; } } else rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CALC_ERROR, "Hashing content failed unexpectedly (i=%u): %Rrc", i, rc); /* Clean up digests. */ i = cDigests; } while (i-- > 0) { int rc2 = RTCrDigestRelease(ahDigests[i]); AssertRC(rc2); } #ifdef IPRT_WITH_OPENSSL /* * Verify using OpenSSL and combine the results (should be identical). */ /** @todo figure out how to verify MS timstamp signatures using OpenSSL. */ if (fFlags & RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING) return rc; int rcOssl = rtCrPkcs7VerifySignedDataUsingOpenSsl(pContentInfo, fFlags, hAdditionalCerts, hTrustedCerts, pvContent, cbContent, RT_SUCCESS(rc) ? pErrInfo : NULL); if (RT_SUCCESS(rcOssl) && RT_SUCCESS(rc)) return rc; // AssertMsg(RT_FAILURE_NP(rcOssl) && RT_FAILURE_NP(rc), ("%Rrc, %Rrc\n", rcOssl, rc)); if (RT_FAILURE(rc)) return rc; return rcOssl; #else return rc; #endif }
/** * Deals with authenticated attributes. * * When authenticated attributes are present (checked by caller) we must: * - fish out the content type and check it against the content inof, * - fish out the message digest among and check it against *phDigest, * - compute the message digest of the authenticated attributes and * replace *phDigest with this for the signature verification. * * @returns IPRT status code. * @param pSignerInfo The signer info being verified. * @param pSignedData The signed data. * @param phDigest On input this is the digest of the content. On * output it will (on success) be a reference to * the message digest of the authenticated * attributes. The input reference is consumed. * The caller shall release the output reference. * @param fFlags Flags. * @param pErrInfo Extended error info, optional. */ static int rtCrPkcs7VerifySignerInfoAuthAttribs(PCRTCRPKCS7SIGNERINFO pSignerInfo, PCRTCRPKCS7SIGNEDDATA pSignedData, PRTCRDIGEST phDigest, uint32_t fFlags, PRTERRINFO pErrInfo) { /* * Scan the attributes and validate the two required attributes * (RFC-2315, chapter 9.2, fourth bullet). Checking that we've got exactly * one of each of them is checked by the santiy checker function, so we'll * just assert that it did it's job here. */ uint32_t cContentTypes = 0; uint32_t cMessageDigests = 0; uint32_t i = pSignerInfo->AuthenticatedAttributes.cItems; while (i-- > 0) { PCRTCRPKCS7ATTRIBUTE pAttrib = &pSignerInfo->AuthenticatedAttributes.paItems[i]; if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0) { AssertReturn(!cContentTypes, VERR_CR_PKCS7_INTERNAL_ERROR); AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS, VERR_CR_PKCS7_INTERNAL_ERROR); AssertReturn(pAttrib->uValues.pObjIds->cItems == 1, VERR_CR_PKCS7_INTERNAL_ERROR); if ( !(fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE) /* See note about microsoft below. */ && RTAsn1ObjId_Compare(&pAttrib->uValues.pObjIds->paItems[0], &pSignedData->ContentInfo.ContentType) != 0) return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_CONTENT_TYPE_ATTRIB_MISMATCH, "Expected content-type %s, found %s", &pAttrib->uValues.pObjIds->paItems[0], pSignedData->ContentInfo.ContentType.szObjId); cContentTypes++; } else if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID) == 0) { AssertReturn(!cMessageDigests, VERR_CR_PKCS7_INTERNAL_ERROR); AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS, VERR_CR_PKCS7_INTERNAL_ERROR); AssertReturn(pAttrib->uValues.pOctetStrings->cItems == 1, VERR_CR_PKCS7_INTERNAL_ERROR); if (!RTCrDigestMatch(*phDigest, pAttrib->uValues.pOctetStrings->paItems[0].Asn1Core.uData.pv, pAttrib->uValues.pOctetStrings->paItems[0].Asn1Core.cb)) { size_t cbHash = RTCrDigestGetHashSize(*phDigest); if (cbHash != pAttrib->uValues.pOctetStrings->paItems[0].Asn1Core.cb) return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH, "Authenticated message-digest attribute mismatch: cbHash=%#zx cbValue=%#x", cbHash, pAttrib->uValues.pOctetStrings->paItems[0].Asn1Core.cb); return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH, "Authenticated message-digest attribute mismatch (cbHash=%#zx):\n" "signed: %.*Rhxs\n" "our: %.*Rhxs\n", cbHash, cbHash, pAttrib->uValues.pOctetStrings->paItems[0].Asn1Core.uData.pv, cbHash, RTCrDigestGetHash(*phDigest)); } cMessageDigests++; } } /* * Full error reporting here as we don't currently extensively santiy check * counter signatures. * Note! Microsoft includes content info in their timestamp counter signatures, * at least for vista, despite the RFC-3852 stating counter signatures * "MUST NOT contain a content-type". */ if (RT_UNLIKELY( cContentTypes != 1 && !(fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE))) return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB, "Missing authenticated content-type attribute."); if (RT_UNLIKELY(cMessageDigests != 1)) return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_MESSAGE_DIGEST_ATTRIB, "Missing authenticated message-digest attribute."); /* * Calculate the digest of the the authenticated attributes for use in the * signature validation. */ if ( pSignerInfo->DigestAlgorithm.Parameters.enmType != RTASN1TYPE_NULL && pSignerInfo->DigestAlgorithm.Parameters.enmType != RTASN1TYPE_NOT_PRESENT) return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_DIGEST_PARAMS_NOT_IMPL, "Digest algorithm has unsupported parameters"); RTCRDIGEST hDigest; int rc = RTCrDigestCreateByObjId(&hDigest, &pSignerInfo->DigestAlgorithm.Algorithm); if (RT_SUCCESS(rc)) { RTCrDigestRelease(*phDigest); *phDigest = hDigest; /* ASSUMES that the attributes are encoded according to DER. */ uint8_t const *pbData = (uint8_t const *)RTASN1CORE_GET_RAW_ASN1_PTR(&pSignerInfo->AuthenticatedAttributes.SetCore.Asn1Core); uint32_t cbData = RTASN1CORE_GET_RAW_ASN1_SIZE(&pSignerInfo->AuthenticatedAttributes.SetCore.Asn1Core); uint8_t bSetOfTag = ASN1_TAG_SET | ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED; rc = RTCrDigestUpdate(hDigest, &bSetOfTag, sizeof(bSetOfTag)); /* Replace the implict tag with a SET-OF tag. */ if (RT_SUCCESS(rc)) rc = RTCrDigestUpdate(hDigest, pbData + sizeof(bSetOfTag), cbData - sizeof(bSetOfTag)); /* Skip the implicit tag. */ if (RT_SUCCESS(rc)) rc = RTCrDigestFinal(hDigest, NULL, 0); } return rc; }