windows-nt/Source/XPSP1/NT/ds/security/cryptoapi/mincrypt/lib/verfile.cpp
2020-09-26 16:20:57 +08:00

535 lines
16 KiB
C++

//+-------------------------------------------------------------------------
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 2001 - 2001
//
// File: verfile.cpp
//
// Contents: Minimal Cryptographic functions to hash files and verify
// Authenticode signed files.
// message
//
// Functions: MinCryptHashFile
// MinCryptVerifySignedFile
//
// History: 21-Jan-01 philh created
//--------------------------------------------------------------------------
#include "global.hxx"
#include <md5.h>
#include <sha.h>
#define PE_EXE_HEADER_TAG "MZ"
#define MIN_PE_FILE_LEN 4
#define MAX_SIGNED_FILE_AUTH_ATTR_CNT 10
typedef struct _DIGEST_DATA {
ALG_ID AlgId;
void *pvSHA1orMD5Ctx;
} DIGEST_DATA, *PDIGEST_DATA;
// #define SPC_INDIRECT_DATA_OBJID "1.3.6.1.4.1.311.2.1.4"
const BYTE rgbSPC_INDIRECT_DATA_OBJID[] =
{0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04};
BOOL
WINAPI
I_DigestFunction(
DIGEST_HANDLE refdata,
PBYTE pbData,
DWORD cbData
)
{
PDIGEST_DATA pDigestData = (PDIGEST_DATA) refdata;
switch (pDigestData->AlgId)
{
case CALG_MD5:
MD5Update((MD5_CTX *)pDigestData->pvSHA1orMD5Ctx, pbData, cbData);
return(TRUE);
case CALG_SHA1:
A_SHAUpdate((A_SHA_CTX *)pDigestData->pvSHA1orMD5Ctx, pbData,
cbData);
return(TRUE);
}
return FALSE;
}
BOOL
WINAPI
I_IsNtPe32File(
IN PCRYPT_DATA_BLOB pFileBlob
)
{
const BYTE *pbFile = pFileBlob->pbData;
DWORD cbFile = pFileBlob->cbData;
if (MIN_PE_FILE_LEN > cbFile)
return FALSE;
if (0 != memcmp(&pbFile[0], PE_EXE_HEADER_TAG, strlen(PE_EXE_HEADER_TAG)))
return FALSE;
// Make sure it is a 32 bit PE
if (sizeof(IMAGE_DOS_HEADER) > cbFile)
return FALSE;
else {
IMAGE_DOS_HEADER *pDosHead = (IMAGE_DOS_HEADER *) pbFile;
if (pDosHead->e_magic != IMAGE_DOS_SIGNATURE)
return FALSE;
if (cbFile < (sizeof(IMAGE_DOS_HEADER) + pDosHead->e_lfanew))
return FALSE;
else {
IMAGE_NT_HEADERS *pNTHead =
(IMAGE_NT_HEADERS *)((ULONG_PTR)pDosHead + pDosHead->e_lfanew);
if (pNTHead->Signature != IMAGE_NT_SIGNATURE)
return FALSE;
}
}
return TRUE;
}
//+-------------------------------------------------------------------------
// Hashes the file according to the Hash ALG_ID.
//
// According to dwFileType, pvFile can be a pwszFilename, hFile or pFileBlob.
// Only requires READ access.
//
// dwFileType:
// MINCRYPT_FILE_NAME : pvFile - LPCWSTR pwszFilename
// MINCRYPT_FILE_HANDLE : pvFile - HANDLE hFile
// MINCRYPT_FILE_BLOB : pvFile - PCRYPT_DATA_BLOB pFileBlob
//
// rgbHash is updated with the resultant hash. *pcbHash is updated with
// the length associated with the hash algorithm.
//
// If the function succeeds, the return value is ERROR_SUCCESS. Otherwise,
// a nonzero error code is returned.
//
// Only CALG_SHA1 and CALG_MD5 are supported.
//
// If a NT PE 32 bit file format, hashed according to imagehlp rules, ie, skip
// section containing potential signature, ... . Otherwise, the entire file
// is hashed.
//--------------------------------------------------------------------------
LONG
WINAPI
MinCryptHashFile(
IN DWORD dwFileType,
IN const VOID *pvFile,
IN ALG_ID HashAlgId,
OUT BYTE rgbHash[MINCRYPT_MAX_HASH_LEN],
OUT DWORD *pcbHash
)
{
LONG lErr;
CRYPT_DATA_BLOB FileBlob = {0, NULL};
DIGEST_DATA DigestData;
A_SHA_CTX ShaCtx;
MD5_CTX Md5Ctx;
__try {
lErr = I_MinCryptMapFile(
dwFileType,
pvFile,
&FileBlob
);
if (ERROR_SUCCESS != lErr)
goto ErrorReturn;
if (!I_IsNtPe32File(&FileBlob)) {
// Hash the entire file
lErr = MinCryptHashMemory(
HashAlgId,
1, // cBlob
&FileBlob,
rgbHash,
pcbHash
);
goto CommonReturn;
}
DigestData.AlgId = HashAlgId;
switch (HashAlgId) {
case CALG_MD5:
DigestData.pvSHA1orMD5Ctx = &Md5Ctx;
MD5Init(&Md5Ctx);
break;
case CALG_SHA1:
DigestData.pvSHA1orMD5Ctx = &ShaCtx;
A_SHAInit(&ShaCtx);
break;
default:
goto InvalidHashAlgId;
}
if (!imagehack_ImageGetDigestStream(
&FileBlob,
0, // DigestLevel, ignored
I_DigestFunction,
&DigestData
))
goto DigestStreamError;
else {
DWORD dwPadBeforeCerts;
dwPadBeforeCerts = (FileBlob.cbData + 7) & ~7;
dwPadBeforeCerts -= FileBlob.cbData;
if (0 < dwPadBeforeCerts) {
BYTE rgb[8];
// imagehlp put nulls before the signature!
memset(rgb, 0x00, dwPadBeforeCerts);
if (!I_DigestFunction(&DigestData, rgb, dwPadBeforeCerts))
goto DigestFunctionError;
}
}
switch (HashAlgId) {
case CALG_MD5:
MD5Final(&Md5Ctx);
memcpy(rgbHash, Md5Ctx.digest, MD5DIGESTLEN);
*pcbHash = MINCRYPT_MD5_HASH_LEN;
break;
case CALG_SHA1:
A_SHAFinal(&ShaCtx, rgbHash);
*pcbHash = MINCRYPT_SHA1_HASH_LEN;
break;
default:
goto InvalidHashAlgId;
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
lErr = GetExceptionCode();
if (ERROR_SUCCESS == lErr)
lErr = E_UNEXPECTED;
goto ErrorReturn;
}
lErr = ERROR_SUCCESS;
CommonReturn:
//**********************************************************************
// WARNING!!!!
//
// UnmapViewOfFile is in another DLL, kernel32.dll.
// lErr and the return hash in rgbHash[] must be protected.
//
//**********************************************************************
if (MINCRYPT_FILE_BLOB != dwFileType && NULL != FileBlob.pbData)
UnmapViewOfFile(FileBlob.pbData);
return lErr;
ErrorReturn:
*pcbHash = 0;
goto CommonReturn;
InvalidHashAlgId:
lErr = NTE_BAD_ALGID;
goto ErrorReturn;
DigestStreamError:
DigestFunctionError:
lErr = NTE_BAD_HASH;
goto ErrorReturn;
}
// Only called when cAttrOID != 0
LONG
WINAPI
I_GetAuthAttributes(
IN PCRYPT_DER_BLOB pAttrsValueBlob,
IN DWORD cAttrOID,
IN CRYPT_DER_BLOB rgAttrEncodedOIDBlob[],
// CRYPT_DER_BLOB rgAttrBlob[cAttrOID] header is at beginning
// with the bytes pointed to immediately following
OUT OPTIONAL CRYPT_DER_BLOB *rgAttrValueBlob,
IN OUT DWORD *pcbAttr
)
{
LONG lErr;
DWORD i;
LONG lRemainExtra;
BYTE *pbExtra;
DWORD cbAttr;
CRYPT_DER_BLOB rgrgAttrBlob[MAX_SIGNED_FILE_AUTH_ATTR_CNT][MINASN1_ATTR_BLOB_CNT];
DWORD cAttr;
assert(0 != cAttrOID);
if (rgAttrValueBlob)
cbAttr = *pcbAttr;
else
cbAttr = 0;
lRemainExtra = cbAttr - sizeof(CRYPT_DER_BLOB) * cAttrOID;
if (0 <= lRemainExtra) {
memset(rgAttrValueBlob, 0, sizeof(CRYPT_DER_BLOB) * cAttrOID);
pbExtra = (BYTE *) &rgAttrValueBlob[cAttrOID];
} else
pbExtra = NULL;
// Parse the authenticated attributes
cAttr = MAX_SIGNED_FILE_AUTH_ATTR_CNT;
if (0 >= MinAsn1ParseAttributes(
pAttrsValueBlob,
&cAttr,
rgrgAttrBlob))
cAttr = 0;
for (i = 0; i < cAttrOID; i++) {
PCRYPT_DER_BLOB rgFindAttrBlob;
rgFindAttrBlob = MinAsn1FindAttribute(
&rgAttrEncodedOIDBlob[i],
cAttr,
rgrgAttrBlob
);
if (rgFindAttrBlob) {
PCRYPT_DER_BLOB pFindAttrValue =
&rgFindAttrBlob[MINASN1_ATTR_VALUE_IDX];
const BYTE *pbFindValue = pFindAttrValue->pbData;
DWORD cbFindValue = pFindAttrValue->cbData;
if (0 < cbFindValue) {
lRemainExtra -= cbFindValue;
if (0 <= lRemainExtra) {
rgAttrValueBlob[i].pbData = pbExtra;
rgAttrValueBlob[i].cbData = cbFindValue;
memcpy(pbExtra, pbFindValue, cbFindValue);
pbExtra += cbFindValue;
}
}
}
}
if (0 <= lRemainExtra) {
*pcbAttr = cbAttr - (DWORD) lRemainExtra;
lErr = ERROR_SUCCESS;
} else {
*pcbAttr = cbAttr + (DWORD) -lRemainExtra;
lErr = ERROR_INSUFFICIENT_BUFFER;
}
return lErr;
}
//+-------------------------------------------------------------------------
// Verifies a previously signed file.
//
// According to dwFileType, pvFile can be a pwszFilename, hFile or pFileBlob.
// Only requires READ access.
//
// dwFileType:
// MINCRYPT_FILE_NAME : pvFile - LPCWSTR pwszFilename
// MINCRYPT_FILE_HANDLE : pvFile - HANDLE hFile
// MINCRYPT_FILE_BLOB : pvFile - PCRYPT_DATA_BLOB pFileBlob
//
// Checks if the file has an embedded PKCS #7 Signed Data message containing
// Indirect Data. The PKCS #7 is verified via MinCryptVerifySignedData().
// The Indirect Data is parsed via MinAsn1ParseIndirectData() to get the
// HashAlgId and the file hash. MinCryptHashFile() is called to hash the
// file. The returned hash is compared against the Indirect Data's hash.
//
// The caller can request one or more signer authenticated attribute values
// to be returned. The still encoded values are returned in the
// caller allocated memory. The beginning of this returned memory will
// be set to an array of attribute value blobs pointing to these
// encoded values. The caller should make every attempt to allow for a
// single pass call. The necessary memory size is:
// (cAttrOID * sizeof(CRYPT_DER_BLOB)) +
// total length of encoded attribute values.
//
// *pcbAttr will be updated with the number of bytes required to contain
// the attribute blobs and values. If the input memory is insufficient,
// ERROR_INSUFFICIENT_BUFFER will be returned if no other error.
//
// For the multi-valued attributes, only the first value is returned.
//
// If the function succeeds, the return value is ERROR_SUCCESS. Otherwise,
// a nonzero error code is returned.
//
// Only NT, PE 32 bit file formats are supported.
//--------------------------------------------------------------------------
LONG
WINAPI
MinCryptVerifySignedFile(
IN DWORD dwFileType,
IN const VOID *pvFile,
IN OPTIONAL DWORD cAttrOID,
IN OPTIONAL CRYPT_DER_BLOB rgAttrEncodedOIDBlob[],
// CRYPT_DER_BLOB rgAttrBlob[cAttrOID] header is at beginning
// with the bytes pointed to immediately following
OUT OPTIONAL CRYPT_DER_BLOB *rgAttrValueBlob,
IN OUT OPTIONAL DWORD *pcbAttr
)
{
LONG lErr;
CRYPT_DATA_BLOB FileBlob = {0, NULL};
__try {
LPWIN_CERTIFICATE pCertHdr = NULL;
const BYTE *pbEncodedSignedData;
DWORD cbEncodedSignedData;
CRYPT_DER_BLOB rgVerSignedDataBlob[MINCRYPT_VER_SIGNED_DATA_BLOB_CNT];
CRYPT_DER_BLOB rgIndirectDataBlob[MINASN1_INDIRECT_DATA_BLOB_CNT];
ALG_ID HashAlgId;
BYTE rgbHash[MINCRYPT_MAX_HASH_LEN];
DWORD cbHash;
lErr = I_MinCryptMapFile(
dwFileType,
pvFile,
&FileBlob
);
if (ERROR_SUCCESS != lErr)
goto ErrorReturn;
if (!I_IsNtPe32File(&FileBlob))
goto NotNtPe32File;
if (!imagehack_ImageGetCertificateData(
&FileBlob,
0, // CertificateIndex
&pCertHdr
))
goto NoSignature;
if (WIN_CERT_REVISION_2_0 != pCertHdr->wRevision ||
WIN_CERT_TYPE_PKCS_SIGNED_DATA != pCertHdr->wCertificateType)
goto UnsupportedSignature;
if (offsetof(WIN_CERTIFICATE, bCertificate) > pCertHdr->dwLength)
goto InvalidSignature;
cbEncodedSignedData = pCertHdr->dwLength -
offsetof(WIN_CERTIFICATE, bCertificate);
pbEncodedSignedData = pCertHdr->bCertificate;
lErr = MinCryptVerifySignedData(
pbEncodedSignedData,
cbEncodedSignedData,
rgVerSignedDataBlob
);
if (ERROR_SUCCESS != lErr)
goto ErrorReturn;
// The data content should be Indirect Data
if (sizeof(rgbSPC_INDIRECT_DATA_OBJID) !=
rgVerSignedDataBlob[
MINCRYPT_VER_SIGNED_DATA_CONTENT_OID_IDX].cbData
||
0 != memcmp(rgbSPC_INDIRECT_DATA_OBJID,
rgVerSignedDataBlob[
MINCRYPT_VER_SIGNED_DATA_CONTENT_OID_IDX].pbData,
sizeof(rgbSPC_INDIRECT_DATA_OBJID)))
goto NotIndirectDataOID;
if (0 >= MinAsn1ParseIndirectData(
&rgVerSignedDataBlob[MINCRYPT_VER_SIGNED_DATA_CONTENT_DATA_IDX],
rgIndirectDataBlob
))
goto ParseIndirectDataError;
HashAlgId = MinCryptDecodeHashAlgorithmIdentifier(
&rgIndirectDataBlob[MINASN1_INDIRECT_DATA_DIGEST_ALGID_IDX]
);
if (0 == HashAlgId)
goto UnknownHashAlgId;
lErr = MinCryptHashFile(
MINCRYPT_FILE_BLOB,
(const VOID *) &FileBlob,
HashAlgId,
rgbHash,
&cbHash
);
if (ERROR_SUCCESS != lErr)
goto ErrorReturn;
// Check that the hash in the indirect data matches the file hash
if (cbHash !=
rgIndirectDataBlob[MINASN1_INDIRECT_DATA_DIGEST_IDX].cbData
||
0 != memcmp(rgbHash,
rgIndirectDataBlob[MINASN1_INDIRECT_DATA_DIGEST_IDX].pbData,
cbHash))
goto InvalidFileHash;
if (cAttrOID)
lErr = I_GetAuthAttributes(
&rgVerSignedDataBlob[MINCRYPT_VER_SIGNED_DATA_AUTH_ATTRS_IDX],
cAttrOID,
rgAttrEncodedOIDBlob,
rgAttrValueBlob,
pcbAttr
);
else
lErr = ERROR_SUCCESS;
} __except(EXCEPTION_EXECUTE_HANDLER) {
lErr = GetExceptionCode();
if (ERROR_SUCCESS == lErr)
lErr = E_UNEXPECTED;
goto ErrorReturn;
}
CommonReturn:
//**********************************************************************
// WARNING!!!!
//
// UnmapViewOfFile is in another DLL, kernel32.dll.
// lErr must be protected.
//
//**********************************************************************
if (MINCRYPT_FILE_BLOB != dwFileType && NULL != FileBlob.pbData)
UnmapViewOfFile(FileBlob.pbData);
return lErr;
ErrorReturn:
assert(ERROR_SUCCESS != lErr);
if (ERROR_INSUFFICIENT_BUFFER == lErr)
// This error can only be set when we determine that the attribute
// buffer isn't big enough.
lErr = E_UNEXPECTED;
goto CommonReturn;
NotNtPe32File:
lErr = ERROR_NOT_SUPPORTED;
goto ErrorReturn;
NoSignature:
UnsupportedSignature:
InvalidSignature:
lErr = TRUST_E_NOSIGNATURE;
goto ErrorReturn;
NotIndirectDataOID:
ParseIndirectDataError:
lErr = CRYPT_E_BAD_MSG;
goto ErrorReturn;
UnknownHashAlgId:
lErr = CRYPT_E_UNKNOWN_ALGO;
goto ErrorReturn;
InvalidFileHash:
lErr = CRYPT_E_HASH_VALUE;
goto ErrorReturn;
}