Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

757 lines
20 KiB

/*
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<PCWSTR>(rbuffFile),
fHasHashData ? "yes" : "no",
HashAlgId,
static_cast<PCWSTR>(rbuffHashDataString));
#endif
if (fHasHashData)
{
CFusionArray<BYTE> 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<BYTE> &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<CFusionHash*>(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<BYTE> 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<BYTE> &rsrgbTheoreticalHash,
ALG_ID whichAlg,
HashValidateResult &HashValid
)
{
FN_PROLOG_WIN32
CFusionArray<BYTE> 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<PBYTE>(pvCatalogData),
static_cast<DWORD>(ullCatalogFile),
NULL,
NULL,
NULL);
if (rfCatalogOk)
{
CSmartPtrWithNamedDestructor<const CERT_CONTEXT, &::SxspCertFreeCertContext> pCertContext;
CSmartPtrWithNamedDestructor<const CTL_CONTEXT, &::SxspCertFreeCtlContext> 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<PBYTE>(pvCatalogData),
static_cast<DWORD>(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<PBYTE>(static_cast<PVOID>(sba.GetBufferPtr()));
ctlSubjectInfo.SubjectIdentifier.cbData = static_cast<DWORD>((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<BYTE> ManifestHash;
BOOL fTempFlag = FALSE;
BOOL fCatalogOk = FALSE;
BOOL fHashFound = FALSE;
CFusionFile ffCatalogFile;
CSmartPtr<CSxspValidateManifestAgainstCatalogLocals> 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;
}