//+------------------------------------------------------------------------- // 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 #include #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; }