/* Copyright (c) Microsoft Corporation */ #include "stdinc.h" #include "sxsp.h" #include "imagehlp.h" #include "windows.h" #include "hashfile.h" #include "wincrypt.h" #include "winbase.h" #include "softpub.h" #include "strongname.h" #include "fusioneventlog.h" #include "sxsp.h" BOOL SxspImageDigesterFunc( DIGEST_HANDLE hSomething, PBYTE pbDataBlock, DWORD dwLength); BOOL SxspSimpleHashRoutine(CFusionHash &rhHash, HANDLE hFile); BOOL SxspImageHashRoutine(CFusionHash &rhHash, HANDLE hFile, BOOL &bInvalidImage); CRITICAL_SECTION g_csHashFile; struct _HASH_ALG_NAME_MAP { PWSTR wsName; ULONG cchName; ALG_ID cId; } HashAlgNameMap[] = { { L"SHA1", 4, CALG_SHA1 }, { L"SHA", 3, CALG_SHA }, { L"MD5", 3, CALG_MD5 }, { L"MD4", 3, CALG_MD4 }, { L"MD2", 3, CALG_MD2 }, { L"MAC", 3, CALG_MAC }, { L"HMAC", 4, CALG_HMAC } }; BOOL SxspEnumKnownHashTypes( DWORD dwIndex, OUT CBaseStringBuffer &rbuffHashTypeName, BOOL &rbNoMoreItems ) { FN_PROLOG_WIN32 rbNoMoreItems = FALSE; if ( dwIndex >= NUMBER_OF( HashAlgNameMap ) ) { rbNoMoreItems = TRUE; } else { IFW32FALSE_EXIT( rbuffHashTypeName.Win32Assign( HashAlgNameMap[dwIndex].wsName, HashAlgNameMap[dwIndex].cchName ) ); } FN_EPILOG } BOOL SxspHashAlgFromString( const CBaseStringBuffer &strAlgName, ALG_ID &algId ) { FN_PROLOG_WIN32 SIZE_T idx; for (idx = 0; idx < NUMBER_OF(HashAlgNameMap); idx++) { if (::FusionpCompareStrings( strAlgName, strAlgName.Cch(), HashAlgNameMap[idx].wsName, HashAlgNameMap[idx].cchName, false) == 0) { algId = HashAlgNameMap[idx].cId; break; } } if (idx == NUMBER_OF(HashAlgNameMap)) ORIGINATE_WIN32_FAILURE_AND_EXIT(HashAlgDoesNotMatch, ERROR_SXS_MANIFEST_PARSE_ERROR); FN_EPILOG } BOOL SxspHashStringFromAlg( ALG_ID algId, CBaseStringBuffer &strAlgName ) { FN_PROLOG_WIN32 SIZE_T idx; strAlgName.Clear(); for (idx = 0; idx < NUMBER_OF(HashAlgNameMap); idx++) { if (HashAlgNameMap[idx].cId == algId) { IFW32FALSE_EXIT(strAlgName.Win32Assign(HashAlgNameMap[idx].wsName, HashAlgNameMap[idx].cchName)); break; } } PARAMETER_CHECK(idx != NUMBER_OF(HashAlgNameMap)); FN_EPILOG } BOOL SxspCheckHashDuringInstall( BOOL fHasHashData, const CBaseStringBuffer &rbuffFile, const CBaseStringBuffer &rbuffHashDataString, ALG_ID HashAlgId, HashValidateResult &rHashValid ) { FN_PROLOG_WIN32 rHashValid = HashValidate_OtherProblems; #if DBG ::FusionpDbgPrintEx( FUSION_DBG_LEVEL_INFO, "SXS.DLL: %s - Validating install-time hash: File=%ls tHasHash=%s tAlgId=0x%08x\n\tHash=%ls\n", __FUNCTION__, static_cast(rbuffFile), fHasHashData ? "yes" : "no", HashAlgId, static_cast(rbuffHashDataString)); #endif if (fHasHashData) { CFusionArray rgbHashData; IFW32FALSE_EXIT(rgbHashData.Win32Initialize()); IFW32FALSE_EXIT( ::SxspHashStringToBytes( rbuffHashDataString, rbuffHashDataString.Cch(), rgbHashData)); IFW32FALSE_EXIT( ::SxspVerifyFileHash( 0, rbuffFile, rgbHashData, HashAlgId, rHashValid)); } else { // // If there's no hash data, or we're in OS setup mode, then the hash of the // file is "implicitly" correct. // rHashValid = HashValidate_Matches; } FN_EPILOG } BOOL SxspCreateFileHash( DWORD dwFlags, ALG_ID PreferredAlgorithm, const CBaseStringBuffer &pwsFileName, CFusionArray &rgbHashDestination ) /*++ Purpose: Parameters: Returns: --*/ { FN_PROLOG_WIN32 CFusionFile hFile; CFusionHash hCurrentHash; // Initialization hFile = INVALID_HANDLE_VALUE; PARAMETER_CHECK((dwFlags & ~HASHFLAG_VALID_PARAMS) == 0); // // First try and open the file. No sense in doing anything else if we // can't get to the data to start with. Use a very friendly set of // rights to check the file. Future users might want to be sure that // you're in the right security context before doing this - system // level to check system files, etc. // IFW32FALSE_EXIT(hFile.Win32CreateFile(pwsFileName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING)); // // We'll be using SHA1 for the file hash // IFW32FALSE_EXIT(hCurrentHash.Win32Initialize(CALG_SHA1)); // // So first try hashing it via the image, and if that fails, try the // normal file-reading hash routine instead. // if (dwFlags & HASHFLAG_AUTODETECT) { BOOL fInvalidImage; IFW32FALSE_EXIT(::SxspImageHashRoutine(hCurrentHash, hFile, fInvalidImage)); if (fInvalidImage) IFW32FALSE_EXIT(::SxspSimpleHashRoutine(hCurrentHash, hFile)); } else if (dwFlags & HASHFLAG_STRAIGHT_HASH) { IFW32FALSE_EXIT(::SxspSimpleHashRoutine(hCurrentHash, hFile)); } else if (dwFlags & HASHFLAG_PROCESS_IMAGE) { BOOL fInvalidImage; IFW32FALSE_EXIT(::SxspImageHashRoutine(hCurrentHash, hFile, fInvalidImage)); if (fInvalidImage) ORIGINATE_WIN32_FAILURE_AND_EXIT(SxspCreateFileHash, ERROR_INVALID_PARAMETER); } // // We know the buffer is the right size, so we just call down to the hash parameter // getter, which will be smart and bop out (setting the pdwDestinationSize parameter) // if the user passed an incorrect parameter. // IFW32FALSE_EXIT(hCurrentHash.Win32GetValue(rgbHashDestination)); FN_EPILOG } BOOL SxspImageDigesterFunc( DIGEST_HANDLE hSomething, PBYTE pbDataBlock, DWORD dwLength ) { FN_PROLOG_WIN32 CFusionHash* pHashObject = reinterpret_cast(hSomething); if (pHashObject != NULL) IFW32FALSE_EXIT(pHashObject->Win32HashData(pbDataBlock, dwLength)); FN_EPILOG } BOOL SxspSimpleHashRoutine( CFusionHash &rhHash, HANDLE hFile ) { FN_PROLOG_WIN32 DWORD dwDataRead; BOOL fKeepReading = TRUE; BOOL b = FALSE; CFusionArray srgbBuffer; IFW32FALSE_EXIT( srgbBuffer.Win32SetSize( 64 * 1024 ) ); while (fKeepReading) { IFW32FALSE_ORIGINATE_AND_EXIT(::ReadFile(hFile, srgbBuffer.GetArrayPtr(), srgbBuffer.GetSizeAsDWORD(), &dwDataRead, NULL)); // // if we're out of data, quit. // if (dwDataRead == 0) { fKeepReading = FALSE; continue; } // // If we've gotten this far, we need to add the data found // to our existing hash // IFW32FALSE_EXIT(rhHash.Win32HashData(srgbBuffer.GetArrayPtr(), dwDataRead)); } FN_EPILOG } BOOL SxspImageHashRoutine( CFusionHash &rhHash, HANDLE hFile, BOOL &rfInvalidImage ) { FN_PROLOG_WIN32 CSxsLockCriticalSection lock(g_csHashFile); rfInvalidImage = FALSE; PARAMETER_CHECK((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)); // The ImageGetDigestStream() function is not thread safe, so we have to ensure that it's // not called by other threads while we're using it. IFW32FALSE_EXIT(lock.Lock()); IFW32FALSE_EXIT_UNLESS( ::ImageGetDigestStream( hFile, CERT_PE_IMAGE_DIGEST_ALL_IMPORT_INFO, &SxspImageDigesterFunc, (DIGEST_HANDLE)(&rhHash)), (::FusionpGetLastWin32Error() == ERROR_INVALID_PARAMETER), rfInvalidImage); lock.Unlock(); FN_EPILOG } BOOL SxspVerifyFileHash( const DWORD dwFlags, const CBaseStringBuffer &hsFullFilePath, const CFusionArray &rsrgbTheoreticalHash, ALG_ID whichAlg, HashValidateResult &HashValid ) { FN_PROLOG_WIN32 CFusionArray bGotHash; HashValid = HashValidate_OtherProblems; BOOL fFileNotFoundError; LONG ulRetriesLeft = 0; LONG ulBackoffAmount = 1000; LONG ulBackoffAmountCap = 3000; float ulBackoffRate = 1.5f; PARAMETER_CHECK( (dwFlags == SVFH_DEFAULT_ACTION) || (dwFlags == SVFH_RETRY_LOGIC_SIMPLE) || (dwFlags == SVFH_RETRY_WAIT_UNTIL)); if ( dwFlags == SVFH_RETRY_LOGIC_SIMPLE ) ulRetriesLeft = 10; TryAgain: IFW32FALSE_EXIT_UNLESS2( ::SxspCreateFileHash( HASHFLAG_AUTODETECT, whichAlg, hsFullFilePath, bGotHash), LIST_5( ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_BAD_NETPATH, ERROR_BAD_NET_NAME, ERROR_SHARING_VIOLATION), fFileNotFoundError); // // If this was a sharing violation and we've got retries left, then try again. // if (fFileNotFoundError && (::FusionpGetLastWin32Error() == ERROR_SHARING_VIOLATION) && (ulRetriesLeft > 0)) { ulRetriesLeft--; ::Sleep( ulBackoffAmount ); if (ulBackoffAmount < ulBackoffAmountCap) ulBackoffAmount = (ULONG)((float)ulBackoffAmount * ulBackoffRate); if (dwFlags == SVFH_RETRY_WAIT_UNTIL) ulRetriesLeft = 1; goto TryAgain; } // // If the file was able to be hashed, and the return error isn't "file not found", // then compare the hashes // if (!fFileNotFoundError &&(rsrgbTheoreticalHash.GetSize() == bGotHash.GetSize())) { HashValid = (::memcmp( bGotHash.GetArrayPtr(), rsrgbTheoreticalHash.GetArrayPtr(), bGotHash.GetSize()) == 0) ? HashValidate_Matches : HashValidate_HashNotMatched; } else { HashValid = HashValidate_HashesCantBeMatched; } FN_EPILOG } BOOL SxspGetStrongNameFromManifestName( PCWSTR pszManifestName, CBaseStringBuffer &rbuffStrongName, BOOL &rfHasPublicKey ) { BOOL fSuccess = TRUE; FN_TRACE_WIN32(fSuccess); PCWSTR wsCursor; SIZE_T cchJump, cchPubKey; rfHasPublicKey = FALSE; rbuffStrongName.Clear(); wsCursor = pszManifestName; // // Tricky: Zips through the name of the manifest to find the strong name string. // for (int i = 0; i < 2; i++) { cchJump = ::wcscspn(wsCursor, L"_"); PARAMETER_CHECK(cchJump != 0); wsCursor += (cchJump + 1); // x86_foo_strongname -> foo_strongname } // // Are we mysteriously at the end of the string? // PARAMETER_CHECK(wsCursor[0] != L'\0'); // // Find the length of the public key string // cchPubKey = wcscspn(wsCursor, L"_"); PARAMETER_CHECK(cchPubKey != 0); IFW32FALSE_EXIT(rbuffStrongName.Win32Assign(wsCursor, cchPubKey)); rfHasPublicKey = (::FusionpCompareStrings( rbuffStrongName, rbuffStrongName.Cch(), SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_PUBLICKEY_MISSING_VALUE, NUMBER_OF(SXS_ASSEMBLY_IDENTITY_STD_ATTRIBUTE_PUBLICKEY_MISSING_VALUE) - 1, false) != 0); FN_EPILOG } static GUID p_WintrustVerifyGenericV2 = WINTRUST_ACTION_GENERIC_VERIFY_V2; BOOL SxspValidateManifestAgainstCatalog( const CBaseStringBuffer &rbuffManifestName, // "c:\foo\x86_comctl32_6.0.0.0_0000.manifest" ManifestValidationResult &rResult, DWORD dwOptionsFlags ) { FN_PROLOG_WIN32 CStringBuffer sbCatalogName; // // Take the manifest name (which should be c:\foo\bar\blort.manifest) and switch // it to contain the catalog name instead: // // c:\foo\bar\blort.cat // IFW32FALSE_EXIT(sbCatalogName.Win32Assign(rbuffManifestName)); IFW32FALSE_EXIT( sbCatalogName.Win32ChangePathExtension( FILE_EXTENSION_CATALOG, FILE_EXTENSION_CATALOG_CCH, eAddIfNoExtension)); IFW32FALSE_EXIT(::SxspValidateManifestAgainstCatalog(rbuffManifestName, sbCatalogName, rResult, dwOptionsFlags)); FN_EPILOG } void SxspCertFreeCtlContext( PCCTL_CONTEXT CtlContext ) { if (CtlContext != NULL) ::CertFreeCTLContext(CtlContext); } void SxspCertFreeCertContext( PCCERT_CONTEXT CertContext ) { if (CertContext != NULL) ::CertFreeCertificateContext(CertContext); } BOOL SxspValidateCatalogAndFindManifestHash( IN HANDLE hCatalogFile, IN PBYTE prgbHash, IN SIZE_T cbHash, OUT BOOL &rfCatalogOk, OUT BOOL &rfHashInCatalog ) { FN_PROLOG_WIN32 CFileMapping fmCatalogMapping; CMappedViewOfFile mvCatalogView; LARGE_INTEGER liCatalogFile; ULONGLONG ullCatalogFile; PVOID pvCatalogData; CRYPT_VERIFY_MESSAGE_PARA vfmParameters; // // Default value // rfHashInCatalog = FALSE; rfCatalogOk = FALSE; // // Create a CTL context from the catalog file. // IFW32FALSE_ORIGINATE_AND_EXIT(::GetFileSizeEx(hCatalogFile, &liCatalogFile)); ullCatalogFile = liCatalogFile.QuadPart; IFW32FALSE_EXIT(fmCatalogMapping.Win32CreateFileMapping(hCatalogFile, PAGE_READONLY, ullCatalogFile, NULL)); IFW32FALSE_EXIT(mvCatalogView.Win32MapViewOfFile(fmCatalogMapping, FILE_MAP_READ, 0, (SIZE_T)ullCatalogFile)); pvCatalogData = mvCatalogView; // // First, validate that the message (catalog) is OK // ZeroMemory(&vfmParameters, sizeof(vfmParameters)); vfmParameters.cbSize = sizeof(vfmParameters); vfmParameters.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; // NTRAID#NTBUG9 - 591808 - 2002/04/01 - mgrier - Missing error check (well, missing // handling of the error case). And no, returning the "failure" via // rfCatalogOk == NULL does not count. rfCatalogOk = ::CryptVerifyMessageSignature( &vfmParameters, 0, static_cast(pvCatalogData), static_cast(ullCatalogFile), NULL, NULL, NULL); if (rfCatalogOk) { CSmartPtrWithNamedDestructor pCertContext; CSmartPtrWithNamedDestructor pCtlContext; PCTL_ENTRY pFoundCtlEntry; CSmallStringBuffer buffStringizedHash; CTL_ANY_SUBJECT_INFO ctlSubjectInfo; // // The search routine needs a string to find, says the crypto guys. // IFW32FALSE_EXIT(::SxspHashBytesToString( prgbHash, cbHash, buffStringizedHash)); IFW32FALSE_EXIT(buffStringizedHash.Win32ConvertCase(eConvertToUpperCase)); // // If this failed, something bad happened with the CTL - maybe the catalog // was invalid, maybe something else happened. Whatever it was, let the // caller decide. // // NTRAID#NTBUG9 - 591808 - 2002/04/01 - mgrier - Missing error check (well, missing // handling of the error case). pCtlContext.AttachForDelete( ::CertCreateCTLContext( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, static_cast(pvCatalogData), static_cast(ullCatalogFile))); if (pCtlContext != NULL) { // // Fill out this data with the string information. // CStringBufferAccessor sba; sba.Attach(&buffStringizedHash); ZeroMemory(&ctlSubjectInfo, sizeof(ctlSubjectInfo)); ctlSubjectInfo.SubjectAlgorithm.pszObjId = NULL; ctlSubjectInfo.SubjectIdentifier.pbData = static_cast(static_cast(sba.GetBufferPtr())); ctlSubjectInfo.SubjectIdentifier.cbData = static_cast((sba.Cch() + 1) * sizeof(WCHAR)); sba.Detach(); // // Look for it in the CTL // pFoundCtlEntry = CertFindSubjectInCTL( X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CTL_ANY_SUBJECT_TYPE, &ctlSubjectInfo, pCtlContext, 0); rfHashInCatalog = ( pFoundCtlEntry != NULL ); } } FN_EPILOG } class CSxspValidateManifestAgainstCatalogLocals { public: CSxspValidateManifestAgainstCatalogLocals() { } ~CSxspValidateManifestAgainstCatalogLocals() { } CSmallStringBuffer rbuffStrongNameString; CPublicKeyInformation pkiCatalogInfo; }; BOOL SxspValidateManifestAgainstCatalog( IN const CBaseStringBuffer &rbuffManifestName, IN const CBaseStringBuffer &rbuffCatalogName, OUT ManifestValidationResult &rResult, IN DWORD dwOptionsFlags ) { FN_PROLOG_WIN32 CFusionArray ManifestHash; BOOL fTempFlag = FALSE; BOOL fCatalogOk = FALSE; BOOL fHashFound = FALSE; CFusionFile ffCatalogFile; CSmartPtr Locals; IFW32FALSE_EXIT(Locals.Win32Allocate(__FILE__, __LINE__)); CSmallStringBuffer &rbuffStrongNameString = Locals->rbuffStrongNameString; CPublicKeyInformation &pkiCatalogInfo = Locals->pkiCatalogInfo; // // Generate the hash of the manifest first // IFW32FALSE_EXIT_UNLESS2( ::SxspCreateFileHash( HASHFLAG_STRAIGHT_HASH, CALG_SHA1, rbuffManifestName, ManifestHash), LIST_4(ERROR_PATH_NOT_FOUND, ERROR_FILE_NOT_FOUND, ERROR_BAD_NET_NAME, ERROR_BAD_NETPATH), fTempFlag); if (fTempFlag) { rResult = ManifestValidate_ManifestMissing; FN_SUCCESSFUL_EXIT(); } // // Open the catalog file for now, we'll use it later. // IFW32FALSE_EXIT_UNLESS2( ffCatalogFile.Win32CreateFile( rbuffCatalogName, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING), LIST_4(ERROR_PATH_NOT_FOUND, ERROR_FILE_NOT_FOUND, ERROR_BAD_NET_NAME, ERROR_BAD_NETPATH), fTempFlag); if (fTempFlag) { rResult = ManifestValidate_CatalogMissing; FN_SUCCESSFUL_EXIT(); } // // Now look in the file to see if the catalog contains the hash of the manifest // in the CTL // IFW32FALSE_EXIT( ::SxspValidateCatalogAndFindManifestHash( ffCatalogFile, ManifestHash.GetArrayPtr(), ManifestHash.GetSize(), fCatalogOk, fHashFound)); if (!fCatalogOk) { rResult = ManifestValidate_OtherProblems; FN_SUCCESSFUL_EXIT(); } else if (!fHashFound) { rResult = ManifestValidate_NotCertified; FN_SUCCESSFUL_EXIT(); } // // Are we supposed to validate the strong name of this catalog? // if ((dwOptionsFlags & MANIFESTVALIDATE_MODE_NO_STRONGNAME) == 0) { IFW32FALSE_EXIT(::SxspGetStrongNameFromManifestName( rbuffManifestName, rbuffStrongNameString, fTempFlag)); if (!fTempFlag) { rResult = ManifestValidate_OtherProblems; FN_SUCCESSFUL_EXIT(); } IFW32FALSE_EXIT(pkiCatalogInfo.Initialize(rbuffCatalogName)); } // // Huzzah! // rResult = ManifestValidate_IsIntact; FN_EPILOG } bool SxspIsFullHexString( PCWSTR wsString, SIZE_T Cch ) { for (SIZE_T i = 0; i < Cch; i++) { WCHAR ch = wsString[i]; if (!::SxspIsHexDigit(ch)) return false; } return true; }